mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-07-30 19:41:24 +00:00
Transaction: Calculate signature for witness transactions.
Adds calculateWitnessSignature() and hashForSignatureWitness() variants.
This commit is contained in:
committed by
Andreas Schildbach
parent
81c262ab4c
commit
d953ced801
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>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)</p>
|
||||
*
|
||||
* @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()
|
||||
|
@@ -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
|
||||
* <a href="https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki">BIP 16</a> to learn more about this
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user