UNPKG

@runonflux/aa-schnorr-multisig-sdk

Version:

Account Abstraction Schnorr Multi-Signatures SDK

243 lines (242 loc) 9.97 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createSchnorrSigner = createSchnorrSigner; exports.sumSchnorrSigs = sumSchnorrSigs; exports.getAllCombos = getAllCombos; exports.getCombinedAddrFromSigners = getCombinedAddrFromSigners; exports.getCombinedAddrFromKeys = getCombinedAddrFromKeys; exports.generateSingleSigDataAndHash = generateSingleSigDataAndHash; exports.getAllCombinedAddrFromSigners = getAllCombinedAddrFromSigners; exports.getAllCombinedAddrFromKeys = getAllCombinedAddrFromKeys; exports.getAllCombinedAddrFromSignersStrict = getAllCombinedAddrFromSignersStrict; exports.getAllCombinedAddrFromKeysStrict = getAllCombinedAddrFromKeysStrict; exports.getAllCombinedPubKeysFromSigners = getAllCombinedPubKeysFromSigners; exports.generateCombinedSigDataAndHash = generateCombinedSigDataAndHash; /* eslint-disable @typescript-eslint/no-explicit-any */ const ethers_1 = require("ethers"); const types_1 = require("../types"); const core_1 = require("../core"); const signers_1 = require("../signers"); /** * Creates new Schnorr Signer from given private key. * @param privKey private key hexadecimal value * @returns Schnorr Signer */ function createSchnorrSigner(privKey) { const privKeyBuffer = new types_1.Key(Buffer.from(ethers_1.ethers.getBytes(privKey))).buffer; return new signers_1.SchnorrSigner(privKeyBuffer); } /** * Creates the summed signature from all given Schnorr signatures. * @param signatures array of Schnorr signatures * @returns summed signature */ function sumSchnorrSigs(signatures) { return signers_1.Schnorrkel.sumSigs(signatures); } /** * Internal function to create all combinations from given array of objects. * @param arr array of any given objects (array length = Y) * @returns all possible combinatyions */ function _getCombos(arr) { // eslint-disable-next-line no-undefined if (arr[0] === undefined) return [arr]; return _getCombos(arr.slice(1)).flatMap((element) => [element.concat(arr[0]), element]); } /** * Creates an array of possible combinations. Optionally limited by given X (out of Y). * * @param arr array of any given objects (array length = Y) * @param x minimum combination length for X of Y (default = 1) * @returns all possible combinations limited by given x * * @example * X of Y array defined as [A, B, C] * 3 of 3: [ABC] * 2 of 3: [AB, AC, BC, ABC] * 1 of 3: [A, B, C, AB, AC, BC, ABC] * */ function getAllCombos(arr, x = 1) { const allCombos = _getCombos(arr); return allCombos.filter((combo) => combo.length >= x); } /** * Generates combined public address out of all given Schnorr signers' addresses. * * @param signers array of Schnorr signers * @returns combined address */ function getCombinedAddrFromSigners(signers) { // get the public key const pubKeys = signers.map((signer) => signer.getPubKey()); return getCombinedAddrFromKeys(pubKeys); } /** * Generates combined public address out of all given Schnorr signers' public keys. * * @param pubKeys array of signers' public keys * @returns combined address */ function getCombinedAddrFromKeys(pubKeys) { const combinedPublicKey = signers_1.Schnorrkel.getCombinedPublicKey(pubKeys); const px = ethers_1.ethers.hexlify(combinedPublicKey.buffer.subarray(1, 33)); const combinedAddress = `0x${px.slice(-40, px.length)}`; return combinedAddress; } /** * generate single signature data * can be used only if 1 single is defined for Schnorr signature */ /** * Generates a single signature data for single Schnorr signer. * * @param signer Schnorr signer which signs the message * @param msg message to be signed * @returns sigData and msgHash */ function generateSingleSigDataAndHash(signer, msg) { // generate signature for a signer const signatureOutput = signer.signMessage(msg); // the multisig px and parity const pk = signer.getPubKey().buffer; const px = ethers_1.ethers.hexlify(pk.subarray(1, 33)); const parity = pk[0] - 2 + 27; const { challenge, signature } = signatureOutput; // wrap the result const abiCoder = new ethers_1.AbiCoder(); const sigData = abiCoder.encode(["bytes32", "bytes32", "bytes32", "uint8"], [px, challenge.buffer, signature.buffer, parity]); const msgHash = ethers_1.ethers.solidityPackedKeccak256(["string"], [msg]); return { sigData, msgHash }; } /** * Creates an array of possible Schnorr combined addresses from signers. * Optionally limited by given X (out of Y) * * @param signers array of Schnorr signers * @param x minimum combination length for X of Y (default = 1) * @returns * * @example * X of Y array defined as [A, B, C] * 3 of 3: [ABC] * 2 of 3: [AB, AC, BC, ABC] * 1 of 3: [A, B, C, AB, AC, BC, ABC] */ function getAllCombinedAddrFromSigners(signers, x) { const allSignersCombos = getAllCombos(signers, x); const allCombinedAddresses = allSignersCombos.map((signersCombo) => signersCombo.length > 1 ? getCombinedAddrFromSigners(signersCombo) : (0, core_1._generatePk)(signersCombo[0].getPubKey().buffer)); return allCombinedAddresses; } /** * Creates an array of possible Schnorr combined addresses from public keys. * Optionally limited by given X (out of Y). * * @param signers array of Schnorr signers * @param x minimum combination length for X of Y (default = 1) * @returns * * @example * X of Y array defined as [A, B, C] * 3 of 3: [ABC] * 2 of 3: [AB, AC, BC, ABC] * 1 of 3: [A, B, C, AB, AC, BC, ABC] */ function getAllCombinedAddrFromKeys(pubKeys, x) { const allPubKeysCombos = getAllCombos(pubKeys, x); const allCombinedAddresses = allPubKeysCombos.map((pubKeysCombo) => pubKeysCombo.length > 1 ? getCombinedAddrFromKeys(pubKeysCombo) : (0, core_1._generatePk)(pubKeysCombo[0].buffer)); return allCombinedAddresses; } /** * Creates an array of Schnorr combined addresses from signers for exactly X of Y. * Unlike getAllCombinedAddrFromSigners which returns combinations of size >= X, * this returns only combinations of exactly size X. * * @param signers array of Schnorr signers * @param x exact combination length for X of Y * @returns combined addresses for exactly X-sized groups * * @example * X of Y array defined as [A, B, C] * 3 of 3: [ABC] * 2 of 3: [AB, AC, BC] * 1 of 3: [A, B, C] */ function getAllCombinedAddrFromSignersStrict(signers, x) { const allSignersCombos = getAllCombos(signers, x).filter((combo) => combo.length === x); const allCombinedAddresses = allSignersCombos.map((signersCombo) => signersCombo.length > 1 ? getCombinedAddrFromSigners(signersCombo) : (0, core_1._generatePk)(signersCombo[0].getPubKey().buffer)); return allCombinedAddresses; } /** * Creates an array of Schnorr combined addresses from public keys for exactly X of Y. * Unlike getAllCombinedAddrFromKeys which returns combinations of size >= X, * this returns only combinations of exactly size X. * * @param pubKeys array of signers' public keys * @param x exact combination length for X of Y * @returns combined addresses for exactly X-sized groups * * @example * X of Y array defined as [A, B, C] * 3 of 3: [ABC] * 2 of 3: [AB, AC, BC] * 1 of 3: [A, B, C] */ function getAllCombinedAddrFromKeysStrict(pubKeys, x) { const exactCombos = getAllCombos(pubKeys, x).filter((combo) => combo.length === x); const allCombinedAddresses = exactCombos.map((pubKeysCombo) => pubKeysCombo.length > 1 ? getCombinedAddrFromKeys(pubKeysCombo) : (0, core_1._generatePk)(pubKeysCombo[0].buffer)); return allCombinedAddresses; } /** * Creates an array of possible Schnorr combined public keys from signers. * Optionally limited by given X (out of Y). * * @param signers array of Schnorr signers * @param x minimum combination length for X of Y (default = 1) * @returns * * @example * X of Y array defined as [A, B, C] * 3 of 3: [ABC] * 2 of 3: [AB, AC, BC, ABC] * 1 of 3: [A, B, C, AB, AC, BC, ABC] */ function getAllCombinedPubKeysFromSigners(signers, x) { if (signers.length < 2) throw new Error("At least 2 signers should be provided"); const allSignersCombos = getAllCombos(signers, x); const publicKeysCombos = allSignersCombos.map((s) => s.map((signer) => signer.getPubKey())); const allCombinedPubKeys = publicKeysCombos.map((publicKeys) => signers_1.Schnorrkel.getCombinedPublicKey(publicKeys)); return allCombinedPubKeys; } /** * Generates a single signature data for multiple Schnorr signer. * * @WARNING FOR TESTING PURPOSE ONLY! * @dev it's not possible to sign msg by every signer at once within single function * @param signer Schnorr signer which signs the message * @param msg message to be signed * @returns sigData and msgHash */ function generateCombinedSigDataAndHash(signers, msg) { const publicKeys = signers.map((signer) => signer.getPubKey()); const publicNonces = signers.map((signer) => signer.getPubNonces()); const combinedPublicKey = signers_1.Schnorrkel.getCombinedPublicKey(publicKeys); // generate signature for every signer const signatureOutputs = signers.map((signer) => signer.signMultiSigMsg(msg, publicKeys, publicNonces)); const signatures = signatureOutputs.map((sig) => sig.signature); const challenges = signatureOutputs.map((sig) => sig.challenge); const challenge = challenges[0]; // challenge for every signer is the same // sum signatures const sSummed = signers_1.Schnorrkel.sumSigs(signatures); // the multisig px and parity const px = ethers_1.ethers.hexlify(combinedPublicKey.buffer.subarray(1, 33)); const parity = combinedPublicKey.buffer[0] - 2 + 27; // wrap the result const abiCoder = new ethers_1.AbiCoder(); const sigData = abiCoder.encode(["bytes32", "bytes32", "bytes32", "uint8"], [px, challenge.buffer, sSummed.buffer, parity]); const msgHash = ethers_1.ethers.solidityPackedKeccak256(["string"], [msg]); return { sigData, msgHash }; }