UNPKG

ox

Version:

Ethereum Standard Library

300 lines 10.3 kB
import { bls12_381 as bls } from '@noble/curves/bls12-381.js'; import * as BlsPoint from './BlsPoint.js'; import * as Bytes from './Bytes.js'; import * as Errors from './Errors.js'; import * as Hex from './Hex.js'; /** * Coerces a serialized or structured BLS point into a structured * {@link ox#BlsPoint.BlsPoint}. * * @internal */ function normalizeBlsPoint(value, group) { if (typeof value === 'string') return BlsPoint.fromHex(value, group); if (value instanceof Uint8Array) return BlsPoint.fromBytes(value, group); return value; } /** * Formats a structured BLS point as the requested representation. * * @internal */ function formatBlsPoint(point, as) { if (as === 'Hex') return BlsPoint.toHex(point); if (as === 'Bytes') return BlsPoint.toBytes(point); return point; } /** * Returns the byte length of a serialized BLS point. Hex strings are measured * minus the `0x` prefix, divided by two; `Uint8Array`s use `byteLength`. * * @internal */ function signatureByteLength(value) { if (typeof value === 'string') return (value.length - 2) / 2; return value.byteLength; } /** Re-export of noble/curves BLS12-381 utilities. */ export const noble = bls; // eslint-disable-next-line jsdoc-js/require-jsdoc export function aggregate(points, options = {}) { if (points.length === 0) throw new Errors.BaseError('Bls.aggregate expects a non-empty array of points.'); // Normalize once -- accept structured points, hex strings, or `Uint8Array`s. const groupHint = options.group; const normalized = points.map((point) => { if (typeof point === 'string' || point instanceof Uint8Array) { if (!groupHint) throw new Errors.BaseError('Bls.aggregate requires `options.group` (`"G1"` or `"G2"`) when passing serialized points.'); return normalizeBlsPoint(point, groupHint); } return point; }); const first = normalized[0]; // Fast path: a single point aggregates to itself. if (normalized.length === 1) return first; const isG1 = typeof first.x === 'string'; for (let i = 1; i < normalized.length; i++) { if ((typeof normalized[i].x === 'string') !== isG1) throw new Errors.BaseError('Bls.aggregate expects all points to be from the same group (G1 or G2).'); } const groupName = isG1 ? 'G1' : 'G2'; const group = isG1 ? bls.G1 : bls.G2; let acc = group.Point.ZERO; for (let i = 0; i < normalized.length; i++) { const p = normalized[i]; acc = acc.add(BlsPoint.toNoblePoint(p, groupName)); } return BlsPoint.fromNoblePoint(acc, groupName); } /** * Creates a new BLS12-381 key pair consisting of a private key and its corresponding public key. * * - G1 Point (Default): * - short (48 bytes) * - computes longer G2 Signatures (96 bytes) * - G2 Point: * - long (96 bytes) * - computes short G1 Signatures (48 bytes) * * @example * ### Short G1 Public Keys (Default) * * ```ts twoslash * import { Bls } from 'ox' * * const { publicKey } = Bls.createKeyPair() * // ^? * ``` * * @example * ### Long G2 Public Keys * * A G2 Public Key can be derived as a G2 point (96 bytes) using `size: 'long-key:short-sig'`. * * This will allow you to compute G1 Signatures (48 bytes) with {@link ox#Bls.(sign:function)}. * * ```ts twoslash * import { Bls } from 'ox' * * const { publicKey } = Bls.createKeyPair({ * size: 'long-key:short-sig' * }) * * publicKey * // ^? * ``` * * ### Serializing * * Public Keys can be serialized to hex or bytes using {@link ox#BlsPoint.(toHex:function)} or {@link ox#BlsPoint.(toBytes:function)}: * * ```ts twoslash * import { Bls, BlsPoint } from 'ox' * * const { publicKey } = Bls.createKeyPair() * * const publicKeyHex = BlsPoint.toHex(publicKey) * // ^? * * const publicKeyBytes = BlsPoint.toBytes(publicKey) * // ^? * ``` * * They can also be deserialized from hex or bytes using {@link ox#BlsPoint.(fromHex:function)} or {@link ox#BlsPoint.(fromBytes:function)}: * * ```ts twoslash * import { Bls, BlsPoint } from 'ox' * * const publicKeyHex = '0x...' * * const publicKey = BlsPoint.fromHex(publicKeyHex, 'G1') * // ^? * ``` * * @param options - The options to generate the key pair. * @returns The generated key pair containing both private and public keys. */ export function createKeyPair(options = {}) { const { as = 'Hex', size = 'short-key:long-sig' } = options; const privateKey = randomPrivateKey({ as }); const publicKey = getPublicKey({ privateKey, size }); return { privateKey: privateKey, publicKey: publicKey, }; } // eslint-disable-next-line jsdoc-js/require-jsdoc export function getPublicKey(options) { const { as = 'Object', privateKey, size = 'short-key:long-sig' } = options; const groupName = size === 'short-key:long-sig' ? 'G1' : 'G2'; const group = groupName === 'G1' ? bls.G1 : bls.G2; const point = group.Point.BASE.multiply(group.Point.Fn.fromBytes(Bytes.from(privateKey))); const publicKey = BlsPoint.fromNoblePoint(point, groupName); return formatBlsPoint(publicKey, as); } /** * Generates a random BLS12-381 private key. * * @example * ```ts twoslash * import { Bls } from 'ox' * * const privateKey = Bls.randomPrivateKey() * ``` * * @param options - The options to generate the private key. * @returns The generated private key. */ export function randomPrivateKey(options = {}) { const { as = 'Hex' } = options; const bytes = bls.utils.randomSecretKey(); if (as === 'Hex') return Hex.fromBytes(bytes); return bytes; } // eslint-disable-next-line jsdoc-js/require-jsdoc export function sign(options) { const { as = 'Object', payload, privateKey, suite, size = 'short-key:long-sig', } = options; const signatureGroupName = size === 'short-key:long-sig' ? 'G2' : 'G1'; const payloadGroup = signatureGroupName === 'G2' ? bls.G2 : bls.G1; const payloadPoint = payloadGroup.hashToCurve(Bytes.from(payload), suite ? { DST: Bytes.fromString(suite) } : undefined); const privateKeyGroup = size === 'short-key:long-sig' ? bls.G1 : bls.G2; const signature = payloadPoint.multiply(privateKeyGroup.Point.Fn.fromBytes(Bytes.from(privateKey))); const result = BlsPoint.fromNoblePoint(signature, signatureGroupName); return formatBlsPoint(result, as); } /** * Verifies a payload was signed by the provided public key(s). * * @example * * ```ts twoslash * import { Bls, Hex } from 'ox' * * const payload = Hex.random(32) * const privateKey = Bls.randomPrivateKey() * * const publicKey = Bls.getPublicKey({ privateKey }) * const signature = Bls.sign({ payload, privateKey }) * * const verified = Bls.verify({ * // [!code focus] * payload, // [!code focus] * publicKey, // [!code focus] * signature // [!code focus] * }) // [!code focus] * ``` * * @example * ### Verify Aggregated Signatures * * We can also pass a public key and signature that was aggregated with {@link ox#Bls.(aggregate:function)} to `Bls.verify`. * * ```ts twoslash * import { Bls, Hex } from 'ox' * * const payload = Hex.random(32) * const privateKeys = Array.from({ length: 100 }, () => * Bls.randomPrivateKey() * ) * * const publicKeys = privateKeys.map((privateKey) => * Bls.getPublicKey({ privateKey }) * ) * const signatures = privateKeys.map((privateKey) => * Bls.sign({ payload, privateKey }) * ) * * const publicKey = Bls.aggregate(publicKeys) // [!code focus] * const signature = Bls.aggregate(signatures) // [!code focus] * * const valid = Bls.verify({ payload, publicKey, signature }) // [!code focus] * ``` * * @param options - Verification options. * @returns Whether the payload was signed by the provided public key. */ export function verify(options) { const { payload, suite } = options; // Accept structured / hex / bytes inputs. Inspect the *signature* group // first when structured, otherwise fall back to the explicit `group` / // pair-shape hint to know how to deserialize. const signatureRaw = options.signature; const publicKeyRaw = options.publicKey; // If signature is structured, infer signature group via field shape. const signatureIsStructured = typeof signatureRaw === 'object' && !(signatureRaw instanceof Uint8Array) && 'z' in signatureRaw; const publicKeyIsStructured = typeof publicKeyRaw === 'object' && !(publicKeyRaw instanceof Uint8Array) && 'z' in publicKeyRaw; // Determine signature group: G1 (short sig, x is hex string) or G2 (long // sig, x is `{ c0, c1 }`). For serialized signatures, infer from the // byte/hex length. const signatureGroup = signatureIsStructured ? typeof signatureRaw.x === 'string' ? 'G1' : 'G2' : signatureByteLength(signatureRaw) === 48 ? 'G1' : 'G2'; const publicKeyGroup = signatureGroup === 'G1' ? 'G2' : 'G1'; const signature = (signatureIsStructured ? signatureRaw : normalizeBlsPoint(signatureRaw, signatureGroup)); const publicKey = (publicKeyIsStructured ? publicKeyRaw : normalizeBlsPoint(publicKeyRaw, publicKeyGroup)); const isShortSig = signatureGroup === 'G1'; const group = isShortSig ? bls.G1 : bls.G2; const payloadPoint = group.hashToCurve(Bytes.from(payload), suite ? { DST: Bytes.fromString(suite) } : undefined); const shortSigPairing = () => bls.pairingBatch([ { g1: payloadPoint, g2: BlsPoint.toNoblePoint(publicKey, 'G2'), }, { g1: BlsPoint.toNoblePoint(signature, 'G1'), g2: bls.G2.Point.BASE.negate(), }, ]); const longSigPairing = () => bls.pairingBatch([ { g1: BlsPoint.toNoblePoint(publicKey, 'G1').negate(), g2: payloadPoint, }, { g1: bls.G1.Point.BASE, g2: BlsPoint.toNoblePoint(signature, 'G2'), }, ]); return bls.fields.Fp12.eql(isShortSig ? shortSigPairing() : longSigPairing(), bls.fields.Fp12.ONE); } //# sourceMappingURL=Bls.js.map