diff --git a/src/cjs/psbt.cjs b/src/cjs/psbt.cjs index c6ccbf07d..e17192cf1 100644 --- a/src/cjs/psbt.cjs +++ b/src/cjs/psbt.cjs @@ -46,7 +46,6 @@ var __importStar = Object.defineProperty(exports, '__esModule', { value: true }); exports.Psbt = exports.toXOnly = void 0; const bip174_1 = require('bip174'); -const varuint = __importStar(require('varuint-bitcoin')); const bip174_2 = require('bip174'); const address_js_1 = require('./address.cjs'); const bufferutils_js_1 = require('./bufferutils.cjs'); @@ -63,6 +62,8 @@ Object.defineProperty(exports, 'toXOnly', { }, }); const psbtutils_js_1 = require('./psbt/psbtutils.cjs'); +const witness_js_1 = require('./psbt/internal/witness.cjs'); +const scriptType_js_1 = require('./psbt/internal/scriptType.cjs'); const tools = __importStar(require('uint8array-tools')); /** * These are the default arguments for a Psbt instance. @@ -261,7 +262,8 @@ class Psbt { } (0, bip371_js_1.checkTaprootInputFields)(inputData, inputData, 'addInput'); checkInputsForPartialSig(this.data.inputs, 'addInput'); - if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript); + if (inputData.witnessScript) + checkInvalidP2WSHScript(inputData.witnessScript); const c = this.__CACHE; this.data.addInput(inputData); const txIn = c.__TX.ins[c.__TX.ins.length - 1]; @@ -421,16 +423,20 @@ class Psbt { getInputType(inputIndex) { const input = (0, bip174_2.checkForInput)(this.data.inputs, inputIndex); const script = getScriptFromUtxo(inputIndex, input, this.__CACHE); - const result = getMeaningfulScript( + const result = (0, scriptType_js_1.getMeaningfulScript)( script, inputIndex, 'input', input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig), input.witnessScript || redeemFromFinalWitnessScript(input.finalScriptWitness), + SCRIPT_TYPE_DEPS, ); const type = result.type === 'raw' ? '' : result.type + '-'; - const mainType = classifyScript(result.meaningfulScript); + const mainType = (0, scriptType_js_1.classifyScript)( + result.meaningfulScript, + SCRIPT_TYPE_DEPS, + ); return type + mainType; } inputHasPubkey(inputIndex, pubkey) { @@ -932,7 +938,8 @@ class Psbt { return this; } updateInput(inputIndex, updateData) { - if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript); + if (updateData.witnessScript) + checkInvalidP2WSHScript(updateData.witnessScript); (0, bip371_js_1.checkTaprootInputFields)( this.data.inputs[inputIndex], updateData, @@ -1173,6 +1180,22 @@ const checkWitnessScript = scriptCheckerFactory( payments.p2wsh, 'Witness script', ); +const SCRIPT_TYPE_DEPS = { + isP2WPKH: psbtutils_js_1.isP2WPKH, + isP2PKH: psbtutils_js_1.isP2PKH, + isP2MS: psbtutils_js_1.isP2MS, + isP2PK: psbtutils_js_1.isP2PK, + isP2SHScript: psbtutils_js_1.isP2SHScript, + isP2WSHScript: psbtutils_js_1.isP2WSHScript, + checkRedeemScript, + checkWitnessScript, +}; +const checkInvalidP2WSHScript = script => + (0, scriptType_js_1.checkInvalidP2WSH)( + script, + psbtutils_js_1.isP2WPKH, + psbtutils_js_1.isP2SHScript, + ); function getTxCacheValue(key, name, inputs, c) { if (!inputs.every(isFinalized)) throw new Error(`PSBT must be finalized to calculate ${name}`); @@ -1191,7 +1214,10 @@ function getTxCacheValue(key, name, inputs, c) { else if (key === '__FEE') return c.__FEE; } function getFinalScripts(inputIndex, input, script, isSegwit, isP2SH, isP2WSH) { - const scriptType = classifyScript(script); + const scriptType = (0, scriptType_js_1.classifyScript)( + script, + SCRIPT_TYPE_DEPS, + ); if (!canFinalize(input, script, scriptType)) throw new Error(`Can not finalize input #${inputIndex}`); return prepareFinalScripts( @@ -1291,12 +1317,13 @@ function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) { } else { throw new Error('Need a Utxo input item for signing'); } - const { meaningfulScript, type } = getMeaningfulScript( + const { meaningfulScript, type } = (0, scriptType_js_1.getMeaningfulScript)( prevout.script, inputIndex, 'input', input.redeemScript, input.witnessScript, + SCRIPT_TYPE_DEPS, ); if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) { hash = unsignedTx.hashForWitnessV0( @@ -1554,28 +1581,6 @@ function getSortedSigs(script, partialSig) { }) .filter(v => !!v); } -function scriptWitnessToWitnessStack(buffer) { - let offset = 0; - function readSlice(n) { - offset += n; - return buffer.slice(offset - n, offset); - } - function readVarInt() { - const vi = varuint.decode(buffer, offset); - offset += varuint.encodingLength(vi.bigintValue); - return vi.numberValue; - } - function readVarSlice() { - return readSlice(readVarInt()); - } - function readVector() { - const count = readVarInt(); - const vector = []; - for (let i = 0; i < count; i++) vector.push(readVarSlice()); - return vector; - } - return readVector(); -} function sighashTypeToString(sighashType) { let text = sighashType & transaction_js_1.Transaction.SIGHASH_ANYONECANPAY @@ -1626,7 +1631,7 @@ function inputFinalizeGetAmts(inputs, tx, cache, mustFinalize) { if (mustFinalize && input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig; if (mustFinalize && input.finalScriptWitness) { - tx.ins[idx].witness = scriptWitnessToWitnessStack( + tx.ins[idx].witness = (0, witness_js_1.scriptWitnessToWitnessStack)( input.finalScriptWitness, ); } @@ -1680,23 +1685,25 @@ function getScriptAndAmountFromUtxo(inputIndex, input, cache) { } function pubkeyInInput(pubkey, input, inputIndex, cache) { const script = getScriptFromUtxo(inputIndex, input, cache); - const { meaningfulScript } = getMeaningfulScript( + const { meaningfulScript } = (0, scriptType_js_1.getMeaningfulScript)( script, inputIndex, 'input', input.redeemScript, input.witnessScript, + SCRIPT_TYPE_DEPS, ); return (0, psbtutils_js_1.pubkeyInScript)(pubkey, meaningfulScript); } function pubkeyInOutput(pubkey, output, outputIndex, cache) { const script = cache.__TX.outs[outputIndex].script; - const { meaningfulScript } = getMeaningfulScript( + const { meaningfulScript } = (0, scriptType_js_1.getMeaningfulScript)( script, outputIndex, 'output', output.redeemScript, output.witnessScript, + SCRIPT_TYPE_DEPS, ); return (0, psbtutils_js_1.pubkeyInScript)(pubkey, meaningfulScript); } @@ -1717,7 +1724,7 @@ function redeemFromFinalScriptSig(finalScript) { } function redeemFromFinalWitnessScript(finalScript) { if (!finalScript) return; - const decomp = scriptWitnessToWitnessStack(finalScript); + const decomp = (0, witness_js_1.scriptWitnessToWitnessStack)(finalScript); const lastItem = decomp[decomp.length - 1]; if (isPubkeyLike(lastItem)) return; const sDecomp = bscript.decompile(lastItem); @@ -1739,65 +1746,6 @@ function isPubkeyLike(buf) { function isSigLike(buf) { return bscript.isCanonicalScriptSignature(buf); } -function getMeaningfulScript( - script, - index, - ioType, - redeemScript, - witnessScript, -) { - const isP2SH = (0, psbtutils_js_1.isP2SHScript)(script); - const isP2SHP2WSH = - isP2SH && redeemScript && (0, psbtutils_js_1.isP2WSHScript)(redeemScript); - const isP2WSH = (0, psbtutils_js_1.isP2WSHScript)(script); - if (isP2SH && redeemScript === undefined) - throw new Error('scriptPubkey is P2SH but redeemScript missing'); - if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined) - throw new Error( - 'scriptPubkey or redeemScript is P2WSH but witnessScript missing', - ); - let meaningfulScript; - if (isP2SHP2WSH) { - meaningfulScript = witnessScript; - checkRedeemScript(index, script, redeemScript, ioType); - checkWitnessScript(index, redeemScript, witnessScript, ioType); - checkInvalidP2WSH(meaningfulScript); - } else if (isP2WSH) { - meaningfulScript = witnessScript; - checkWitnessScript(index, script, witnessScript, ioType); - checkInvalidP2WSH(meaningfulScript); - } else if (isP2SH) { - meaningfulScript = redeemScript; - checkRedeemScript(index, script, redeemScript, ioType); - } else { - meaningfulScript = script; - } - return { - meaningfulScript, - type: isP2SHP2WSH - ? 'p2sh-p2wsh' - : isP2SH - ? 'p2sh' - : isP2WSH - ? 'p2wsh' - : 'raw', - }; -} -function checkInvalidP2WSH(script) { - if ( - (0, psbtutils_js_1.isP2WPKH)(script) || - (0, psbtutils_js_1.isP2SHScript)(script) - ) { - throw new Error('P2WPKH or P2SH can not be contained within P2WSH'); - } -} -function classifyScript(script) { - if ((0, psbtutils_js_1.isP2WPKH)(script)) return 'witnesspubkeyhash'; - if ((0, psbtutils_js_1.isP2PKH)(script)) return 'pubkeyhash'; - if ((0, psbtutils_js_1.isP2MS)(script)) return 'multisig'; - if ((0, psbtutils_js_1.isP2PK)(script)) return 'pubkey'; - return 'nonstandard'; -} function range(n) { return [...Array(n).keys()]; } diff --git a/src/cjs/psbt/internal/scriptType.cjs b/src/cjs/psbt/internal/scriptType.cjs new file mode 100644 index 000000000..db94345cd --- /dev/null +++ b/src/cjs/psbt/internal/scriptType.cjs @@ -0,0 +1,62 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.classifyScript = classifyScript; +exports.getMeaningfulScript = getMeaningfulScript; +exports.checkInvalidP2WSH = checkInvalidP2WSH; +function classifyScript(script, deps) { + if (deps.isP2WPKH(script)) return 'witnesspubkeyhash'; + if (deps.isP2PKH(script)) return 'pubkeyhash'; + if (deps.isP2MS(script)) return 'multisig'; + if (deps.isP2PK(script)) return 'pubkey'; + return 'nonstandard'; +} +function getMeaningfulScript( + script, + index, + ioType, + redeemScript, + witnessScript, + deps, +) { + const isP2SH = deps.isP2SHScript(script); + const isP2SHP2WSH = + isP2SH && redeemScript && deps.isP2WSHScript(redeemScript); + const isP2WSH = deps.isP2WSHScript(script); + if (isP2SH && redeemScript === undefined) + throw new Error('scriptPubkey is P2SH but redeemScript missing'); + if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined) + throw new Error( + 'scriptPubkey or redeemScript is P2WSH but witnessScript missing', + ); + let meaningfulScript; + if (isP2SHP2WSH) { + meaningfulScript = witnessScript; + deps.checkRedeemScript(index, script, redeemScript, ioType); + deps.checkWitnessScript(index, redeemScript, witnessScript, ioType); + checkInvalidP2WSH(meaningfulScript, deps.isP2WPKH, deps.isP2SHScript); + } else if (isP2WSH) { + meaningfulScript = witnessScript; + deps.checkWitnessScript(index, script, witnessScript, ioType); + checkInvalidP2WSH(meaningfulScript, deps.isP2WPKH, deps.isP2SHScript); + } else if (isP2SH) { + meaningfulScript = redeemScript; + deps.checkRedeemScript(index, script, redeemScript, ioType); + } else { + meaningfulScript = script; + } + return { + meaningfulScript, + type: isP2SHP2WSH + ? 'p2sh-p2wsh' + : isP2SH + ? 'p2sh' + : isP2WSH + ? 'p2wsh' + : 'raw', + }; +} +function checkInvalidP2WSH(script, isP2WPKH, isP2SHScript) { + if (isP2WPKH(script) || isP2SHScript(script)) { + throw new Error('P2WPKH or P2SH can not be contained within P2WSH'); + } +} diff --git a/src/cjs/psbt/internal/scriptType.d.ts b/src/cjs/psbt/internal/scriptType.d.ts new file mode 100644 index 000000000..fe5b843c8 --- /dev/null +++ b/src/cjs/psbt/internal/scriptType.d.ts @@ -0,0 +1,17 @@ +export type ScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'nonstandard'; +export interface ScriptTypeDeps { + isP2WPKH(script: Uint8Array): boolean; + isP2PKH(script: Uint8Array): boolean; + isP2MS(script: Uint8Array): boolean; + isP2PK(script: Uint8Array): boolean; + isP2SHScript(script: Uint8Array): boolean; + isP2WSHScript(script: Uint8Array): boolean; + checkRedeemScript(index: number, script: Uint8Array, redeemScript: Uint8Array, ioType: 'input' | 'output'): void; + checkWitnessScript(index: number, script: Uint8Array, witnessScript: Uint8Array, ioType: 'input' | 'output'): void; +} +export declare function classifyScript(script: Uint8Array, deps: Pick): ScriptType; +export declare function getMeaningfulScript(script: Uint8Array, index: number, ioType: 'input' | 'output', redeemScript: Uint8Array | undefined, witnessScript: Uint8Array | undefined, deps: Pick): { + meaningfulScript: Uint8Array; + type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'raw'; +}; +export declare function checkInvalidP2WSH(script: Uint8Array, isP2WPKH: (script: Uint8Array) => boolean, isP2SHScript: (script: Uint8Array) => boolean): void; diff --git a/src/cjs/psbt/internal/witness.cjs b/src/cjs/psbt/internal/witness.cjs new file mode 100644 index 000000000..b2fc6ce3e --- /dev/null +++ b/src/cjs/psbt/internal/witness.cjs @@ -0,0 +1,70 @@ +'use strict'; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if ( + !desc || + ('get' in desc ? !m.__esModule : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k]; + }, + }; + } + Object.defineProperty(o, k2, desc); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, 'default', { enumerable: true, value: v }); + } + : function (o, v) { + o['default'] = v; + }); +var __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) + __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.scriptWitnessToWitnessStack = scriptWitnessToWitnessStack; +const varuint = __importStar(require('varuint-bitcoin')); +function scriptWitnessToWitnessStack(buffer) { + let offset = 0; + function readSlice(n) { + offset += n; + return buffer.slice(offset - n, offset); + } + function readVarInt() { + const vi = varuint.decode(buffer, offset); + offset += varuint.encodingLength(vi.bigintValue); + return vi.numberValue; + } + function readVarSlice() { + return readSlice(readVarInt()); + } + function readVector() { + const count = readVarInt(); + const vector = []; + for (let i = 0; i < count; i++) vector.push(readVarSlice()); + return vector; + } + return readVector(); +} diff --git a/src/cjs/psbt/internal/witness.d.ts b/src/cjs/psbt/internal/witness.d.ts new file mode 100644 index 000000000..8debfc5a1 --- /dev/null +++ b/src/cjs/psbt/internal/witness.d.ts @@ -0,0 +1 @@ +export declare function scriptWitnessToWitnessStack(buffer: Uint8Array): Uint8Array[]; diff --git a/src/cjs/types.d.ts b/src/cjs/types.d.ts index 7abcd67bb..6123a3bf4 100644 --- a/src/cjs/types.d.ts +++ b/src/cjs/types.d.ts @@ -1,5 +1,5 @@ import * as v from 'valibot'; -export declare const NBufferSchemaFactory: (size: number) => v.SchemaWithPipe<[v.InstanceSchema, v.LengthAction]>; +export declare const NBufferSchemaFactory: (size: number) => v.SchemaWithPipe, v.LengthAction]>; /** * Checks if two arrays of Buffers are equal. * @param a - The first array of Buffers. @@ -34,12 +34,12 @@ export interface TinySecp256k1Interface { isXOnlyPoint(p: Uint8Array): boolean; xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null; } -export declare const Buffer256bitSchema: v.SchemaWithPipe<[v.InstanceSchema, v.LengthAction]>; -export declare const Hash160bitSchema: v.SchemaWithPipe<[v.InstanceSchema, v.LengthAction]>; -export declare const Hash256bitSchema: v.SchemaWithPipe<[v.InstanceSchema, v.LengthAction]>; +export declare const Buffer256bitSchema: v.SchemaWithPipe, v.LengthAction]>; +export declare const Hash160bitSchema: v.SchemaWithPipe, v.LengthAction]>; +export declare const Hash256bitSchema: v.SchemaWithPipe, v.LengthAction]>; export declare const BufferSchema: v.InstanceSchema; -export declare const HexSchema: v.SchemaWithPipe<[v.StringSchema, v.RegexAction]>; -export declare const UInt8Schema: v.SchemaWithPipe<[v.NumberSchema, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; -export declare const UInt32Schema: v.SchemaWithPipe<[v.NumberSchema, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; -export declare const SatoshiSchema: v.SchemaWithPipe<[v.BigintSchema, v.MinValueAction, v.MaxValueAction]>; +export declare const HexSchema: v.SchemaWithPipe, v.RegexAction]>; +export declare const UInt8Schema: v.SchemaWithPipe, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; +export declare const UInt32Schema: v.SchemaWithPipe, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; +export declare const SatoshiSchema: v.SchemaWithPipe, v.MinValueAction, v.MaxValueAction]>; export declare const NullablePartial: (a: Record) => v.ObjectSchema<{}, undefined>; diff --git a/src/esm/psbt.js b/src/esm/psbt.js index 5e2e39b93..765d9da8c 100644 --- a/src/esm/psbt.js +++ b/src/esm/psbt.js @@ -1,5 +1,4 @@ import { Psbt as PsbtBase } from 'bip174'; -import * as varuint from 'varuint-bitcoin'; import { checkForInput, checkForOutput } from 'bip174'; import { fromOutputScript, toOutputScript } from './address.js'; import { cloneBuffer, reverseBuffer } from './bufferutils.js'; @@ -29,6 +28,12 @@ import { isP2SHScript, isP2TR, } from './psbt/psbtutils.js'; +import { scriptWitnessToWitnessStack } from './psbt/internal/witness.js'; +import { + classifyScript, + getMeaningfulScript, + checkInvalidP2WSH, +} from './psbt/internal/scriptType.js'; import * as tools from 'uint8array-tools'; export { toXOnly }; /** @@ -225,7 +230,8 @@ export class Psbt { } checkTaprootInputFields(inputData, inputData, 'addInput'); checkInputsForPartialSig(this.data.inputs, 'addInput'); - if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript); + if (inputData.witnessScript) + checkInvalidP2WSHScript(inputData.witnessScript); const c = this.__CACHE; this.data.addInput(inputData); const txIn = c.__TX.ins[c.__TX.ins.length - 1]; @@ -387,9 +393,10 @@ export class Psbt { input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig), input.witnessScript || redeemFromFinalWitnessScript(input.finalScriptWitness), + SCRIPT_TYPE_DEPS, ); const type = result.type === 'raw' ? '' : result.type + '-'; - const mainType = classifyScript(result.meaningfulScript); + const mainType = classifyScript(result.meaningfulScript, SCRIPT_TYPE_DEPS); return type + mainType; } inputHasPubkey(inputIndex, pubkey) { @@ -872,7 +879,8 @@ export class Psbt { return this; } updateInput(inputIndex, updateData) { - if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript); + if (updateData.witnessScript) + checkInvalidP2WSHScript(updateData.witnessScript); checkTaprootInputFields( this.data.inputs[inputIndex], updateData, @@ -1104,6 +1112,18 @@ const checkWitnessScript = scriptCheckerFactory( payments.p2wsh, 'Witness script', ); +const SCRIPT_TYPE_DEPS = { + isP2WPKH, + isP2PKH, + isP2MS, + isP2PK, + isP2SHScript, + isP2WSHScript, + checkRedeemScript, + checkWitnessScript, +}; +const checkInvalidP2WSHScript = script => + checkInvalidP2WSH(script, isP2WPKH, isP2SHScript); function getTxCacheValue(key, name, inputs, c) { if (!inputs.every(isFinalized)) throw new Error(`PSBT must be finalized to calculate ${name}`); @@ -1122,7 +1142,7 @@ function getTxCacheValue(key, name, inputs, c) { else if (key === '__FEE') return c.__FEE; } function getFinalScripts(inputIndex, input, script, isSegwit, isP2SH, isP2WSH) { - const scriptType = classifyScript(script); + const scriptType = classifyScript(script, SCRIPT_TYPE_DEPS); if (!canFinalize(input, script, scriptType)) throw new Error(`Can not finalize input #${inputIndex}`); return prepareFinalScripts( @@ -1223,6 +1243,7 @@ function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) { 'input', input.redeemScript, input.witnessScript, + SCRIPT_TYPE_DEPS, ); if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) { hash = unsignedTx.hashForWitnessV0( @@ -1477,28 +1498,6 @@ function getSortedSigs(script, partialSig) { }) .filter(v => !!v); } -function scriptWitnessToWitnessStack(buffer) { - let offset = 0; - function readSlice(n) { - offset += n; - return buffer.slice(offset - n, offset); - } - function readVarInt() { - const vi = varuint.decode(buffer, offset); - offset += varuint.encodingLength(vi.bigintValue); - return vi.numberValue; - } - function readVarSlice() { - return readSlice(readVarInt()); - } - function readVector() { - const count = readVarInt(); - const vector = []; - for (let i = 0; i < count; i++) vector.push(readVarSlice()); - return vector; - } - return readVector(); -} function sighashTypeToString(sighashType) { let text = sighashType & Transaction.SIGHASH_ANYONECANPAY @@ -1609,6 +1608,7 @@ function pubkeyInInput(pubkey, input, inputIndex, cache) { 'input', input.redeemScript, input.witnessScript, + SCRIPT_TYPE_DEPS, ); return pubkeyInScript(pubkey, meaningfulScript); } @@ -1620,6 +1620,7 @@ function pubkeyInOutput(pubkey, output, outputIndex, cache) { 'output', output.redeemScript, output.witnessScript, + SCRIPT_TYPE_DEPS, ); return pubkeyInScript(pubkey, meaningfulScript); } @@ -1662,61 +1663,6 @@ function isPubkeyLike(buf) { function isSigLike(buf) { return bscript.isCanonicalScriptSignature(buf); } -function getMeaningfulScript( - script, - index, - ioType, - redeemScript, - witnessScript, -) { - const isP2SH = isP2SHScript(script); - const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript); - const isP2WSH = isP2WSHScript(script); - if (isP2SH && redeemScript === undefined) - throw new Error('scriptPubkey is P2SH but redeemScript missing'); - if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined) - throw new Error( - 'scriptPubkey or redeemScript is P2WSH but witnessScript missing', - ); - let meaningfulScript; - if (isP2SHP2WSH) { - meaningfulScript = witnessScript; - checkRedeemScript(index, script, redeemScript, ioType); - checkWitnessScript(index, redeemScript, witnessScript, ioType); - checkInvalidP2WSH(meaningfulScript); - } else if (isP2WSH) { - meaningfulScript = witnessScript; - checkWitnessScript(index, script, witnessScript, ioType); - checkInvalidP2WSH(meaningfulScript); - } else if (isP2SH) { - meaningfulScript = redeemScript; - checkRedeemScript(index, script, redeemScript, ioType); - } else { - meaningfulScript = script; - } - return { - meaningfulScript, - type: isP2SHP2WSH - ? 'p2sh-p2wsh' - : isP2SH - ? 'p2sh' - : isP2WSH - ? 'p2wsh' - : 'raw', - }; -} -function checkInvalidP2WSH(script) { - if (isP2WPKH(script) || isP2SHScript(script)) { - throw new Error('P2WPKH or P2SH can not be contained within P2WSH'); - } -} -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 range(n) { return [...Array(n).keys()]; } diff --git a/src/esm/psbt/internal/scriptType.js b/src/esm/psbt/internal/scriptType.js new file mode 100644 index 000000000..5f03373f0 --- /dev/null +++ b/src/esm/psbt/internal/scriptType.js @@ -0,0 +1,57 @@ +export function classifyScript(script, deps) { + if (deps.isP2WPKH(script)) return 'witnesspubkeyhash'; + if (deps.isP2PKH(script)) return 'pubkeyhash'; + if (deps.isP2MS(script)) return 'multisig'; + if (deps.isP2PK(script)) return 'pubkey'; + return 'nonstandard'; +} +export function getMeaningfulScript( + script, + index, + ioType, + redeemScript, + witnessScript, + deps, +) { + const isP2SH = deps.isP2SHScript(script); + const isP2SHP2WSH = + isP2SH && redeemScript && deps.isP2WSHScript(redeemScript); + const isP2WSH = deps.isP2WSHScript(script); + if (isP2SH && redeemScript === undefined) + throw new Error('scriptPubkey is P2SH but redeemScript missing'); + if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined) + throw new Error( + 'scriptPubkey or redeemScript is P2WSH but witnessScript missing', + ); + let meaningfulScript; + if (isP2SHP2WSH) { + meaningfulScript = witnessScript; + deps.checkRedeemScript(index, script, redeemScript, ioType); + deps.checkWitnessScript(index, redeemScript, witnessScript, ioType); + checkInvalidP2WSH(meaningfulScript, deps.isP2WPKH, deps.isP2SHScript); + } else if (isP2WSH) { + meaningfulScript = witnessScript; + deps.checkWitnessScript(index, script, witnessScript, ioType); + checkInvalidP2WSH(meaningfulScript, deps.isP2WPKH, deps.isP2SHScript); + } else if (isP2SH) { + meaningfulScript = redeemScript; + deps.checkRedeemScript(index, script, redeemScript, ioType); + } else { + meaningfulScript = script; + } + return { + meaningfulScript, + type: isP2SHP2WSH + ? 'p2sh-p2wsh' + : isP2SH + ? 'p2sh' + : isP2WSH + ? 'p2wsh' + : 'raw', + }; +} +export function checkInvalidP2WSH(script, isP2WPKH, isP2SHScript) { + if (isP2WPKH(script) || isP2SHScript(script)) { + throw new Error('P2WPKH or P2SH can not be contained within P2WSH'); + } +} diff --git a/src/esm/psbt/internal/witness.js b/src/esm/psbt/internal/witness.js new file mode 100644 index 000000000..c6c3a5fb0 --- /dev/null +++ b/src/esm/psbt/internal/witness.js @@ -0,0 +1,23 @@ +import * as varuint from 'varuint-bitcoin'; +export function scriptWitnessToWitnessStack(buffer) { + let offset = 0; + function readSlice(n) { + offset += n; + return buffer.slice(offset - n, offset); + } + function readVarInt() { + const vi = varuint.decode(buffer, offset); + offset += varuint.encodingLength(vi.bigintValue); + return vi.numberValue; + } + function readVarSlice() { + return readSlice(readVarInt()); + } + function readVector() { + const count = readVarInt(); + const vector = []; + for (let i = 0; i < count; i++) vector.push(readVarSlice()); + return vector; + } + return readVector(); +} diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 357a059cd..870c6e116 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -1,5 +1,4 @@ import { Psbt as PsbtBase } from 'bip174'; -import * as varuint from 'varuint-bitcoin'; import { Bip32Derivation, KeyValue, @@ -44,6 +43,12 @@ import { isP2SHScript, isP2TR, } from './psbt/psbtutils.js'; +import { scriptWitnessToWitnessStack } from './psbt/internal/witness.js'; +import { + classifyScript, + getMeaningfulScript, + checkInvalidP2WSH, +} from './psbt/internal/scriptType.js'; import * as tools from 'uint8array-tools'; export { toXOnly }; @@ -295,7 +300,8 @@ export class Psbt { } checkTaprootInputFields(inputData, inputData, 'addInput'); checkInputsForPartialSig(this.data.inputs, 'addInput'); - if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript); + if (inputData.witnessScript) + checkInvalidP2WSHScript(inputData.witnessScript); const c = this.__CACHE; this.data.addInput(inputData); const txIn = c.__TX.ins[c.__TX.ins.length - 1]; @@ -494,9 +500,10 @@ export class Psbt { input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig), input.witnessScript || redeemFromFinalWitnessScript(input.finalScriptWitness), + SCRIPT_TYPE_DEPS, ); const type = result.type === 'raw' ? '' : result.type + '-'; - const mainType = classifyScript(result.meaningfulScript); + const mainType = classifyScript(result.meaningfulScript, SCRIPT_TYPE_DEPS); return (type + mainType) as AllScriptType; } @@ -1097,7 +1104,8 @@ export class Psbt { } updateInput(inputIndex: number, updateData: PsbtInputUpdate): this { - if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript); + if (updateData.witnessScript) + checkInvalidP2WSHScript(updateData.witnessScript); checkTaprootInputFields( this.data.inputs[inputIndex], updateData, @@ -1478,6 +1486,20 @@ const checkWitnessScript = scriptCheckerFactory( 'Witness script', ); +const SCRIPT_TYPE_DEPS = { + isP2WPKH, + isP2PKH, + isP2MS, + isP2PK, + isP2SHScript, + isP2WSHScript, + checkRedeemScript, + checkWitnessScript, +}; + +const checkInvalidP2WSHScript = (script: Uint8Array): void => + checkInvalidP2WSH(script, isP2WPKH, isP2SHScript); + type TxCacheNumberKey = '__FEE_RATE' | '__FEE'; function getTxCacheValue( key: T, @@ -1538,7 +1560,7 @@ function getFinalScripts( finalScriptSig: Uint8Array | undefined; finalScriptWitness: Uint8Array | undefined; } { - const scriptType = classifyScript(script); + const scriptType = classifyScript(script, SCRIPT_TYPE_DEPS); if (!canFinalize(input, script, scriptType)) throw new Error(`Can not finalize input #${inputIndex}`); return prepareFinalScripts( @@ -1666,6 +1688,7 @@ function getHashForSig( 'input', input.redeemScript, input.witnessScript, + SCRIPT_TYPE_DEPS, ); if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) { @@ -1976,34 +1999,6 @@ function getSortedSigs( .filter(v => !!v); } -function scriptWitnessToWitnessStack(buffer: Uint8Array): Uint8Array[] { - let offset = 0; - - function readSlice(n: number): Uint8Array { - offset += n; - return buffer.slice(offset - n, offset); - } - - function readVarInt(): number { - const vi = varuint.decode(buffer, offset); - offset += varuint.encodingLength(vi.bigintValue); - return vi.numberValue!; - } - - function readVarSlice(): Uint8Array { - return readSlice(readVarInt()); - } - - function readVector(): Uint8Array[] { - const count = readVarInt(); - const vector: Uint8Array[] = []; - for (let i = 0; i < count; i++) vector.push(readVarSlice()); - return vector; - } - - return readVector(); -} - function sighashTypeToString(sighashType: number): string { let text = sighashType & Transaction.SIGHASH_ANYONECANPAY @@ -2151,6 +2146,7 @@ function pubkeyInInput( 'input', input.redeemScript, input.witnessScript, + SCRIPT_TYPE_DEPS, ); return pubkeyInScript(pubkey, meaningfulScript); } @@ -2168,6 +2164,7 @@ function pubkeyInOutput( 'output', output.redeemScript, output.witnessScript, + SCRIPT_TYPE_DEPS, ); return pubkeyInScript(pubkey, meaningfulScript); } @@ -2220,62 +2217,6 @@ function isSigLike(buf: Uint8Array): boolean { return bscript.isCanonicalScriptSignature(buf); } -function getMeaningfulScript( - script: Uint8Array, - index: number, - ioType: 'input' | 'output', - redeemScript?: Uint8Array, - witnessScript?: Uint8Array, -): { - meaningfulScript: Uint8Array; - type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'raw'; -} { - const isP2SH = isP2SHScript(script); - const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript); - const isP2WSH = isP2WSHScript(script); - - if (isP2SH && redeemScript === undefined) - throw new Error('scriptPubkey is P2SH but redeemScript missing'); - if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined) - throw new Error( - 'scriptPubkey or redeemScript is P2WSH but witnessScript missing', - ); - - let meaningfulScript: Uint8Array; - - if (isP2SHP2WSH) { - meaningfulScript = witnessScript!; - checkRedeemScript(index, script, redeemScript!, ioType); - checkWitnessScript(index, redeemScript!, witnessScript!, ioType); - checkInvalidP2WSH(meaningfulScript); - } else if (isP2WSH) { - meaningfulScript = witnessScript!; - checkWitnessScript(index, script, witnessScript!, ioType); - checkInvalidP2WSH(meaningfulScript); - } else if (isP2SH) { - meaningfulScript = redeemScript!; - checkRedeemScript(index, script, redeemScript!, ioType); - } else { - meaningfulScript = script; - } - return { - meaningfulScript, - type: isP2SHP2WSH - ? 'p2sh-p2wsh' - : isP2SH - ? 'p2sh' - : isP2WSH - ? 'p2wsh' - : 'raw', - }; -} - -function checkInvalidP2WSH(script: Uint8Array): void { - if (isP2WPKH(script) || isP2SHScript(script)) { - throw new Error('P2WPKH or P2SH can not be contained within P2WSH'); - } -} - type AllScriptType = | 'witnesspubkeyhash' | 'pubkeyhash' @@ -2295,20 +2236,6 @@ type AllScriptType = | 'p2sh-p2wsh-multisig' | 'p2sh-p2wsh-pubkey' | 'p2sh-p2wsh-nonstandard'; -type ScriptType = - | 'witnesspubkeyhash' - | 'pubkeyhash' - | 'multisig' - | 'pubkey' - | 'nonstandard'; -function classifyScript(script: Uint8Array): ScriptType { - if (isP2WPKH(script)) return 'witnesspubkeyhash'; - if (isP2PKH(script)) return 'pubkeyhash'; - if (isP2MS(script)) return 'multisig'; - if (isP2PK(script)) return 'pubkey'; - return 'nonstandard'; -} - function range(n: number): number[] { return [...Array(n).keys()]; } diff --git a/ts_src/psbt/internal/scriptType.ts b/ts_src/psbt/internal/scriptType.ts new file mode 100644 index 000000000..33d0e1dae --- /dev/null +++ b/ts_src/psbt/internal/scriptType.ts @@ -0,0 +1,107 @@ +export type ScriptType = + | 'witnesspubkeyhash' + | 'pubkeyhash' + | 'multisig' + | 'pubkey' + | 'nonstandard'; + +export interface ScriptTypeDeps { + isP2WPKH(script: Uint8Array): boolean; + isP2PKH(script: Uint8Array): boolean; + isP2MS(script: Uint8Array): boolean; + isP2PK(script: Uint8Array): boolean; + isP2SHScript(script: Uint8Array): boolean; + isP2WSHScript(script: Uint8Array): boolean; + checkRedeemScript( + index: number, + script: Uint8Array, + redeemScript: Uint8Array, + ioType: 'input' | 'output', + ): void; + checkWitnessScript( + index: number, + script: Uint8Array, + witnessScript: Uint8Array, + ioType: 'input' | 'output', + ): void; +} + +export function classifyScript( + script: Uint8Array, + deps: Pick, +): ScriptType { + if (deps.isP2WPKH(script)) return 'witnesspubkeyhash'; + if (deps.isP2PKH(script)) return 'pubkeyhash'; + if (deps.isP2MS(script)) return 'multisig'; + if (deps.isP2PK(script)) return 'pubkey'; + return 'nonstandard'; +} + +export function getMeaningfulScript( + script: Uint8Array, + index: number, + ioType: 'input' | 'output', + redeemScript: Uint8Array | undefined, + witnessScript: Uint8Array | undefined, + deps: Pick< + ScriptTypeDeps, + | 'isP2SHScript' + | 'isP2WSHScript' + | 'isP2WPKH' + | 'checkRedeemScript' + | 'checkWitnessScript' + >, +): { + meaningfulScript: Uint8Array; + type: 'p2sh' | 'p2wsh' | 'p2sh-p2wsh' | 'raw'; +} { + const isP2SH = deps.isP2SHScript(script); + const isP2SHP2WSH = + isP2SH && redeemScript && deps.isP2WSHScript(redeemScript); + const isP2WSH = deps.isP2WSHScript(script); + + if (isP2SH && redeemScript === undefined) + throw new Error('scriptPubkey is P2SH but redeemScript missing'); + if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined) + throw new Error( + 'scriptPubkey or redeemScript is P2WSH but witnessScript missing', + ); + + let meaningfulScript: Uint8Array; + + if (isP2SHP2WSH) { + meaningfulScript = witnessScript!; + deps.checkRedeemScript(index, script, redeemScript!, ioType); + deps.checkWitnessScript(index, redeemScript!, witnessScript!, ioType); + checkInvalidP2WSH(meaningfulScript, deps.isP2WPKH, deps.isP2SHScript); + } else if (isP2WSH) { + meaningfulScript = witnessScript!; + deps.checkWitnessScript(index, script, witnessScript!, ioType); + checkInvalidP2WSH(meaningfulScript, deps.isP2WPKH, deps.isP2SHScript); + } else if (isP2SH) { + meaningfulScript = redeemScript!; + deps.checkRedeemScript(index, script, redeemScript!, ioType); + } else { + meaningfulScript = script; + } + return { + meaningfulScript, + type: isP2SHP2WSH + ? 'p2sh-p2wsh' + : isP2SH + ? 'p2sh' + : isP2WSH + ? 'p2wsh' + : 'raw', + }; +} + +export function checkInvalidP2WSH( + script: Uint8Array, + isP2WPKH: (script: Uint8Array) => boolean, + isP2SHScript: (script: Uint8Array) => boolean, +): void { + if (isP2WPKH(script) || isP2SHScript(script)) { + throw new Error('P2WPKH or P2SH can not be contained within P2WSH'); + } +} diff --git a/ts_src/psbt/internal/witness.ts b/ts_src/psbt/internal/witness.ts new file mode 100644 index 000000000..303c4b4ee --- /dev/null +++ b/ts_src/psbt/internal/witness.ts @@ -0,0 +1,29 @@ +import * as varuint from 'varuint-bitcoin'; + +export function scriptWitnessToWitnessStack(buffer: Uint8Array): Uint8Array[] { + let offset = 0; + + function readSlice(n: number): Uint8Array { + offset += n; + return buffer.slice(offset - n, offset); + } + + function readVarInt(): number { + const vi = varuint.decode(buffer, offset); + offset += varuint.encodingLength(vi.bigintValue); + return vi.numberValue!; + } + + function readVarSlice(): Uint8Array { + return readSlice(readVarInt()); + } + + function readVector(): Uint8Array[] { + const count = readVarInt(); + const vector: Uint8Array[] = []; + for (let i = 0; i < count; i++) vector.push(readVarSlice()); + return vector; + } + + return readVector(); +}