@runonflux/aa-schnorr-multisig-sdk
Version:
Account Abstraction Schnorr Multi-Signatures SDK
273 lines (272 loc) • 11.5 kB
JavaScript
"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;