WIP: Allow nonWitnessUtxo with segwit

This commit is contained in:
junderw 2019-08-23 12:52:04 +09:00
parent 27473d7fdb
commit bf45f3638b
No known key found for this signature in database
GPG key ID: B256185D3A971908
4 changed files with 236 additions and 51 deletions

View file

@ -815,13 +815,29 @@ function getHashForSig(inputIndex, input, cache, sighashTypes) {
} else { } else {
script = prevout.script; script = prevout.script;
} }
if (isP2WPKH(script) || isP2WSHScript(script)) { if (isP2WSHScript(script)) {
throw new Error( if (!input.witnessScript)
`Input #${inputIndex} has nonWitnessUtxo but segwit script: ` + throw new Error('Segwit input needs witnessScript if not P2WPKH');
`${script.toString('hex')}`, checkWitnessScript(inputIndex, script, input.witnessScript);
hash = unsignedTx.hashForWitnessV0(
inputIndex,
input.witnessScript,
prevout.value,
sighashType,
); );
} script = input.witnessScript;
} else if (isP2WPKH(script)) {
// P2WPKH uses the P2PKH template for prevoutScript when signing
const signingScript = payments.p2pkh({ hash: script.slice(2) }).output;
hash = unsignedTx.hashForWitnessV0(
inputIndex,
signingScript,
prevout.value,
sighashType,
);
} else {
hash = unsignedTx.hashForSignature(inputIndex, script, sighashType); hash = unsignedTx.hashForSignature(inputIndex, script, sighashType);
}
} else if (input.witnessUtxo) { } else if (input.witnessUtxo) {
let _script; // so we don't shadow the `let script` above let _script; // so we don't shadow the `let script` above
if (input.redeemScript) { if (input.redeemScript) {
@ -927,11 +943,14 @@ function getScriptFromInput(inputIndex, input, cache) {
isP2SH: false, isP2SH: false,
isP2WSH: false, isP2WSH: false,
}; };
if (input.nonWitnessUtxo) { res.isP2SH = !!input.redeemScript;
if (input.redeemScript) { res.isP2WSH = !!input.witnessScript;
res.isP2SH = true; if (input.witnessScript) {
res.script = input.witnessScript;
} else if (input.redeemScript) {
res.script = input.redeemScript; res.script = input.redeemScript;
} else { } else {
if (input.nonWitnessUtxo) {
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
cache, cache,
input, input,
@ -939,23 +958,13 @@ function getScriptFromInput(inputIndex, input, cache) {
); );
const prevoutIndex = unsignedTx.ins[inputIndex].index; const prevoutIndex = unsignedTx.ins[inputIndex].index;
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script; res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
}
} else if (input.witnessUtxo) { } else if (input.witnessUtxo) {
res.isSegwit = true; res.script = input.witnessUtxo.script;
res.isP2SH = !!input.redeemScript;
res.isP2WSH = !!input.witnessScript;
if (input.witnessScript) {
res.script = input.witnessScript;
} else if (input.redeemScript) {
res.script = payments.p2wpkh({
hash: input.redeemScript.slice(2),
}).output;
} else {
res.script = payments.p2wpkh({
hash: input.witnessUtxo.script.slice(2),
}).output;
} }
} }
if (input.witnessScript || isP2WPKH(res.script)) {
res.isSegwit = true;
}
return res; return res;
} }
function getSignersFromHD(inputIndex, inputs, hdKeyPair) { function getSignersFromHD(inputIndex, inputs, hdKeyPair) {

View file

@ -513,7 +513,7 @@
{ {
"type": "P2SH-P2WPKH", "type": "P2SH-P2WPKH",
"psbt": "cHNidP8BAFUCAAAAATIK6DMTn8bbrG7ZdiiVU3/YAgzyk3dwa56En58YfXbDAAAAAAD/////AYA4AQAAAAAAGXapFNU4SHWUW9ZNz+BcxCiuU/7UtJoMiKwAAAAAAAEAvgIAAAABly2BCiYZ3slqurlLwE7b8UXINYKfrJ9sQlBovzBAwFsBAAAAa0gwRQIhAJJ+Hyniw+KneWomeQYrP1duH7cfQ3j8GN6/RshZCfuvAiBux7Uu/5QqmSmL+LjoWZY2b9TWdluY6zLTkQWIornmYwEhAvUo9Sy7Pu44z84ZZPrQMQxBPpDJyy9WlLQMGdGIuUy7/////wGQXwEAAAAAABepFPwojcWCH2oE9dUjMmLC3bdK/xeWhwAAAAAiAgKj88rhJwk3Zxm0p0Rp+xC/6cxmj+I741DHPWPWN7iA+0cwRAIgTRhd9WUpoHYl9tUVmoJ336fJAJInIjdYsoatvRiW8hgCIGOYMlpKRHiHA428Sfa2CdAIIGGQCGhuIgIzj2FN6USnAQEEFgAU4sZupXPxqhcsOB1ghJxBvH4XcesAAA==", "psbt": "cHNidP8BAFUCAAAAATIK6DMTn8bbrG7ZdiiVU3/YAgzyk3dwa56En58YfXbDAAAAAAD/////AYA4AQAAAAAAGXapFNU4SHWUW9ZNz+BcxCiuU/7UtJoMiKwAAAAAAAEAvgIAAAABly2BCiYZ3slqurlLwE7b8UXINYKfrJ9sQlBovzBAwFsBAAAAa0gwRQIhAJJ+Hyniw+KneWomeQYrP1duH7cfQ3j8GN6/RshZCfuvAiBux7Uu/5QqmSmL+LjoWZY2b9TWdluY6zLTkQWIornmYwEhAvUo9Sy7Pu44z84ZZPrQMQxBPpDJyy9WlLQMGdGIuUy7/////wGQXwEAAAAAABepFPwojcWCH2oE9dUjMmLC3bdK/xeWhwAAAAAiAgKj88rhJwk3Zxm0p0Rp+xC/6cxmj+I741DHPWPWN7iA+0cwRAIgTRhd9WUpoHYl9tUVmoJ336fJAJInIjdYsoatvRiW8hgCIGOYMlpKRHiHA428Sfa2CdAIIGGQCGhuIgIzj2FN6USnAQEEFgAU4sZupXPxqhcsOB1ghJxBvH4XcesAAA==",
"result": "cHNidP8BAFUCAAAAATIK6DMTn8bbrG7ZdiiVU3/YAgzyk3dwa56En58YfXbDAAAAAAD/////AYA4AQAAAAAAGXapFNU4SHWUW9ZNz+BcxCiuU/7UtJoMiKwAAAAAAAEAvgIAAAABly2BCiYZ3slqurlLwE7b8UXINYKfrJ9sQlBovzBAwFsBAAAAa0gwRQIhAJJ+Hyniw+KneWomeQYrP1duH7cfQ3j8GN6/RshZCfuvAiBux7Uu/5QqmSmL+LjoWZY2b9TWdluY6zLTkQWIornmYwEhAvUo9Sy7Pu44z84ZZPrQMQxBPpDJyy9WlLQMGdGIuUy7/////wGQXwEAAAAAABepFPwojcWCH2oE9dUjMmLC3bdK/xeWhwAAAAABBxcWABTixm6lc/GqFyw4HWCEnEG8fhdx6wAA" "result": "cHNidP8BAFUCAAAAATIK6DMTn8bbrG7ZdiiVU3/YAgzyk3dwa56En58YfXbDAAAAAAD/////AYA4AQAAAAAAGXapFNU4SHWUW9ZNz+BcxCiuU/7UtJoMiKwAAAAAAAEAvgIAAAABly2BCiYZ3slqurlLwE7b8UXINYKfrJ9sQlBovzBAwFsBAAAAa0gwRQIhAJJ+Hyniw+KneWomeQYrP1duH7cfQ3j8GN6/RshZCfuvAiBux7Uu/5QqmSmL+LjoWZY2b9TWdluY6zLTkQWIornmYwEhAvUo9Sy7Pu44z84ZZPrQMQxBPpDJyy9WlLQMGdGIuUy7/////wGQXwEAAAAAABepFPwojcWCH2oE9dUjMmLC3bdK/xeWhwAAAAABBxcWABTixm6lc/GqFyw4HWCEnEG8fhdx6wEIawJHMEQCIE0YXfVlKaB2JfbVFZqCd9+nyQCSJyI3WLKGrb0YlvIYAiBjmDJaSkR4hwONvEn2tgnQCCBhkAhobiICM49hTelEpwEhAqPzyuEnCTdnGbSnRGn7EL/pzGaP4jvjUMc9Y9Y3uID7AAA="
}, },
{ {
"type": "P2WPKH", "type": "P2WPKH",

View file

@ -282,6 +282,50 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
}); });
}); });
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input with nonWitnessUtxo', async () => {
const p2sh = createPayment('p2sh-p2wpkh');
const inputData = await getInputData(5e4, p2sh.payment, false, 'p2sh');
const inputData2 = await getInputData(5e4, p2sh.payment, false, 'p2sh');
{
const {
hash,
index,
nonWitnessUtxo,
redeemScript,
} = inputData;
assert.deepStrictEqual(
{ hash, index, nonWitnessUtxo, redeemScript },
inputData,
);
}
const keyPair = p2sh.keys[0];
const outputData = {
script: p2sh.payment.output, // sending to myself for fun
value: 2e4,
};
const outputData2 = {
script: p2sh.payment.output, // sending to myself for fun
value: 7e4,
};
const tx = new bitcoin.Psbt()
.addInputs([inputData, inputData2])
.addOutputs([outputData, outputData2])
.signAllInputs(keyPair)
.finalizeAllInputs()
.extractTransaction();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: p2sh.payment.address,
vout: 0,
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => { it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => {
// the only thing that changes is you don't give a redeemscript for input data // the only thing that changes is you don't give a redeemscript for input data
@ -316,6 +360,40 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
}); });
}); });
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input with nonWitnessUtxo', async () => {
// the only thing that changes is you don't give a redeemscript for input data
const p2wpkh = createPayment('p2wpkh');
const inputData = await getInputData(5e4, p2wpkh.payment, false, 'noredeem');
{
const { hash, index, nonWitnessUtxo } = inputData;
assert.deepStrictEqual({ hash, index, nonWitnessUtxo }, inputData);
}
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInput(0, p2wpkh.keys[0]);
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => { it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => {
const p2wsh = createPayment('p2wsh-p2pk'); const p2wsh = createPayment('p2wsh-p2pk');
const inputData = await getInputData(5e4, p2wsh.payment, true, 'p2wsh'); const inputData = await getInputData(5e4, p2wsh.payment, true, 'p2wsh');
@ -356,6 +434,46 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
}); });
}); });
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input with nonWitnessUtxo', async () => {
const p2wsh = createPayment('p2wsh-p2pk');
const inputData = await getInputData(5e4, p2wsh.payment, false, 'p2wsh');
{
const {
hash,
index,
nonWitnessUtxo,
witnessScript, // NEW: A Buffer of the witnessScript
} = inputData;
assert.deepStrictEqual(
{ hash, index, nonWitnessUtxo, witnessScript },
inputData,
);
}
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInput(0, p2wsh.keys[0]);
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => { it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => {
const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)'); const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)');
const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh-p2wsh'); const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh-p2wsh');
@ -406,6 +524,56 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
}); });
}); });
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input with nonWitnessUtxo', async () => {
const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)');
const inputData = await getInputData(5e4, p2sh.payment, false, 'p2sh-p2wsh');
{
const {
hash,
index,
nonWitnessUtxo,
redeemScript,
witnessScript,
} = inputData;
assert.deepStrictEqual(
{ hash, index, nonWitnessUtxo, redeemScript, witnessScript },
inputData,
);
}
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInput(0, p2sh.keys[0])
.signInput(0, p2sh.keys[2])
.signInput(0, p2sh.keys[3]);
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
assert.strictEqual(
psbt.validateSignaturesOfInput(0, p2sh.keys[3].publicKey),
true,
);
assert.throws(() => {
psbt.validateSignaturesOfInput(0, p2sh.keys[1].publicKey);
}, new RegExp('No signatures for this pubkey'));
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input using HD', async () => { it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input using HD', async () => {
const hdRoot = bip32.fromSeed(rng(64)); const hdRoot = bip32.fromSeed(rng(64));
const masterFingerprint = hdRoot.fingerprint; const masterFingerprint = hdRoot.fingerprint;

View file

@ -1025,7 +1025,7 @@ function getHashForSig(
} }
const prevoutIndex = unsignedTx.ins[inputIndex].index; const prevoutIndex = unsignedTx.ins[inputIndex].index;
const prevout = nonWitnessUtxoTx.outs[prevoutIndex]; const prevout = nonWitnessUtxoTx.outs[prevoutIndex] as Output;
if (input.redeemScript) { if (input.redeemScript) {
// If a redeemScript is provided, the scriptPubKey must be for that redeemScript // If a redeemScript is provided, the scriptPubKey must be for that redeemScript
@ -1035,14 +1035,29 @@ function getHashForSig(
script = prevout.script; script = prevout.script;
} }
if (isP2WPKH(script) || isP2WSHScript(script)) { if (isP2WSHScript(script)) {
throw new Error( if (!input.witnessScript)
`Input #${inputIndex} has nonWitnessUtxo but segwit script: ` + throw new Error('Segwit input needs witnessScript if not P2WPKH');
`${script.toString('hex')}`, checkWitnessScript(inputIndex, script, input.witnessScript);
hash = unsignedTx.hashForWitnessV0(
inputIndex,
input.witnessScript,
prevout.value,
sighashType,
); );
} script = input.witnessScript;
} else if (isP2WPKH(script)) {
// P2WPKH uses the P2PKH template for prevoutScript when signing
const signingScript = payments.p2pkh({ hash: script.slice(2) }).output!;
hash = unsignedTx.hashForWitnessV0(
inputIndex,
signingScript,
prevout.value,
sighashType,
);
} else {
hash = unsignedTx.hashForSignature(inputIndex, script, sighashType); hash = unsignedTx.hashForSignature(inputIndex, script, sighashType);
}
} else if (input.witnessUtxo) { } else if (input.witnessUtxo) {
let _script: Buffer; // so we don't shadow the `let script` above let _script: Buffer; // so we don't shadow the `let script` above
if (input.redeemScript) { if (input.redeemScript) {
@ -1165,11 +1180,14 @@ function getScriptFromInput(
isP2SH: false, isP2SH: false,
isP2WSH: false, isP2WSH: false,
}; };
if (input.nonWitnessUtxo) { res.isP2SH = !!input.redeemScript;
if (input.redeemScript) { res.isP2WSH = !!input.witnessScript;
res.isP2SH = true; if (input.witnessScript) {
res.script = input.witnessScript;
} else if (input.redeemScript) {
res.script = input.redeemScript; res.script = input.redeemScript;
} else { } else {
if (input.nonWitnessUtxo) {
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
cache, cache,
input, input,
@ -1177,23 +1195,13 @@ function getScriptFromInput(
); );
const prevoutIndex = unsignedTx.ins[inputIndex].index; const prevoutIndex = unsignedTx.ins[inputIndex].index;
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script; res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
}
} else if (input.witnessUtxo) { } else if (input.witnessUtxo) {
res.isSegwit = true; res.script = input.witnessUtxo.script;
res.isP2SH = !!input.redeemScript;
res.isP2WSH = !!input.witnessScript;
if (input.witnessScript) {
res.script = input.witnessScript;
} else if (input.redeemScript) {
res.script = payments.p2wpkh({
hash: input.redeemScript.slice(2),
}).output!;
} else {
res.script = payments.p2wpkh({
hash: input.witnessUtxo.script.slice(2),
}).output!;
} }
} }
if (input.witnessScript || isP2WPKH(res.script!)) {
res.isSegwit = true;
}
return res; return res;
} }