A quick writeup of an internal ad-hoc conversation we had at GP today. Just wanted to get it on paper in case anyone else is facing it.
The source of the conversation is this line in the AnyHedge contract (require(tx.inputs.length == 1)
) which requires an AnyHedge payout transaction to have exactly one input. That’s fine in an isolated situation, but makes it not composable. The reason for that requirement and potential solutions that are friendly to composability are described below. Mostly written by @im_uname based on a discussion with @jimtendo and myself, with some edits and additions by Jim/me.
The problem
If a contract evaluates an output (or multiple outputs for that matter) to fulfill its script, presumably another contract that evaluates to the same output value (or any other non-unique output criteria) can be placed into the same transaction, “re-use” the same output to satisfy its script. its coins can then be burned or stolen against intention of the contract funder.
To solve this, we need to prevent an output from being “reused” in such a way. preventing this re-use requires that the output has some kind of identifier that can be tied to a unique input .
Solution 0: require exactly one input ( not composable)
Can’t spend multiple inputs if there is only one! This is what AnyHedge did before introspection and before it was really conceivable to compose contracts
Solution 1: the “SIGHASH_SINGLE” method ( not generally composable)
Script evaluates its own input_index, compare to the output_index it’s evaluating, make sure they match (or some other 1:1 transformation). While this solves the reuse problem, it unnecessarily constrains the tx shape in a rigid way and is incompatible with general composability.
Solution 2: output value hack ()
Script evaluates the necessary output amount, then before the evaluation concludes, subtracts in sats its own input_index. it is extremely unlikely that some other contract will subtract its own index, pay to the same people, and somehow end up with the same compatible output. problem is this may not be very robust, opening up known unknowns in math holes. Also just an ugly hack to mess around with money amounts.
Solution 3: Nonce in initial contract parameters ()
While not a theoretical solution, this is a solution that probably works in practice. It’s not a solution in theory because you can still make two contracts with exactly the same parameters and nonce. But in practice… you wouldn’t do that, even in an adversarial situation.
Solution 4: Tokens ()
The transaction generates outputs as cashtokens NFT outputs instead of normal p2pkh/p2sh outputs. the NFT has a commitment field each, and the commitment field carries the index of the input it’s supposed to evaluate against. since input indexes are unique within a tx, you can’t evaluate against another script employing a similar identifier.
More solutions…
Surely there are other options.