UNPKG

@runonflux/aa-schnorr-multisig-sdk

Version:

Account Abstraction Schnorr Multi-Signatures SDK

240 lines (239 loc) 12.2 kB
"use strict"; 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();