Skip to content

Instantly share code, notes, and snippets.

@cpacia
Last active May 26, 2016 23:57
Show Gist options
  • Save cpacia/987905d75eca1bf089f53eecde166d44 to your computer and use it in GitHub Desktop.
Save cpacia/987905d75eca1bf089f53eecde166d44 to your computer and use it in GitHub Desktop.
OpenBazaar P2SH script ideas.

OpenBazaar P2SH Script Enhancements

We're currently in the middle of planning for the next major version of OpenBazaar. In the course of doing so we're taking a hard look at the entire app to find ways to improve user experience. What I want to do here is throw around some ideas to improve the checkout flow and solicit feedback.

The current checkout process

Currently, each vendor has an opportunity to add one or more "moderators" (basically escrow/arbitration agents) to his listings. The vendor chooses only moderators he trusts and those moderators are part of the goods/service bundle offered for sale.

During the checkout process, the buyer is first given the option to select either a direct payment (which sends the bitcoins directly to the vendor) or a moderated payment. If he chooses a moderated payment, in the next step he's asked to pick from the list of moderators the vendor selected. If he finds none acceptable, he can always decline to purchase. Assuming the buyer proceeds, the public key for the chosen moderator is used to build a 2 of 3 P2SH script which serves as the escrow for the order.

Can we improve on this UX?

In the designs for our next UI our lead designer Mike moved the selection of the moderators by the buyer from before the payment is made to after a dispute has arisen. Something I don't think any of us thought of. His argument for doing so is pretty compelling:

​*Currently half of our checkout process is prevention steps for future events that may never happen.*​

Admittedly it does feel a bit clumsy and it's certainly a dramatic departure from what consumers are used to. Ideally the checkout process would be extremely simple and any complexity should be pushed downstream and only introduced if it's needed.

But is this change even technically possible? How in the world do you do multisig without defining the moderator in advance? Can we possibly create some kind of exotic P2SH redeem script to do this? Let's try to work through it.

First stab

