Skip to content

Commit

Permalink
Merge pull request #13 from taproot-wizards/op-add
Browse files Browse the repository at this point in the history
More effienct tx grinding and script
  • Loading branch information
rot13maxi authored Sep 26, 2024
2 parents 1e49297 + 4cc1926 commit a275943
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 8 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ Right now the vault always spends back to itself when you cancel. In real life y
That would be an easy change to make, but was elided for simplicity in this demo.

The Schnorr signature that you create on the stack is equal to SigMsg + 1. You need to grind the transaction data to get the right last bytes of the signature.
I use a combination of grinding the low-order bits of the Locktime and the Sequence number of the last input in order to get a signature with the last byte. For my construction,
that was fine. For other constructions, you might need to grind the last byte of the signature in a different way.
I use a combination of grinding the low-order bits of the Locktime and the Sequence number of the last input in order to get a signature with the last byte.
Luckily, there are only two values that are not allowed for the last byte (0x7f and 0xff), so the grinding is easy. See [this post for more details](https://delvingbitcoin.org/t/efficient-multi-input-transaction-grinding-for-op-cat-based-bitcoin-covenants/1080).

Re-building a TXID on the stack to introspect previous transactions was actually easier than I expected. Two wrinkles I ran into were:
- There is a standardness rule on witness stack items (80 bytes). I had to split the outputs of the previous transaction into two chunks in order to get them on the stack and then glue it together with OP_CAT.
Expand Down
2 changes: 1 addition & 1 deletion scripts/build_bitcoincore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ git clone --depth 1 --branch dont-success-cat [email protected]:rot13maxi/bitcoin.g

pushd bitcoin-core-cat
./autogen.sh
./configure
./configure --without-tests --disable-bench
make -j4
popd
6 changes: 6 additions & 0 deletions src/vault/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ impl VaultCovenant {
)?;
let mangled_signature: [u8; 63] = computed_signature[0..63].try_into().unwrap(); // chop off the last byte, so we can provide the 0x00 and 0x01 bytes on the stack
vault_txin.witness.push(mangled_signature);
vault_txin.witness.push([computed_signature[63]]); // push the last byte of the signature
vault_txin.witness.push([computed_signature[63] + 1]); // push the last byte of the signature

vault_txin
.witness
Expand Down Expand Up @@ -413,6 +415,8 @@ impl VaultCovenant {
)?;
let mangled_signature: [u8; 63] = computed_signature[0..63].try_into().unwrap(); // chop off the last byte, so we can provide the 0x00 and 0x01 bytes on the stack
vault_txin.witness.push(mangled_signature);
vault_txin.witness.push([computed_signature[63]]); // push the last byte of the signature
vault_txin.witness.push([computed_signature[63] + 1]); // push the last byte of the signature

vault_txin
.witness
Expand Down Expand Up @@ -523,6 +527,8 @@ impl VaultCovenant {

let mangled_signature: [u8; 63] = computed_signature[0..63].try_into().unwrap(); // chop off the last byte, so we can provide the 0x00 and 0x01 bytes on the stack
vault_txin.witness.push(mangled_signature);
vault_txin.witness.push([computed_signature[63]]); // push the last byte of the signature
vault_txin.witness.push([computed_signature[63] + 1]); // push the last byte of the signature

vault_txin
.witness
Expand Down
17 changes: 14 additions & 3 deletions src/vault/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub(crate) fn vault_trigger_withdrawal() -> ScriptBuf {
// and finally the mangled signature
builder = builder
.push_opcode(OP_TOALTSTACK) // move pre-computed signature minus last byte to alt stack
.push_opcode(OP_TOALTSTACK) // move last byte to alt stack
.push_opcode(OP_TOALTSTACK) // move last byte to alt stack
.push_opcode(OP_TOALTSTACK) // push the fee-paying scriptpubkey to the alt stack
.push_opcode(OP_TOALTSTACK) // push the fee amount to the alt stack
.push_opcode(OP_2DUP) // make a second copy of the vault scriptpubkey and amount so we can check input = output
Expand Down Expand Up @@ -80,6 +82,8 @@ pub(crate) fn vault_complete_withdrawal(timelock_in_blocks: u16) -> ScriptBuf {
.push_opcode(OP_CSV) // check relative timelock on withdrawal
.push_opcode(OP_DROP) // drop the result
.push_opcode(OP_TOALTSTACK) // move pre-computed signature minus last byte to alt stack
.push_opcode(OP_TOALTSTACK) // move last byte to alt stack
.push_opcode(OP_TOALTSTACK) // move last byte to alt stack
.push_opcode(OP_TOALTSTACK) // move the fee-paying txout to the alt stack
.push_opcode(OP_DUP) // make a second copy of the target scriptpubkey so we can use it later
.push_opcode(OP_TOALTSTACK) // push the target scriptpubkey to the alt stack
Expand Down Expand Up @@ -145,6 +149,8 @@ pub(crate) fn vault_cancel_withdrawal() -> ScriptBuf {
// and finally the mangled signature
builder = builder
.push_opcode(OP_TOALTSTACK) // move pre-computed signature minus last byte to alt stack
.push_opcode(OP_TOALTSTACK) // move last byte to alt stack
.push_opcode(OP_TOALTSTACK) // move last byte to alt stack
.push_opcode(OP_TOALTSTACK) // push the fee-paying scriptpubkey to the alt stack
.push_opcode(OP_TOALTSTACK) // push the fee amount to the alt stack
.push_opcode(OP_2DUP) // make a second copy of the vault scriptpubkey and amount so we can check input = output
Expand Down Expand Up @@ -210,6 +216,8 @@ pub(crate) fn add_signature_construction_and_check(builder: Builder) -> Builder
.push_slice(*G_X) // G is used for the pubkey and K
.push_opcode(OP_DUP)
.push_opcode(OP_DUP)
.push_opcode(OP_DUP)
.push_opcode(OP_TOALTSTACK) // we'll need a copy of G later to be our R value in the signature
.push_opcode(OP_TOALTSTACK) // we'll need a copy of G later to be our R value in the signature
.push_opcode(OP_ROT) // bring the challenge to the top of the stack
.push_opcode(OP_CAT)
Expand All @@ -220,14 +228,17 @@ pub(crate) fn add_signature_construction_and_check(builder: Builder) -> Builder
.push_opcode(OP_FROMALTSTACK) // bring G back from the alt stack to use as the R value in the signature
.push_opcode(OP_SWAP)
.push_opcode(OP_CAT) // cat the R value with the s value for a complete signature
.push_opcode(OP_FROMALTSTACK) // bring G back from the alt stack to use as the R value in the signature
.push_opcode(OP_FROMALTSTACK) // grab the pre-computed signature minus the last byte from the alt stack
.push_opcode(OP_ROT)// Move the G value to the bottom of the stack
.push_opcode(OP_SWAP) // put the pre-computed signature on the top of the stack
.push_opcode(OP_DUP) // we'll need a second copy later to do the actual signature verification
.push_slice([0x00u8]) // add the last byte of the signature, which should match what we computed. NOTE ⚠️: push_int(0) will not work here because it will push OP_FALSE, but we want an actual 0 byte
.push_opcode(OP_FROMALTSTACK) // grab the last byte of the signature hash from the alt stack
.push_opcode(OP_CAT)
.push_opcode(OP_ROT) // bring the script-computed signature to the top of the stack
.push_opcode(OP_EQUALVERIFY) // check that the script-computed and pre-computed signatures match
.push_int(0x01) // we need the last byte of the signature to be 0x01 because our k value is 1 (because K is G)
.push_opcode(OP_FROMALTSTACK) // grab the last byte of the signature from the alt stack, should be +1 from the pre-computed signature
.push_opcode(OP_CAT)
.push_slice(*G_X) // push G again. TODO: DUP this from before and stick it in the alt stack or something
.push_opcode(OP_SWAP) // bring G to the top of the stack
.push_opcode(OP_CHECKSIG)
}
4 changes: 2 additions & 2 deletions src/vault/signature_building.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,8 @@ where
let sigmsg = compute_sigmsg_from_components(&components_for_signature)?;
let challenge = compute_challenge(&sigmsg);

if challenge[31] == 0x00 {
debug!("Found a challenge with a 0 at the end!");
if challenge[31] != 0x7f && challenge[31] != 0xff {
debug!("Found a challenge with a {} at the end!", challenge[31]);
debug!("{:?} is {}", grind_field, counter);
debug!(
"Here's the challenge: {}",
Expand Down

0 comments on commit a275943

Please sign in to comment.