Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More effienct tx grinding and script #13

Merged
merged 3 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading