UNPKG

lotus-sdk

Version:

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

147 lines (146 loc) 7.4 kB
import { Signature } from '../crypto/signature.js'; import { Hash } from '../crypto/hash.js'; import { Random } from '../crypto/random.js'; import { musigKeyAgg, musigNonceGen, musigNonceAgg, musigPartialSign, musigPartialSigVerify, musigSigAgg, } from '../crypto/musig2.js'; import { MuSigSessionManager, } from './session.js'; import { buildMuSigTaprootKey, signTaprootKeyPathWithMuSig2, } from '../taproot/musig2.js'; import { sighash } from '../transaction/sighash.js'; import { BN } from '../crypto/bn.js'; export class MuSig2Signer { config; sessionManager; constructor(config) { if (!config.signers || config.signers.length === 0) { throw new Error('MuSig2Signer: At least one signer required'); } if (!config.myPrivateKey) { throw new Error('MuSig2Signer: myPrivateKey is required'); } const myPubKey = config.myPrivateKey.publicKey; const myIndex = config.signers.findIndex(signer => signer.toString() === myPubKey.toString()); if (myIndex === -1) { throw new Error('MuSig2Signer: myPrivateKey does not correspond to any signer'); } this.config = config; } prepare(message, useSession = false) { const normalizedMessage = this._normalizeMessage(message); const keyAggContext = musigKeyAgg(this.config.signers); const entropy = this.config.extraInput !== undefined ? this.config.extraInput : Random.getRandomBuffer(32); const nonce = musigNonceGen(this.config.myPrivateKey, keyAggContext.aggregatedPubKey, normalizedMessage, entropy); const myIndex = keyAggContext.pubkeys.findIndex(s => s.toString() === this.config.myPrivateKey.publicKey.toString()); const result = { keyAggContext, myPublicNonces: nonce.publicNonces, mySecretNonces: nonce.secretNonces, myIndex, }; if (useSession) { if (!this.sessionManager) { this.sessionManager = new MuSigSessionManager(); } const session = this.sessionManager.createSession(this.config.signers, this.config.myPrivateKey, normalizedMessage); this.sessionManager.generateNonces(session, this.config.myPrivateKey, this.config.extraInput); result.sessionId = session.sessionId; } return result; } createPartialSignature(prepare, allPublicNonces, message) { const normalizedMessage = this._normalizeMessage(message); const aggregatedNonce = musigNonceAgg(allPublicNonces); const secretNonce = { secretNonces: prepare.mySecretNonces, publicNonces: prepare.myPublicNonces, }; return musigPartialSign(secretNonce, this.config.myPrivateKey, prepare.keyAggContext, prepare.myIndex, aggregatedNonce, normalizedMessage); } verifyPartialSignature(partialSig, publicNonce, publicKey, signerIndex, prepare, allPublicNonces, message) { const normalizedMessage = this._normalizeMessage(message); const aggregatedNonce = musigNonceAgg(allPublicNonces); return musigPartialSigVerify(partialSig, publicNonce, publicKey, prepare.keyAggContext, signerIndex, aggregatedNonce, normalizedMessage); } sign(prepare, allPublicNonces, message, allPartialSigs) { if (allPublicNonces.length !== this.config.signers.length) { throw new Error(`Invalid number of public nonces: expected ${this.config.signers.length}, got ${allPublicNonces.length}`); } if (allPartialSigs.length !== this.config.signers.length) { throw new Error(`Invalid number of partial signatures: expected ${this.config.signers.length}, got ${allPartialSigs.length}`); } const normalizedMessage = this._normalizeMessage(message); const aggregatedNonce = musigNonceAgg(allPublicNonces); const signature = musigSigAgg(allPartialSigs, aggregatedNonce, normalizedMessage, prepare.keyAggContext.aggregatedPubKey); return { signature, aggregatedPubKey: prepare.keyAggContext.aggregatedPubKey, isAggregator: true, }; } prepareTaproot(state) { const result = buildMuSigTaprootKey(this.config.signers, state); const keyAggContext = musigKeyAgg(this.config.signers); return { ...result, keyAggContext, }; } signTaprootInput(prepare, allPublicNonces, transaction, inputIndex, amount, sighashType) { const sigType = sighashType || Signature.SIGHASH_ALL | Signature.SIGHASH_LOTUS; const satoshisBN = new BN(amount); const sighashBuffer = sighash(transaction, sigType, inputIndex, prepare.script, satoshisBN); const normalizedSighash = this._normalizeMessage(sighashBuffer); const aggregatedNonce = musigNonceAgg(allPublicNonces); const entropy = this.config.extraInput !== undefined ? this.config.extraInput : Random.getRandomBuffer(32); const nonce = musigNonceGen(this.config.myPrivateKey, prepare.keyAggContext.aggregatedPubKey, normalizedSighash, entropy); const myIndex = prepare.keyAggContext.pubkeys.findIndex(s => s.toString() === this.config.myPrivateKey.publicKey.toString()); return signTaprootKeyPathWithMuSig2(nonce, this.config.myPrivateKey, prepare.keyAggContext, myIndex, aggregatedNonce, normalizedSighash, prepare.tweak); } completeTaprootSigning(prepare, allPublicNonces, allPartialSigs, transaction, inputIndex, amount, sighashType) { const sigType = sighashType || Signature.SIGHASH_ALL | Signature.SIGHASH_LOTUS; const satoshisBN = new BN(amount); const sighashBuffer = sighash(transaction, sigType, inputIndex, prepare.script, satoshisBN); const normalizedSighash = this._normalizeMessage(sighashBuffer); const aggregatedNonce = musigNonceAgg(allPublicNonces); return musigSigAgg(allPartialSigs, aggregatedNonce, normalizedSighash, prepare.commitment); } createSession(message, metadata) { if (!this.sessionManager) { this.sessionManager = new MuSigSessionManager(); } const normalizedMessage = this._normalizeMessage(message); const session = this.sessionManager.createSession(this.config.signers, this.config.myPrivateKey, normalizedMessage, metadata); return { manager: this.sessionManager, session, }; } get myPublicKey() { return this.config.myPrivateKey.publicKey; } get allSigners() { return [...this.config.signers]; } get myIndex() { const sortedSigners = [...this.config.signers].sort((a, b) => { const bufA = a.toBuffer(); const bufB = b.toBuffer(); return bufA.compare(bufB); }); return sortedSigners.findIndex(s => s.toString() === this.config.myPrivateKey.publicKey.toString()); } _normalizeMessage(message) { if (typeof message === 'string') { return Hash.sha256(Buffer.from(message, 'utf8')); } if (message.length === 32) { return message; } return Hash.sha256(message); } } export function createMuSig2Signer(signers, myPrivateKey) { return new MuSig2Signer({ signers, myPrivateKey }); }