diff --git a/core/src/main/java/org/bitcoinj/core/Transaction.java b/core/src/main/java/org/bitcoinj/core/Transaction.java index 7a81be00..653fbb5c 100644 --- a/core/src/main/java/org/bitcoinj/core/Transaction.java +++ b/core/src/main/java/org/bitcoinj/core/Transaction.java @@ -1185,6 +1185,154 @@ public class Transaction extends ChildMessage { } } + public TransactionSignature calculateWitnessSignature( + int inputIndex, + ECKey key, + byte[] redeemScript, + Coin value, + SigHash hashType, + boolean anyoneCanPay) { + Sha256Hash hash = hashForSignatureWitness(inputIndex, redeemScript, value, hashType, anyoneCanPay); + return new TransactionSignature(key.sign(hash), hashType, anyoneCanPay); + } + + public TransactionSignature calculateWitnessSignature( + int inputIndex, + ECKey key, + Script redeemScript, + Coin value, + SigHash hashType, + boolean anyoneCanPay) { + return calculateWitnessSignature(inputIndex, key, redeemScript.getProgram(), value, hashType, anyoneCanPay); + } + + public TransactionSignature calculateWitnessSignature( + int inputIndex, + ECKey key, + @Nullable KeyParameter aesKey, + byte[] redeemScript, + Coin value, + SigHash hashType, + boolean anyoneCanPay) { + Sha256Hash hash = hashForSignatureWitness(inputIndex, redeemScript, value, hashType, anyoneCanPay); + return new TransactionSignature(key.sign(hash, aesKey), hashType, anyoneCanPay); + } + + public TransactionSignature calculateWitnessSignature( + int inputIndex, + ECKey key, + @Nullable KeyParameter aesKey, + Script redeemScript, + Coin value, + SigHash hashType, + boolean anyoneCanPay) { + return calculateWitnessSignature(inputIndex, key, aesKey, redeemScript.getProgram(), value, hashType, anyoneCanPay); + } + + public synchronized Sha256Hash hashForSignatureWitness( + int inputIndex, + byte[] scriptCode, + Coin prevValue, + SigHash type, + boolean anyoneCanPay) { + int sigHash = TransactionSignature.calcSigHashValue(type, anyoneCanPay); + return hashForSignatureWitness(inputIndex, scriptCode, prevValue, (byte) sigHash); + } + + /** + *

Calculates a signature hash, that is, a hash of a simplified form of the transaction. How exactly the transaction + * is simplified is specified by the type and anyoneCanPay parameters.

+ * + *

This is a low level API and when using the regular {@link Wallet} class you don't have to call this yourself. + * When working with more complex transaction types and contracts, it can be necessary. When signing a Witness output + * the scriptCode should be the script encoded into the scriptSig field, for normal transactions, it's the + * scriptPubKey of the output you're signing for. (See BIP143: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki)

+ * + * @param inputIndex input the signature is being calculated for. Tx signatures are always relative to an input. + * @param scriptCode the script that should be in the given input during signing. + * @param prevValue the value of the coin being spent + * @param type Should be SigHash.ALL + * @param anyoneCanPay should be false. + */ + public synchronized Sha256Hash hashForSignatureWitness( + int inputIndex, + Script scriptCode, + Coin prevValue, + SigHash type, + boolean anyoneCanPay) { + return hashForSignatureWitness(inputIndex, scriptCode.getProgram(), prevValue, type, anyoneCanPay); + } + + public synchronized Sha256Hash hashForSignatureWitness( + int inputIndex, + byte[] connectedScript, + Coin prevValue, + byte sigHashType){ + ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(length == UNKNOWN_LENGTH ? 256 : length + 4); + try { + byte[] hashPrevouts = new byte[32]; + byte[] hashSequence = new byte[32]; + byte[] hashOutputs = new byte[32]; + int basicSigHashType = sigHashType & 0x1f; + boolean anyoneCanPay = (sigHashType & SigHash.ANYONECANPAY.value) == SigHash.ANYONECANPAY.value; + boolean signAll = (basicSigHashType != SigHash.SINGLE.value) && (basicSigHashType != SigHash.NONE.value); + + if (!anyoneCanPay) { + ByteArrayOutputStream bosHashPrevouts = new UnsafeByteArrayOutputStream(256); + for (int i = 0; i < this.inputs.size(); ++i) { + bosHashPrevouts.write(this.inputs.get(i).getOutpoint().getHash().getReversedBytes()); + uint32ToByteStreamLE(this.inputs.get(i).getOutpoint().getIndex(), bosHashPrevouts); + } + hashPrevouts = Sha256Hash.hashTwice(bosHashPrevouts.toByteArray()); + } + + if (!anyoneCanPay && signAll) { + ByteArrayOutputStream bosSequence = new UnsafeByteArrayOutputStream(256); + for (int i = 0; i < this.inputs.size(); ++i) { + uint32ToByteStreamLE(this.inputs.get(i).getSequenceNumber(), bosSequence); + } + hashSequence = Sha256Hash.hashTwice(bosSequence.toByteArray()); + } + + if (signAll) { + ByteArrayOutputStream bosHashOutputs = new UnsafeByteArrayOutputStream(256); + for (int i = 0; i < this.outputs.size(); ++i) { + uint64ToByteStreamLE( + BigInteger.valueOf(this.outputs.get(i).getValue().getValue()), + bosHashOutputs + ); + bosHashOutputs.write(new VarInt(this.outputs.get(i).getScriptBytes().length).encode()); + bosHashOutputs.write(this.outputs.get(i).getScriptBytes()); + } + hashOutputs = Sha256Hash.hashTwice(bosHashOutputs.toByteArray()); + } else if (basicSigHashType == SigHash.SINGLE.value && inputIndex < outputs.size()) { + ByteArrayOutputStream bosHashOutputs = new UnsafeByteArrayOutputStream(256); + uint64ToByteStreamLE( + BigInteger.valueOf(this.outputs.get(inputIndex).getValue().getValue()), + bosHashOutputs + ); + bosHashOutputs.write(new VarInt(this.outputs.get(inputIndex).getScriptBytes().length).encode()); + bosHashOutputs.write(this.outputs.get(inputIndex).getScriptBytes()); + hashOutputs = Sha256Hash.hashTwice(bosHashOutputs.toByteArray()); + } + uint32ToByteStreamLE(version, bos); + bos.write(hashPrevouts); + bos.write(hashSequence); + bos.write(inputs.get(inputIndex).getOutpoint().getHash().getReversedBytes()); + uint32ToByteStreamLE(inputs.get(inputIndex).getOutpoint().getIndex(), bos); + bos.write(connectedScript); + uint64ToByteStreamLE(BigInteger.valueOf(prevValue.getValue()), bos); + uint32ToByteStreamLE(inputs.get(inputIndex).getSequenceNumber(), bos); + bos.write(hashOutputs); + uint32ToByteStreamLE(this.lockTime, bos); + uint32ToByteStreamLE(0x000000ff & sigHashType, bos); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + + return Sha256Hash.twiceOf(bos.toByteArray()); + } + @Override protected void bitcoinSerializeToStream(OutputStream stream) throws IOException { boolean useSegwit = hasWitnesses() diff --git a/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java b/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java index eb6dfb04..0305c5ec 100644 --- a/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java +++ b/core/src/main/java/org/bitcoinj/script/ScriptBuilder.java @@ -438,6 +438,22 @@ public class ScriptBuilder { return builder.build(); } + /** + * Creates a segwit scriptPubKey that sends to the given public key hash. + */ + public static Script createP2WPKHOutputScript(byte[] hash) { + checkArgument(hash.length == SegwitAddress.WITNESS_PROGRAM_LENGTH_PKH); + return new ScriptBuilder().smallNum(0).data(hash).build(); + } + + /** + * Creates a segwit scriptPubKey that sends to the given public key. + */ + public static Script createP2WPKHOutputScript(ECKey key) { + checkArgument(key.isCompressed()); + return createP2WPKHOutputScript(key.getPubKeyHash()); + } + /** * Creates a scriptPubKey that sends to the given script hash. Read * BIP 16 to learn more about this diff --git a/core/src/test/java/org/bitcoinj/core/TransactionTest.java b/core/src/test/java/org/bitcoinj/core/TransactionTest.java index 5a14971c..9b096709 100644 --- a/core/src/test/java/org/bitcoinj/core/TransactionTest.java +++ b/core/src/test/java/org/bitcoinj/core/TransactionTest.java @@ -305,6 +305,150 @@ public class TransactionTest { tx.getHash().toString()); } + @Test + public void testWitnessSignatureP2WPKH() { + // test vector P2WPKH from: + // https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki + NetworkParameters netParams = TestNet3Params.get(); + + Transaction unsignedTx = new Transaction(netParams, HEX.decode("01000000" + // version + "02" + + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" + "00000000" + "00" + "eeffffff" + + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" + "01000000" + "00" + "ffffffff" + + "02" + + "202cb20600000000" + "1976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac" + + "9093510d00000000" + "1976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac" + + "11000000")); + assertEquals(2, unsignedTx.getInputs().size()); + assertEquals(2, unsignedTx.getOutputs().size()); + + ECKey key0 = ECKey.fromPrivate( + HEX.decode("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + assertEquals("2103c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432ac", + HEX.encode(ScriptBuilder.createOutputScript(key0).getProgram())); + ECKey key1 = ECKey.fromPrivate( + HEX.decode("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + assertEquals("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357", + key1.getPublicKeyAsHex()); + + TransactionSignature txSig0 = unsignedTx.calculateSignature(0, key0, + ScriptBuilder.createOutputScript(key0).getProgram(), + Transaction.SigHash.ALL, false); + assertEquals("30450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01", + HEX.encode(txSig0.encodeToBitcoin())); + + Script scriptCode = new ScriptBuilder() + .data(ScriptBuilder.createOutputScript(LegacyAddress.fromKey(netParams, key1)) + .getProgram()) + .build(); + assertEquals("1976a9141d0f172a0ecb48aee1be1f2687d2963ae33f71a188ac", + HEX.encode(scriptCode.getProgram())); + + TransactionSignature txSig1 = unsignedTx.calculateWitnessSignature(1, key1, + scriptCode, Coin.COIN.multiply(6), + Transaction.SigHash.ALL, false); + assertEquals("304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee" + + "01", + HEX.encode(txSig1.encodeToBitcoin())); + + TransactionInput txIn0 = unsignedTx.getInput(0); + txIn0.setScriptSig(new ScriptBuilder() + .data(txSig0.encodeToBitcoin()) + .build()); + + TransactionWitness witness = new TransactionWitness(2); + witness.setPush(0, txSig1.encodeToBitcoin()); + witness.setPush(1, key1.getPubKey()); + + TransactionInput txIn1 = unsignedTx.getInput(1); + txIn1.setWitness(witness); + // no redeem script for p2wpkh + + assertEquals("01000000" + "00" + "01" + + "02" + + "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f" + "00000000" + + "494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01" + + "eeffffff" + + "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a" + "01000000" + "00" + + "ffffffff" + + "02" + + "202cb20600000000" + "1976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac" + + "9093510d00000000" + "1976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac" + + "00" + // witness 0 push 0 + "02" + // witness 1 push 2 + "47304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee01" + + "21025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357" + + "11000000", + HEX.encode(unsignedTx.bitcoinSerialize())); + } + + @Test + public void testWitnessSignatureP2SH_P2WPKH() { + // test vector P2SH-P2WPKH from: + // https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki + NetworkParameters netParams = TestNet3Params.get(); + + Transaction unsignedTx = new Transaction(netParams, HEX.decode("01000000" + // version + "01" + // input * 1 + "db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477" + "01000000" + "00" + "feffffff" + + "02" + // output * 2 + "b8b4eb0b00000000" + "1976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac" + + "0008af2f00000000" + "1976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac" + + "92040000")); + assertEquals(1, unsignedTx.getInputs().size()); + assertEquals(2, unsignedTx.getOutputs().size()); + + ECKey key = ECKey.fromPrivate( + HEX.decode("eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf")); + assertEquals("03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873", + key.getPublicKeyAsHex()); + + Script redeemScript = ScriptBuilder.createP2WPKHOutputScript(key); + assertEquals("001479091972186c449eb1ded22b78e40d009bdf0089", + HEX.encode(redeemScript.getProgram())); + + byte[] p2wpkhHash = Utils.sha256hash160(redeemScript.getProgram()); + Script scriptPubKey = ScriptBuilder.createOutputScript( + LegacyAddress.fromScriptHash(netParams, p2wpkhHash)); + assertEquals("a9144733f37cf4db86fbc2efed2500b4f4e49f31202387", + HEX.encode(scriptPubKey.getProgram())); + + Script scriptCode = new ScriptBuilder() + .data(ScriptBuilder.createOutputScript(LegacyAddress.fromKey(netParams, key)) + .getProgram()) + .build(); + assertEquals("1976a91479091972186c449eb1ded22b78e40d009bdf008988ac", + HEX.encode(scriptCode.getProgram())); + + TransactionSignature txSig = unsignedTx.calculateWitnessSignature(0, key, + scriptCode, Coin.COIN.multiply(10), + Transaction.SigHash.ALL, false); + assertEquals("3044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb" + + "01", + HEX.encode(txSig.encodeToBitcoin())); + + TransactionWitness witness = new TransactionWitness(2); + witness.setPush(0, txSig.encodeToBitcoin()); + witness.setPush(1, key.getPubKey()); + + TransactionInput txIn = unsignedTx.getInput(0); + txIn.setWitness(witness); + txIn.setScriptSig(new ScriptBuilder().data(redeemScript.getProgram()).build()); + + assertEquals("01000000" + "0001" + // version / tag / flag + "01" + // input * 1 + "db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477" + "01000000" + + "1716001479091972186c449eb1ded22b78e40d009bdf0089" + "feffffff" + + "02" + // output * 2 + "b8b4eb0b00000000" + "1976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac" + + "0008af2f00000000" + "1976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac" + + "02" + // witness 0 push count + "473044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb01" + + "2103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873" + + "92040000", + HEX.encode(unsignedTx.bitcoinSerialize())); + } + @Test public void testToStringWhenLockTimeIsSpecifiedInBlockHeight() { Transaction tx = FakeTxBuilder.createFakeTx(UNITTEST);