Skip to content

Commit d02ee01

Browse files
authored
Merge pull request #1461 from bitcoinjs/allowNonForSegwit
[PSBT] Allow nonWitnessUtxo with segwit
2 parents 27473d7 + 2389b5b commit d02ee01

File tree

6 files changed

+175
-57
lines changed

6 files changed

+175
-57
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bitcoinjs-lib",
3-
"version": "5.1.3",
3+
"version": "5.1.4",
44
"description": "Client-side Bitcoin JavaScript library",
55
"main": "./src/index.js",
66
"types": "./types/index.d.ts",
@@ -47,7 +47,7 @@
4747
"dependencies": {
4848
"@types/node": "10.12.18",
4949
"bech32": "^1.1.2",
50-
"bip174": "^1.0.0",
50+
"bip174": "^1.0.1",
5151
"bip32": "^2.0.4",
5252
"bip66": "^1.1.0",
5353
"bitcoin-ops": "^1.4.0",

src/psbt.js

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -815,13 +815,29 @@ function getHashForSig(inputIndex, input, cache, sighashTypes) {
815815
} else {
816816
script = prevout.script;
817817
}
818-
if (isP2WPKH(script) || isP2WSHScript(script)) {
819-
throw new Error(
820-
`Input #${inputIndex} has nonWitnessUtxo but segwit script: ` +
821-
`${script.toString('hex')}`,
818+
if (isP2WSHScript(script)) {
819+
if (!input.witnessScript)
820+
throw new Error('Segwit input needs witnessScript if not P2WPKH');
821+
checkWitnessScript(inputIndex, script, input.witnessScript);
822+
hash = unsignedTx.hashForWitnessV0(
823+
inputIndex,
824+
input.witnessScript,
825+
prevout.value,
826+
sighashType,
827+
);
828+
script = input.witnessScript;
829+
} else if (isP2WPKH(script)) {
830+
// P2WPKH uses the P2PKH template for prevoutScript when signing
831+
const signingScript = payments.p2pkh({ hash: script.slice(2) }).output;
832+
hash = unsignedTx.hashForWitnessV0(
833+
inputIndex,
834+
signingScript,
835+
prevout.value,
836+
sighashType,
822837
);
838+
} else {
839+
hash = unsignedTx.hashForSignature(inputIndex, script, sighashType);
823840
}
824-
hash = unsignedTx.hashForSignature(inputIndex, script, sighashType);
825841
} else if (input.witnessUtxo) {
826842
let _script; // so we don't shadow the `let script` above
827843
if (input.redeemScript) {
@@ -927,34 +943,27 @@ function getScriptFromInput(inputIndex, input, cache) {
927943
isP2SH: false,
928944
isP2WSH: false,
929945
};
930-
if (input.nonWitnessUtxo) {
931-
if (input.redeemScript) {
932-
res.isP2SH = true;
933-
res.script = input.redeemScript;
934-
} else {
946+
res.isP2SH = !!input.redeemScript;
947+
res.isP2WSH = !!input.witnessScript;
948+
if (input.witnessScript) {
949+
res.script = input.witnessScript;
950+
} else if (input.redeemScript) {
951+
res.script = input.redeemScript;
952+
} else {
953+
if (input.nonWitnessUtxo) {
935954
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
936955
cache,
937956
input,
938957
inputIndex,
939958
);
940959
const prevoutIndex = unsignedTx.ins[inputIndex].index;
941960
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
961+
} else if (input.witnessUtxo) {
962+
res.script = input.witnessUtxo.script;
942963
}
943-
} else if (input.witnessUtxo) {
964+
}
965+
if (input.witnessScript || isP2WPKH(res.script)) {
944966
res.isSegwit = true;
945-
res.isP2SH = !!input.redeemScript;
946-
res.isP2WSH = !!input.witnessScript;
947-
if (input.witnessScript) {
948-
res.script = input.witnessScript;
949-
} else if (input.redeemScript) {
950-
res.script = payments.p2wpkh({
951-
hash: input.redeemScript.slice(2),
952-
}).output;
953-
} else {
954-
res.script = payments.p2wpkh({
955-
hash: input.witnessUtxo.script.slice(2),
956-
}).output;
957-
}
958967
}
959968
return res;
960969
}

test/fixtures/psbt.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@
513513
{
514514
"type": "P2SH-P2WPKH",
515515
"psbt": "cHNidP8BAFUCAAAAATIK6DMTn8bbrG7ZdiiVU3/YAgzyk3dwa56En58YfXbDAAAAAAD/////AYA4AQAAAAAAGXapFNU4SHWUW9ZNz+BcxCiuU/7UtJoMiKwAAAAAAAEAvgIAAAABly2BCiYZ3slqurlLwE7b8UXINYKfrJ9sQlBovzBAwFsBAAAAa0gwRQIhAJJ+Hyniw+KneWomeQYrP1duH7cfQ3j8GN6/RshZCfuvAiBux7Uu/5QqmSmL+LjoWZY2b9TWdluY6zLTkQWIornmYwEhAvUo9Sy7Pu44z84ZZPrQMQxBPpDJyy9WlLQMGdGIuUy7/////wGQXwEAAAAAABepFPwojcWCH2oE9dUjMmLC3bdK/xeWhwAAAAAiAgKj88rhJwk3Zxm0p0Rp+xC/6cxmj+I741DHPWPWN7iA+0cwRAIgTRhd9WUpoHYl9tUVmoJ336fJAJInIjdYsoatvRiW8hgCIGOYMlpKRHiHA428Sfa2CdAIIGGQCGhuIgIzj2FN6USnAQEEFgAU4sZupXPxqhcsOB1ghJxBvH4XcesAAA==",
516-
"result": "cHNidP8BAFUCAAAAATIK6DMTn8bbrG7ZdiiVU3/YAgzyk3dwa56En58YfXbDAAAAAAD/////AYA4AQAAAAAAGXapFNU4SHWUW9ZNz+BcxCiuU/7UtJoMiKwAAAAAAAEAvgIAAAABly2BCiYZ3slqurlLwE7b8UXINYKfrJ9sQlBovzBAwFsBAAAAa0gwRQIhAJJ+Hyniw+KneWomeQYrP1duH7cfQ3j8GN6/RshZCfuvAiBux7Uu/5QqmSmL+LjoWZY2b9TWdluY6zLTkQWIornmYwEhAvUo9Sy7Pu44z84ZZPrQMQxBPpDJyy9WlLQMGdGIuUy7/////wGQXwEAAAAAABepFPwojcWCH2oE9dUjMmLC3bdK/xeWhwAAAAABBxcWABTixm6lc/GqFyw4HWCEnEG8fhdx6wAA"
516+
"result": "cHNidP8BAFUCAAAAATIK6DMTn8bbrG7ZdiiVU3/YAgzyk3dwa56En58YfXbDAAAAAAD/////AYA4AQAAAAAAGXapFNU4SHWUW9ZNz+BcxCiuU/7UtJoMiKwAAAAAAAEAvgIAAAABly2BCiYZ3slqurlLwE7b8UXINYKfrJ9sQlBovzBAwFsBAAAAa0gwRQIhAJJ+Hyniw+KneWomeQYrP1duH7cfQ3j8GN6/RshZCfuvAiBux7Uu/5QqmSmL+LjoWZY2b9TWdluY6zLTkQWIornmYwEhAvUo9Sy7Pu44z84ZZPrQMQxBPpDJyy9WlLQMGdGIuUy7/////wGQXwEAAAAAABepFPwojcWCH2oE9dUjMmLC3bdK/xeWhwAAAAABBxcWABTixm6lc/GqFyw4HWCEnEG8fhdx6wEIawJHMEQCIE0YXfVlKaB2JfbVFZqCd9+nyQCSJyI3WLKGrb0YlvIYAiBjmDJaSkR4hwONvEn2tgnQCCBhkAhobiICM49hTelEpwEhAqPzyuEnCTdnGbSnRGn7EL/pzGaP4jvjUMc9Y9Y3uID7AAA="
517517
},
518518
{
519519
"type": "P2WPKH",

test/integration/transactions-psbt.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,36 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
282282
});
283283
});
284284

285+
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input with nonWitnessUtxo', async () => {
286+
// For learning purposes, ignore this test.
287+
// REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
288+
const p2sh = createPayment('p2sh-p2wpkh');
289+
const inputData = await getInputData(5e4, p2sh.payment, false, 'p2sh');
290+
const inputData2 = await getInputData(5e4, p2sh.payment, false, 'p2sh');
291+
const keyPair = p2sh.keys[0];
292+
const outputData = {
293+
script: p2sh.payment.output,
294+
value: 2e4,
295+
};
296+
const outputData2 = {
297+
script: p2sh.payment.output,
298+
value: 7e4,
299+
};
300+
const tx = new bitcoin.Psbt()
301+
.addInputs([inputData, inputData2])
302+
.addOutputs([outputData, outputData2])
303+
.signAllInputs(keyPair)
304+
.finalizeAllInputs()
305+
.extractTransaction();
306+
await regtestUtils.broadcast(tx.toHex());
307+
await regtestUtils.verify({
308+
txId: tx.getId(),
309+
address: p2sh.payment.address,
310+
vout: 0,
311+
value: 2e4,
312+
});
313+
});
314+
285315
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => {
286316
// the only thing that changes is you don't give a redeemscript for input data
287317

@@ -316,6 +346,29 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
316346
});
317347
});
318348

