UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

130 lines 6.3 kB
import OP from '../OP.js'; import { fromBase58Check } from '../../primitives/utils.js'; import LockingScript from '../LockingScript.js'; import UnlockingScript from '../UnlockingScript.js'; import TransactionSignature from '../../primitives/TransactionSignature.js'; import { sha256 } from '../../primitives/Hash.js'; function verifyTruthy(v) { if (v == null) throw new Error('must have value'); return v; } /** * P2PKH (Pay To Public Key Hash) class implementing ScriptTemplate. * * This class provides methods to create Pay To Public Key Hash locking and unlocking scripts, including the unlocking of P2PKH UTXOs with the private key. */ export default class P2PKH { /** * Creates a P2PKH locking script for a given public key hash or address string * * @param {number[] | string} pubkeyhash or address - An array or address representing the public key hash. * @returns {LockingScript} - A P2PKH locking script. */ lock(pubkeyhash) { let data; if (typeof pubkeyhash === 'string') { const hash = fromBase58Check(pubkeyhash); if (hash.prefix[0] !== 0x00 && hash.prefix[0] !== 0x6f) { throw new Error('only P2PKH is supported'); } data = hash.data; } else { data = pubkeyhash; } if (data.length !== 20) { throw new Error('P2PKH hash length must be 20 bytes'); } return new LockingScript([ { op: OP.OP_DUP }, { op: OP.OP_HASH160 }, { op: data.length, data }, { op: OP.OP_EQUALVERIFY }, { op: OP.OP_CHECKSIG } ]); } /** * Creates a function that generates a P2PKH unlocking script along with its signature and length estimation. * * The returned object contains: * 1. `sign` - A function that, when invoked with a transaction and an input index, * produces an unlocking script suitable for a P2PKH locked output. * 2. `estimateLength` - A function that returns the estimated length of the unlocking script in bytes. * * @param {PrivateKey} privateKey - The private key used for signing the transaction. * @param {'all'|'none'|'single'} signOutputs - The signature scope for outputs. * @param {boolean} anyoneCanPay - Flag indicating if the signature allows for other inputs to be added later. * @param {number} sourceSatoshis - Optional. The amount being unlocked. Otherwise the input.sourceTransaction is required. * @param {Script} lockingScript - Optional. The lockinScript. Otherwise the input.sourceTransaction is required. * @returns {Object} - An object containing the `sign` and `estimateLength` functions. */ unlock(privateKey, signOutputs = 'all', anyoneCanPay = false, sourceSatoshis, lockingScript) { return { sign: async (tx, inputIndex) => { let signatureScope = TransactionSignature.SIGHASH_FORKID; if (signOutputs === 'all') { signatureScope |= TransactionSignature.SIGHASH_ALL; } if (signOutputs === 'none') { signatureScope |= TransactionSignature.SIGHASH_NONE; } if (signOutputs === 'single') { signatureScope |= TransactionSignature.SIGHASH_SINGLE; } if (anyoneCanPay) { signatureScope |= TransactionSignature.SIGHASH_ANYONECANPAY; } const input = tx.inputs[inputIndex]; const otherInputs = tx.inputs.filter((_, index) => index !== inputIndex); const sourceTXID = input.sourceTXID ?? input.sourceTransaction?.id('hex'); if (sourceTXID == null || sourceTXID === undefined) { throw new Error('The input sourceTXID or sourceTransaction is required for transaction signing.'); } if (sourceTXID === '') { throw new Error('The input sourceTXID or sourceTransaction is required for transaction signing.'); } sourceSatoshis ||= input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis; if (sourceSatoshis == null || sourceSatoshis === undefined) { throw new Error('The sourceSatoshis or input sourceTransaction is required for transaction signing.'); } lockingScript ||= input.sourceTransaction?.outputs[input.sourceOutputIndex] .lockingScript; if (lockingScript == null) { throw new Error('The lockingScript or input sourceTransaction is required for transaction signing.'); } const preimage = TransactionSignature.format({ sourceTXID, sourceOutputIndex: verifyTruthy(input.sourceOutputIndex), sourceSatoshis, transactionVersion: tx.version, otherInputs, inputIndex, outputs: tx.outputs, inputSequence: verifyTruthy(input.sequence), subscript: lockingScript, lockTime: tx.lockTime, scope: signatureScope }); const rawSignature = privateKey.sign(sha256(preimage)); const sig = new TransactionSignature(rawSignature.r, rawSignature.s, signatureScope); const sigForScript = sig.toChecksigFormat(); const pubkeyForScript = privateKey .toPublicKey() .encode(true); return new UnlockingScript([ { op: sigForScript.length, data: sigForScript }, { op: pubkeyForScript.length, data: pubkeyForScript } ]); }, estimateLength: async () => { // public key (1+33) + signature (1+73) // Note: We add 1 to each element's length because of the associated OP_PUSH return 108; } }; } } //# sourceMappingURL=P2PKH.js.map