From 833e031a5368aca65717af34fc228b8ddba1cf40 Mon Sep 17 00:00:00 2001 From: jasonandjay <342690199@qq.com> Date: Fri, 12 Apr 2024 13:20:55 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=85=20perfs:=20Simplify=20psbt=20util?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/psbt/bip371.js | 4 +- src/psbt/global/cache.d.ts | 6 +- src/psbt/global/index.js | 10 +--- src/psbt/global/sign.d.ts | 18 +++++- src/psbt/global/sign.js | 89 +++++++++++++++++++++++++++++- src/psbt/input/index.js | 4 +- src/psbt/input/script.d.ts | 4 +- src/psbt/interfaces.d.ts | 4 ++ src/psbt/psbtutils.d.ts | 20 ------- src/psbt/psbtutils.js | 87 ----------------------------- ts_src/psbt/bip371.ts | 7 ++- ts_src/psbt/global/cache.ts | 14 ++--- ts_src/psbt/global/hash.ts | 2 +- ts_src/psbt/global/index.ts | 14 +---- ts_src/psbt/global/sign.ts | 95 +++++++++++++++++++++++++++++++- ts_src/psbt/input/index.ts | 2 +- ts_src/psbt/input/script.ts | 29 +++++----- ts_src/psbt/interfaces.ts | 19 ++++--- ts_src/psbt/psbtutils.ts | 107 ++++-------------------------------- 19 files changed, 264 insertions(+), 271 deletions(-) diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js index 93fe10659..7cd682427 100644 --- a/src/psbt/bip371.js +++ b/src/psbt/bip371.js @@ -16,8 +16,8 @@ const types_1 = require('../types'); const transaction_1 = require('../transaction'); const bip341_1 = require('../payments/bip341'); const payments_1 = require('../payments'); -const psbtutils_1 = require('./psbtutils'); const script_1 = require('./input/script'); +const sign_1 = require('./global/sign'); const toXOnly = pubKey => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33)); exports.toXOnly = toXOnly; /** @@ -158,7 +158,7 @@ exports.tapTreeFromList = tapTreeFromList; function checkTaprootInputForSigs(input, action) { const sigs = extractTaprootSigs(input); return sigs.some(sig => - (0, psbtutils_1.signatureBlocksAction)(sig, decodeSchnorrSignature, action), + (0, sign_1.signatureBlocksAction)(sig, decodeSchnorrSignature, action), ); } exports.checkTaprootInputForSigs = checkTaprootInputForSigs; diff --git a/src/psbt/global/cache.d.ts b/src/psbt/global/cache.d.ts index fdb9fd161..2a9efaea3 100644 --- a/src/psbt/global/cache.d.ts +++ b/src/psbt/global/cache.d.ts @@ -1,7 +1,7 @@ /// -import { PsbtInput } from "bip174/src/lib/interfaces"; -import { PsbtCache, TxCacheNumberKey } from "../interfaces"; -import { Transaction } from "../../transaction"; +import { PsbtInput } from 'bip174/src/lib/interfaces'; +import { PsbtCache, TxCacheNumberKey } from '../interfaces'; +import { Transaction } from '../../transaction'; export declare function checkCache(cache: PsbtCache): void; export declare function checkTxInputCache(cache: PsbtCache, input: { hash: Buffer; diff --git a/src/psbt/global/index.js b/src/psbt/global/index.js index 9d7943108..9f5659a73 100644 --- a/src/psbt/global/index.js +++ b/src/psbt/global/index.js @@ -8,9 +8,8 @@ exports.isFinalized = const sign_1 = require('./sign'); const payments = require('../../payments'); const script_1 = require('../input/script'); -const { isP2MS, isP2PK, isP2PKH, isP2WPKH } = payments; function getFinalScripts(inputIndex, input, script, isSegwit, isP2SH, isP2WSH) { - const scriptType = classifyScript(script); + const scriptType = (0, script_1.classifyScript)(script); if (!canFinalize(input, script, scriptType)) throw new Error(`Can not finalize input #${inputIndex}`); return prepareFinalScripts( @@ -119,13 +118,6 @@ function getSortedSigs(script, partialSig) { }) .filter(v => !!v); } -function classifyScript(script) { - if (isP2WPKH(script)) return 'witnesspubkeyhash'; - if (isP2PKH(script)) return 'pubkeyhash'; - if (isP2MS(script)) return 'multisig'; - if (isP2PK(script)) return 'pubkey'; - return 'nonstandard'; -} function canFinalize(input, script, scriptType) { switch (scriptType) { case 'pubkey': diff --git a/src/psbt/global/sign.d.ts b/src/psbt/global/sign.d.ts index 7ee39ab13..1595118a1 100644 --- a/src/psbt/global/sign.d.ts +++ b/src/psbt/global/sign.d.ts @@ -1,6 +1,22 @@ /// -import { PsbtInput } from "bip174/src/lib/interfaces"; +import { PsbtInput } from 'bip174/src/lib/interfaces'; +import { SignatureDecodeFunc } from '../interfaces'; export declare function hasSigs(neededSigs: number, partialSig?: any[], pubkeys?: Buffer[]): boolean; export declare function checkPartialSigSighashes(input: PsbtInput): void; export declare function trimTaprootSig(signature: Buffer): Buffer; export declare function isSigLike(buf: Buffer): boolean; +/** + * Checks if an input contains a signature for a specific action. + * @param input - The input to check. + * @param action - The action to check for. + * @returns A boolean indicating whether the input contains a signature for the specified action. + */ +export declare function checkInputForSig(input: PsbtInput, action: string): boolean; +/** + * Determines if a given action is allowed for a signature block. + * @param signature - The signature block. + * @param signatureDecodeFn - The function used to decode the signature. + * @param action - The action to be checked. + * @returns True if the action is allowed, false otherwise. + */ +export declare function signatureBlocksAction(signature: Buffer, signatureDecodeFn: SignatureDecodeFunc, action: string): boolean; diff --git a/src/psbt/global/sign.js b/src/psbt/global/sign.js index f4e675e31..bb86f9545 100644 --- a/src/psbt/global/sign.js +++ b/src/psbt/global/sign.js @@ -1,11 +1,14 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -exports.isSigLike = +exports.signatureBlocksAction = + exports.checkInputForSig = + exports.isSigLike = exports.trimTaprootSig = exports.checkPartialSigSighashes = exports.hasSigs = void 0; const script = require('../../script'); +const transaction_1 = require('../../transaction'); function hasSigs(neededSigs, partialSig, pubkeys) { if (!partialSig) return false; let sigs; @@ -42,6 +45,90 @@ function isSigLike(buf) { return script.isCanonicalScriptSignature(buf); } exports.isSigLike = isSigLike; +/** + * Checks if an input contains a signature for a specific action. + * @param input - The input to check. + * @param action - The action to check for. + * @returns A boolean indicating whether the input contains a signature for the specified action. + */ +function checkInputForSig(input, action) { + const pSigs = extractPartialSigs(input); + return pSigs.some(pSig => + signatureBlocksAction(pSig, script.signature.decode, action), + ); +} +exports.checkInputForSig = checkInputForSig; +/** + * Determines if a given action is allowed for a signature block. + * @param signature - The signature block. + * @param signatureDecodeFn - The function used to decode the signature. + * @param action - The action to be checked. + * @returns True if the action is allowed, false otherwise. + */ +function signatureBlocksAction(signature, signatureDecodeFn, action) { + const { hashType } = signatureDecodeFn(signature); + const whitelist = []; + const isAnyoneCanPay = + hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY; + if (isAnyoneCanPay) whitelist.push('addInput'); + const hashMod = hashType & 0x1f; + switch (hashMod) { + case transaction_1.Transaction.SIGHASH_ALL: + break; + case transaction_1.Transaction.SIGHASH_SINGLE: + case transaction_1.Transaction.SIGHASH_NONE: + whitelist.push('addOutput'); + whitelist.push('setInputSequence'); + break; + } + if (whitelist.indexOf(action) === -1) { + return true; + } + return false; +} +exports.signatureBlocksAction = signatureBlocksAction; +/** + * Extracts the signatures from a PsbtInput object. + * If the input has partial signatures, it returns an array of the signatures. + * If the input does not have partial signatures, it checks if it has a finalScriptSig or finalScriptWitness. + * If it does, it extracts the signatures from the final scripts and returns them. + * If none of the above conditions are met, it returns an empty array. + * + * @param input - The PsbtInput object from which to extract the signatures. + * @returns An array of signatures extracted from the PsbtInput object. + */ +function extractPartialSigs(input) { + let pSigs = []; + if ((input.partialSig || []).length === 0) { + if (!input.finalScriptSig && !input.finalScriptWitness) return []; + pSigs = getPsigsFromInputFinalScripts(input); + } else { + pSigs = input.partialSig; + } + return pSigs.map(p => p.signature); +} +/** + * Retrieves the partial signatures (Psigs) from the input's final scripts. + * Psigs are extracted from both the final scriptSig and final scriptWitness of the input. + * Only canonical script signatures are considered. + * + * @param input - The PsbtInput object representing the input. + * @returns An array of PartialSig objects containing the extracted Psigs. + */ +function getPsigsFromInputFinalScripts(input) { + const scriptItems = !input.finalScriptSig + ? [] + : script.decompile(input.finalScriptSig) || []; + const witnessItems = !input.finalScriptWitness + ? [] + : script.decompile(input.finalScriptWitness) || []; + return scriptItems + .concat(witnessItems) + .filter(item => { + return Buffer.isBuffer(item) && script.isCanonicalScriptSignature(item); + }) + .map(sig => ({ signature: sig })); +} function compressPubkey(pubkey) { if (pubkey.length === 65) { const parity = pubkey[64] & 1; diff --git a/src/psbt/input/index.js b/src/psbt/input/index.js index f8af373ae..d9d697271 100644 --- a/src/psbt/input/index.js +++ b/src/psbt/input/index.js @@ -7,7 +7,7 @@ exports.pubkeyInInput = exports.checkInputsForPartialSig = void 0; const bip371_1 = require('../bip371'); -const psbtutils_1 = require('../psbtutils'); +const sign_1 = require('../global/sign'); const cache_1 = require('../global/cache'); const script_1 = require('./script'); const payments_1 = require('../../payments'); @@ -15,7 +15,7 @@ function checkInputsForPartialSig(inputs, action) { inputs.forEach(input => { const throws = (0, bip371_1.isTaprootInput)(input) ? (0, bip371_1.checkTaprootInputForSigs)(input, action) - : (0, psbtutils_1.checkInputForSig)(input, action); + : (0, sign_1.checkInputForSig)(input, action); if (throws) throw new Error('Can not modify transaction, signatures exist.'); }); diff --git a/src/psbt/input/script.d.ts b/src/psbt/input/script.d.ts index 5cf2df16a..09fbf9a77 100644 --- a/src/psbt/input/script.d.ts +++ b/src/psbt/input/script.d.ts @@ -1,6 +1,6 @@ /// -import { PsbtInput } from "bip174/src/lib/interfaces"; -import { GetScriptReturn, PsbtCache, ScriptType } from "../interfaces"; +import { PsbtInput } from 'bip174/src/lib/interfaces'; +import { GetScriptReturn, PsbtCache, ScriptType } from '../interfaces'; export declare function getMeaningfulScript(script: Buffer, index: number, ioType: 'input' | 'output', redeemScript?: Buffer, witnessScript?: Buffer): { meaningfulScript: Buffer; type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'raw'; diff --git a/src/psbt/interfaces.d.ts b/src/psbt/interfaces.d.ts index 7f5da5926..48d7daf21 100644 --- a/src/psbt/interfaces.d.ts +++ b/src/psbt/interfaces.d.ts @@ -121,4 +121,8 @@ tapLeafHashToFinalize?: Buffer) => { export type ValidateSigFunction = (pubkey: Buffer, msghash: Buffer, signature: Buffer) => boolean; export 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 type ScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'nonstandard'; +export type SignatureDecodeFunc = (buffer: Buffer) => { + signature: Buffer; + hashType: number; +}; export {}; diff --git a/src/psbt/psbtutils.d.ts b/src/psbt/psbtutils.d.ts index 7b8c2a142..ed4717d3a 100644 --- a/src/psbt/psbtutils.d.ts +++ b/src/psbt/psbtutils.d.ts @@ -2,28 +2,8 @@ import { PsbtInput } from 'bip174/src/lib/interfaces'; import { HDSigner, HDSignerAsync, PsbtCache, PsbtOpts, Signer, SignerAsync } from './interfaces'; import { Psbt } from '../psbt'; -/** - * Checks if an input contains a signature for a specific action. - * @param input - The input to check. - * @param action - The action to check for. - * @returns A boolean indicating whether the input contains a signature for the specified action. - */ -export declare function checkInputForSig(input: PsbtInput, action: string): boolean; -type SignatureDecodeFunc = (buffer: Buffer) => { - signature: Buffer; - hashType: number; -}; -/** - * Determines if a given action is allowed for a signature block. - * @param signature - The signature block. - * @param signatureDecodeFn - The function used to decode the signature. - * @param action - The action to be checked. - * @returns True if the action is allowed, false otherwise. - */ -export declare function signatureBlocksAction(signature: Buffer, signatureDecodeFn: SignatureDecodeFunc, action: string): boolean; export declare function check32Bit(num: number): void; export declare function checkFees(psbt: Psbt, cache: PsbtCache, opts: PsbtOpts): void; export declare function getSignersFromHD(inputIndex: number, inputs: PsbtInput[], hdKeyPair: HDSigner | HDSignerAsync): Array; export declare function range(n: number): number[]; export declare function isPubkeyLike(buf: Buffer): boolean; -export {}; diff --git a/src/psbt/psbtutils.js b/src/psbt/psbtutils.js index cf5b30b28..152fcb820 100644 --- a/src/psbt/psbtutils.js +++ b/src/psbt/psbtutils.js @@ -5,96 +5,9 @@ exports.isPubkeyLike = exports.getSignersFromHD = exports.checkFees = exports.check32Bit = - exports.signatureBlocksAction = - exports.checkInputForSig = void 0; const bscript = require('../script'); -const transaction_1 = require('../transaction'); const utils_1 = require('bip174/src/lib/utils'); -/** - * Checks if an input contains a signature for a specific action. - * @param input - The input to check. - * @param action - The action to check for. - * @returns A boolean indicating whether the input contains a signature for the specified action. - */ -function checkInputForSig(input, action) { - const pSigs = extractPartialSigs(input); - return pSigs.some(pSig => - signatureBlocksAction(pSig, bscript.signature.decode, action), - ); -} -exports.checkInputForSig = checkInputForSig; -/** - * Determines if a given action is allowed for a signature block. - * @param signature - The signature block. - * @param signatureDecodeFn - The function used to decode the signature. - * @param action - The action to be checked. - * @returns True if the action is allowed, false otherwise. - */ -function signatureBlocksAction(signature, signatureDecodeFn, action) { - const { hashType } = signatureDecodeFn(signature); - const whitelist = []; - const isAnyoneCanPay = - hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY; - if (isAnyoneCanPay) whitelist.push('addInput'); - const hashMod = hashType & 0x1f; - switch (hashMod) { - case transaction_1.Transaction.SIGHASH_ALL: - break; - case transaction_1.Transaction.SIGHASH_SINGLE: - case transaction_1.Transaction.SIGHASH_NONE: - whitelist.push('addOutput'); - whitelist.push('setInputSequence'); - break; - } - if (whitelist.indexOf(action) === -1) { - return true; - } - return false; -} -exports.signatureBlocksAction = signatureBlocksAction; -/** - * Extracts the signatures from a PsbtInput object. - * If the input has partial signatures, it returns an array of the signatures. - * If the input does not have partial signatures, it checks if it has a finalScriptSig or finalScriptWitness. - * If it does, it extracts the signatures from the final scripts and returns them. - * If none of the above conditions are met, it returns an empty array. - * - * @param input - The PsbtInput object from which to extract the signatures. - * @returns An array of signatures extracted from the PsbtInput object. - */ -function extractPartialSigs(input) { - let pSigs = []; - if ((input.partialSig || []).length === 0) { - if (!input.finalScriptSig && !input.finalScriptWitness) return []; - pSigs = getPsigsFromInputFinalScripts(input); - } else { - pSigs = input.partialSig; - } - return pSigs.map(p => p.signature); -} -/** - * Retrieves the partial signatures (Psigs) from the input's final scripts. - * Psigs are extracted from both the final scriptSig and final scriptWitness of the input. - * Only canonical script signatures are considered. - * - * @param input - The PsbtInput object representing the input. - * @returns An array of PartialSig objects containing the extracted Psigs. - */ -function getPsigsFromInputFinalScripts(input) { - const scriptItems = !input.finalScriptSig - ? [] - : bscript.decompile(input.finalScriptSig) || []; - const witnessItems = !input.finalScriptWitness - ? [] - : bscript.decompile(input.finalScriptWitness) || []; - return scriptItems - .concat(witnessItems) - .filter(item => { - return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); - }) - .map(sig => ({ signature: sig })); -} function check32Bit(num) { if ( typeof num !== 'number' || diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts index c2dda4d7f..ed8801dd5 100644 --- a/ts_src/psbt/bip371.ts +++ b/ts_src/psbt/bip371.ts @@ -20,8 +20,11 @@ import { } from '../payments/bip341'; import { isP2TR, p2tr } from '../payments'; -import { signatureBlocksAction } from './psbtutils'; -import { pubkeyPositionInScript, witnessStackToScriptWitness } from './input/script'; +import { + pubkeyPositionInScript, + witnessStackToScriptWitness, +} from './input/script'; +import { signatureBlocksAction } from './global/sign'; export const toXOnly = (pubKey: Buffer) => pubKey.length === 32 ? pubKey : pubKey.slice(1, 33); diff --git a/ts_src/psbt/global/cache.ts b/ts_src/psbt/global/cache.ts index 2420accf0..d72e79a18 100644 --- a/ts_src/psbt/global/cache.ts +++ b/ts_src/psbt/global/cache.ts @@ -1,9 +1,9 @@ -import { PsbtInput } from "bip174/src/lib/interfaces"; -import { reverseBuffer } from "../../bufferutils"; -import { PsbtCache, TxCacheNumberKey } from "../interfaces"; -import { Transaction } from "../../transaction"; -import { isFinalized } from "."; -import { inputFinalizeGetAmts } from "../input"; +import { PsbtInput } from 'bip174/src/lib/interfaces'; +import { reverseBuffer } from '../../bufferutils'; +import { PsbtCache, TxCacheNumberKey } from '../interfaces'; +import { Transaction } from '../../transaction'; +import { isFinalized } from '.'; +import { inputFinalizeGetAmts } from '../input'; export function checkCache(cache: PsbtCache): void { if (cache.__UNSAFE_SIGN_NONSEGWIT !== false) { @@ -86,4 +86,4 @@ export function addNonWitnessTxCache( self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data; }, }); -} \ No newline at end of file +} diff --git a/ts_src/psbt/global/hash.ts b/ts_src/psbt/global/hash.ts index acb76012b..5979af64d 100644 --- a/ts_src/psbt/global/hash.ts +++ b/ts_src/psbt/global/hash.ts @@ -5,7 +5,7 @@ import { checkScriptForPubkey, getMeaningfulScript, getScriptAndAmountFromUtxo, - pubkeyInScript + pubkeyInScript, } from '../input/script'; import { Output, Transaction } from '../../transaction'; import { nonWitnessUtxoTxFromCache } from './cache'; diff --git a/ts_src/psbt/global/index.ts b/ts_src/psbt/global/index.ts index 112db7bce..1040f8717 100644 --- a/ts_src/psbt/global/index.ts +++ b/ts_src/psbt/global/index.ts @@ -5,10 +5,8 @@ import { } from 'bip174/src/lib/interfaces'; import { hasSigs } from './sign'; import * as payments from '../../payments'; -import { HDSigner, ScriptType } from '../interfaces'; -import { witnessStackToScriptWitness } from '../input/script'; - -const { isP2MS, isP2PK, isP2PKH, isP2WPKH } = payments; +import { HDSigner } from '../interfaces'; +import { classifyScript, witnessStackToScriptWitness } from '../input/script'; export function getFinalScripts( inputIndex: number, @@ -139,14 +137,6 @@ function getSortedSigs(script: Buffer, partialSig: PartialSig[]): Buffer[] { .filter(v => !!v); } -function classifyScript(script: Buffer): ScriptType { - if (isP2WPKH(script)) return 'witnesspubkeyhash'; - if (isP2PKH(script)) return 'pubkeyhash'; - if (isP2MS(script)) return 'multisig'; - if (isP2PK(script)) return 'pubkey'; - return 'nonstandard'; -} - export function canFinalize( input: PsbtInput, script: Buffer, diff --git a/ts_src/psbt/global/sign.ts b/ts_src/psbt/global/sign.ts index 873ada15a..d293aca5b 100644 --- a/ts_src/psbt/global/sign.ts +++ b/ts_src/psbt/global/sign.ts @@ -1,5 +1,7 @@ -import { PsbtInput } from "bip174/src/lib/interfaces"; +import { PartialSig, PsbtInput } from 'bip174/src/lib/interfaces'; import * as script from '../../script'; +import { Transaction } from '../../transaction'; +import { SignatureDecodeFunc } from '../interfaces'; export function hasSigs( neededSigs: number, @@ -41,6 +43,95 @@ export function isSigLike(buf: Buffer): boolean { return script.isCanonicalScriptSignature(buf); } +/** + * Checks if an input contains a signature for a specific action. + * @param input - The input to check. + * @param action - The action to check for. + * @returns A boolean indicating whether the input contains a signature for the specified action. + */ +export function checkInputForSig(input: PsbtInput, action: string): boolean { + const pSigs = extractPartialSigs(input); + return pSigs.some(pSig => + signatureBlocksAction(pSig, script.signature.decode, action), + ); +} + +/** + * Determines if a given action is allowed for a signature block. + * @param signature - The signature block. + * @param signatureDecodeFn - The function used to decode the signature. + * @param action - The action to be checked. + * @returns True if the action is allowed, false otherwise. + */ +export function signatureBlocksAction( + signature: Buffer, + signatureDecodeFn: SignatureDecodeFunc, + action: string, +): boolean { + const { hashType } = signatureDecodeFn(signature); + const whitelist: string[] = []; + const isAnyoneCanPay = hashType & Transaction.SIGHASH_ANYONECANPAY; + if (isAnyoneCanPay) whitelist.push('addInput'); + const hashMod = hashType & 0x1f; + switch (hashMod) { + case Transaction.SIGHASH_ALL: + break; + case Transaction.SIGHASH_SINGLE: + case Transaction.SIGHASH_NONE: + whitelist.push('addOutput'); + whitelist.push('setInputSequence'); + break; + } + if (whitelist.indexOf(action) === -1) { + return true; + } + return false; +} + +/** + * Extracts the signatures from a PsbtInput object. + * If the input has partial signatures, it returns an array of the signatures. + * If the input does not have partial signatures, it checks if it has a finalScriptSig or finalScriptWitness. + * If it does, it extracts the signatures from the final scripts and returns them. + * If none of the above conditions are met, it returns an empty array. + * + * @param input - The PsbtInput object from which to extract the signatures. + * @returns An array of signatures extracted from the PsbtInput object. + */ +function extractPartialSigs(input: PsbtInput): Buffer[] { + let pSigs: PartialSig[] = []; + if ((input.partialSig || []).length === 0) { + if (!input.finalScriptSig && !input.finalScriptWitness) return []; + pSigs = getPsigsFromInputFinalScripts(input); + } else { + pSigs = input.partialSig!; + } + return pSigs.map(p => p.signature); +} + +/** + * Retrieves the partial signatures (Psigs) from the input's final scripts. + * Psigs are extracted from both the final scriptSig and final scriptWitness of the input. + * Only canonical script signatures are considered. + * + * @param input - The PsbtInput object representing the input. + * @returns An array of PartialSig objects containing the extracted Psigs. + */ +function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] { + const scriptItems = !input.finalScriptSig + ? [] + : script.decompile(input.finalScriptSig) || []; + const witnessItems = !input.finalScriptWitness + ? [] + : script.decompile(input.finalScriptWitness) || []; + return scriptItems + .concat(witnessItems) + .filter(item => { + return Buffer.isBuffer(item) && script.isCanonicalScriptSignature(item); + }) + .map(sig => ({ signature: sig })) as PartialSig[]; +} + function compressPubkey(pubkey: Buffer): Buffer { if (pubkey.length === 65) { const parity = pubkey[64] & 1; @@ -49,4 +140,4 @@ function compressPubkey(pubkey: Buffer): Buffer { return newKey; } return pubkey.slice(); -} \ No newline at end of file +} diff --git a/ts_src/psbt/input/index.ts b/ts_src/psbt/input/index.ts index 7e7426283..822ab0f70 100644 --- a/ts_src/psbt/input/index.ts +++ b/ts_src/psbt/input/index.ts @@ -1,6 +1,6 @@ import { PsbtInput } from 'bip174/src/lib/interfaces'; import { checkTaprootInputForSigs, isTaprootInput } from '../bip371'; -import { checkInputForSig } from '../psbtutils'; +import { checkInputForSig } from '../global/sign'; import { Output, Transaction } from '../../transaction'; import { PsbtCache } from '../interfaces'; import { checkTxInputCache, nonWitnessUtxoTxFromCache } from '../global/cache'; diff --git a/ts_src/psbt/input/script.ts b/ts_src/psbt/input/script.ts index 19ce0d161..e598569d8 100644 --- a/ts_src/psbt/input/script.ts +++ b/ts_src/psbt/input/script.ts @@ -1,14 +1,15 @@ -import { PsbtInput } from "bip174/src/lib/interfaces"; -import { varuint } from "../../bufferutils"; -import * as payments from "../../payments"; -import { GetScriptReturn, PsbtCache, ScriptType } from "../interfaces"; -import { nonWitnessUtxoTxFromCache } from "../global/cache"; -import { hash160 } from "../../crypto"; +import { PsbtInput } from 'bip174/src/lib/interfaces'; +import { varuint } from '../../bufferutils'; +import * as payments from '../../payments'; +import { GetScriptReturn, PsbtCache, ScriptType } from '../interfaces'; +import { nonWitnessUtxoTxFromCache } from '../global/cache'; +import { hash160 } from '../../crypto'; import * as bscript from '../../script'; -import { isPubkeyLike } from "../psbtutils"; -import { isSigLike } from "../global/sign"; +import { isPubkeyLike } from '../psbtutils'; +import { isSigLike } from '../global/sign'; -const {isP2MS, isP2PK, isP2PKH, isP2SHScript, isP2WPKH, isP2WSHScript} = payments; +const { isP2MS, isP2PK, isP2PKH, isP2SHScript, isP2WPKH, isP2WSHScript } = + payments; export function getMeaningfulScript( script: Buffer, @@ -53,10 +54,10 @@ export function getMeaningfulScript( type: isP2SHP2WSH ? 'p2sh-p2wsh' : isP2SH - ? 'p2sh' - : isP2WSH - ? 'p2wsh' - : 'raw', + ? 'p2sh' + : isP2WSH + ? 'p2wsh' + : 'raw', }; } @@ -309,4 +310,4 @@ const checkRedeemScript = scriptCheckerFactory(payments.p2sh, 'Redeem script'); const checkWitnessScript = scriptCheckerFactory( payments.p2wsh, 'Witness script', -); \ No newline at end of file +); diff --git a/ts_src/psbt/interfaces.ts b/ts_src/psbt/interfaces.ts index d0ded62c1..3c949380c 100644 --- a/ts_src/psbt/interfaces.ts +++ b/ts_src/psbt/interfaces.ts @@ -1,9 +1,6 @@ import { Network } from '../networks'; import { Transaction } from '../transaction'; -import { - PsbtInput, - PsbtOutput, -} from 'bip174/src/lib/interfaces'; +import { PsbtInput, PsbtOutput } from 'bip174/src/lib/interfaces'; export interface PsbtCache { __NON_WITNESS_UTXO_TX_CACHE: Transaction[]; @@ -26,9 +23,11 @@ export interface PsbtOpts { maximumFeeRate: number; } -export interface PsbtInputExtended extends PsbtInput, TransactionInput { } +export interface PsbtInputExtended extends PsbtInput, TransactionInput {} -export type PsbtOutputExtended = PsbtOutputExtendedAddress | PsbtOutputExtendedScript; +export type PsbtOutputExtended = + | PsbtOutputExtendedAddress + | PsbtOutputExtendedScript; export interface PsbtOutputExtendedAddress extends PsbtOutput { address: string; @@ -95,7 +94,6 @@ export interface GetScriptReturn { isP2WSH: boolean; } - export interface TransactionInput { hash: string | Buffer; index: number; @@ -175,4 +173,9 @@ export type ScriptType = | 'pubkeyhash' | 'multisig' | 'pubkey' - | 'nonstandard'; \ No newline at end of file + | 'nonstandard'; + +export type SignatureDecodeFunc = (buffer: Buffer) => { + signature: Buffer; + hashType: number; +}; diff --git a/ts_src/psbt/psbtutils.ts b/ts_src/psbt/psbtutils.ts index de2ab4287..84d35dcf6 100644 --- a/ts_src/psbt/psbtutils.ts +++ b/ts_src/psbt/psbtutils.ts @@ -1,103 +1,16 @@ -import { PartialSig, PsbtInput } from 'bip174/src/lib/interfaces'; +import { PsbtInput } from 'bip174/src/lib/interfaces'; import * as bscript from '../script'; -import { Transaction } from '../transaction'; -import { HDSigner, HDSignerAsync, PsbtCache, PsbtOpts, Signer, SignerAsync } from './interfaces'; +import { + HDSigner, + HDSignerAsync, + PsbtCache, + PsbtOpts, + Signer, + SignerAsync, +} from './interfaces'; import { Psbt } from '../psbt'; import { checkForInput } from 'bip174/src/lib/utils'; -/** - * Checks if an input contains a signature for a specific action. - * @param input - The input to check. - * @param action - The action to check for. - * @returns A boolean indicating whether the input contains a signature for the specified action. - */ -export function checkInputForSig(input: PsbtInput, action: string): boolean { - const pSigs = extractPartialSigs(input); - return pSigs.some(pSig => - signatureBlocksAction(pSig, bscript.signature.decode, action), - ); -} - -type SignatureDecodeFunc = (buffer: Buffer) => { - signature: Buffer; - hashType: number; -}; -/** - * Determines if a given action is allowed for a signature block. - * @param signature - The signature block. - * @param signatureDecodeFn - The function used to decode the signature. - * @param action - The action to be checked. - * @returns True if the action is allowed, false otherwise. - */ -export function signatureBlocksAction( - signature: Buffer, - signatureDecodeFn: SignatureDecodeFunc, - action: string, -): boolean { - const { hashType } = signatureDecodeFn(signature); - const whitelist: string[] = []; - const isAnyoneCanPay = hashType & Transaction.SIGHASH_ANYONECANPAY; - if (isAnyoneCanPay) whitelist.push('addInput'); - const hashMod = hashType & 0x1f; - switch (hashMod) { - case Transaction.SIGHASH_ALL: - break; - case Transaction.SIGHASH_SINGLE: - case Transaction.SIGHASH_NONE: - whitelist.push('addOutput'); - whitelist.push('setInputSequence'); - break; - } - if (whitelist.indexOf(action) === -1) { - return true; - } - return false; -} - -/** - * Extracts the signatures from a PsbtInput object. - * If the input has partial signatures, it returns an array of the signatures. - * If the input does not have partial signatures, it checks if it has a finalScriptSig or finalScriptWitness. - * If it does, it extracts the signatures from the final scripts and returns them. - * If none of the above conditions are met, it returns an empty array. - * - * @param input - The PsbtInput object from which to extract the signatures. - * @returns An array of signatures extracted from the PsbtInput object. - */ -function extractPartialSigs(input: PsbtInput): Buffer[] { - let pSigs: PartialSig[] = []; - if ((input.partialSig || []).length === 0) { - if (!input.finalScriptSig && !input.finalScriptWitness) return []; - pSigs = getPsigsFromInputFinalScripts(input); - } else { - pSigs = input.partialSig!; - } - return pSigs.map(p => p.signature); -} - -/** - * Retrieves the partial signatures (Psigs) from the input's final scripts. - * Psigs are extracted from both the final scriptSig and final scriptWitness of the input. - * Only canonical script signatures are considered. - * - * @param input - The PsbtInput object representing the input. - * @returns An array of PartialSig objects containing the extracted Psigs. - */ -function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] { - const scriptItems = !input.finalScriptSig - ? [] - : bscript.decompile(input.finalScriptSig) || []; - const witnessItems = !input.finalScriptWitness - ? [] - : bscript.decompile(input.finalScriptWitness) || []; - return scriptItems - .concat(witnessItems) - .filter(item => { - return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); - }) - .map(sig => ({ signature: sig })) as PartialSig[]; -} - export function check32Bit(num: number): void { if ( typeof num !== 'number' || @@ -163,4 +76,4 @@ export function range(n: number): number[] { export function isPubkeyLike(buf: Buffer): boolean { return buf.length === 33 && bscript.isCanonicalPubKey(buf); -} \ No newline at end of file +}