@runonflux/aa-schnorr-multisig-sdk
Version:
Account Abstraction Schnorr Multi-Signatures SDK
243 lines (242 loc) • 9.97 kB
JavaScript
;
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 };
}