349+
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input with nonWitnessUtxo', async () => {
350+
// For learning purposes, ignore this test.
351+
// REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
352+
const p2wpkh = createPayment('p2wpkh');
353+
const inputData = await getInputData(5e4, p2wpkh.payment, false, 'noredeem');
354+
const psbt = new bitcoin.Psbt({ network: regtest })
355+
.addInput(inputData)
356+
.addOutput({
357+
address: regtestUtils.RANDOM_ADDRESS,
358+
value: 2e4,
359+
})
360+
.signInput(0, p2wpkh.keys[0]);
361+
psbt.finalizeAllInputs();
362+
const tx = psbt.extractTransaction();
363+
await regtestUtils.broadcast(tx.toHex());
364+
await regtestUtils.verify({
365+
txId: tx.getId(),
366+
address: regtestUtils.RANDOM_ADDRESS,
367+
vout: 0,
368+
value: 2e4,
369+
});
370+
});
371+
319372
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => {
320373
const p2wsh = createPayment('p2wsh-p2pk');
321374
const inputData = await getInputData(5e4, p2wsh.payment, true, 'p2wsh');
@@ -356,6 +409,29 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
356409
});
357410
});
358411

412+
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input with nonWitnessUtxo', async () => {
413+
// For learning purposes, ignore this test.
414+
// REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
415+
const p2wsh = createPayment('p2wsh-p2pk');
416+
const inputData = await getInputData(5e4, p2wsh.payment, false, 'p2wsh');
417+
const psbt = new bitcoin.Psbt({ network: regtest })
418+
.addInput(inputData)
419+
.addOutput({
420+
address: regtestUtils.RANDOM_ADDRESS,
421+
value: 2e4,
422+
})
423+
.signInput(0, p2wsh.keys[0]);
424+
psbt.finalizeAllInputs();
425+
const tx = psbt.extractTransaction();
426+
await regtestUtils.broadcast(tx.toHex());
427+
await regtestUtils.verify({
428+
txId: tx.getId(),
429+
address: regtestUtils.RANDOM_ADDRESS,
430+
vout: 0,
431+
value: 2e4,
432+
});
433+
});
434+
359435
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => {
360436
const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)');
361437
const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh-p2wsh');
@@ -406,6 +482,31 @@ describe('bitcoinjs-lib (transactions with psbt)', () => {
406482
});
407483
});
408484

