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
JavaScript
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;