@runonflux/aa-schnorr-multisig-sdk
Version:
Account Abstraction Schnorr Multi-Signatures SDK
240 lines (239 loc) • 12.2 kB
JavaScript
;
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _Schnorrkel_nonces;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Schnorrkel = void 0;
const secp256k1_1 = __importDefault(require("secp256k1"));
const types_1 = require("../types");
const core_1 = require("../core");
const signature_1 = require("../types/signature");
// hash of private key + nonce
const usedNonces = new Set();
class Schnorrkel {
constructor() {
_Schnorrkel_nonces.set(this, {});
}
markNonceAsUsed(privateKey, publicNonce) {
const x = privateKey.buffer;
const hash = (0, core_1._hashPrivateKey)(x);
const hashPKplusNonce = hash + publicNonce;
usedNonces.add(hashPKplusNonce);
}
isNonceUsed(hashPK, publicNonce) {
return usedNonces.has(hashPK + publicNonce);
}
resetUsedNonces() {
usedNonces.clear();
}
_setNonce(privateKey) {
const { publicNonceData, privateNonceData, hash } = (0, core_1._generatePublicNonces)(privateKey);
if (this.isNonceUsed(hash, publicNonceData.kPublic.toString("hex")) ||
this.isNonceUsed(hash, publicNonceData.kTwoPublic.toString("hex")))
throw new Error("Nonce has already been used and cannot be reused.");
const mappedPublicNonce = {
kPublic: new types_1.Key(Buffer.from(publicNonceData.kPublic)),
kTwoPublic: new types_1.Key(Buffer.from(publicNonceData.kTwoPublic)),
};
const mappedPrivateNonce = {
k: new types_1.Key(Buffer.from(privateNonceData.k)),
kTwo: new types_1.Key(Buffer.from(privateNonceData.kTwo)),
};
__classPrivateFieldGet(this, _Schnorrkel_nonces, "f")[hash] = { ...mappedPrivateNonce, ...mappedPublicNonce };
return hash;
}
_restoreNonce(privateKey, kPrivateKey, kTwoPrivateKey) {
const { publicNonceData, privateNonceData, hash } = (0, core_1._restorePublicNonces)(privateKey, kPrivateKey, kTwoPrivateKey);
if (this.isNonceUsed(hash, publicNonceData.kPublic.toString("hex")) ||
this.isNonceUsed(hash, publicNonceData.kTwoPublic.toString("hex")))
throw new Error("Nonce has already been used and cannot be reused.");
const mappedPublicNonce = {
kPublic: new types_1.Key(Buffer.from(publicNonceData.kPublic)),
kTwoPublic: new types_1.Key(Buffer.from(publicNonceData.kTwoPublic)),
};
const mappedPrivateNonce = {
k: new types_1.Key(Buffer.from(privateNonceData.k)),
kTwo: new types_1.Key(Buffer.from(privateNonceData.kTwo)),
};
__classPrivateFieldGet(this, _Schnorrkel_nonces, "f")[hash] = { ...mappedPrivateNonce, ...mappedPublicNonce };
return hash;
}
clearNonces(privateKey) {
const x = privateKey.buffer;
const hash = (0, core_1._hashPrivateKey)(x);
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete __classPrivateFieldGet(this, _Schnorrkel_nonces, "f")[hash];
}
getMappedPublicNonces(publicNonces) {
return publicNonces.map((publicNonce) => {
return {
kPublic: publicNonce.kPublic.buffer,
kTwoPublic: publicNonce.kTwoPublic.buffer,
};
});
}
getMappedNonces() {
return Object.fromEntries(Object.entries(__classPrivateFieldGet(this, _Schnorrkel_nonces, "f")).map(([hash, nonce]) => {
return [
hash,
{
k: nonce.k.buffer,
kTwo: nonce.kTwo.buffer,
kPublic: nonce.kPublic.buffer,
kTwoPublic: nonce.kTwoPublic.buffer,
},
];
}));
}
getMultisigOutput(multiSig) {
return {
signature: new types_1.SchnorrSignature(Buffer.from(multiSig.signature)),
finalPublicNonce: new signature_1.FinalPublicNonce(Buffer.from(multiSig.finalPublicNonce)),
challenge: new signature_1.Challenge(Buffer.from(multiSig.challenge)),
};
}
static getCombinedPublicKey(publicKeys) {
if (publicKeys.length < 2)
throw new Error("At least 2 public keys should be provided");
// validate that public keys are valid points on the secp256k1 curve
publicKeys.forEach((publicKey) => {
if (!secp256k1_1.default.publicKeyVerify(publicKey.buffer))
throw new Error("Invalid public key provided");
});
const bufferPublicKeys = publicKeys.map((publicKey) => publicKey.buffer);
const L = (0, core_1._generateL)(bufferPublicKeys);
const modifiedKeys = bufferPublicKeys.map((publicKey) => {
return secp256k1_1.default.publicKeyTweakMul(publicKey, (0, core_1._aCoefficient)(publicKey, L));
});
return new types_1.Key(Buffer.from(secp256k1_1.default.publicKeyCombine(modifiedKeys)));
}
static getCombinedAddress(publicKeys) {
if (publicKeys.length < 2)
throw new Error("At least 2 public keys should be provided");
const combinedPublicKey = Schnorrkel.getCombinedPublicKey(publicKeys);
const px = (0, core_1._generatePk)(combinedPublicKey.buffer);
return px;
}
generatePublicNonces(privateKey) {
const hash = this._setNonce(privateKey.buffer);
const nonce = __classPrivateFieldGet(this, _Schnorrkel_nonces, "f")[hash];
return {
kPublic: nonce.kPublic,
kTwoPublic: nonce.kTwoPublic,
};
}
// bucket system for nonces as signers may share nonces between each other before transaction construction
restorePublicNonces(privateKey, kPrivateKey, kTwoPrivateKey) {
const hash = this._restoreNonce(privateKey.buffer, kPrivateKey.buffer, kTwoPrivateKey.buffer);
const nonce = __classPrivateFieldGet(this, _Schnorrkel_nonces, "f")[hash];
return {
kPublic: nonce.kPublic,
kTwoPublic: nonce.kTwoPublic,
};
}
getPublicNonces(privateKey) {
const hash = (0, core_1._hashPrivateKey)(privateKey.buffer);
const nonce = __classPrivateFieldGet(this, _Schnorrkel_nonces, "f")[hash];
return {
kPublic: nonce.kPublic,
kTwoPublic: nonce.kTwoPublic,
};
}
hasNonce(privateKey) {
const hash = (0, core_1._hashPrivateKey)(privateKey.buffer);
return hash in __classPrivateFieldGet(this, _Schnorrkel_nonces, "f");
}
multiSigSign(privateKey, msg, publicKeys, publicNonces, hashFn = core_1._hashMessage) {
const combinedPublicKey = Schnorrkel.getCombinedPublicKey(publicKeys);
const mappedPublicNonce = this.getMappedPublicNonces(publicNonces);
const mappedNonces = this.getMappedNonces();
const x = privateKey.buffer;
const hash = (0, core_1._hashPrivateKey)(x);
// check for each nonce if it has already been used
mappedPublicNonce.forEach((nonce) => {
if (this.isNonceUsed(hash, nonce.kPublic.toString("hex")) || this.isNonceUsed(hash, nonce.kTwoPublic.toString("hex")))
throw new Error("Nonce has already been used and cannot be reused.");
});
try {
const musigData = (0, core_1._multiSigSign)(mappedNonces, combinedPublicKey.buffer, privateKey.buffer, msg, publicKeys.map((key) => key.buffer), mappedPublicNonce, hashFn);
// absolutely crucial to delete the nonces once a signature has been crafted with them.
// nonce reuse will lead to private key leakage!
this.clearNonces(privateKey);
return this.getMultisigOutput(musigData);
}
finally {
// absolutely crucial to delete the nonces once a signature has been crafted with them.
// nonce reuse will lead to private key leakage!
this.clearNonces(privateKey);
// Ensure nonces are marked as used and cleared regardless of success or failure
mappedPublicNonce.forEach((nonce) => {
this.markNonceAsUsed(privateKey, nonce.kPublic.toString("hex"));
this.markNonceAsUsed(privateKey, nonce.kTwoPublic.toString("hex"));
});
}
}
multiSigSignHash(privateKey, hash, publicKeys, publicNonces) {
const combinedPublicKey = Schnorrkel.getCombinedPublicKey(publicKeys);
const mappedPublicNonce = this.getMappedPublicNonces(publicNonces);
const mappedNonces = this.getMappedNonces();
const x = privateKey.buffer;
const hashPK = (0, core_1._hashPrivateKey)(x);
// check for each nonce if it has already been used
mappedPublicNonce.forEach((nonce) => {
if (this.isNonceUsed(hashPK, nonce.kPublic.toString("hex")) || this.isNonceUsed(hashPK, nonce.kTwoPublic.toString("hex")))
throw new Error("Nonce has already been used and cannot be reused.");
});
try {
const musigData = (0, core_1._multiSigSignHash)(mappedNonces, combinedPublicKey.buffer, privateKey.buffer, hash, publicKeys.map((key) => key.buffer), mappedPublicNonce);
// absolutely crucial to delete the nonces once a signature has been crafted with them.
// nonce reuse will lead to private key leakage!
this.clearNonces(privateKey);
return this.getMultisigOutput(musigData);
}
finally {
// absolutely crucial to delete the nonces once a signature has been crafted with them.
// nonce reuse will lead to private key leakage!
this.clearNonces(privateKey);
// Ensure nonces are marked as used and cleared regardless of success or failure
mappedPublicNonce.forEach((nonce) => {
this.markNonceAsUsed(privateKey, nonce.kPublic.toString("hex"));
this.markNonceAsUsed(privateKey, nonce.kTwoPublic.toString("hex"));
});
}
}
static sign(privateKey, msg, hashFn = core_1._hashMessage) {
const output = (0, core_1._sign)(privateKey.buffer, msg, hashFn);
return {
signature: new types_1.SchnorrSignature(Buffer.from(output.signature)),
finalPublicNonce: new signature_1.FinalPublicNonce(Buffer.from(output.finalPublicNonce)),
challenge: new signature_1.Challenge(Buffer.from(output.challenge)),
};
}
static signHash(privateKey, hash) {
const output = (0, core_1._signHash)(privateKey.buffer, hash);
return {
signature: new types_1.SchnorrSignature(Buffer.from(output.signature)),
finalPublicNonce: new signature_1.FinalPublicNonce(Buffer.from(output.finalPublicNonce)),
challenge: new signature_1.Challenge(Buffer.from(output.challenge)),
};
}
static sumSigs(signatures) {
const mappedSignatures = signatures.map((signature) => signature.buffer);
const sum = (0, core_1._sumSigs)(mappedSignatures);
return new types_1.SchnorrSignature(Buffer.from(sum));
}
static verify(signature, msg, finalPublicNonce, publicKey, hashFn = core_1._hashMessage) {
return (0, core_1._verify)(signature.buffer, msg, finalPublicNonce.buffer, publicKey.buffer, hashFn);
}
static verifyHash(signature, hash, finalPublicNonce, publicKey) {
return (0, core_1._verifyHash)(signature.buffer, hash, finalPublicNonce.buffer, publicKey.buffer);
}
}
exports.Schnorrkel = Schnorrkel;
_Schnorrkel_nonces = new WeakMap();