With current version of Bitcoin Script, that is not possible.
However, with some upgrades to Bitcoin’s ScriptVM it would become possible, the prerequisites are:
- A way to commit to transaction’s outputs (e.g. OP_CHECKTEMPLATEVERIFY or TX introspection opcodes + OP_CAT)
- A way to split & concatenate stack items (OP_CAT & OP_SPLIT)
- A way for inputs to “see” each other (introspection opcodes)
With that, one could code a hash-lock but with an additional requirement: another input must reveal an aged commitment to (prevout + output contents of the TX). This is something only the person who knows the secret would be able to produce ahead of revealing the secret. Once he spends from the contract he will reveal it, but he’ll already have the aged commitment and others won’t be able to steal his funds.
Once posted to mempool, security would rely on the spending TX getting mined before an attacker would be able to age an alternative commitment and post an alternative spending TX.
A proof-of-concept for such a contract exists for Bitcoin Cash (BCH), a blockchain fork of Bitcoin (BTC) which upgraded the ScriptVM with the required L1 primitives in ’18 (OP_CAT & OP_SPLIT) and ’22 (introspection opcodes) network upgrades.
The “lock” redeem script, placed on the UTXO which would hold the balance
(note: there’s no unlocking data, the input’s unlocking script is just the redeem script push)
// sha256(one_time_secret + commit_script_tail)
<93168bb98087a29aeb40733ef301f907c3e4125568dd0ddf851f304b43438a14>
// slice associated input's script
OP_INPUTINDEX OP_1ADD
OP_INPUTBYTECODE
<1> OP_SPLIT
OP_SWAP OP_SPLIT
<2> OP_SPLIT
<1> OP_SPLIT
<32> OP_SPLIT
<1> OP_SPLIT
<1> OP_SPLIT
// verify sha256(one_time_secret + commit_script_tail)
<6> OP_ROLL OP_SWAP OP_CAT OP_SHA256
<6> OP_ROLL OP_EQUALVERIFY
// verify input script head format
<0x51> <0x61> OP_WITHIN OP_VERIFY
<0x51> <0x61> OP_WITHIN OP_VERIFY
OP_SIZE <32> OP_EQUALVERIFY
OP_DROP <32> OP_EQUALVERIFY
<0x4c72> OP_EQUAL
The “commit” redeem script, placed on the UTXO which would reveal the aged commitment
(note: the unlocking data is just the one_time_secret
)
// sha256(associated_outpoint + one_time_secret + {first 3 outputs})
<0x8bda8c89d438b6da3fd9d289da59532736bfb23d93bba1e2e8da41c194ea43e9>
// age_reveal
<2>
// age_cleanup
<4>
OP_DEPTH <4> OP_LESSTHAN
// if no secret is provided then this is a cleanup spend
OP_IF
// once redeem script is revealed and utxo aged beyond age_cleanup,
// any miner can claim the dust to himself
// verify age_cleanup
OP_CHECKSEQUENCEVERIFY OP_DROP
OP_2DROP
// else it is a reveal spend
OP_ELSE
// drop age_cleanup, not needed here
OP_DROP
// verify age_spend
OP_CHECKSEQUENCEVERIFY OP_DROP
// get associated outpoint on top of stack
OP_INPUTINDEX OP_1SUB OP_DUP
OP_OUTPOINTTXHASH
OP_SWAP OP_OUTPOINTINDEX OP_CAT
// get one_time_secret on top of stack and concatenate
OP_ROT OP_CAT
// concatenate outputs 00 [& 01 [& 02]]
<0> OP_OUTPUTVALUE OP_CAT
<0> OP_OUTPUTTOKENCATEGORY OP_CAT
<0> OP_OUTPUTTOKENCOMMITMENT OP_CAT
<0> OP_OUTPUTTOKENAMOUNT OP_CAT
<0> OP_OUTPUTBYTECODE OP_CAT
OP_TXOUTPUTCOUNT <2> OP_GREATERTHANOREQUAL
OP_IF
<1> OP_OUTPUTVALUE OP_CAT
<1> OP_OUTPUTTOKENCATEGORY OP_CAT
<1> OP_OUTPUTTOKENCOMMITMENT OP_CAT
<1> OP_OUTPUTTOKENAMOUNT OP_CAT
<1> OP_OUTPUTBYTECODE OP_CAT
OP_ENDIF
OP_TXOUTPUTCOUNT <3> OP_GREATERTHANOREQUAL
OP_IF
<2> OP_OUTPUTVALUE OP_CAT
<2> OP_OUTPUTTOKENCATEGORY OP_CAT
<2> OP_OUTPUTTOKENCOMMITMENT OP_CAT
<2> OP_OUTPUTTOKENAMOUNT OP_CAT
<2> OP_OUTPUTBYTECODE OP_CAT
OP_ENDIF
OP_SHA256
// verify against embedded pre-commitment
OP_EQUALVERIFY
OP_ENDIF
OP_1
The contracts can be loaded into BitAuthIDE debugger using this link.
The contracts have been successfully spent from on BCH mainnet:
This proof-of-concept was first published here