Add getInputType

This commit is contained in:
junderw 2020-04-29 11:05:33 +09:00
parent d02483473b
commit c9f399e509
No known key found for this signature in database
GPG key ID: A9273B5AD3E47B45
5 changed files with 208 additions and 17 deletions

View file

@ -189,6 +189,7 @@ class Psbt {
); );
} }
checkInputsForPartialSig(this.data.inputs, 'addInput'); checkInputsForPartialSig(this.data.inputs, 'addInput');
if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript);
const c = this.__CACHE; const c = this.__CACHE;
this.data.addInput(inputData); this.data.addInput(inputData);
const txIn = c.__TX.ins[c.__TX.ins.length - 1]; const txIn = c.__TX.ins[c.__TX.ins.length - 1];
@ -285,6 +286,20 @@ class Psbt {
this.data.clearFinalizedInput(inputIndex); this.data.clearFinalizedInput(inputIndex);
return this; return this;
} }
getInputType(inputIndex) {
const input = utils_1.checkForInput(this.data.inputs, inputIndex);
const script = getScriptFromUtxo(inputIndex, input, this.__CACHE);
const result = getMeaningfulScript(
script,
inputIndex,
'input',
input.redeemScript,
input.witnessScript,
);
const type = result.type === 'raw' ? '' : result.type + '-';
const mainType = classifyScript(result.meaningfulScript);
return type + mainType;
}
inputHasPubkey(inputIndex, pubkey) { inputHasPubkey(inputIndex, pubkey) {
const input = utils_1.checkForInput(this.data.inputs, inputIndex); const input = utils_1.checkForInput(this.data.inputs, inputIndex);
return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE); return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE);
@ -538,6 +553,7 @@ class Psbt {
return this; return this;
} }
updateInput(inputIndex, updateData) { updateInput(inputIndex, updateData) {
if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript);
this.data.updateInput(inputIndex, updateData); this.data.updateInput(inputIndex, updateData);
if (updateData.nonWitnessUtxo) { if (updateData.nonWitnessUtxo) {
addNonWitnessTxCache( addNonWitnessTxCache(
@ -924,7 +940,7 @@ function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) {
input.redeemScript, input.redeemScript,
input.witnessScript, input.witnessScript,
); );
if (['p2shp2wsh', 'p2wsh'].indexOf(type) >= 0) { if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) {
hash = unsignedTx.hashForWitnessV0( hash = unsignedTx.hashForWitnessV0(
inputIndex, inputIndex,
meaningfulScript, meaningfulScript,
@ -1220,20 +1236,22 @@ function nonWitnessUtxoTxFromCache(cache, input, inputIndex) {
} }
return c[inputIndex]; return c[inputIndex];
} }
function pubkeyInInput(pubkey, input, inputIndex, cache) { function getScriptFromUtxo(inputIndex, input, cache) {
let script;
if (input.witnessUtxo !== undefined) { if (input.witnessUtxo !== undefined) {
script = input.witnessUtxo.script; return input.witnessUtxo.script;
} else if (input.nonWitnessUtxo !== undefined) { } else if (input.nonWitnessUtxo !== undefined) {
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
cache, cache,
input, input,
inputIndex, inputIndex,
); );
script = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script; return nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
} else { } else {
throw new Error("Can't find pubkey in input without Utxo data"); throw new Error("Can't find pubkey in input without Utxo data");
} }
}
function pubkeyInInput(pubkey, input, inputIndex, cache) {
const script = getScriptFromUtxo(inputIndex, input, cache);
const { meaningfulScript } = getMeaningfulScript( const { meaningfulScript } = getMeaningfulScript(
script, script,
inputIndex, inputIndex,
@ -1275,9 +1293,11 @@ function getMeaningfulScript(
meaningfulScript = witnessScript; meaningfulScript = witnessScript;
checkRedeemScript(index, script, redeemScript, ioType); checkRedeemScript(index, script, redeemScript, ioType);
checkWitnessScript(index, redeemScript, witnessScript, ioType); checkWitnessScript(index, redeemScript, witnessScript, ioType);
checkInvalidP2WSH(meaningfulScript);
} else if (isP2WSH) { } else if (isP2WSH) {
meaningfulScript = witnessScript; meaningfulScript = witnessScript;
checkWitnessScript(index, script, witnessScript, ioType); checkWitnessScript(index, script, witnessScript, ioType);
checkInvalidP2WSH(meaningfulScript);
} else if (isP2SH) { } else if (isP2SH) {
meaningfulScript = redeemScript; meaningfulScript = redeemScript;
checkRedeemScript(index, script, redeemScript, ioType); checkRedeemScript(index, script, redeemScript, ioType);
@ -1287,7 +1307,7 @@ function getMeaningfulScript(
return { return {
meaningfulScript, meaningfulScript,
type: isP2SHP2WSH type: isP2SHP2WSH
? 'p2shp2wsh' ? 'p2sh-p2wsh'
: isP2SH : isP2SH
? 'p2sh' ? 'p2sh'
: isP2WSH : isP2WSH
@ -1295,6 +1315,11 @@ function getMeaningfulScript(
: 'raw', : 'raw',
}; };
} }
function checkInvalidP2WSH(script) {
if (isP2WPKH(script) || isP2SHScript(script)) {
throw new Error('P2WPKH or P2SH can not be contained within P2WSH');
}
}
function pubkeyInScript(pubkey, script) { function pubkeyInScript(pubkey, script) {
const pubkeyHash = crypto_1.hash160(pubkey); const pubkeyHash = crypto_1.hash160(pubkey);
const decompiled = bscript.decompile(script); const decompiled = bscript.decompile(script);

View file

@ -313,6 +313,24 @@
}, },
"exception": "Invalid arguments for Psbt\\.addInput\\. Requires single object with at least \\[hash\\] and \\[index\\]" "exception": "Invalid arguments for Psbt\\.addInput\\. Requires single object with at least \\[hash\\] and \\[index\\]"
}, },
{
"description": "checks for invalid p2wsh witnessScript",
"inputData": {
"hash": "Buffer.from('000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f', 'hex')",
"index": 0,
"witnessScript": "Buffer.from('0014000102030405060708090a0b0c0d0e0f00010203', 'hex')"
},
"exception": "P2WPKH or P2SH can not be contained within P2WSH"
},
{
"description": "checks for invalid p2wsh witnessScript",
"inputData": {
"hash": "Buffer.from('000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f', 'hex')",
"index": 0,
"witnessScript": "Buffer.from('a914000102030405060708090a0b0c0d0e0f0001020387', 'hex')"
},
"exception": "P2WPKH or P2SH can not be contained within P2WSH"
},
{ {
"description": "should be equal", "description": "should be equal",
"inputData": { "inputData": {

View file

@ -542,6 +542,95 @@ describe(`Psbt`, () => {
}); });
}); });
describe('getInputType', () => {
const { publicKey } = ECPair.makeRandom();
const p2wpkhPub = (pubkey: Buffer): Buffer =>
payments.p2wpkh({
pubkey,
}).output!;
const p2pkhPub = (pubkey: Buffer): Buffer =>
payments.p2pkh({
pubkey,
}).output!;
const p2shOut = (output: Buffer): Buffer =>
payments.p2sh({
redeem: { output },
}).output!;
const p2wshOut = (output: Buffer): Buffer =>
payments.p2wsh({
redeem: { output },
}).output!;
const p2shp2wshOut = (output: Buffer): Buffer => p2shOut(p2wshOut(output));
const noOuter = (output: Buffer): Buffer => output;
function getInputTypeTest({
innerScript,
outerScript,
redeemGetter,
witnessGetter,
expectedType,
}: any): void {
const psbt = new Psbt();
psbt.addInput({
hash:
'0000000000000000000000000000000000000000000000000000000000000000',
index: 0,
witnessUtxo: {
script: outerScript(innerScript(publicKey)),
value: 2e3,
},
...(redeemGetter ? { redeemScript: redeemGetter(publicKey) } : {}),
...(witnessGetter ? { witnessScript: witnessGetter(publicKey) } : {}),
});
const type = psbt.getInputType(0);
assert.strictEqual(type, expectedType, 'incorrect input type');
}
[
{
innerScript: p2pkhPub,
outerScript: noOuter,
redeemGetter: null,
witnessGetter: null,
expectedType: 'pubkeyhash',
},
{
innerScript: p2wpkhPub,
outerScript: noOuter,
redeemGetter: null,
witnessGetter: null,
expectedType: 'witnesspubkeyhash',
},
{
innerScript: p2pkhPub,
outerScript: p2shOut,
redeemGetter: p2pkhPub,
witnessGetter: null,
expectedType: 'p2sh-pubkeyhash',
},
{
innerScript: p2wpkhPub,
outerScript: p2shOut,
redeemGetter: p2wpkhPub,
witnessGetter: null,
expectedType: 'p2sh-witnesspubkeyhash',
},
{
innerScript: p2pkhPub,
outerScript: p2wshOut,
redeemGetter: null,
witnessGetter: p2pkhPub,
expectedType: 'p2wsh-pubkeyhash',
},
{
innerScript: p2pkhPub,
outerScript: p2shp2wshOut,
redeemGetter: (pk: Buffer): Buffer => p2wshOut(p2pkhPub(pk)),
witnessGetter: p2pkhPub,
expectedType: 'p2sh-p2wsh-pubkeyhash',
},
].forEach(getInputTypeTest);
});
describe('inputHasPubkey', () => { describe('inputHasPubkey', () => {
it('should throw', () => { it('should throw', () => {
const psbt = new Psbt(); const psbt = new Psbt();

View file

@ -242,6 +242,7 @@ export class Psbt {
); );
} }
checkInputsForPartialSig(this.data.inputs, 'addInput'); checkInputsForPartialSig(this.data.inputs, 'addInput');
if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript);
const c = this.__CACHE; const c = this.__CACHE;
this.data.addInput(inputData); this.data.addInput(inputData);
const txIn = c.__TX.ins[c.__TX.ins.length - 1]; const txIn = c.__TX.ins[c.__TX.ins.length - 1];
@ -355,6 +356,21 @@ export class Psbt {
return this; return this;
} }
getInputType(inputIndex: number): AllScriptType {
const input = checkForInput(this.data.inputs, inputIndex);
const script = getScriptFromUtxo(inputIndex, input, this.__CACHE);
const result = getMeaningfulScript(
script,
inputIndex,
'input',
input.redeemScript,
input.witnessScript,
);
const type = result.type === 'raw' ? '' : result.type + '-';
const mainType = classifyScript(result.meaningfulScript);
return (type + mainType) as AllScriptType;
}
inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean { inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean {
const input = checkForInput(this.data.inputs, inputIndex); const input = checkForInput(this.data.inputs, inputIndex);
return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE); return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE);
@ -648,6 +664,7 @@ export class Psbt {
} }
updateInput(inputIndex: number, updateData: PsbtInputUpdate): this { updateInput(inputIndex: number, updateData: PsbtInputUpdate): this {
if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript);
this.data.updateInput(inputIndex, updateData); this.data.updateInput(inputIndex, updateData);
if (updateData.nonWitnessUtxo) { if (updateData.nonWitnessUtxo) {
addNonWitnessTxCache( addNonWitnessTxCache(
@ -1215,7 +1232,7 @@ function getHashForSig(
input.witnessScript, input.witnessScript,
); );
if (['p2shp2wsh', 'p2wsh'].indexOf(type) >= 0) { if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) {
hash = unsignedTx.hashForWitnessV0( hash = unsignedTx.hashForWitnessV0(
inputIndex, inputIndex,
meaningfulScript, meaningfulScript,
@ -1572,25 +1589,32 @@ function nonWitnessUtxoTxFromCache(
return c[inputIndex]; return c[inputIndex];
} }
function pubkeyInInput( function getScriptFromUtxo(
pubkey: Buffer,
input: PsbtInput,
inputIndex: number, inputIndex: number,
input: PsbtInput,
cache: PsbtCache, cache: PsbtCache,
): boolean { ): Buffer {
let script: Buffer;
if (input.witnessUtxo !== undefined) { if (input.witnessUtxo !== undefined) {
script = input.witnessUtxo.script; return input.witnessUtxo.script;
} else if (input.nonWitnessUtxo !== undefined) { } else if (input.nonWitnessUtxo !== undefined) {
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
cache, cache,
input, input,
inputIndex, inputIndex,
); );
script = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script; return nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
} else { } else {
throw new Error("Can't find pubkey in input without Utxo data"); throw new Error("Can't find pubkey in input without Utxo data");
} }
}
function pubkeyInInput(
pubkey: Buffer,
input: PsbtInput,
inputIndex: number,
cache: PsbtCache,
): boolean {
const script = getScriptFromUtxo(inputIndex, input, cache);
const { meaningfulScript } = getMeaningfulScript( const { meaningfulScript } = getMeaningfulScript(
script, script,
inputIndex, inputIndex,
@ -1626,7 +1650,7 @@ function getMeaningfulScript(
witnessScript?: Buffer, witnessScript?: Buffer,
): { ): {
meaningfulScript: Buffer; meaningfulScript: Buffer;
type: 'p2sh' | 'p2wsh' | 'p2shp2wsh' | 'raw'; type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'raw';
} { } {
const isP2SH = isP2SHScript(script); const isP2SH = isP2SHScript(script);
const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript); const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript);
@ -1645,9 +1669,11 @@ function getMeaningfulScript(
meaningfulScript = witnessScript!; meaningfulScript = witnessScript!;
checkRedeemScript(index, script, redeemScript!, ioType); checkRedeemScript(index, script, redeemScript!, ioType);
checkWitnessScript(index, redeemScript!, witnessScript!, ioType); checkWitnessScript(index, redeemScript!, witnessScript!, ioType);
checkInvalidP2WSH(meaningfulScript);
} else if (isP2WSH) { } else if (isP2WSH) {
meaningfulScript = witnessScript!; meaningfulScript = witnessScript!;
checkWitnessScript(index, script, witnessScript!, ioType); checkWitnessScript(index, script, witnessScript!, ioType);
checkInvalidP2WSH(meaningfulScript);
} else if (isP2SH) { } else if (isP2SH) {
meaningfulScript = redeemScript!; meaningfulScript = redeemScript!;
checkRedeemScript(index, script, redeemScript!, ioType); checkRedeemScript(index, script, redeemScript!, ioType);
@ -1657,7 +1683,7 @@ function getMeaningfulScript(
return { return {
meaningfulScript, meaningfulScript,
type: isP2SHP2WSH type: isP2SHP2WSH
? 'p2shp2wsh' ? 'p2sh-p2wsh'
: isP2SH : isP2SH
? 'p2sh' ? 'p2sh'
: isP2WSH : isP2WSH
@ -1666,6 +1692,12 @@ function getMeaningfulScript(
}; };
} }
function checkInvalidP2WSH(script: Buffer): void {
if (isP2WPKH(script) || isP2SHScript(script)) {
throw new Error('P2WPKH or P2SH can not be contained within P2WSH');
}
}
function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean { function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {
const pubkeyHash = hash160(pubkey); const pubkeyHash = hash160(pubkey);
@ -1678,7 +1710,32 @@ function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {
}); });
} }
function classifyScript(script: Buffer): string { type AllScriptType =
| 'witnesspubkeyhash'
| 'pubkeyhash'
| 'multisig'
| 'pubkey'
| 'nonstandard'
| 'p2sh-witnesspubkeyhash'
| 'p2sh-pubkeyhash'
| 'p2sh-multisig'
| 'p2sh-pubkey'
| 'p2sh-nonstandard'
| 'p2wsh-pubkeyhash'
| 'p2wsh-multisig'
| 'p2wsh-pubkey'
| 'p2wsh-nonstandard'
| 'p2sh-p2wsh-pubkeyhash'
| 'p2sh-p2wsh-multisig'
| 'p2sh-p2wsh-pubkey'
| 'p2sh-p2wsh-nonstandard';
type ScriptType =
| 'witnesspubkeyhash'
| 'pubkeyhash'
| 'multisig'
| 'pubkey'
| 'nonstandard';
function classifyScript(script: Buffer): ScriptType {
if (isP2WPKH(script)) return 'witnesspubkeyhash'; if (isP2WPKH(script)) return 'witnesspubkeyhash';
if (isP2PKH(script)) return 'pubkeyhash'; if (isP2PKH(script)) return 'pubkeyhash';
if (isP2MS(script)) return 'multisig'; if (isP2MS(script)) return 'multisig';

2
types/psbt.d.ts vendored
View file

@ -69,6 +69,7 @@ export declare class Psbt {
getFee(): number; getFee(): number;
finalizeAllInputs(): this; finalizeAllInputs(): this;
finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc): this; finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc): this;
getInputType(inputIndex: number): AllScriptType;
inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean; inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean;
outputHasPubkey(outputIndex: number, pubkey: Buffer): boolean; outputHasPubkey(outputIndex: number, pubkey: Buffer): boolean;
validateSignaturesOfAllInputs(): boolean; validateSignaturesOfAllInputs(): boolean;
@ -151,4 +152,5 @@ isP2WSH: boolean) => {
finalScriptSig: Buffer | undefined; finalScriptSig: Buffer | undefined;
finalScriptWitness: Buffer | undefined; finalScriptWitness: Buffer | undefined;
}; };
declare type AllScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'nonstandard' | 'p2sh-witnesspubkeyhash' | 'p2sh-pubkeyhash' | 'p2sh-multisig' | 'p2sh-pubkey' | 'p2sh-nonstandard' | 'p2wsh-pubkeyhash' | 'p2wsh-multisig' | 'p2wsh-pubkey' | 'p2wsh-nonstandard' | 'p2sh-p2wsh-pubkeyhash' | 'p2sh-p2wsh-multisig' | 'p2sh-p2wsh-pubkey' | 'p2sh-p2wsh-nonstandard';
export {}; export {};