ox
Version:
Ethereum Standard Library
311 lines (290 loc) • 8.82 kB
text/typescript
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
}