UNPKG

gib-cli

Version:
188 lines (187 loc) 10 kB
import { Hash, LockingScript, OP, TransactionSignature, UnlockingScript, Utils, } from "@bsv/sdk"; /** * 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 CosignTemplate { /** * Creates a P2PKH locking script for a given public key hash or address string * * @param {number[] | string} userPKHash or address - An array or address representing the public key hash of the owning user. * @param {PublicKey} approverPubKey - Public key of the approver. * @returns {LockingScript} - A P2PKH locking script. */ lock(userPKHash, approverPubKey) { let pkhash = []; if (typeof userPKHash === "string") { const hash = Utils.fromBase58Check(userPKHash); if (hash.prefix[0] !== 0x00 && hash.prefix[0] !== 0x6f) throw new Error("only P2PKH is supported"); pkhash = hash.data; } else { pkhash = userPKHash; } const lockingScript = new LockingScript(); lockingScript .writeOpCode(OP.OP_DUP) .writeOpCode(OP.OP_HASH160) .writeBin(pkhash) .writeOpCode(OP.OP_EQUALVERIFY) .writeOpCode(OP.OP_CHECKSIGVERIFY) .writeBin(approverPubKey.encode(true)) .writeOpCode(OP.OP_CHECKSIG); return lockingScript; } /** * 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} userPrivateKey - 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. */ userUnlock(userPrivateKey, 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.sourceTXID : input.sourceTransaction?.id("hex"); if (!sourceTXID) { throw new Error("The input sourceTXID or sourceTransaction is required for transaction signing."); } sourceSatoshis || (sourceSatoshis = input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis); if (!sourceSatoshis) { throw new Error("The sourceSatoshis or input sourceTransaction is required for transaction signing."); } lockingScript || (lockingScript = input.sourceTransaction?.outputs[input.sourceOutputIndex] .lockingScript); if (!lockingScript) { throw new Error("The lockingScript or input sourceTransaction is required for transaction signing."); } const preimage = TransactionSignature.format({ sourceTXID, sourceOutputIndex: input.sourceOutputIndex, sourceSatoshis, transactionVersion: tx.version, otherInputs, inputIndex, outputs: tx.outputs, inputSequence: input.sequence || 0xffffffff, subscript: lockingScript, lockTime: tx.lockTime, scope: signatureScope, }); const rawSignature = userPrivateKey.sign(Hash.sha256(preimage)); const sig = new TransactionSignature(rawSignature.r, rawSignature.s, signatureScope); const unlockScript = new UnlockingScript(); unlockScript.writeBin(sig.toChecksigFormat()); unlockScript.writeBin(userPrivateKey.toPublicKey().encode(true)); return unlockScript; }, estimateLength: async () => { // public key (1+33) + signature (1+73) + approver signature (1+73) // Note: We add 1 to each element's length because of the associated OP_PUSH return 182; }, }; } /** * 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} approverPrivateKey - 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(approverPrivateKey, userSigScript, 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.sourceTXID : input.sourceTransaction?.id("hex"); if (!sourceTXID) { throw new Error("The input sourceTXID or sourceTransaction is required for transaction signing."); } sourceSatoshis || (sourceSatoshis = input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis); if (!sourceSatoshis) { throw new Error("The sourceSatoshis or input sourceTransaction is required for transaction signing."); } lockingScript || (lockingScript = input.sourceTransaction?.outputs[input.sourceOutputIndex] .lockingScript); if (!lockingScript) { throw new Error("The lockingScript or input sourceTransaction is required for transaction signing."); } const preimage = TransactionSignature.format({ sourceTXID, sourceOutputIndex: input.sourceOutputIndex, sourceSatoshis, transactionVersion: tx.version, otherInputs, inputIndex, outputs: tx.outputs, inputSequence: input.sequence || 0xffffffff, subscript: lockingScript, lockTime: tx.lockTime, scope: signatureScope, }); const rawSignature = approverPrivateKey.sign(Hash.sha256(preimage)); const sig = new TransactionSignature(rawSignature.r, rawSignature.s, signatureScope); const unlockScript = new UnlockingScript(); unlockScript.writeBin(sig.toChecksigFormat()); unlockScript.writeScript(userSigScript); return unlockScript; }, estimateLength: async () => { // public key (1+33) + signature (1+73) + approver signature (1+73) // Note: We add 1 to each element's length because of the associated OP_PUSH return 182; }, }; } }