lotus-sdk
Version:
Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem
105 lines (104 loc) • 4.64 kB
JavaScript
import { Address } from '../address.js';
import { BN, Point, Hash } from '../crypto/index.js';
import { musigKeyAgg, musigPartialSign, musigPartialSigVerify, musigTaggedHash, MUSIG_TAG_NONCE_COEFF, } from '../crypto/musig2.js';
import { buildKeyPathTaproot, buildScriptPathTaproot, calculateTapTweak, tweakPublicKey, } from '../taproot.js';
export function buildMuSigTaprootKey(signerPubKeys, state) {
const keyAggContext = musigKeyAgg(signerPubKeys);
const aggregatedPubKey = keyAggContext.aggregatedPubKey;
const merkleRoot = Buffer.alloc(32);
const tweak = calculateTapTweak(aggregatedPubKey, merkleRoot);
const commitment = tweakPublicKey(aggregatedPubKey, merkleRoot);
const script = buildKeyPathTaproot(aggregatedPubKey, state);
return {
aggregatedPubKey,
commitment,
script,
keyAggContext,
merkleRoot,
tweak,
};
}
export function buildMuSigTaprootKeyWithScripts(signerPubKeys, scriptTree, state) {
const keyAggContext = musigKeyAgg(signerPubKeys);
const aggregatedPubKey = keyAggContext.aggregatedPubKey;
const { script, commitment, merkleRoot, leaves } = buildScriptPathTaproot(aggregatedPubKey, scriptTree, state);
const tweak = calculateTapTweak(aggregatedPubKey, merkleRoot);
return {
aggregatedPubKey,
commitment,
script,
keyAggContext,
merkleRoot,
tweak,
leaves,
};
}
export function signTaprootKeyPathWithMuSig2(secretNonce, privateKey, keyAggContext, signerIndex, aggregatedNonce, message, tweak) {
const n = Point.getN();
const commitment = keyAggContext.aggregatedPubKey.addScalar(tweak);
const modifiedKeyAggContext = {
...keyAggContext,
aggregatedPubKey: commitment,
};
const partialSig = musigPartialSign(secretNonce, privateKey, modifiedKeyAggContext, signerIndex, aggregatedNonce, message);
if (signerIndex === 0) {
const { R1, R2 } = aggregatedNonce;
const nonceCoefData = Buffer.concat([
commitment.toBuffer(),
Point.pointToCompressed(R1),
Point.pointToCompressed(R2),
message,
]);
const b = new BN(musigTaggedHash(MUSIG_TAG_NONCE_COEFF, nonceCoefData), 'be');
const R = R1.add(R2.mul(b));
const R_x = R.getX().toArrayLike(Buffer, 'be', 32);
const commitment_compressed = Point.pointToCompressed(commitment.point);
const challengeData = Buffer.concat([R_x, commitment_compressed, message]);
const e = new BN(Hash.sha256(challengeData), 'be').umod(n);
const tweakBN = new BN(tweak, 'be').umod(n);
const tweakTerm = e.mul(tweakBN).umod(n);
return partialSig.add(tweakTerm).umod(n);
}
return partialSig;
}
export function verifyTaprootKeyPathMuSigPartial(partialSig, publicNonce, publicKey, keyAggContext, signerIndex, aggregatedNonce, message, tweak) {
const n = Point.getN();
const commitment = keyAggContext.aggregatedPubKey.addScalar(tweak);
const modifiedKeyAggContext = {
...keyAggContext,
aggregatedPubKey: commitment,
};
let adjustedPartialSig = partialSig;
if (signerIndex === 0) {
const { R1, R2 } = aggregatedNonce;
const nonceCoefData = Buffer.concat([
commitment.toBuffer(),
Point.pointToCompressed(R1),
Point.pointToCompressed(R2),
message,
]);
const b = new BN(musigTaggedHash(MUSIG_TAG_NONCE_COEFF, nonceCoefData), 'be');
const R = R1.add(R2.mul(b));
const R_x = R.getX().toArrayLike(Buffer, 'be', 32);
const commitment_compressed = Point.pointToCompressed(commitment.point);
const challengeData = Buffer.concat([R_x, commitment_compressed, message]);
const e = new BN(Hash.sha256(challengeData), 'be').umod(n);
const tweakBN = new BN(tweak, 'be').umod(n);
const tweakTerm = e.mul(tweakBN).umod(n);
adjustedPartialSig = partialSig.sub(tweakTerm).umod(n);
}
return musigPartialSigVerify(adjustedPartialSig, publicNonce, publicKey, modifiedKeyAggContext, signerIndex, aggregatedNonce, message);
}
export function isMuSigTaprootOutput(script) {
return script.isPayToTaproot();
}
export function createMuSigTaprootAddress(signerPubKeys, network = 'livenet', state) {
const result = buildMuSigTaprootKey(signerPubKeys, state);
const address = Address.fromTaprootCommitment(result.commitment, network);
return {
address,
script: result.script,
commitment: result.commitment,
keyAggContext: result.keyAggContext,
};
}