UNPKG

@runonflux/aa-schnorr-multisig-sdk

Version:

Account Abstraction Schnorr Multi-Signatures SDK

273 lines (272 loc) 11.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports._signHash = exports._sign = exports._generatePk = exports._verifyHash = exports._verify = exports._sumSigs = exports._multiSigSignHash = exports._multiSigSign = exports._restorePublicNonces = exports._generatePublicNonces = exports._hashMessage = exports._hashPrivateKey = exports.generateRandomKeys = exports._aCoefficient = exports._generateL = exports._concatTypedArrays = exports._generateNonce = void 0; const ethers_1 = require("ethers"); const secp256k1_1 = __importDefault(require("secp256k1")); const ecurve_1 = __importDefault(require("ecurve")); const elliptic_1 = __importDefault(require("elliptic")); const bigi_1 = __importDefault(require("bigi")); const bn_js_1 = require("bn.js"); const types_1 = require("../types"); const curve = ecurve_1.default.getCurveByName("secp256k1"); const n = curve?.n; const EC = elliptic_1.default.ec; const ec = new EC("secp256k1"); const generatorPoint = ec.g; /** * Generate a pair of nonces (k and kTwo) needed for `internalMultiSigSign`. * Schnorr signature scheme is linear where `k` and `kTwo` are function parameters. * * @returns InternalNoncePairs */ const _generateNonce = () => { const k = Buffer.from(ethers_1.ethers.randomBytes(32)); const kTwo = Buffer.from(ethers_1.ethers.randomBytes(32)); const kPublic = Buffer.from(secp256k1_1.default.publicKeyCreate(k)); const kTwoPublic = Buffer.from(secp256k1_1.default.publicKeyCreate(kTwo)); return { k, kTwo, kPublic, kTwoPublic, }; }; exports._generateNonce = _generateNonce; /** * Restore a pair of nonces (k and kTwo) needed for `internalMultiSigSign` given privateKeys * Schnorr signature scheme is linear where `k` and `kTwo` are function parameters. * * @returns InternalNoncePairs */ const _restoreNonce = (kPrivateKey, kTwoPrivateKey) => { const k = kPrivateKey; const kTwo = kTwoPrivateKey; const kPublic = Buffer.from(secp256k1_1.default.publicKeyCreate(k)); const kTwoPublic = Buffer.from(secp256k1_1.default.publicKeyCreate(kTwo)); return { k, kTwo, kPublic, kTwoPublic, }; }; const _bCoefficient = (combinedPublicKey, msgHash, publicNonces) => { const arrayColumn = (arr, nonces) => arr.map((x) => x[nonces]); const kPublicNonces = secp256k1_1.default.publicKeyCombine(arrayColumn(publicNonces, "kPublic")); const kTwoPublicNonces = secp256k1_1.default.publicKeyCombine(arrayColumn(publicNonces, "kTwoPublic")); return Buffer.from(ethers_1.ethers.getBytes(ethers_1.ethers.solidityPackedKeccak256(["bytes", "bytes32", "bytes", "bytes"], [combinedPublicKey, msgHash, kPublicNonces, kTwoPublicNonces]))); }; const areBuffersSame = (buf1, buf2) => { if (buf1.byteLength !== buf2.byteLength) return false; const dv1 = Buffer.from(buf1); const dv2 = Buffer.from(buf2); for (let index = 0; index !== buf1.byteLength; index++) if (dv1[index] !== dv2[index]) return false; return true; }; const challenge = (R, msgHash, publicKey) => { // convert R to address const R_uncomp = secp256k1_1.default.publicKeyConvert(R, false); const R_addr = ethers_1.ethers.getBytes(ethers_1.ethers.keccak256(R_uncomp.slice(1, 65))).slice(12, 32); // e = keccak256(address(R) || compressed publicKey || msgHash) return Buffer.from(ethers_1.ethers.getBytes(ethers_1.ethers.solidityPackedKeccak256(["address", "uint8", "bytes32", "bytes32"], [ethers_1.ethers.hexlify(R_addr), publicKey[0] + 27 - 2, publicKey.slice(1, 33), msgHash]))); }; /** * Sign the given hash by the private key * * @param Buffer privateKey * @param string hash * @returns InternalSignature */ const internalSign = (privateKey, hash) => { const localPk = Buffer.from(privateKey); const publicKey = Buffer.from(secp256k1_1.default.publicKeyCreate(localPk)); // R = G * k const k = ethers_1.ethers.randomBytes(32); const R = Buffer.from(secp256k1_1.default.publicKeyCreate(k)); // e = h(address(R) || compressed pubkey || m) const e = challenge(R, hash, publicKey); // xe = x * e const xe = secp256k1_1.default.privateKeyTweakMul(localPk, e); // s = k + xe mod(n) let s = Buffer.from(secp256k1_1.default.privateKeyTweakAdd(k, xe)); s = bigi_1.default.fromBuffer(s).mod(n).toBuffer(32); return { finalPublicNonce: R, challenge: e, signature: s, }; }; const internalMultiSigSign = (nonces, combinedPublicKey, privateKey, hash, publicKeys, publicNonces) => { if (publicKeys.length < 2) throw new Error("At least 2 public keys should be provided"); const localPk = Buffer.from(privateKey); const xHashed = (0, exports._hashPrivateKey)(localPk); if (!(xHashed in nonces) || Object.keys(nonces[xHashed]).length === 0) throw new Error("Nonces should be exchanged before signing"); const publicKey = Buffer.from(secp256k1_1.default.publicKeyCreate(localPk)); const L = (0, exports._generateL)(publicKeys); const a = (0, exports._aCoefficient)(publicKey, L); const b = _bCoefficient(combinedPublicKey, hash, publicNonces); const effectiveNonces = publicNonces.map((batch) => { return Buffer.from(secp256k1_1.default.publicKeyCombine([batch.kPublic, secp256k1_1.default.publicKeyTweakMul(batch.kTwoPublic, b)])); }); const signerEffectiveNonce = Buffer.from(secp256k1_1.default.publicKeyCombine([nonces[xHashed].kPublic, secp256k1_1.default.publicKeyTweakMul(nonces[xHashed].kTwoPublic, b)])); const isInArray = effectiveNonces.some((nonce) => areBuffersSame(nonce, signerEffectiveNonce)); if (!isInArray) throw new Error("Passed nonces are invalid"); const R = Buffer.from(secp256k1_1.default.publicKeyCombine(effectiveNonces)); const e = challenge(R, hash, combinedPublicKey); const { k, kTwo } = nonces[xHashed]; // xe = x * e const xe = secp256k1_1.default.privateKeyTweakMul(localPk, e); // xea = a * xe const xea = secp256k1_1.default.privateKeyTweakMul(xe, a); // k + xea const kPlusxea = secp256k1_1.default.privateKeyTweakAdd(xea, k); // kTwo * b const kTwoMulB = secp256k1_1.default.privateKeyTweakMul(kTwo, b); // k + kTwoMulB + xea const final = secp256k1_1.default.privateKeyTweakAdd(kPlusxea, kTwoMulB); return { // s = k + xea mod(n) signature: bigi_1.default.fromBuffer(final).mod(n).toBuffer(32), challenge: e, finalPublicNonce: R, }; }; /** * Verify a signature for the given hash, public nonce and public key * * @param Buffer s * @param string hash * @param Buffer R * @param Buffer publicKey * @returns boolean */ const internalVerify = (s, hash, R, publicKey) => { const eC = challenge(R, hash, publicKey); const sG = generatorPoint.mul(ethers_1.ethers.getBytes(s)); const P = ec.keyFromPublic(publicKey).getPublic(); const bnEC = new bn_js_1.BN(Buffer.from(eC).toString("hex"), "hex"); const Pe = P.mul(bnEC); const toPublicR = ec.keyFromPublic(R).getPublic(); const RplusPe = toPublicR.add(Pe); return sG.eq(RplusPe); }; const _concatTypedArrays = (publicKeys) => { const c = Buffer.alloc(publicKeys.reduce((partialSum, publicKey) => partialSum + publicKey.length, 0)); publicKeys.map((publicKey, index) => c.set(publicKey, index * publicKey.length)); return Buffer.from(c.buffer); }; exports._concatTypedArrays = _concatTypedArrays; const _generateL = (publicKeys) => { return ethers_1.ethers.keccak256((0, exports._concatTypedArrays)(publicKeys.sort(Buffer.compare))); }; exports._generateL = _generateL; const _aCoefficient = (publicKey, L) => { return Buffer.from(ethers_1.ethers.getBytes(ethers_1.ethers.solidityPackedKeccak256(["bytes", "bytes"], [L, publicKey]))); }; exports._aCoefficient = _aCoefficient; const generateRandomKeys = () => { let privKeyBytes; do privKeyBytes = Buffer.from(ethers_1.ethers.randomBytes(32)); while (!secp256k1_1.default.privateKeyVerify(privKeyBytes)); const pubKey = Buffer.from(secp256k1_1.default.publicKeyCreate(privKeyBytes)); const data = { publicKey: pubKey, privateKey: privKeyBytes, }; return new types_1.KeyPair(data); }; exports.generateRandomKeys = generateRandomKeys; const _hashPrivateKey = (privateKey) => { return ethers_1.ethers.keccak256(privateKey); }; exports._hashPrivateKey = _hashPrivateKey; const _hashMessage = (message) => { return ethers_1.ethers.solidityPackedKeccak256(["string"], [message]); }; exports._hashMessage = _hashMessage; const _generatePublicNonces = (privateKey) => { const hash = (0, exports._hashPrivateKey)(privateKey); const nonce = (0, exports._generateNonce)(); return { hash, privateNonceData: { k: nonce.k, kTwo: nonce.kTwo, }, publicNonceData: { kPublic: nonce.kPublic, kTwoPublic: nonce.kTwoPublic, }, }; }; exports._generatePublicNonces = _generatePublicNonces; const _restorePublicNonces = (privateKey, kPrivateKey, kTwoPrivateKey) => { const hash = (0, exports._hashPrivateKey)(privateKey); const nonce = _restoreNonce(kPrivateKey, kTwoPrivateKey); return { hash, privateNonceData: { k: nonce.k, kTwo: nonce.kTwo, }, publicNonceData: { kPublic: nonce.kPublic, kTwoPublic: nonce.kTwoPublic, }, }; }; exports._restorePublicNonces = _restorePublicNonces; const _multiSigSign = (nonces, combinedPublicKey, privateKey, msg, publicKeys, publicNonces, hashFn = exports._hashMessage) => { const hashMsg = hashFn; const hash = hashMsg(msg); return internalMultiSigSign(nonces, combinedPublicKey, privateKey, hash, publicKeys, publicNonces); }; exports._multiSigSign = _multiSigSign; const _multiSigSignHash = (nonces, combinedPublicKey, privateKey, hash, publicKeys, publicNonces) => { return internalMultiSigSign(nonces, combinedPublicKey, privateKey, hash, publicKeys, publicNonces); }; exports._multiSigSignHash = _multiSigSignHash; const _sumSigs = (signatures) => { let combined = bigi_1.default.fromBuffer(signatures[0]); signatures.shift(); signatures.forEach((sig) => { combined = combined.add(bigi_1.default.fromBuffer(sig)); }); return combined.mod(n).toBuffer(32); }; exports._sumSigs = _sumSigs; const _verify = (s, msg, R, publicKey, hashFn = exports._hashMessage) => { const hashMsg = hashFn; const hash = hashMsg(msg); return internalVerify(s, hash, R, publicKey); }; exports._verify = _verify; const _verifyHash = (s, hash, R, publicKey) => { return internalVerify(s, hash, R, publicKey); }; exports._verifyHash = _verifyHash; const _generatePk = (combinedPublicKey) => { const px = ethers_1.ethers.hexlify(combinedPublicKey.subarray(1, 33)); return `0x${px.slice(-40, px.length)}`; }; exports._generatePk = _generatePk; const _sign = (privateKey, msg, hashFn = exports._hashMessage) => { const hashMsg = hashFn; const hash = hashMsg(msg); return internalSign(privateKey, hash); }; exports._sign = _sign; const _signHash = (privateKey, hash) => { return internalSign(privateKey, hash); }; exports._signHash = _signHash;