UNPKG

@guru_test/mpc-core-kit

Version:
195 lines (185 loc) 7.08 kB
import { Point, KeyType, secp256k1 } from '@tkey/common-types'; import { generatePrivateBN } from '@tkey/core'; import { factorKeyCurve } from '@tkey/tss'; import { safeatob } from '@toruslabs/openlogin-utils'; import { keccak256 } from '@toruslabs/torus.js'; import BN from 'bn.js'; import { eddsa } from 'elliptic'; import loglevel from 'loglevel'; import { SCALAR_LEN, DELIMITERS } from './constants.js'; const ed25519 = () => { return new eddsa("ed25519"); }; /** * Secure PRNG. Uses `crypto.getRandomValues`, which defers to OS. */ function randomBytes(bytesLength = 32) { // We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+. const crypto = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : undefined; if (crypto && typeof crypto.getRandomValues === "function") { return crypto.getRandomValues(new Uint8Array(bytesLength)); } throw new Error("crypto.getRandomValues must be defined"); } function generateEd25519Seed() { return Buffer.from(randomBytes(32)); } const generateFactorKey = () => { const keyPair = factorKeyCurve.genKeyPair(); const pub = Point.fromElliptic(keyPair.getPublic()); return { private: keyPair.getPrivate(), pub }; }; const generateTSSEndpoints = (tssNodeEndpoints, parties, clientIndex, nodeIndexes) => { const endpoints = []; const tssWSEndpoints = []; const partyIndexes = []; const nodeIndexesReturned = []; for (let i = 0; i < parties; i++) { partyIndexes.push(i); if (i === clientIndex) { // eslint-disable-next-line @typescript-eslint/no-explicit-any endpoints.push(null); // eslint-disable-next-line @typescript-eslint/no-explicit-any tssWSEndpoints.push(null); } else { const targetNodeIndex = nodeIndexes[i] - 1; endpoints.push(tssNodeEndpoints[targetNodeIndex]); tssWSEndpoints.push(new URL(tssNodeEndpoints[targetNodeIndex]).origin); nodeIndexesReturned.push(nodeIndexes[i]); } } return { endpoints, tssWSEndpoints, partyIndexes, nodeIndexesReturned }; }; async function storageAvailable(storage) { try { const x = "__storage_test__"; const rand = Math.random().toString(); await storage.setItem(x, rand); const value = await storage.getItem(rand); if (value !== rand) { throw new Error("Value mismatch"); } return true; } catch (error) { return false; } } // TODO think which conversion functions to keep and how to export them. /** * Parses a JWT Token, without verifying the signature. * @param token - JWT Token * @returns Extracted JSON payload from the token */ function parseToken(token) { const payload = token.split(".")[1]; return JSON.parse(safeatob(payload)); } const getHashedPrivateKey = (postboxKey, clientId) => { const uid = `${postboxKey}_${clientId}`; let hashUid = keccak256(Buffer.from(uid, "utf8")); hashUid = hashUid.replace("0x", ""); return new BN(hashUid, "hex"); }; /** * Converts an elliptic curve scalar represented by a BN to a byte buffer in SEC1 * format (i.e., padded to maximum length). * @param s - The scalar of type BN. * @returns The SEC1 encoded representation of the scalar. */ function scalarBNToBufferSEC1(s) { return s.toArrayLike(Buffer, "be", SCALAR_LEN); } function sampleEndpoints(endpoints, n) { if (n > endpoints.length) { throw new Error("Invalid number of endpoints"); } const shuffledEndpoints = endpoints.slice().sort(() => Math.random() - 0.5); return shuffledEndpoints.slice(0, n).sort((a, b) => a.index - b.index); } function fraction(curve, nom, denom) { return nom.mul(denom.invm(curve.n)).umod(curve.n); } function lagrangeCoefficient(curve, xCoords, targetCoeff, targetX) { return xCoords.filter((_, i) => i !== targetCoeff).reduce((prev, cur) => { const frac = fraction(curve, targetX.sub(cur), xCoords[targetCoeff].sub(cur)); return prev.mul(frac).umod(curve.n); }, new BN(1)); } function lagrangeCoefficients(curve, xCoords, targetX) { const xCoordsBN = xCoords.map(i => new BN(i)); const targetXBN = new BN(targetX); return xCoordsBN.map((_value, i) => lagrangeCoefficient(curve, xCoordsBN, i, targetXBN)); } const SERVER_XCOORD_L1 = 1; const CLIENT_XCOORD_L1 = 2; /** * Derive share coefficients for client and servers. * * @param curve - The curve to be used. * @param serverXCoords - The source and target x-coordinates of the selected * servers. * @param targetClientXCoord - The target x-coordinate of the client. * @param sourceClientXCoord - The source x-coordinate of the client in the L1 * hierarchy. * @returns - The share coefficients for the client and the servers. */ function deriveShareCoefficients(ec, serverXCoords, targetClientXCoord, sourceClientXCoord = CLIENT_XCOORD_L1) { const l1Coefficients = lagrangeCoefficients(ec, [SERVER_XCOORD_L1, sourceClientXCoord], 0); const l2Coefficients = lagrangeCoefficients(ec, serverXCoords, 0); if (serverXCoords.includes(targetClientXCoord)) { throw new Error(`Invalid server x-coordinates: overlapping with client x-coordinate: ${serverXCoords} ${targetClientXCoord}`); } const targetCoefficients = lagrangeCoefficients(ec, [targetClientXCoord, ...serverXCoords], 0); // Derive server coefficients. const serverCoefficients = l2Coefficients.map((coeff, i) => fraction(ec, l1Coefficients[0].mul(coeff), targetCoefficients[i + 1])); // Derive client coefficient. const clientCoefficient = fraction(ec, l1Coefficients[1], targetCoefficients[0]); return { serverCoefficients, clientCoefficient }; } function generateSessionNonce() { return keccak256(Buffer.from(generatePrivateBN().toString("hex") + Date.now(), "utf8")); } function getSessionId(verifier, verifierId, tssTag, tssNonce, sessionNonce) { return `${verifier}${DELIMITERS.Delimiter1}${verifierId}${DELIMITERS.Delimiter2}${tssTag}${DELIMITERS.Delimiter3}${tssNonce}${DELIMITERS.Delimiter4}${sessionNonce}`; } function sigToRSV(sig) { if (sig.length !== 65) { throw new Error(`Invalid signature length: expected 65, got ${sig.length}`); } return { r: sig.subarray(0, 32), s: sig.subarray(32, 64), v: sig[64] }; } function makeEthereumSigner(kit) { if (kit.keyType !== KeyType.secp256k1) { throw new Error(`Invalid key type: expected secp256k1, got ${kit.keyType}`); } return { sign: async msgHash => { const sig = await kit.sign(msgHash, { hashed: true }); return sigToRSV(sig); }, getPublic: async () => { const pk = Point.fromSEC1(secp256k1, kit.getPubKey().toString("hex")); return pk.toSEC1(secp256k1).subarray(1); } }; } const log = loglevel.getLogger("mpc-core-kit"); log.disableAll(); export { deriveShareCoefficients, ed25519, fraction, generateEd25519Seed, generateFactorKey, generateSessionNonce, generateTSSEndpoints, getHashedPrivateKey, getSessionId, lagrangeCoefficient, lagrangeCoefficients, log, makeEthereumSigner, parseToken, randomBytes, sampleEndpoints, scalarBNToBufferSEC1, sigToRSV, storageAvailable };