485+
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input with nonWitnessUtxo', async () => {
486+
// For learning purposes, ignore this test.
487+
// REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
488+
const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)');
489+
const inputData = await getInputData(5e4, p2sh.payment, false, 'p2sh-p2wsh');
490+
const psbt = new bitcoin.Psbt({ network: regtest })
491+
.addInput(inputData)
492+
.addOutput({
493+
address: regtestUtils.RANDOM_ADDRESS,
494+
value: 2e4,
495+
})
496+
.signInput(0, p2sh.keys[0])
497+
.signInput(0, p2sh.keys[2])
498+
.signInput(0, p2sh.keys[3]);
499+
psbt.finalizeAllInputs();
500+
const tx = psbt.extractTransaction();
501+
await regtestUtils.broadcast(tx.toHex());
502+
await regtestUtils.verify({
503+
txId: tx.getId(),
504+
address: regtestUtils.RANDOM_ADDRESS,
505+
vout: 0,
506+
value: 2e4,
507+
});
508+
});
509+
409510
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input using HD', async () => {
410511
const hdRoot = bip32.fromSeed(rng(64));
411512
const masterFingerprint = hdRoot.fingerprint;

ts_src/psbt.ts

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,7 +1025,7 @@ function getHashForSig(
10251025
}
10261026

