Trustless crowdfunding without SIGHASH_ANYONECANPAY?

0 48

The idea is simple and implemented naively in the repo: https://github.com/SahidMiller/bch-zakat

Two contracts are included, a pledge contract and a campaign contract. Both rely on an append-only "ledger" that lists all the pledgers' pkhs and the amounts they've pledged.

Surprisingly, it's actually not very different from P2SH... I'll attempt to explain the process.

First, pledgers have it easy. They simply lock their coins in a "pledger contract" that ensures the following in order to be spent:

1) the output is a P2SH contract of the target campaign... so it matches the expected "campaign contract" template + has the expected parameters (ie. campaigner pkh, goal amount, and block height).
2) the output/target campaign contract includes a line-item for the current pledge in its ledger. This line-item in the ledger will come in handy if/when the campaign goal isn't reached, God willing.

Then, after locking coins into this particular P2SH script, they can notify the campaigner to come collect the pledge into the target campaign contract.

It's as simple as that for the pledger... and technically, they don't even have to do the notifying. Fortunately, no waiting is involved either way and they can still back out up until the campaigner collects it into the "target campaign contract".

Finally, once it's collected/spent into a campaign contract, they'll have to wait until the block height is reached and the goal isn't met in order to redeem their coins back. More on that later.


Campaigners have it a bit harder. They have to manage the ledger state so that it satisfies both the pledger's contract constraints and the campaign contract constraints.

Luckily, it's actually not very different from P2SH. So let me explain...

The ledger isn't actually included in our campaign script. Instead, it's passed in via unlocking script. What is included in our campaign script is a hash of the ledger, just like P2SH. By including the ledger hash at the beginning of the campaign contract, OP_PUSHDATA + {ledgerhash} + OP_DROP... we gain the benefits of state with the least amount of manipulating bytecode.

For example, the pledger contract ensures the pledger is on the ledger of the output in the following steps. First, it expects a ledger to be passed in. Then, it adds the expected line-item to this ledger and hashes the result.. It then wraps it in OP_PUSH and OP_DROP and appends the target campaign bytecode. Finally, this is all hashed together and compared to the preimage hashOutputs... So we're fairly sure we're the last line-item on the ledger!*

*We get out of validating the passed in ledger by ensuring LIFO ordering when redeeming our pledge from the campaign... instead just checking for correct size. We just care we're the last line-item.

Another more complex example is how the campaign contract ensures that whatever ledger is passed in matches the current ledger state. It does this by first hashing the passed in ledger and comparing it to the first 22bytes (OP_PUSH + 20 byte hash + OP_DROP) of the current script via its preimage. Then, depending on the action, the campaign contract will actually ensure the next output has the intended ledger hash based on the current ledger and action. For example, to ensure we add a line-item to the ledger when accepting pledges or to ensure we remove a line-item to the ledger when redeeming a pledge after the campaign failed.


So campaigners have to manage these ledgers carefully to say the least but the danger is rejected transactions. Pledgers coins should be safe based on the valid ledger enforced in the contracts. Here's how it's done...

First, the campaigner creates an initial campaign contract which they fund with a dust amount and initialized with the following:

1) the specified campaigner pkh
2) the goal amount
3) block height for the goal to be reached.
4) an empty ledger (hashed)

Then, the campaigner can begin collecting pledges and adding the appropriate line-items for each one collected (the pledge contracts will invalidate it if not) by spending both the pledge and campaign utxos into one appropriate output "new" campaign utxo that satisfies both contracts.

Finally, the campaign contract ensures the coins locked in can only be spent outside this contract in the following ways:

1) When the goal has been reached by the specified block height, the campaigner can output to their own address.
2) When the goal hasn't been reached by the specified block height, one output must be a P2PKH to the pkh specified in the last line-item with the same amount specified in that line-item (maybe minus a fee, especially for the later pledges). This ensures LIFO order for the redemption of pledges. Another output is required for the change (the amount specified in last line-item minus total collected) which is another campaign contract that's identical byte-for-byte to the current except for the ledger hash. The ledger will have a line-item removed... This continues until all line-items are removed, God willing.

Make sense?

2
$ 1.00
$ 1.00 from @emergent_reasons

Comments