UNPKG

lotus-sdk

Version:

Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem

915 lines (914 loc) 35.4 kB
import { Preconditions } from '../util/preconditions.js'; import { BitcoreError } from '../errors.js'; import { BufferWriter } from '../encoding/bufferwriter.js'; import { BufferUtil } from '../util/buffer.js'; import { JSUtil } from '../util/js.js'; import { Script, empty } from '../script.js'; import { Opcode } from '../opcode.js'; import { BN } from '../crypto/bn.js'; import { Output } from './output.js'; import { Signature } from '../crypto/signature.js'; import { TransactionSignature } from './signature.js'; import { sign, verify } from './sighash.js'; import { Hash } from '../crypto/hash.js'; import { tweakPrivateKey, TAPROOT_SIGHASH_TYPE, extractTaprootCommitment, } from '../taproot.js'; import { musigNonceAgg, musigSigAgg } from '../crypto/musig2.js'; export class Input { static MAXINT = 0xffffffff; static DEFAULT_SEQNUMBER = 0xffffffff; static DEFAULT_LOCKTIME_SEQNUMBER = 0xfffffffe; static DEFAULT_RBF_SEQNUMBER = 0xfffffffd; static SEQUENCE_LOCKTIME_TYPE_FLAG = 0x400000; static SEQUENCE_LOCKTIME_DISABLE_FLAG = 0x80000000; static SEQUENCE_LOCKTIME_MASK = 0xffff; static SEQUENCE_LOCKTIME_GRANULARITY = 512; static SEQUENCE_BLOCKDIFF_LIMIT = 0xffff; static PublicKey; static PublicKeyHash; static Multisig; static MultisigScriptHash; static Taproot; static MuSigTaproot; static P2PKH; static P2SH; static P2TR; prevTxId; outputIndex; sequenceNumber; _scriptBuffer; _script; output; constructor(params) { if (params) { this._fromObject(params); } } static create(params) { return new Input(params); } static fromObject(obj) { Preconditions.checkArgument(typeof obj === 'object' && obj !== null, 'Must provide an object'); const input = new Input(); return input._fromObject(obj); } _fromObject(params) { let prevTxId; if (typeof params.prevTxId === 'string' && JSUtil.isHexa(params.prevTxId)) { prevTxId = Buffer.from(params.prevTxId, 'hex'); } else if (Buffer.isBuffer(params.prevTxId)) { prevTxId = params.prevTxId; } else { prevTxId = Buffer.alloc(0); } this.output = params.output; this.prevTxId = prevTxId; this.outputIndex = params.outputIndex ?? 0; this.sequenceNumber = params.sequenceNumber !== undefined ? params.sequenceNumber : Input.DEFAULT_SEQNUMBER; if (params.scriptBuffer === undefined && params.script === undefined) { throw new BitcoreError.Transaction.Input.MissingScript(); } this.setScript(params.scriptBuffer || params.script); return this; } get script() { if (this.isNull()) { return null; } if (!this._script) { this._script = new Script(this._scriptBuffer); this._script._isInput = true; } return this._script; } get scriptBuffer() { return this._scriptBuffer; } setScript(script) { this._script = undefined; if (script instanceof Script) { this._script = script; this._scriptBuffer = script.toBuffer(); } else if (script === null) { this._script = empty(); this._scriptBuffer = this._script.toBuffer(); } else if (Buffer.isBuffer(script)) { this._scriptBuffer = script; this._script = Script.fromBuffer(script); } else if (typeof script === 'string') { if (JSUtil.isHexa(script)) { this._scriptBuffer = Buffer.from(script, 'hex'); this._script = Script.fromBuffer(this._scriptBuffer); } else { this._scriptBuffer = Buffer.from(script, 'utf8'); this._script = Script.fromBuffer(this._scriptBuffer); } } else { throw new TypeError('Invalid script type'); } return this; } isNull() { return (this.prevTxId.toString('hex') === '0000000000000000000000000000000000000000000000000000000000000000' && this.outputIndex === 0xffffffff); } isFinal() { return this.sequenceNumber !== 4294967295; } hasSequence() { return this.sequenceNumber !== Input.DEFAULT_SEQNUMBER; } hasRelativeLockTime() { return ((this.sequenceNumber & Input.SEQUENCE_LOCKTIME_DISABLE_FLAG) !== Input.SEQUENCE_LOCKTIME_DISABLE_FLAG && this.sequenceNumber !== Input.DEFAULT_SEQNUMBER); } getRelativeLockTime() { if (!this.hasRelativeLockTime()) { return BigInt(0); } return BigInt(this.sequenceNumber & Input.SEQUENCE_LOCKTIME_MASK); } isRelativeLockTimeInBlocks() { if (!this.hasRelativeLockTime()) { return false; } return (this.sequenceNumber & Input.SEQUENCE_LOCKTIME_TYPE_FLAG) !== 0; } getRelativeLockTimeInBlocks() { if (!this.isRelativeLockTimeInBlocks()) { return 0; } return Number(this.getRelativeLockTime()); } getRelativeLockTimeInSeconds() { if (this.isRelativeLockTimeInBlocks()) { return 0; } return (Number(this.getRelativeLockTime()) * Number(Input.SEQUENCE_LOCKTIME_GRANULARITY)); } toObject() { const obj = { prevTxId: Buffer.from(this.prevTxId).toString('hex'), outputIndex: this.outputIndex, sequenceNumber: this.sequenceNumber, script: this._scriptBuffer.toString('hex'), }; if (this.script) { ; obj.scriptString = this.script.toASM(); } if (this.output) { ; obj.output = this.output; } return obj; } toJSON = this.toObject; static fromBufferReader(br) { const input = new Input(); input.prevTxId = br.readReverse(32); input.outputIndex = br.readUInt32LE(); input._scriptBuffer = br.readVarLengthBuffer(); input.sequenceNumber = br.readUInt32LE(); return input; } toBuffer() { const bw = new BufferWriter(); bw.writeReverse(this.prevTxId); bw.writeUInt32LE(this.outputIndex); bw.writeVarLengthBuffer(this._scriptBuffer); bw.writeUInt32LE(this.sequenceNumber); return bw.concat(); } toBufferWriter(writer) { if (!writer) { writer = new BufferWriter(); } writer.writeReverse(this.prevTxId); writer.writeUInt32LE(this.outputIndex); const script = this._scriptBuffer; writer.writeVarintNum(script.length); writer.write(script); writer.writeUInt32LE(this.sequenceNumber); return writer; } getSize() { return (32 + 4 + BufferWriter.varintBufNum(this._scriptBuffer.length).length + this._scriptBuffer.length + 4); } isValid() { if (this.isNull()) { return true; } return (this.prevTxId.length === 32 && this.outputIndex >= 0 && this.outputIndex <= 0xffffffff && this._scriptBuffer.length > 0); } clone() { return new Input({ prevTxId: Buffer.from(this.prevTxId), outputIndex: this.outputIndex, sequenceNumber: this.sequenceNumber, scriptBuffer: Buffer.from(this._scriptBuffer), output: this.output, }); } getSignatures(transaction, privateKey, index, sigtype, hashData, signingMethod) { Preconditions.checkState(this.output instanceof Output, 'Output is required'); sigtype = sigtype || Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID; const publicKey = privateKey.publicKey; if (this.output.script.isPublicKeyHashOut()) { const addressHash = hashData || Hash.sha256ripemd160(publicKey.toBuffer()); if (BufferUtil.equals(addressHash, this.output.script.getPublicKeyHash())) { return [ new TransactionSignature({ publicKey: publicKey, prevTxId: this.prevTxId, outputIndex: this.outputIndex, inputIndex: index, signature: sign(transaction, privateKey, sigtype, index, this.output.script, new BN(this.output.satoshis.toString()), undefined, signingMethod), sigtype: sigtype, }), ]; } } else if (this.output.script.isPublicKeyOut()) { if (publicKey.toString() === this.output.script.getPublicKey().toString('hex')) { return [ new TransactionSignature({ publicKey: publicKey, prevTxId: this.prevTxId, outputIndex: this.outputIndex, inputIndex: index, signature: sign(transaction, privateKey, sigtype, index, this.output.script, new BN(this.output.satoshis.toString()), undefined, signingMethod), sigtype: sigtype, }), ]; } } return []; } isFullySigned() { throw new Error('Input#isFullySigned'); } addSignature(transaction, signature, signingMethod) { Preconditions.checkState(this.isValidSignature(transaction, signature, signingMethod), 'Signature is invalid'); if (this.output?.script.isPublicKeyHashOut()) { const script = new Script(); script.add(signature.signature.toTxFormat(signingMethod)); script.add(signature.publicKey.toBuffer()); this.setScript(script); } else if (this.output?.script.isPublicKeyOut()) { const script = new Script(); script.add(signature.signature.toTxFormat(signingMethod)); this.setScript(script); } else { const script = new Script(); script.add(signature.signature.toTxFormat(signingMethod)); if (signature.publicKey) { script.add(signature.publicKey.toBuffer()); } this.setScript(script); } return this; } clearSignatures() { throw new Error('Input#clearSignatures'); } isValidSignature(transaction, signature, signingMethod) { signature.signature.nhashtype = signature.sigtype; return verify(transaction, signature.signature, signature.publicKey, signature.inputIndex, this.output.script, new BN(this.output.satoshis.toString()), undefined, signingMethod); } lockForSeconds(seconds) { Preconditions.checkArgument(typeof seconds === 'number', 'seconds must be a number'); if (seconds < 0 || seconds >= Input.SEQUENCE_LOCKTIME_GRANULARITY * Input.SEQUENCE_LOCKTIME_MASK) { throw new Error('Lock time range error'); } seconds = Math.floor(seconds / Input.SEQUENCE_LOCKTIME_GRANULARITY); this.sequenceNumber = seconds | Input.SEQUENCE_LOCKTIME_TYPE_FLAG; return this; } lockUntilBlockHeight(heightDiff) { Preconditions.checkArgument(typeof heightDiff === 'number', 'heightDiff must be a number'); if (heightDiff < 0 || heightDiff >= Input.SEQUENCE_BLOCKDIFF_LIMIT) { throw new Error('Block height out of range'); } this.sequenceNumber = heightDiff; return this; } getLockTime() { if (this.sequenceNumber & Input.SEQUENCE_LOCKTIME_DISABLE_FLAG) { return null; } if (this.sequenceNumber & Input.SEQUENCE_LOCKTIME_TYPE_FLAG) { const seconds = Input.SEQUENCE_LOCKTIME_GRANULARITY * (this.sequenceNumber & Input.SEQUENCE_LOCKTIME_MASK); return seconds; } else { const blockHeight = this.sequenceNumber & Input.SEQUENCE_LOCKTIME_MASK; return blockHeight; } } _estimateSize() { return this.toBufferWriter().toBuffer().length; } toString() { if (this.isNull()) { return 'Input(coinbase)'; } return `Input(${this.prevTxId.toString('hex')}:${this.outputIndex})`; } } export class MultisigInput extends Input { static OPCODES_SIZE = 1; static SIGNATURE_SIZE = 73; publicKeys; threshold; signatures; publicKeyIndex; constructor(input, pubkeys, threshold, signatures, opts) { super({ prevTxId: input.prevTxId, outputIndex: input.outputIndex, sequenceNumber: input.sequenceNumber, scriptBuffer: input.script?.toBuffer(), output: input.output, }); opts = opts || {}; pubkeys = pubkeys || input.publicKeys; threshold = threshold || input.threshold; signatures = signatures || input.signatures; if (opts.noSorting) { this.publicKeys = pubkeys; } else { this.publicKeys = pubkeys.sort((a, b) => a.toString().localeCompare(b.toString())); } Preconditions.checkState(Script.buildMultisigOut(this.publicKeys, threshold).equals(this.output.script), "Provided public keys don't match to the provided output script"); this.publicKeyIndex = {}; this.publicKeys.forEach((publicKey, index) => { this.publicKeyIndex[publicKey.toString()] = index; }); this.threshold = threshold; this.signatures = signatures ? this._deserializeSignatures(signatures) : new Array(this.publicKeys.length); } toObject() { const obj = super.toObject(); return { ...obj, threshold: this.threshold, publicKeys: this.publicKeys.map(pk => pk.toString()), signatures: this._serializeSignatures(), }; } _deserializeSignatures(signatures) { return signatures.map(signature => { if (!signature) { return undefined; } return new TransactionSignature(signature); }); } _serializeSignatures() { return this.signatures.map(signature => { if (!signature) { return undefined; } return signature.toObject(); }); } getSignatures(transaction, privateKey, index, sigtype, hashData, signingMethod) { Preconditions.checkState(this.output instanceof Output, 'Output is required'); sigtype = sigtype || Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID; const results = []; this.publicKeys.forEach(publicKey => { if (publicKey.toString() === privateKey.publicKey.toString()) { results.push(new TransactionSignature({ publicKey: privateKey.publicKey, prevTxId: this.prevTxId, outputIndex: this.outputIndex, inputIndex: index, signature: sign(transaction, privateKey, sigtype, index, this.output.script, new BN(this.output.satoshis.toString()), undefined, signingMethod), sigtype: sigtype, })); } }); return results; } addSignature(transaction, signature, signingMethod) { Preconditions.checkState(!this.isFullySigned(), 'All needed signatures have already been added'); Preconditions.checkArgument(this.publicKeyIndex[signature.publicKey.toString()] !== undefined, 'Signature has no matching public key'); Preconditions.checkState(this.isValidSignature(transaction, signature, signingMethod), 'Invalid signature'); this.signatures[this.publicKeyIndex[signature.publicKey.toString()]] = signature; this._updateScript(signingMethod); return this; } _updateScript(signingMethod) { const script = new Script(); script.add(Opcode.OP_0); const signatures = this._createSignatures(signingMethod); for (const sig of signatures) { script.add(sig); } this.setScript(script); return this; } _createSignatures(signingMethod) { return this.signatures .filter(signature => signature !== undefined) .map(signature => { return Buffer.concat([ signature.signature.toDER(signingMethod), Buffer.from([signature.sigtype]), ]); }); } clearSignatures() { this.signatures = new Array(this.publicKeys.length); this._updateScript(); return this; } isFullySigned() { return this.countSignatures() === this.threshold; } countMissingSignatures() { return this.threshold - this.countSignatures(); } countSignatures() { return this.signatures.reduce((sum, signature) => sum + (signature ? 1 : 0), 0); } publicKeysWithoutSignature() { return this.publicKeys.filter(publicKey => { return !this.signatures[this.publicKeyIndex[publicKey.toString()]]; }); } isValidSignature(transaction, signature, signingMethod) { signature.signature.nhashtype = signature.sigtype; return verify(transaction, signature.signature, signature.publicKey, signature.inputIndex, this.output.script, new BN(this.output.satoshis.toString()), undefined, signingMethod); } normalizeSignatures(transaction, input, inputIndex, signatures, publicKeys, signingMethod) { return publicKeys .map(pubKey => { let signatureMatch = null; signatures = signatures.filter(signatureBuffer => { if (signatureMatch) { return true; } const signature = new TransactionSignature({ signature: Signature.fromTxFormat(signatureBuffer), publicKey: pubKey, prevTxId: input.prevTxId, outputIndex: input.outputIndex, inputIndex: inputIndex, sigtype: Signature.SIGHASH_ALL, }); signature.signature.nhashtype = signature.sigtype; const isMatch = verify(transaction, signature.signature, signature.publicKey, signature.inputIndex, input.output.script, new BN(input.output.satoshis.toString()), undefined, signingMethod); if (isMatch) { signatureMatch = signature; return false; } return true; }); return signatureMatch ? signatureMatch : null; }) .filter(sig => sig !== null); } _estimateSize() { return (MultisigInput.OPCODES_SIZE + this.threshold * MultisigInput.SIGNATURE_SIZE); } } export class MultisigScriptHashInput extends Input { static OPCODES_SIZE = 7; static SIGNATURE_SIZE = 74; static PUBKEY_SIZE = 34; publicKeys; threshold; signatures; redeemScript; publicKeyIndex; checkBitsField; constructor(input, pubkeys, threshold, signatures, opts) { super({ prevTxId: input.prevTxId, outputIndex: input.outputIndex, sequenceNumber: input.sequenceNumber, scriptBuffer: input.script?.toBuffer(), output: input.output, }); opts = opts || {}; pubkeys = pubkeys || input.publicKeys; threshold = threshold || input.threshold; signatures = signatures || input.signatures; if (opts.noSorting) { this.publicKeys = pubkeys; } else { this.publicKeys = pubkeys.sort((a, b) => a.toString().localeCompare(b.toString())); } this.redeemScript = Script.buildMultisigOut(this.publicKeys, threshold, opts); Preconditions.checkState(Script.buildScriptHashOut(this.redeemScript).equals(this.output.script), "Provided public keys don't hash to the provided output"); this.publicKeyIndex = {}; this.publicKeys.forEach((publicKey, index) => { this.publicKeyIndex[publicKey.toString()] = index; }); this.threshold = threshold; this.signatures = signatures ? this._deserializeSignatures(signatures) : new Array(this.publicKeys.length); this.checkBitsField = new Uint8Array(this.publicKeys.length); } toObject() { const obj = super.toObject(); return { ...obj, threshold: this.threshold, publicKeys: this.publicKeys.map(pk => pk.toString()), signatures: this._serializeSignatures(), }; } _deserializeSignatures(signatures) { return signatures.map(signature => { if (!signature) { return undefined; } return new TransactionSignature(signature); }); } _serializeSignatures() { return this.signatures.map(signature => { if (!signature) { return undefined; } return signature.toObject(); }); } getSignatures(transaction, privateKey, index, sigtype, hashData, signingMethod) { Preconditions.checkState(this.output instanceof Output, 'Output is required'); sigtype = sigtype || Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID; const results = []; this.publicKeys.forEach(publicKey => { if (publicKey.toString() === privateKey.publicKey.toString()) { results.push(new TransactionSignature({ publicKey: privateKey.publicKey, prevTxId: this.prevTxId, outputIndex: this.outputIndex, inputIndex: index, signature: sign(transaction, privateKey, sigtype, index, this.redeemScript, new BN(this.output.satoshis.toString()), undefined, signingMethod), sigtype: sigtype, })); } }); return results; } addSignature(transaction, signature, signingMethod) { Preconditions.checkState(!this.isFullySigned(), 'All needed signatures have already been added'); Preconditions.checkArgument(this.publicKeyIndex[signature.publicKey.toString()] !== undefined, 'Signature has no matching public key'); Preconditions.checkState(this.isValidSignature(transaction, signature, signingMethod), 'Invalid signature'); this.signatures[this.publicKeyIndex[signature.publicKey.toString()]] = signature; this.checkBitsField[this.publicKeyIndex[signature.publicKey.toString()]] = signature !== undefined ? 1 : 0; this._updateScript(signingMethod, this.checkBitsField); return this; } _updateScript(signingMethod, checkBitsField) { const script = new Script(); script.add(Opcode.OP_0); const signatures = this._createSignatures(signingMethod); for (const sig of signatures) { script.add(sig); } script.add(this.redeemScript.toBuffer()); this.setScript(script); return this; } _createSignatures(signingMethod) { return this.signatures .filter(signature => signature !== undefined) .map(signature => { return Buffer.concat([ signature.signature.toDER(signingMethod), Buffer.from([signature.sigtype]), ]); }); } clearSignatures() { this.signatures = new Array(this.publicKeys.length); this._updateScript(); return this; } isFullySigned() { return this.countSignatures() === this.threshold; } countMissingSignatures() { return this.threshold - this.countSignatures(); } countSignatures() { return this.signatures.reduce((sum, signature) => sum + (signature ? 1 : 0), 0); } publicKeysWithoutSignature() { return this.publicKeys.filter(publicKey => { return !this.signatures[this.publicKeyIndex[publicKey.toString()]]; }); } isValidSignature(transaction, signature, signingMethod) { signingMethod = signingMethod || 'ecdsa'; signature.signature.nhashtype = signature.sigtype; return verify(transaction, signature.signature, signature.publicKey, signature.inputIndex, this.redeemScript, new BN(this.output.satoshis.toString()), undefined, signingMethod); } normalizeSignatures(transaction, input, inputIndex, signatures, publicKeys, signingMethod) { return []; } _estimateSize() { return (MultisigScriptHashInput.OPCODES_SIZE + this.threshold * MultisigScriptHashInput.SIGNATURE_SIZE + this.publicKeys.length * MultisigScriptHashInput.PUBKEY_SIZE); } } export class PublicKeyInput extends Input { static SCRIPT_MAX_SIZE = 73; getSignatures(transaction, privateKey, index, sigtype, hashData, signingMethod) { Preconditions.checkState(this.output instanceof Output, 'Output is required'); sigtype = sigtype || Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID; const publicKey = privateKey.publicKey; if (publicKey.toString() === this.output.script.getPublicKey().toString('hex')) { return [ new TransactionSignature({ publicKey: publicKey, prevTxId: this.prevTxId, outputIndex: this.outputIndex, inputIndex: index, signature: sign(transaction, privateKey, sigtype, index, this.output.script, new BN(this.output.satoshis.toString()), undefined, signingMethod), sigtype: sigtype, }), ]; } return []; } addSignature(transaction, signature, signingMethod) { Preconditions.checkState(this.isValidSignature(transaction, signature, signingMethod), 'Signature is invalid'); const script = new Script(); script.add(signature.signature.toTxFormat(signingMethod)); this.setScript(script); return this; } clearSignatures() { this.setScript(new Script()); return this; } isFullySigned() { return this.script.isPublicKeyIn(); } _estimateSize() { return PublicKeyInput.SCRIPT_MAX_SIZE; } } export class PublicKeyHashInput extends Input { static SCRIPT_MAX_SIZE = 73 + 34; getSignatures(transaction, privateKey, index, sigtype, hashData, signingMethod) { Preconditions.checkState(this.output instanceof Output, 'Output is required'); hashData = hashData || Hash.sha256ripemd160(privateKey.publicKey.toBuffer()); sigtype = sigtype || Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID; if (BufferUtil.equals(hashData, this.output.script.getPublicKeyHash())) { return [ new TransactionSignature({ publicKey: privateKey.publicKey, prevTxId: this.prevTxId, outputIndex: this.outputIndex, inputIndex: index, signature: sign(transaction, privateKey, sigtype, index, this.output.script, new BN(this.output.satoshis.toString()), undefined, signingMethod), sigtype: sigtype, }), ]; } return []; } addSignature(transaction, signature, signingMethod) { Preconditions.checkState(this.isValidSignature(transaction, signature, signingMethod), 'Signature is invalid'); const script = new Script(); script.add(signature.signature.toTxFormat(signingMethod)); script.add(signature.publicKey.toBuffer()); this.setScript(script); return this; } clearSignatures() { this.setScript(new Script()); return this; } isFullySigned() { return this.script.isPublicKeyHashIn(); } _estimateSize() { return PublicKeyHashInput.SCRIPT_MAX_SIZE; } } export class TaprootInput extends Input { internalPubKey; merkleRoot; controlBlock; tapScript; constructor(params) { super(params); if (params) { this.internalPubKey = params.internalPubKey; this.merkleRoot = params.merkleRoot; this.controlBlock = params.controlBlock; this.tapScript = params.tapScript; } } getSignatures(transaction, privateKey, index, sigtype, hashData, signingMethod) { sigtype = sigtype || TAPROOT_SIGHASH_TYPE; signingMethod = signingMethod || 'schnorr'; Preconditions.checkState(this.output instanceof Output, 'Output is required'); Preconditions.checkState(this.output.script.isPayToTaproot(), 'Output must be Pay-To-Taproot'); sigtype ||= TAPROOT_SIGHASH_TYPE; if ((sigtype & 0x60) !== Signature.SIGHASH_LOTUS) { throw new Error('Taproot key spend signatures must use "SIGHASH_ALL | SIGHASH_LOTUS" (0x61)'); } signingMethod ||= 'schnorr'; if (signingMethod !== 'schnorr') { throw new Error('Taproot key spend signature must be Schnorr'); } let tweakedPrivateKey = privateKey; if (this.internalPubKey) { const merkleRoot = this.merkleRoot || Buffer.alloc(32); tweakedPrivateKey = tweakPrivateKey(privateKey, merkleRoot); } const signature = sign(transaction, tweakedPrivateKey, sigtype, index, this.output.script, new BN(this.output.satoshis.toString()), undefined, signingMethod); return [ new TransactionSignature({ publicKey: tweakedPrivateKey.publicKey, prevTxId: this.prevTxId, outputIndex: this.outputIndex, inputIndex: index, signature: signature, sigtype: sigtype, }), ]; } addSignature(transaction, signature, signingMethod) { Preconditions.checkState(this.isValidSignature(transaction, signature, signingMethod), 'Signature is invalid'); const script = new Script(); if (!signature.signature.nhashtype && signature.sigtype) { signature.signature.nhashtype = signature.sigtype; } script.add(signature.signature.toTxFormat('schnorr')); this.setScript(script); return this; } isValidSignature(transaction, signature, signingMethod) { Preconditions.checkState(this.output instanceof Output, 'Output is required'); signingMethod = signingMethod || 'schnorr'; if (signingMethod !== 'schnorr') { return false; } return transaction.verifySignature(signature.signature, signature.publicKey, signature.inputIndex, this.output.script, new BN(this.output.satoshis), undefined, signingMethod); } clearSignatures() { this.setScript(new Script()); return this; } isFullySigned() { return this.script !== null && this.script.chunks.length > 0; } _estimateSize() { return 66; } } export class MuSigTaprootInput extends TaprootInput { keyAggContext; publicNonces; aggregatedNonce; partialSignatures; mySignerIndex; constructor(params) { super(params); if (params) { this.keyAggContext = params.keyAggContext; this.mySignerIndex = params.mySignerIndex; this.publicNonces = new Map(); this.partialSignatures = new Map(); } } initMuSigSession(keyAggContext, mySignerIndex) { this.keyAggContext = keyAggContext; this.mySignerIndex = mySignerIndex; this.publicNonces = new Map(); this.partialSignatures = new Map(); return this; } addPublicNonce(signerIndex, publicNonce) { if (!this.publicNonces) { this.publicNonces = new Map(); } this.publicNonces.set(signerIndex, publicNonce); return this; } hasAllNonces() { if (!this.keyAggContext || !this.publicNonces) { return false; } const numSigners = this.keyAggContext.pubkeys.length; return this.publicNonces.size === numSigners; } aggregateNonces() { if (!this.hasAllNonces()) { throw new Error('Not all public nonces received'); } const noncesArray = []; for (let i = 0; i < this.keyAggContext.pubkeys.length; i++) { const nonce = this.publicNonces.get(i); if (!nonce) { throw new Error(`Missing nonce for signer ${i}`); } noncesArray.push(nonce); } this.aggregatedNonce = musigNonceAgg(noncesArray); return this; } addPartialSignature(signerIndex, partialSig) { if (!this.partialSignatures) { this.partialSignatures = new Map(); } this.partialSignatures.set(signerIndex, partialSig); return this; } hasAllPartialSignatures() { if (!this.keyAggContext || !this.partialSignatures) { return false; } const numSigners = this.keyAggContext.pubkeys.length; return this.partialSignatures.size === numSigners; } finalizeMuSigSignature(transaction, message) { if (!this.hasAllPartialSignatures()) { throw new Error('Not all partial signatures received'); } if (!this.aggregatedNonce) { throw new Error('Nonces must be aggregated first'); } const commitment = extractTaprootCommitment(this.output.script); const partialSigsArray = []; for (let i = 0; i < this.keyAggContext.pubkeys.length; i++) { const partialSig = this.partialSignatures.get(i); if (!partialSig) { throw new Error(`Missing partial signature for signer ${i}`); } partialSigsArray.push(partialSig); } const finalSignature = musigSigAgg(partialSigsArray, this.aggregatedNonce, message, commitment); const script = new Script(); script.add(finalSignature.toTxFormat('schnorr')); this.setScript(script); return this; } isFullySigned() { return (super.isFullySigned() || (this.hasAllPartialSignatures() && this.script !== null && this.script.chunks.length > 0)); } } Input.PublicKey = PublicKeyInput; Input.PublicKeyHash = PublicKeyHashInput; Input.Multisig = MultisigInput; Input.MultisigScriptHash = MultisigScriptHashInput; Input.Taproot = TaprootInput; Input.MuSigTaproot = MuSigTaprootInput; Input.P2PKH = PublicKeyHashInput; Input.P2SH = MultisigScriptHashInput; Input.P2TR = TaprootInput;