diff --git a/CHANGELOG.md b/CHANGELOG.md index bc7b864..d9a97f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 1.0.0 +* Bugfix: Taproot sighash now uses actual transaction values instead of hardcoded locktime=0 and version=2 (#14) * Migrate code from `base` to `core` * *Breaking:* Remove `toBytes` function in `bitcoin/TxOutput.mo` (use class method instead) * *Breaking:* Add length assertions inside `Bech32.encode()` diff --git a/src/bitcoin/Transaction.mo b/src/bitcoin/Transaction.mo index ae9cbad..0b7fb9f 100644 --- a/src/bitcoin/Transaction.mo +++ b/src/bitcoin/Transaction.mo @@ -265,10 +265,12 @@ module { let sighash_type : [Nat8] = [0x00]; let nVersion_buffer = VarArray.repeat(0, 4); - Common.writeLE32(nVersion_buffer, 0, 2); + Common.writeLE32(nVersion_buffer, 0, version); let nVersion = nVersion_buffer.toArray(); - let nLockTime : [Nat8] = VarArray.repeat(0, 4).toArray(); + let nLockTime_buffer = VarArray.repeat(0, 4); + Common.writeLE32(nLockTime_buffer, 0, locktime); + let nLockTime = nLockTime_buffer.toArray(); let sha_prevouts : [Nat8] = Sha256.fromArray(#sha256, prevouts.flatten()).toArray(); let amounts_bytes = amounts.map( diff --git a/test/bitcoin/p2trKeyPathSigHash.test.mo b/test/bitcoin/p2trKeyPathSigHash.test.mo index fcde4c0..4229402 100644 --- a/test/bitcoin/p2trKeyPathSigHash.test.mo +++ b/test/bitcoin/p2trKeyPathSigHash.test.mo @@ -1,8 +1,11 @@ import Blob "mo:core/Blob"; import Nat "mo:core/Nat"; +import VarArray "mo:core/VarArray"; import { expect; test } "mo:test"; +import Transaction "../../src/bitcoin/Transaction"; +import Witness "../../src/bitcoin/Witness"; import TestVectors "p2trTestVectors"; for (testCase in TestVectors.testCases().vals()) { @@ -20,3 +23,51 @@ for (testCase in TestVectors.testCases().vals()) { }, ); }; + +test( + "non-zero locktime changes sighash", + func() { + let testCase = TestVectors.testCases()[0]; + let txLocktime0 = testCase.transaction(); + let txLocktime42 = Transaction.Transaction( + TestVectors.version, + testCase.inputs(), + testCase.outputs(), + VarArray.repeat(Witness.EMPTY_WITNESS, testCase.numInputs), + 42, + ); + + let hash0 = txLocktime0.createTaprootKeySpendSignatureHash( + testCase.amounts(), testCase.ownScript(), 0, + ); + let hash42 = txLocktime42.createTaprootKeySpendSignatureHash( + testCase.amounts(), testCase.ownScript(), 0, + ); + + expect.blob(Blob.fromArray(hash0)).notEqual(Blob.fromArray(hash42)); + }, +); + +test( + "different version changes sighash", + func() { + let testCase = TestVectors.testCases()[0]; + let txVersion2 = testCase.transaction(); + let txVersion1 = Transaction.Transaction( + 1, + testCase.inputs(), + testCase.outputs(), + VarArray.repeat(Witness.EMPTY_WITNESS, testCase.numInputs), + 0, + ); + + let hash2 = txVersion2.createTaprootKeySpendSignatureHash( + testCase.amounts(), testCase.ownScript(), 0, + ); + let hash1 = txVersion1.createTaprootKeySpendSignatureHash( + testCase.amounts(), testCase.ownScript(), 0, + ); + + expect.blob(Blob.fromArray(hash2)).notEqual(Blob.fromArray(hash1)); + }, +); diff --git a/test/bitcoin/p2trScriptPathSigHash.test.mo b/test/bitcoin/p2trScriptPathSigHash.test.mo index 20b997e..c565413 100644 --- a/test/bitcoin/p2trScriptPathSigHash.test.mo +++ b/test/bitcoin/p2trScriptPathSigHash.test.mo @@ -1,8 +1,12 @@ import Blob "mo:core/Blob"; import Nat "mo:core/Nat"; +import VarArray "mo:core/VarArray"; import { expect; test } "mo:test"; +import P2tr "../../src/bitcoin/P2tr"; +import Transaction "../../src/bitcoin/Transaction"; +import Witness "../../src/bitcoin/Witness"; import TestVectors "p2trTestVectors"; for (testCase in TestVectors.testCases().vals()) { @@ -20,3 +24,28 @@ for (testCase in TestVectors.testCases().vals()) { }, ); }; + +test( + "non-zero locktime changes script spend sighash", + func() { + let testCase = TestVectors.testCases()[0]; + let txLocktime0 = testCase.transaction(); + let txLocktime42 = Transaction.Transaction( + TestVectors.version, + testCase.inputs(), + testCase.outputs(), + VarArray.repeat(Witness.EMPTY_WITNESS, testCase.numInputs), + 42, + ); + + let leafHash = P2tr.leafHash(testCase.leafScript()); + let hash0 = txLocktime0.createTaprootScriptSpendSignatureHash( + testCase.amounts(), testCase.ownScript(), 0, leafHash, + ); + let hash42 = txLocktime42.createTaprootScriptSpendSignatureHash( + testCase.amounts(), testCase.ownScript(), 0, leafHash, + ); + + expect.blob(Blob.fromArray(hash0)).notEqual(Blob.fromArray(hash42)); + }, +);