UNPKG

@runonflux/aa-schnorr-multisig-sdk

Version:

Account Abstraction Schnorr Multi-Signatures SDK

257 lines (256 loc) 11.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MultiSigUserOp = void 0; const ajv_1 = __importDefault(require("ajv")); const ethers_1 = require("ethers"); const secp256k1_1 = require("secp256k1"); const types_1 = require("../types"); const serializers_1 = require("../serializers"); const schnorr_helpers_1 = require("../helpers/schnorr-helpers"); const converters_1 = require("../helpers/converters"); const signers_1 = require("../signers"); const errors_1 = require("../errors"); class MultiSigUserOp { constructor(publicKeys, publicNonces, opHash, userOpRequest) { this.publicNonces = {}; this.publicKeys = {}; this.signatures = {}; if (publicKeys.length < 2) throw new Error("At least 2 signers should be provided"); this.opHash = opHash; this.userOpRequest = userOpRequest; // map public keys and public nonces const _publicKeys = publicKeys.map((pk, index) => { const _address = (0, converters_1.pubKey2Address)(Buffer.from((0, secp256k1_1.publicKeyConvert)(pk.buffer, false))); this.publicNonces[_address] = publicNonces[index]; this.publicKeys[_address] = pk; return pk; }); // get combined public key created from all signers' public keys const _combinedPubKey = signers_1.Schnorrkel.getCombinedPublicKey(_publicKeys); this.combinedPubKey = _combinedPubKey; // create unique tx id const _salt = Buffer.from(ethers_1.ethers.randomBytes(32)); const coder = new ethers_1.AbiCoder(); const encodedParams = coder.encode(["bytes", "bytes", "bytes"], [_combinedPubKey.buffer, opHash, _salt]); this.id = ethers_1.ethers.keccak256(encodedParams); } getOpHash() { return this.opHash; } signMultiSigHash(signer) { const op = this.opHash; const pk = this._getPublicKeys(); const pn = this._getPublicNonces(); const _signatureOutput = signer.signMultiSigHash(op, pk, pn); this.signatures[signer.getAddress()] = _signatureOutput; return _signatureOutput; } getSummedSigData() { if (!this.combinedPubKey || !this.signatures) throw new Error("Summed signature input data is missing"); const _signatureOutputs = this._getSignatures(); const _sigs = _signatureOutputs.map((sig) => sig.signature); const _challenges = _signatureOutputs.map((sig) => sig.challenge); // sum all signers signatures const _summed = (0, schnorr_helpers_1.sumSchnorrSigs)(_sigs); // challenge for every signature must be the same - check and if so, assign first one const isEveryChallengeEqual = _challenges.every((e) => { if (e.toHex() === _challenges[0].toHex()) return true; }); if (!isEveryChallengeEqual) throw new Error("Challenges for all signers should be the same"); const e = _challenges[0]; // the multisig px and parity const px = ethers_1.ethers.hexlify(this.combinedPubKey.buffer.subarray(1, 33)); const parity = this.combinedPubKey.buffer[0] - 2 + 27; // wrap the result const abiCoder = new ethers_1.AbiCoder(); const sigData = abiCoder.encode(["bytes32", "bytes32", "bytes32", "uint8"], [px, e.buffer, _summed.buffer, parity]); return sigData; } getAddressSignature(signerAddress) { return this._getSignatures()[signerAddress]; } getAddressPublicNonces(signerAddress) { return this._getPublicNonces()[signerAddress]; } getAddressPubKeys(signerAddress) { return this._getPublicKeys()[signerAddress]; } _getSignatures() { return Object.entries(this.signatures).map(([, sig]) => { return sig; }); } _getPublicNonces() { return Object.entries(this.publicNonces).map(([, nonce]) => { return nonce; }); } _getPublicKeys() { return Object.entries(this.publicKeys).map(([, pk]) => { return pk; }); } toJson() { return { id: this.id, opHash: this.opHash, userOpRequest: { sender: this.userOpRequest.sender, nonce: new serializers_1.BigNumberSerializer(this.userOpRequest.nonce).toString(), initCode: this.userOpRequest.initCode.toString(), callData: this.userOpRequest.callData.toString(), callGasLimit: new serializers_1.BigNumberSerializer(this.userOpRequest.callGasLimit).toString(), verificationGasLimit: new serializers_1.BigNumberSerializer(this.userOpRequest.verificationGasLimit).toString(), preVerificationGas: new serializers_1.BigNumberSerializer(this.userOpRequest.preVerificationGas).toString(), maxFeePerGas: new serializers_1.BigNumberSerializer(this.userOpRequest.maxFeePerGas).toString(), maxPriorityFeePerGas: new serializers_1.BigNumberSerializer(this.userOpRequest.maxPriorityFeePerGas).toString(), paymasterAndData: this.userOpRequest.paymasterAndData.toString(), signature: this.userOpRequest.signature.toString(), }, combinedPubKey: this.combinedPubKey.toHex(), publicKeys: Object.fromEntries(Object.entries(this.publicKeys).map(([address, key]) => [address, key.toHex()])), publicNonces: Object.fromEntries(Object.entries(this.publicNonces).map(([address, nonces]) => [ address, { kPublic: nonces.kPublic.toHex(), kTwoPublic: nonces.kTwoPublic.toHex(), }, ])), signatures: Object.fromEntries(Object.entries(this.signatures).map(([address, output]) => [ address, { finalPublicNonce: output.finalPublicNonce.toHex(), challenge: output.challenge.toHex(), signature: output.signature.toHex(), }, ])), }; } } exports.MultiSigUserOp = MultiSigUserOp; MultiSigUserOp.fromJson = (serialized) => { const schema = { type: "object", properties: { id: { type: "string" }, opHash: { type: "string" }, userOpRequest: { type: "object", properties: { sender: { type: "string" }, nonce: { type: "string" }, initCode: { type: "string" }, callData: { type: "string" }, callGasLimit: { type: "string" }, verificationGasLimit: { type: "string" }, preVerificationGas: { type: "string" }, maxFeePerGas: { type: "string" }, maxPriorityFeePerGas: { type: "string" }, paymasterAndData: { type: "string" }, signature: { type: "string" }, }, required: [ "sender", "nonce", "initCode", "callData", "callGasLimit", "verificationGasLimit", "preVerificationGas", "maxFeePerGas", "maxPriorityFeePerGas", "paymasterAndData", "signature", ], }, combinedPubKey: { type: "string" }, publicNonces: { type: "object", patternProperties: { ".*": { type: "object", properties: { kPublic: { type: "string" }, kTwoPublic: { type: "string" }, }, required: ["kPublic", "kTwoPublic"], }, }, }, publicKeys: { type: "object", patternProperties: { ".*": { type: "string" }, }, }, signatures: { type: "object", patternProperties: { ".*": { type: "object", properties: { finalPublicNonce: { type: "string" }, challenge: { type: "string" }, signature: { type: "string" }, }, required: ["finalPublicNonce", "challenge", "signature"], }, }, }, }, }; const ajv = new ajv_1.default(); const validate = ajv.compile(schema); const isValid = validate(serialized); if (!isValid) throw new errors_1.ValidationError("[MultiSigUserOP]: Invalid JSON format", validate.errors); const { id } = serialized; const { opHash } = serialized; const userOpRequest = { sender: serialized.userOpRequest.sender, nonce: serializers_1.BigNumberSerializer.fromString(serialized.userOpRequest.nonce).number, initCode: serialized.userOpRequest.initCode, callData: serialized.userOpRequest.callData, callGasLimit: serialized.userOpRequest.callGasLimit, verificationGasLimit: serialized.userOpRequest.verificationGasLimit, preVerificationGas: serializers_1.BigNumberSerializer.fromString(serialized.userOpRequest.preVerificationGas).number, maxFeePerGas: serializers_1.BigNumberSerializer.fromString(serialized.userOpRequest.maxFeePerGas).number, maxPriorityFeePerGas: serializers_1.BigNumberSerializer.fromString(serialized.userOpRequest.maxPriorityFeePerGas).number, paymasterAndData: serialized.userOpRequest.paymasterAndData, signature: serialized.userOpRequest.signature, }; const combinedPubKey = types_1.Key.fromHex(serialized.combinedPubKey); const publicNonces = Object.fromEntries(Object.entries(serialized.publicNonces).map(([address, nonces]) => [ address, { kPublic: types_1.Key.fromHex(nonces.kPublic), kTwoPublic: types_1.Key.fromHex(nonces.kTwoPublic), }, ])); const publicKeys = Object.fromEntries(Object.entries(serialized.publicKeys).map(([address, key]) => [address, types_1.Key.fromHex(key)])); const signatures = Object.fromEntries(Object.entries(serialized.signatures).map(([address, output]) => [ address, { finalPublicNonce: types_1.Key.fromHex(output.finalPublicNonce), challenge: types_1.Key.fromHex(output.challenge), signature: types_1.Key.fromHex(output.signature), }, ])); const instance = Object.create(MultiSigUserOp.prototype); return Object.assign(instance, { id, opHash, userOpRequest, combinedPubKey, publicNonces, publicKeys, signatures, }); };