Skip to content

Commit

Permalink
🚅 perfs: Simplify psbt util
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonandjay committed Apr 12, 2024
1 parent ea78574 commit 833e031
Show file tree
Hide file tree
Showing 19 changed files with 264 additions and 271 deletions.
4 changes: 2 additions & 2 deletions src/psbt/bip371.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
/**
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions src/psbt/global/cache.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference types="node" />
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;
Expand Down
10 changes: 1 addition & 9 deletions src/psbt/global/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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':
Expand Down
18 changes: 17 additions & 1 deletion src/psbt/global/sign.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
/// <reference types="node" />
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;
89 changes: 88 additions & 1 deletion src/psbt/global/sign.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/psbt/input/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ 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');
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.');
});
Expand Down
4 changes: 2 additions & 2 deletions src/psbt/input/script.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="node" />
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';
Expand Down
4 changes: 4 additions & 0 deletions src/psbt/interfaces.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {};
20 changes: 0 additions & 20 deletions src/psbt/psbtutils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Signer | SignerAsync>;
export declare function range(n: number): number[];
export declare function isPubkeyLike(buf: Buffer): boolean;
export {};
87 changes: 0 additions & 87 deletions src/psbt/psbtutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' ||
Expand Down
7 changes: 5 additions & 2 deletions ts_src/psbt/bip371.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 7 additions & 7 deletions ts_src/psbt/global/cache.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -86,4 +86,4 @@ export function addNonWitnessTxCache(
self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data;
},
});
}
}
2 changes: 1 addition & 1 deletion ts_src/psbt/global/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
checkScriptForPubkey,
getMeaningfulScript,
getScriptAndAmountFromUtxo,
pubkeyInScript
pubkeyInScript,
} from '../input/script';
import { Output, Transaction } from '../../transaction';
import { nonWitnessUtxoTxFromCache } from './cache';
Expand Down
Loading

0 comments on commit 833e031

Please sign in to comment.