10271027
const prevoutIndex = unsignedTx.ins[inputIndex].index;
1028-
const prevout = nonWitnessUtxoTx.outs[prevoutIndex];
1028+
const prevout = nonWitnessUtxoTx.outs[prevoutIndex] as Output;
10291029

10301030
if (input.redeemScript) {
10311031
// If a redeemScript is provided, the scriptPubKey must be for that redeemScript
@@ -1035,14 +1035,29 @@ function getHashForSig(
10351035
script = prevout.script;
10361036
}
10371037

1038-
if (isP2WPKH(script) || isP2WSHScript(script)) {
1039-
throw new Error(
1040-
`Input #${inputIndex} has nonWitnessUtxo but segwit script: ` +
1041-
`${script.toString('hex')}`,
1038+
if (isP2WSHScript(script)) {
1039+
if (!input.witnessScript)
1040+
throw new Error('Segwit input needs witnessScript if not P2WPKH');
1041+
checkWitnessScript(inputIndex, script, input.witnessScript);
1042+
hash = unsignedTx.hashForWitnessV0(
1043+
inputIndex,
1044+
input.witnessScript,
1045+
prevout.value,
1046+
sighashType,
10421047
);
1048+
script = input.witnessScript;
1049+
} else if (isP2WPKH(script)) {
1050+
// P2WPKH uses the P2PKH template for prevoutScript when signing
1051+
const signingScript = payments.p2pkh({ hash: script.slice(2) }).output!;
1052+
hash = unsignedTx.hashForWitnessV0(
1053+
inputIndex,
1054+
signingScript,
1055+
prevout.value,
1056+
sighashType,
1057+
);
1058+
} else {
1059+
hash = unsignedTx.hashForSignature(inputIndex, script, sighashType);
10431060
}
1044-
1045-
hash = unsignedTx.hashForSignature(inputIndex, script, sighashType);
10461061
} else if (input.witnessUtxo) {
10471062
let _script: Buffer; // so we don't shadow the `let script` above
10481063
if (input.redeemScript) {
@@ -1165,34 +1180,27 @@ function getScriptFromInput(
11651180
isP2SH: false,
11661181
isP2WSH: false,
11671182
};
1168-
if (input.nonWitnessUtxo) {
1169-
if (input.redeemScript) {
1170-
res.isP2SH = true;
1171-
res.script = input.redeemScript;
1172-
} else {
1183+
res.isP2SH = !!input.redeemScript;
1184+
res.isP2WSH = !!input.witnessScript;
1185+
if (input.witnessScript) {
1186+
res.script = input.witnessScript;
1187+
} else if (input.redeemScript) {
1188+
res.script = input.redeemScript;
1189+
} else {
1190+
if (input.nonWitnessUtxo) {
11731191
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
11741192
cache,
11751193
input,
11761194
inputIndex,
11771195
);
11781196
const prevoutIndex = unsignedTx.ins[inputIndex].index;
11791197
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
1198+
} else if (input.witnessUtxo) {
1199+
res.script = input.witnessUtxo.script;
11801200
}
1181-
} else if (input.witnessUtxo) {
1201+
}
1202+
if (input.witnessScript || isP2WPKH(res.script!)) {
11821203
res.isSegwit = true;
1183-
res.isP2SH = !!input.redeemScript;
1184-
res.isP2WSH = !!input.witnessScript;
1185-
if (input.witnessScript) {
1186-
res.script = input.witnessScript;
1187-
} else if (input.redeemScript) {
1188-
res.script = payments.p2wpkh({
1189-
hash: input.redeemScript.slice(2),
1190-
}).output!;
1191-
} else {
1192-
res.script = payments.p2wpkh({
1193-
hash: input.witnessUtxo.script.slice(2),
1194-
}).output!;
1195-
}
11961204
}
11971205
return res;
11981206
}

0 commit comments

Comments
 (0)