Skip to content

Commit c9f399e

Browse files
committed
Add getInputType
1 parent d024834 commit c9f399e

File tree

5 files changed

+208
-17
lines changed

5 files changed

+208
-17
lines changed

src/psbt.js

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ class Psbt {
189189
);
190190
}
191191
checkInputsForPartialSig(this.data.inputs, 'addInput');
192+
if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript);
192193
const c = this.__CACHE;
193194
this.data.addInput(inputData);
194195
const txIn = c.__TX.ins[c.__TX.ins.length - 1];
@@ -285,6 +286,20 @@ class Psbt {
285286
this.data.clearFinalizedInput(inputIndex);
286287
return this;
287288
}
289+
getInputType(inputIndex) {
290+
const input = utils_1.checkForInput(this.data.inputs, inputIndex);
291+
const script = getScriptFromUtxo(inputIndex, input, this.__CACHE);
292+
const result = getMeaningfulScript(
293+
script,
294+
inputIndex,
295+
'input',
296+
input.redeemScript,
297+
input.witnessScript,
298+
);
299+
const type = result.type === 'raw' ? '' : result.type + '-';
300+
const mainType = classifyScript(result.meaningfulScript);
301+
return type + mainType;
302+
}
288303
inputHasPubkey(inputIndex, pubkey) {
289304
const input = utils_1.checkForInput(this.data.inputs, inputIndex);
290305
return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE);
@@ -538,6 +553,7 @@ class Psbt {
538553
return this;
539554
}
540555
updateInput(inputIndex, updateData) {
556+
if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript);
541557
this.data.updateInput(inputIndex, updateData);
542558
if (updateData.nonWitnessUtxo) {
543559
addNonWitnessTxCache(
@@ -924,7 +940,7 @@ function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) {
924940
input.redeemScript,
925941
input.witnessScript,
926942
);
927-
if (['p2shp2wsh', 'p2wsh'].indexOf(type) >= 0) {
943+
if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) {
928944
hash = unsignedTx.hashForWitnessV0(
929945
inputIndex,
930946
meaningfulScript,
@@ -1220,20 +1236,22 @@ function nonWitnessUtxoTxFromCache(cache, input, inputIndex) {
12201236
}
12211237
return c[inputIndex];
12221238
}
1223-
function pubkeyInInput(pubkey, input, inputIndex, cache) {
1224-
let script;
1239+
function getScriptFromUtxo(inputIndex, input, cache) {
12251240
if (input.witnessUtxo !== undefined) {
1226-
script = input.witnessUtxo.script;
1241+
return input.witnessUtxo.script;
12271242
} else if (input.nonWitnessUtxo !== undefined) {
12281243
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
12291244
cache,
12301245
input,
12311246
inputIndex,
12321247
);
1233-
script = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
1248+
return nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
12341249
} else {
12351250
throw new Error("Can't find pubkey in input without Utxo data");
12361251
}
1252+
}
1253+
function pubkeyInInput(pubkey, input, inputIndex, cache) {
1254+
const script = getScriptFromUtxo(inputIndex, input, cache);
12371255
const { meaningfulScript } = getMeaningfulScript(
12381256
script,
12391257
inputIndex,
@@ -1275,9 +1293,11 @@ function getMeaningfulScript(
12751293
meaningfulScript = witnessScript;
12761294
checkRedeemScript(index, script, redeemScript, ioType);
12771295
checkWitnessScript(index, redeemScript, witnessScript, ioType);
1296+
checkInvalidP2WSH(meaningfulScript);
12781297
} else if (isP2WSH) {
12791298
meaningfulScript = witnessScript;
12801299
checkWitnessScript(index, script, witnessScript, ioType);
1300+
checkInvalidP2WSH(meaningfulScript);
12811301
} else if (isP2SH) {
12821302
meaningfulScript = redeemScript;
12831303
checkRedeemScript(index, script, redeemScript, ioType);
@@ -1287,14 +1307,19 @@ function getMeaningfulScript(
12871307
return {
12881308
meaningfulScript,
12891309
type: isP2SHP2WSH
1290-
? 'p2shp2wsh'
1310+
? 'p2sh-p2wsh'
12911311
: isP2SH
12921312
? 'p2sh'
12931313
: isP2WSH
12941314
? 'p2wsh'
12951315
: 'raw',
12961316
};
12971317
}
1318+
function checkInvalidP2WSH(script) {
1319+
if (isP2WPKH(script) || isP2SHScript(script)) {
1320+
throw new Error('P2WPKH or P2SH can not be contained within P2WSH');
1321+
}
1322+
}
12981323
function pubkeyInScript(pubkey, script) {
12991324
const pubkeyHash = crypto_1.hash160(pubkey);
13001325
const decompiled = bscript.decompile(script);

test/fixtures/psbt.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,24 @@
313313
},
314314
"exception": "Invalid arguments for Psbt\\.addInput\\. Requires single object with at least \\[hash\\] and \\[index\\]"
315315
},
316+
{
317+
"description": "checks for invalid p2wsh witnessScript",
318+
"inputData": {
319+
"hash": "Buffer.from('000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f', 'hex')",
320+
"index": 0,
321+
"witnessScript": "Buffer.from('0014000102030405060708090a0b0c0d0e0f00010203', 'hex')"
322+
},
323+
"exception": "P2WPKH or P2SH can not be contained within P2WSH"
324+
},
325+
{
326+
"description": "checks for invalid p2wsh witnessScript",
327+
"inputData": {
328+
"hash": "Buffer.from('000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f', 'hex')",
329+
"index": 0,
330+
"witnessScript": "Buffer.from('a914000102030405060708090a0b0c0d0e0f0001020387', 'hex')"
331+
},
332+
"exception": "P2WPKH or P2SH can not be contained within P2WSH"
333+
},
316334
{
317335
"description": "should be equal",
318336
"inputData": {

test/psbt.spec.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,95 @@ describe(`Psbt`, () => {
542542
});
543543
});
544544

545+
describe('getInputType', () => {
546+
const { publicKey } = ECPair.makeRandom();
547+
const p2wpkhPub = (pubkey: Buffer): Buffer =>
548+
payments.p2wpkh({
549+
pubkey,
550+
}).output!;
551+
const p2pkhPub = (pubkey: Buffer): Buffer =>
552+
payments.p2pkh({
553+
pubkey,
554+
}).output!;
555+
const p2shOut = (output: Buffer): Buffer =>
556+
payments.p2sh({
557+
redeem: { output },
558+
}).output!;
559+
const p2wshOut = (output: Buffer): Buffer =>
560+
payments.p2wsh({
561+
redeem: { output },
562+
}).output!;
563+
const p2shp2wshOut = (output: Buffer): Buffer => p2shOut(p2wshOut(output));
564+
const noOuter = (output: Buffer): Buffer => output;
565+
566+
function getInputTypeTest({
567+
innerScript,
568+
outerScript,
569+
redeemGetter,
570+
witnessGetter,
571+
expectedType,
572+
}: any): void {
573+
const psbt = new Psbt();
574+
psbt.addInput({
575+
hash:
576+
'0000000000000000000000000000000000000000000000000000000000000000',
577+
index: 0,
578+
witnessUtxo: {
579+
script: outerScript(innerScript(publicKey)),
580+
value: 2e3,
581+
},
582+
...(redeemGetter ? { redeemScript: redeemGetter(publicKey) } : {}),
583+
...(witnessGetter ? { witnessScript: witnessGetter(publicKey) } : {}),
584+
});
585+
const type = psbt.getInputType(0);
586+
assert.strictEqual(type, expectedType, 'incorrect input type');
587+
}
588+
[
589+
{
590+
innerScript: p2pkhPub,
591+
outerScript: noOuter,
592+
redeemGetter: null,
593+
witnessGetter: null,
594+
expectedType: 'pubkeyhash',
595+
},
596+
{
597+
innerScript: p2wpkhPub,
598+
outerScript: noOuter,
599+
redeemGetter: null,
600+
witnessGetter: null,
601+
expectedType: 'witnesspubkeyhash',
602+
},
603+
{
604+
innerScript: p2pkhPub,
605+
outerScript: p2shOut,
606+
redeemGetter: p2pkhPub,
607+
witnessGetter: null,
608+
expectedType: 'p2sh-pubkeyhash',
609+
},
610+
{
611+
innerScript: p2wpkhPub,
612+
outerScript: p2shOut,
613+
redeemGetter: p2wpkhPub,
614+
witnessGetter: null,
615+
expectedType: 'p2sh-witnesspubkeyhash',
616+
},
617+
{
618+
innerScript: p2pkhPub,
619+
outerScript: p2wshOut,
620+
redeemGetter: null,
621+
witnessGetter: p2pkhPub,
622+
expectedType: 'p2wsh-pubkeyhash',
623+
},
624+
{
625+
innerScript: p2pkhPub,
626+
outerScript: p2shp2wshOut,
627+
redeemGetter: (pk: Buffer): Buffer => p2wshOut(p2pkhPub(pk)),
628+
witnessGetter: p2pkhPub,
629+
expectedType: 'p2sh-p2wsh-pubkeyhash',
630+
},
631+
].forEach(getInputTypeTest);
632+
});
633+
545634
describe('inputHasPubkey', () => {
546635
it('should throw', () => {
547636
const psbt = new Psbt();

ts_src/psbt.ts

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ export class Psbt {
242242
);
243243
}
244244
checkInputsForPartialSig(this.data.inputs, 'addInput');
245+
if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript);
245246
const c = this.__CACHE;
246247
this.data.addInput(inputData);
247248
const txIn = c.__TX.ins[c.__TX.ins.length - 1];
@@ -355,6 +356,21 @@ export class Psbt {
355356
return this;
356357
}
357358

359+
getInputType(inputIndex: number): AllScriptType {
360+
const input = checkForInput(this.data.inputs, inputIndex);
361+
const script = getScriptFromUtxo(inputIndex, input, this.__CACHE);
362+
const result = getMeaningfulScript(
363+
script,
364+
inputIndex,
365+
'input',
366+
input.redeemScript,
367+
input.witnessScript,
368+
);
369+
const type = result.type === 'raw' ? '' : result.type + '-';
370+
const mainType = classifyScript(result.meaningfulScript);
371+
return (type + mainType) as AllScriptType;
372+
}
373+
358374
inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean {
359375
const input = checkForInput(this.data.inputs, inputIndex);
360376
return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE);
@@ -648,6 +664,7 @@ export class Psbt {
648664
}
649665

650666
updateInput(inputIndex: number, updateData: PsbtInputUpdate): this {
667+
if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript);
651668
this.data.updateInput(inputIndex, updateData);
652669
if (updateData.nonWitnessUtxo) {
653670
addNonWitnessTxCache(
@@ -1215,7 +1232,7 @@ function getHashForSig(
12151232
input.witnessScript,
12161233
);
12171234

1218-
if (['p2shp2wsh', 'p2wsh'].indexOf(type) >= 0) {
1235+
if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) {
12191236
hash = unsignedTx.hashForWitnessV0(
12201237
inputIndex,
12211238
meaningfulScript,
@@ -1572,25 +1589,32 @@ function nonWitnessUtxoTxFromCache(
15721589
return c[inputIndex];
15731590
}
15741591

1575-
function pubkeyInInput(
1576-
pubkey: Buffer,
1577-
input: PsbtInput,
1592+
function getScriptFromUtxo(
15781593
inputIndex: number,
1594+
input: PsbtInput,
15791595
cache: PsbtCache,
1580-
): boolean {
1581-
let script: Buffer;
1596+
): Buffer {
15821597
if (input.witnessUtxo !== undefined) {
1583-
script = input.witnessUtxo.script;
1598+
return input.witnessUtxo.script;
15841599
} else if (input.nonWitnessUtxo !== undefined) {
15851600
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
15861601
cache,
15871602
input,
15881603
inputIndex,
15891604
);
1590-
script = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
1605+
return nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
15911606
} else {
15921607
throw new Error("Can't find pubkey in input without Utxo data");
15931608
}
1609+
}
1610+
1611+
function pubkeyInInput(
1612+
pubkey: Buffer,
1613+
input: PsbtInput,
1614+
inputIndex: number,
1615+
cache: PsbtCache,
1616+
): boolean {
1617+
const script = getScriptFromUtxo(inputIndex, input, cache);
15941618
const { meaningfulScript } = getMeaningfulScript(
15951619
script,
15961620
inputIndex,
@@ -1626,7 +1650,7 @@ function getMeaningfulScript(
16261650
witnessScript?: Buffer,
16271651
): {
16281652
meaningfulScript: Buffer;
1629-
type: 'p2sh' | 'p2wsh' | 'p2shp2wsh' | 'raw';
1653+
type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'raw';
16301654
} {
16311655
const isP2SH = isP2SHScript(script);
16321656
const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript);
@@ -1645,9 +1669,11 @@ function getMeaningfulScript(
16451669
meaningfulScript = witnessScript!;
16461670
checkRedeemScript(index, script, redeemScript!, ioType);
16471671
checkWitnessScript(index, redeemScript!, witnessScript!, ioType);
1672+
checkInvalidP2WSH(meaningfulScript);
16481673
} else if (isP2WSH) {
16491674
meaningfulScript = witnessScript!;
16501675
checkWitnessScript(index, script, witnessScript!, ioType);
1676+
checkInvalidP2WSH(meaningfulScript);
16511677
} else if (isP2SH) {
16521678
meaningfulScript = redeemScript!;
16531679
checkRedeemScript(index, script, redeemScript!, ioType);
@@ -1657,7 +1683,7 @@ function getMeaningfulScript(
16571683
return {
16581684
meaningfulScript,
16591685
type: isP2SHP2WSH
1660-
? 'p2shp2wsh'
1686+
? 'p2sh-p2wsh'
16611687
: isP2SH
16621688
? 'p2sh'
16631689
: isP2WSH
@@ -1666,6 +1692,12 @@ function getMeaningfulScript(
16661692
};
16671693
}
16681694

1695+
function checkInvalidP2WSH(script: Buffer): void {
1696+
if (isP2WPKH(script) || isP2SHScript(script)) {
1697+
throw new Error('P2WPKH or P2SH can not be contained within P2WSH');
1698+
}
1699+
}
1700+
16691701
function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {
16701702
const pubkeyHash = hash160(pubkey);
16711703

@@ -1678,7 +1710,32 @@ function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {
16781710
});
16791711
}
16801712

1681-
function classifyScript(script: Buffer): string {
1713+
type AllScriptType =
1714+
| 'witnesspubkeyhash'
1715+
| 'pubkeyhash'
1716+
| 'multisig'
1717+
| 'pubkey'
1718+
| 'nonstandard'
1719+
| 'p2sh-witnesspubkeyhash'
1720+
| 'p2sh-pubkeyhash'
1721+
| 'p2sh-multisig'
1722+
| 'p2sh-pubkey'
1723+
| 'p2sh-nonstandard'
1724+
| 'p2wsh-pubkeyhash'
1725+
| 'p2wsh-multisig'
1726+
| 'p2wsh-pubkey'
1727+
| 'p2wsh-nonstandard'
1728+
| 'p2sh-p2wsh-pubkeyhash'
1729+
| 'p2sh-p2wsh-multisig'
1730+
| 'p2sh-p2wsh-pubkey'
1731+
| 'p2sh-p2wsh-nonstandard';
1732+
type ScriptType =
1733+
| 'witnesspubkeyhash'
1734+
| 'pubkeyhash'
1735+
| 'multisig'
1736+
| 'pubkey'
1737+
| 'nonstandard';
1738+
function classifyScript(script: Buffer): ScriptType {
16821739
if (isP2WPKH(script)) return 'witnesspubkeyhash';
16831740
if (isP2PKH(script)) return 'pubkeyhash';
16841741
if (isP2MS(script)) return 'multisig';

0 commit comments

Comments
 (0)