diff --git a/core/src/main/java/org/bitcoinj/core/Transaction.java b/core/src/main/java/org/bitcoinj/core/Transaction.java index c70b98c3500..720b1b356bb 100644 --- a/core/src/main/java/org/bitcoinj/core/Transaction.java +++ b/core/src/main/java/org/bitcoinj/core/Transaction.java @@ -1409,21 +1409,22 @@ public synchronized Sha256Hash hashForWitnessSignature( *

Passing a null scriptCode produces a key-path (BIP341) sighash. Passing the 32-byte tapleaf hash of the * script being spent produces a script-path (BIP342) sighash, which additionally commits to that leaf.

* - *

Limitations: only SigHash.DEFAULT and SigHash.ALL without ANYONECANPAY are supported; SigHash.NONE, - * SigHash.SINGLE and the annex are not implemented. OP_CODESEPARATOR is not supported: the codesep_pos in the + *

Limitations: only SigHash.DEFAULT, SigHash.ALL and SigHash.ANYONECANPAY_ALL are supported; + * SigHash.NONE, SigHash.SINGLE and the annex are not implemented. ANYONECANPAY alone (0x80) is not a valid + * taproot sighash type per BIP341, so it is rejected. OP_CODESEPARATOR is not supported: the codesep_pos in the * script-path extension is always committed as 0xffffffff, so this method produces a correct sighash only for * scripts that contain no executed OP_CODESEPARATOR.

* * @param inputIndex input the signature is being calculated for. Tx signatures are always relative to an input. * @param scriptCode the 32-byte tapleaf hash for a script-path spend, or null for a key-path spend. * @param prevOutputs the previous outputs being spent, one per input, in input order. - * @param sigHashType should be SigHash.DEFAULT or SigHash.ALL, and not ANYONECANPAY. + * @param sigHashType should be SigHash.DEFAULT, SigHash.ALL or SigHash.ANYONECANPAY_ALL. */ public synchronized Sha256Hash hashForTaprootSignature( int inputIndex, byte[] scriptCode, List prevOutputs, - byte sigHashType){ + SigHash sigHashType){ // A non-null scriptCode is the 32-byte tapleaf hash of the script being spent (BIP342 // script path). Null means a key-path spend (BIP341). @@ -1432,19 +1433,21 @@ public synchronized Sha256Hash hashForTaprootSignature( checkArgument(inputIndex < prevOutputs.size()); checkArgument(prevOutputs.size() == inputs.size()); - int basicSigHashType = sigHashType & 0x1f; - boolean anyoneCanPay = (sigHashType & SigHash.ANYONECANPAY.value) == SigHash.ANYONECANPAY.value; + final byte sigHashTypeByte = sigHashType.byteValue(); + int basicSigHashType = sigHashTypeByte & 0x1f; + boolean anyoneCanPay = (sigHashTypeByte & SigHash.ANYONECANPAY.value) == SigHash.ANYONECANPAY.value; boolean signAll = (basicSigHashType != SigHash.SINGLE.value) && (basicSigHashType != SigHash.NONE.value); - // Only SigHash.DEFAULT, SigHash.ALL and !anyoneCanPay supported for now + // Only SigHash.DEFAULT, SigHash.ALL and SigHash.ANYONECANPAY_ALL supported for now. + // ANYONECANPAY alone (0x80) masks to DEFAULT | ANYONECANPAY, not a valid taproot type (BIP341). checkArgument(basicSigHashType == SigHash.DEFAULT.value || basicSigHashType == SigHash.ALL.value); - checkArgument(!anyoneCanPay); + checkArgument(!anyoneCanPay || basicSigHashType == SigHash.ALL.value); final boolean hasAnnex = false; // Reserved. Not used. // According to BIP 341: 175 - is_anyonecanpay * 49 - is_none * 32 + has_annex * 32 int byteArraySize = 175; if (anyoneCanPay) { - byteArraySize += 49; + byteArraySize -= 49; } if (basicSigHashType == SigHash.NONE.value) { byteArraySize += 32; @@ -1463,8 +1466,8 @@ public synchronized Sha256Hash hashForTaprootSignature( // Epoch [1] (not technically part of the message, but every use-case adds this prefix) bos.write(new byte[] { 0x00 }); - // SigHash type [1] - bos.write(new byte[] {(byte) basicSigHashType}); + // SigHash type [1]: the full type byte, including the ANYONECANPAY flag if set + bos.write(new byte[] { sigHashTypeByte }); // nVersion [4] uint32ToByteStreamLE(version, bos); @@ -1472,7 +1475,8 @@ public synchronized Sha256Hash hashForTaprootSignature( // nLockTime [4] uint32ToByteStreamLE(lockTime, bos); - // input data [128 per input] always included since we failed for anyoneCanPay + // input data [128]: midstate hashes over all inputs, skipped for anyoneCanPay, + // which commits to just the signed input below instead if (!anyoneCanPay) { // calcHashPrevOuts [32] @@ -1532,7 +1536,21 @@ public synchronized Sha256Hash hashForTaprootSignature( bos.write(new byte[] { (byte) (hasScriptPath ? 0x02 : 0x00) }); if (anyoneCanPay) { - // MISSING: commit to the spent output and sequence (never since we failed for anyoneCanPay) + final TransactionInput input = this.inputs.get(inputIndex); + final TransactionOutput prevOutput = prevOutputs.get(inputIndex); + final byte[] prevOutScriptBytes = prevOutput.getScriptBytes(); + + // Outpoint [36] + bos.write(input.getOutpoint().getHash().getReversedBytes()); + uint32ToByteStreamLE(input.getOutpoint().getIndex(), bos); + + // Spent output: amount [8] and scriptPubKey [var, with size prefix] + int64ToByteStreamLE(prevOutput.getValue().value, bos); + bos.write(new VarInt(prevOutScriptBytes.length).encode()); + bos.write(prevOutScriptBytes); + + // nSequence [4] + uint32ToByteStreamLE(input.getSequenceNumber(), bos); } else { // Input index [4] diff --git a/core/src/test/java/org/bitcoinj/core/TransactionTest.java b/core/src/test/java/org/bitcoinj/core/TransactionTest.java index a1e7eb2fe97..c5f72c7919e 100644 --- a/core/src/test/java/org/bitcoinj/core/TransactionTest.java +++ b/core/src/test/java/org/bitcoinj/core/TransactionTest.java @@ -549,7 +549,7 @@ private static void assertTaprootSigHash( inputIndex, tapLeafHash, parseTaprootPrevOuts(), - (byte) sigHashType.value + sigHashType ); assertArrayEquals(sigHash.getBytes(), Hex.decode(expectedSigHashHex)); @@ -580,6 +580,20 @@ public void testSigHashTaprootDefault() { ); } + @Test + public void testSigHashTaprootAnyoneCanPay() { + // Same key-path spend as testSigHashTaproot, signed with SIGHASH_ALL | SIGHASH_ANYONECANPAY + // (0x81) instead of ALL. Golden value from btcd v0.24.2: keyPathExpectedSigHash in the + // main.go snippet in testSigHashTaprootScriptPath, with + // txscript.SigHashAll|txscript.SigHashAnyOneCanPay instead of txscript.SigHashAll. + assertTaprootSigHash( + 1, + null, + Transaction.SigHash.ANYONECANPAY_ALL, + "494fcea1db6f47aea9325255ba8b95c9f336e72a02f734c9f0c733878c463f58" + ); + } + @Test public void testSigHashTaprootScriptPath() { // Spending a tapscript leaf (BIP342 script path) of input 0 instead of the key path. @@ -647,6 +661,20 @@ public void testSigHashTaprootScriptPathDefault() { ); } + @Test + public void testSigHashTaprootScriptPathAnyoneCanPay() { + // Same tapscript leaf spend as testSigHashTaprootScriptPath, signed with + // SIGHASH_ALL | SIGHASH_ANYONECANPAY (0x81) instead of ALL. Golden value from btcd v0.24.2: + // the main.go snippet in testSigHashTaprootScriptPath, with + // txscript.SigHashAll|txscript.SigHashAnyOneCanPay instead of txscript.SigHashAll. + assertTaprootSigHash( + 0, + taprootTestTapLeafHash(), + Transaction.SigHash.ANYONECANPAY_ALL, + "29f572084d6730eacc50d4cd5cb42326c13d19a6d4cf20b3f434266115141395" + ); + } + @Test public void testToStringWhenLockTimeIsSpecifiedInBlockHeight() { Transaction tx = FakeTxBuilder.createFakeTx(UNITTEST);