UNPKG

ox

Version:

Ethereum Standard Library

311 lines (290 loc) 8.82 kB
import { bls12_381 as bls } from '@noble/curves/bls12-381.js' import type * as Bytes from './Bytes.js' import * as Errors from './Errors.js' import * as Hex from './Hex.js' import type { Branded, Compute } from './internal/types.js' /** Type for a field element in the base field of the BLS12-381 curve. */ export type Fp = Hex.Hex /** Type for a field element in the extension field of the BLS12-381 curve. */ export type Fp2 = Compute<{ c0: Hex.Hex; c1: Hex.Hex }> /** Root type for a BLS point on the G1 or G2 curve. */ export type BlsPoint<type = Fp | Fp2> = Compute<{ x: type y: type z: type }> /** Type for a BLS point on the G1 curve. */ export type G1 = BlsPoint<Fp> /** Branded type for a bytes representation of a G1 point. */ export type G1Bytes = Branded<Bytes.Bytes, 'G1'> /** Branded type for a hex representation of a G1 point. */ export type G1Hex = Branded<Hex.Hex, 'G1'> /** Type for a BLS point on the G2 curve. */ export type G2 = BlsPoint<Fp2> /** Branded type for a bytes representation of a G2 point. */ export type G2Bytes = Branded<Bytes.Bytes, 'G2'> /** Branded type for a hex representation of a G2 point. */ export type G2Hex = Branded<Hex.Hex, 'G2'> const FP_SIZE = 48 /** @internal */ export function fpToBigInt(value: Fp): bigint { return BigInt(value) } /** @internal */ export function fpFromBigInt(value: bigint): Fp { return Hex.fromNumber(value, { size: FP_SIZE }) } /** @internal */ export function fp2ToBigInt(value: Fp2): { c0: bigint; c1: bigint } { return { c0: BigInt(value.c0), c1: BigInt(value.c1) } } /** @internal */ export function fp2FromBigInt(value: { c0: bigint; c1: bigint }): Fp2 { return { c0: Hex.fromNumber(value.c0, { size: FP_SIZE }), c1: Hex.fromNumber(value.c1, { size: FP_SIZE }), } } /** * Converts a structured {@link ox#BlsPoint.BlsPoint} into a noble/curves * `Point` instance for the appropriate group (G1 or G2). * * @internal */ export function toNoblePoint<group extends 'G1' | 'G2'>( point: group extends 'G1' ? G1 : G2, group: group, ): unknown { if (group === 'G1') { const p = point as G1 return new bls.G1.Point(fpToBigInt(p.x), fpToBigInt(p.y), fpToBigInt(p.z)) } const p = point as G2 // noble's G2.Point ctor accepts Fp2 instances. Construct via the field // helper so we get a real `Fp2` rather than a plain `{c0, c1}`. const Fp2 = (bls.fields as any).Fp2 const x = Fp2.create ? Fp2.create(fp2ToBigInt(p.x)) : Fp2.fromBigTuple([BigInt(p.x.c0), BigInt(p.x.c1)]) const y = Fp2.create ? Fp2.create(fp2ToBigInt(p.y)) : Fp2.fromBigTuple([BigInt(p.y.c0), BigInt(p.y.c1)]) const z = Fp2.create ? Fp2.create(fp2ToBigInt(p.z)) : Fp2.fromBigTuple([BigInt(p.z.c0), BigInt(p.z.c1)]) return new bls.G2.Point(x, y, z) } /** * Converts a noble/curves `Point` into a structured * {@link ox#BlsPoint.BlsPoint}. * * @internal */ export function fromNoblePoint<group extends 'G1' | 'G2'>( point: any, group: group, ): group extends 'G1' ? G1 : G2 { if (group === 'G1') { return { x: fpFromBigInt(point.X), y: fpFromBigInt(point.Y), z: fpFromBigInt(point.Z), } as never } // G2: `point.X/Y/Z` are Fp2 instances exposing `.c0` / `.c1` bigints. return { x: { c0: fpFromBigInt(point.X.c0), c1: fpFromBigInt(point.X.c1) }, y: { c0: fpFromBigInt(point.Y.c0), c1: fpFromBigInt(point.Y.c1) }, z: { c0: fpFromBigInt(point.Z.c0), c1: fpFromBigInt(point.Z.c1) }, } as never } /** * Converts a BLS point to {@link ox#Bytes.Bytes}. * * @example * ### Public Key to Bytes * ```ts twoslash * import { Bls, BlsPoint } from 'ox' * * const publicKey = Bls.getPublicKey({ privateKey: '0x...' }) * const publicKeyBytes = BlsPoint.toBytes(publicKey) * // @log: Uint8Array [172, 175, 255, ...] * ``` * * @example * ### Signature to Bytes * ```ts twoslash * import { Bls, BlsPoint } from 'ox' * * const signature = Bls.sign({ * payload: '0x...', * privateKey: '0x...' * }) * const signatureBytes = BlsPoint.toBytes(signature) * // @log: Uint8Array [172, 175, 255, ...] * ``` * * @param point - The BLS point to convert. * @returns The bytes representation of the BLS point. */ export function toBytes<point extends G1 | G2>( point: point, ): point extends G1 ? G1Bytes : G2Bytes { const isG1 = typeof point.z === 'string' const noblePoint: any = toNoblePoint(point as any, isG1 ? 'G1' : 'G2') return noblePoint.toBytes() } export declare namespace toBytes { type ErrorType = Errors.GlobalErrorType } /** * Converts a BLS point to {@link ox#Hex.Hex}. * * @example * ### Public Key to Hex * * ```ts twoslash * import { Bls, BlsPoint } from 'ox' * * const publicKey = Bls.getPublicKey({ privateKey: '0x...' }) * const publicKeyHex = BlsPoint.toHex(publicKey) * // @log: '0xacafff52270773ad1728df2807c0f1b0b271fa6b37dfb8b2f75448573c76c81bcd6790328a60e40ef5a13343b32d9e66' * ``` * * @example * ### Signature to Hex * * ```ts twoslash * import { Bls, BlsPoint } from 'ox' * * const signature = Bls.sign({ * payload: '0x...', * privateKey: '0x...' * }) * const signatureHex = BlsPoint.toHex(signature) * // @log: '0xb4698f7611999fba87033b9cf72312c76c683bbc48175e2d4cb275907d6a267ab9840a66e3051e5ed36fd13aa712f9a9024f9fa9b67f716dfb74ae4efb7d9f1b7b43b4679abed6644cf476c12e79f309351ea8452487cd93f66e29e04ebe427c' * ``` * * @param point - The BLS point to convert. * @returns The hex representation of the BLS point. */ export function toHex<point extends G1 | G2>( point: point, ): point extends G1 ? G1Hex : G2Hex // eslint-disable-next-line jsdoc-js/require-jsdoc export function toHex(point: G1 | G2): Hex.Hex { return Hex.fromBytes(toBytes(point)) } export declare namespace toHex { type ErrorType = Errors.GlobalErrorType } /** * Converts {@link ox#Bytes.Bytes} to a BLS point. * * @example * ### Bytes to Public Key * * ```ts twoslash * // @noErrors * import { BlsPoint } from 'ox' * * const publicKey = BlsPoint.fromBytes(Bytes.from([172, 175, 255, ...]), 'G1') * // @log: { * // @log: x: '0x00...ac', * // @log: y: '0x00...af', * // @log: z: '0x00...01', * // @log: } * ``` * * @example * ### Bytes to Signature * * ```ts twoslash * // @noErrors * import { BlsPoint } from 'ox' * * const signature = BlsPoint.fromBytes(Bytes.from([172, 175, 255, ...]), 'G2') * // @log: { * // @log: x: { c0: '0x00...11', c1: '0x00...22' }, * // @log: y: { c0: '0x00...33', c1: '0x00...44' }, * // @log: z: { c0: '0x00...01', c1: '0x00...00' }, * // @log: } * ``` * * @param bytes - The bytes to convert. * @returns The BLS point. */ export function fromBytes<group extends 'G1' | 'G2'>( bytes: Bytes.Bytes, group: group, ): group extends 'G1' ? G1 : G2 // eslint-disable-next-line jsdoc-js/require-jsdoc export function fromBytes( bytes: Bytes.Bytes, group: 'G1' | 'G2', ): BlsPoint<any> { const expectedLength = group === 'G1' ? 48 : 96 if (bytes.length !== expectedLength) throw new Errors.BaseError( `Expected ${expectedLength} bytes for a ${group} point, received ${bytes.length}.`, ) const Group = group === 'G1' ? bls.G1 : bls.G2 const point = Group.Point.fromBytes(bytes) return fromNoblePoint(point, group) as BlsPoint<any> } export declare namespace fromBytes { type ErrorType = Errors.GlobalErrorType } /** * Converts {@link ox#Hex.Hex} to a BLS point. * * @example * ### Hex to Public Key * * ```ts twoslash * // @noErrors * import { BlsPoint } from 'ox' * * const publicKey = BlsPoint.fromHex( * '0xacafff52270773ad1728df2807c0f1b0b271fa6b37dfb8b2f75448573c76c81bcd6790328a60e40ef5a13343b32d9e66', * 'G1' * ) * // @log: { * // @log: x: '0x00...ac', * // @log: y: '0x00...af', * // @log: z: '0x00...01', * // @log: } * ``` * * @example * ### Hex to Signature * * ```ts twoslash * // @noErrors * import { BlsPoint } from 'ox' * * const signature = BlsPoint.fromHex( * '0xb4698f7611999fba87033b9cf72312c76c683bbc48175e2d4cb275907d6a267ab9840a66e3051e5ed36fd13aa712f9a9024f9fa9b67f716dfb74ae4efb7d9f1b7b43b4679abed6644cf476c12e79f309351ea8452487cd93f66e29e04ebe427c', * 'G2' * ) * // @log: { * // @log: x: { c0: '0x00...11', c1: '0x00...22' }, * // @log: y: { c0: '0x00...33', c1: '0x00...44' }, * // @log: z: { c0: '0x00...01', c1: '0x00...00' }, * // @log: } * ``` * * @param bytes - The bytes to convert. * @returns The BLS point. */ export function fromHex<group extends 'G1' | 'G2'>( hex: Hex.Hex, group: group, ): group extends 'G1' ? G1 : G2 // eslint-disable-next-line jsdoc-js/require-jsdoc export function fromHex(hex: Hex.Hex, group: 'G1' | 'G2'): BlsPoint<any> { return fromBytes(Hex.toBytes(hex), group) } export declare namespace fromHex { type ErrorType = Errors.GlobalErrorType }