What if we changed the redeem script to something like this: (NOTE: please feel free to correct me if I'm screwing up script syntax).

IF 
	2 <buyer pubkey><vendor pubkey> 2 CHECKMULTISIG 
ELSE 
	1 <buyer pubkey><vendor pubkey> 2 CHECKMULTISIGVERIFY
	1 <mod1 pubkey><mod2 pubkey><mod3 pubkey> 3 CHECKMULTISIG
ENDIF

Basically this says the funds can be released if:

  1. The buyer and seller jointly sign the transaction. This is the normal case with a successful transaction or a refund.

  2. Either the buyer or seller sign plus one out of the three listed moderators.

This satisfies the requirement to shift the selection of the moderator to the dispute resolution process (after the purchase is made), but it unfortunately makes it easy for the vendor to defraud the buyer. Since this script allows for the funds to be released by signing with the vendor's key plus one of the moderator's keys, and since the vendor handpicked the list of moderators, he could have just added one of his sock puppets as a moderator to steal the funds.

So it seems we would need to make it so that the moderators cannot release the funds to the vendor. For example, this will work to prevent the fraud described above:

IF 
	2 <buyer pubkey><vendor pubkey> 2 CHECKMULTISIG 
ELSE 
	<buyer pubkey> CHECKSIGVERIFY
	1 <mod1 pubkey><mod2 pubkey><mod3 pubkey> 3 CHECKMULTISIG
ENDIF

But now the only way the vendor can get paid is if the buyer releases funds from a 2 of 2, which we can't count on. Even before releasing OpenBazaar we were strongly considering using CHECKLOCKTIMEVERIFY to automatically release the funds to the vendor after, say, 30 days. This gives the vendor confidence that he will still get paid even if the buyer and moderator become unresponsive, while still giving the buyer enough time to dispute the transaction. This script would look like:

IF 
	2 <buyer pubkey><vendor pubkey> 2 CHECKMULTISIG
ELSE 
	<now + 30 days> CHECKLOCKTIMEVERIFY DROP
	<vendor pubkey> CHECKSIG
ELSE
	<buyer pubkey> CHECKSIGVERIFY
	1 <mod1 pubkey><mod2 pubkey><mod3 pubkey> 3 CHECKSMULTISIG
ENDIF

The funds release to the vendor if both the vendor and buyer agree and sign or automatically after the 30 day timelock. Otherwise the buyer can file a dispute with one of the three moderators and get a refund that way.

This seems to satisfy most of our requirements, but there are still some edge cases. With this script the moderator cannot release the funds to the vendor if he decides in favor of the vendor. All he can do is say to the vendor "you won" and the vendor has to wait 30 days (or however much time remains). Not only is this a particularly bad UX, but it creates the opportunity for the buyer to go "shopping around" for a favorable decision from another moderator in the list (assuming there are others).

Presumably any moderator worth their salt will, upon the start of a new dispute, send a message to the vendor asking to hear his side of the story. In such a scenario the vendor could easily prove that the dispute was already resolved by another moderator, but we'd still like to have at least some way to guard against it.

Hash values

What we would really like is for there to be some way for the moderator to release the payment to the vendor, but only after the buyer has initiated a dispute. The reason again here is if the moderator can release the payment before the buyer files a dispute, the vendor could employ a sock puppet moderator in his list.

Suppose when generating the redeem script the buyer generates a hash (H) of a random number (R) which he keeps secret. Then creates a script that looks like this:

IF 
	2 <buyer pubkey><vendor pubkey> 2 CHECKMULTISIG
ELSE 
	<now + 30 days> CHECKLOCKTIMEVERIFY DROP
	<vendor pubkey> CHECKSIG
ELSE
	<buyer pubkey> CHECKSIGVERIFY
	1 <mod1 pubkey><mod2 pubkey><mod3 pubkey> 3 CHECKSMULTISIG
ELSE
	<vendor pubkey> CHECKSIGVERIFY
	1 <mod1 pubkey><mod2 pubkey><mod3 pubkey> 3 CHECKSMULTISIGVERIFY
	HASH160 <H value> EQUAL
ENDIF

This is the same as before but with an additional clause which says that the funds can be released to the vendor by signing with the vendor's key, a moderator's key, and by providing the R value.

When the buyer initiates a dispute he discloses the R value to the moderator. If the moderator decides in favor of the vendor, he sends the R value along with his signature to the vendor.

So we have a script in which a payment can be released to the vendor prior to the expiration of the 30 day timelock, but only after the buyer files a dispute. This prevents the sock puppet attack we've been talking about. Of course, the vendor could use his sock puppet to release the funds after he has control of the R value, but that would gain him nothing as he's already won the dispute.

Now does this seem like it could plausibly work? Sadly, there's one more edge case where it doesn't. If the moderator is releasing the full amount to either the buyer or vendor then it works. But in OpenBazaar we also allow the moderator to split the payout in situations where the moderator believes both parties bear some culpability. In the split payment scenario, the moderator's signature is sent to both parties and, for the funds to be released, at least one of them needs to accept the dispute resolution, sign and broadcast. But for this to work, the moderator needs to disclose the R value to the vendor. But once the vendor has the R value, he could use a sock puppet moderator from the list and release the full amount to himself, basically defrauding the buyer out of a partial refund.

The only way around this problem seems to be to make an especially large script. In this one, instead of the buyer creating a single random value (R) he generates one for each of the moderators in the list. The script looks like this:

IF 
	2 <buyer pubkey><vendor pubkey> 2 CHECKMULTISIG
ELSE 
	<now + 30 days> CHECKLOCKTIMEVERIFY DROP
	<vendor pubkey> CHECKSIG
ELSE
	<buyer pubkey> CHECKSIGVERIFY
	1 <mod1 pubkey><mod2 pubkey><mod3 pubkey> 3 CHECKSMULTISIG
ELSE
	<vendor key> CHECKSIGVERIFY
	IF
		<mod1 pubkey> CHECKSIGVERIFY
		HASH160 <H1 value> EQUAL
	ELSE
		<mod2 pubkey> CHECKSIGVERIFY
		HASH160 <H2 value> EQUAL
	ELSE
		<mod3 pubkey> CHECKSIGVERIFY
		HASH160 <H3 value> EQUAL
	ENDIF	
ENDIF

Here the hash is unique for each moderator so the disclosure of one R value does not let the vendor use a sock puppet to defraud a buyer out of a partial payment.

Questions

  1. Am I even using the script correctly?

  2. Can this script be simplified so it isn't so large?

  3. Is the UX gain worth this complexity?

  4. Is the UX gain worth the additional bitcoin fees? The typical release transaction would probably be about 400 bytes larger (possibly more with more moderators in the list) which works out to about .09 USD more than a standard multisig transaction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment