UNPKG

ox

Version:

Ethereum Standard Library

393 lines 12.2 kB
import * as Bytes from './Bytes.js'; import * as Errors from './Errors.js'; import * as Hex from './Hex.js'; import * as Json from './Json.js'; /** * Asserts that a {@link ox#PublicKey.PublicKey} is valid. * * @example * ```ts twoslash * import { PublicKey } from 'ox' * * PublicKey.assert({ * prefix: 4, * y: '0x6e1c1f59ee1cf25b75a8d57b3c89e7e6b3b1da823df8b3b89497f30c1f000000' * }) * // @error: PublicKey.InvalidError: Value \`{"y":"0x..."}\` is not a valid public key. * // @error: Public key must contain: * // @error: - an `x` and `prefix` value (compressed) * // @error: - an `x`, `y`, and `prefix` value (uncompressed) * ``` * * @param publicKey - The public key object to assert. */ export function assert(publicKey, options = {}) { const { compressed } = options; const { prefix, x, y } = publicKey; // Explicit `compressed: false` -- require uncompressed shape. if (compressed === false) { if (typeof x !== 'string' || typeof y !== 'string') throw new InvalidError({ publicKey }); if (prefix !== 4) throw new InvalidPrefixError({ prefix, cause: new InvalidUncompressedPrefixError(), }); return; } // Explicit `compressed: true` -- require compressed shape. if (compressed === true) { if (typeof x !== 'string' || typeof y !== 'undefined') throw new InvalidError({ publicKey }); if (prefix !== 3 && prefix !== 2) throw new InvalidPrefixError({ prefix, cause: new InvalidCompressedPrefixError(), }); return; } // No explicit `compressed` option -- infer from shape. // Uncompressed if (typeof x === 'string' && typeof y === 'string') { if (prefix !== 4) throw new InvalidPrefixError({ prefix, cause: new InvalidUncompressedPrefixError(), }); return; } // Compressed if (typeof x === 'string' && typeof y === 'undefined') { if (prefix !== 3 && prefix !== 2) throw new InvalidPrefixError({ prefix, cause: new InvalidCompressedPrefixError(), }); return; } // Unknown/invalid throw new InvalidError({ publicKey }); } /** * Compresses a {@link ox#PublicKey.PublicKey}. * * @example * ```ts twoslash * import { PublicKey } from 'ox' * * const publicKey = PublicKey.from({ * prefix: 4, * x: '0x83185...', * y: '0x35477...' * }) * * const compressed = PublicKey.compress(publicKey) // [!code focus] * // @log: { * // @log: prefix: 3, * // @log: x: '0x83185...', * // @log: } * ``` * * @param publicKey - The public key to compress. * @returns The compressed public key. */ export function compress(publicKey) { const { x, y } = publicKey; return { prefix: Hex.toBigInt(y) % 2n === 0n ? 2 : 3, x, }; } /** * Instantiates a typed {@link ox#PublicKey.PublicKey} object from a * {@link ox#PublicKey.PublicKey}, {@link ox#Bytes.Bytes}, or {@link ox#Hex.Hex}. * * @example * ```ts twoslash * import { PublicKey } from 'ox' * * const publicKey = PublicKey.from({ * prefix: 4, * x: '0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75', * y: '0x3547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5' * }) * // @log: { * // @log: prefix: 4, * // @log: x: '0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75', * // @log: y: '0x3547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5', * // @log: } * ``` * * @example * ### From Serialized * * ```ts twoslash * import { PublicKey } from 'ox' * * const publicKey = PublicKey.from( * '0x048318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5' * ) * // @log: { * // @log: prefix: 4, * // @log: x: '0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75', * // @log: y: '0x3547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5', * // @log: } * ``` * * @param value - The public key value to instantiate. * @returns The instantiated {@link ox#PublicKey.PublicKey}. */ export function from(value) { const publicKey = (() => { if (Hex.validate(value)) return fromHex(value); if (Bytes.validate(value)) return fromBytes(value); const { prefix, x, y } = value; if (typeof x === 'string' && typeof y === 'string') return { prefix: prefix ?? 0x04, x: Hex.padLeft(x, 32), y: Hex.padLeft(y, 32), }; return { prefix, x: Hex.padLeft(x, 32) }; })(); assert(publicKey); return publicKey; } /** * Deserializes a {@link ox#PublicKey.PublicKey} from a {@link ox#Bytes.Bytes} * value. * * @example * ```ts twoslash * // @noErrors * import { PublicKey } from 'ox' * * const publicKey = PublicKey.fromBytes(new Uint8Array([128, 3, 131, ...])) * // @log: { * // @log: prefix: 4, * // @log: x: '0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75', * // @log: y: '0x3547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5', * // @log: } * ``` * * @param publicKey - The serialized public key. * @returns The deserialized public key. */ export function fromBytes(publicKey) { return fromHex(Hex.fromBytes(publicKey)); } /** * Deserializes a {@link ox#PublicKey.PublicKey} from a {@link ox#Hex.Hex} value. * * @example * ```ts twoslash * import { PublicKey } from 'ox' * * const publicKey = PublicKey.fromHex( * '0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5' * ) * // @log: { * // @log: prefix: 4, * // @log: x: '0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75', * // @log: y: '0x3547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5', * // @log: } * ``` * * @example * ### Deserializing a Compressed Public Key * * ```ts twoslash * import { PublicKey } from 'ox' * * const publicKey = PublicKey.fromHex( * '0x038318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75' * ) * // @log: { * // @log: prefix: 3, * // @log: x: '0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75', * // @log: } * ``` * * @param publicKey - The serialized public key. * @returns The deserialized public key. */ export function fromHex(publicKey) { if (publicKey.length !== 132 && publicKey.length !== 130 && publicKey.length !== 68) throw new InvalidSerializedSizeError({ publicKey }); const result = (() => { if (publicKey.length === 130) { const x = Hex.slice(publicKey, 0, 32); const y = Hex.slice(publicKey, 32, 64); return { prefix: 4, x, y, }; } if (publicKey.length === 132) { const prefix = Number(Hex.slice(publicKey, 0, 1)); const x = Hex.slice(publicKey, 1, 33); const y = Hex.slice(publicKey, 33, 65); return { prefix, x, y, }; } const prefix = Number(Hex.slice(publicKey, 0, 1)); const x = Hex.slice(publicKey, 1, 33); return { prefix, x, }; })(); assert(result); return result; } /** * Serializes a {@link ox#PublicKey.PublicKey} to {@link ox#Bytes.Bytes}. * * @example * ```ts twoslash * import { PublicKey } from 'ox' * * const publicKey = PublicKey.from({ * prefix: 4, * x: '0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75', * y: '0x3547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5' * }) * * const bytes = PublicKey.toBytes(publicKey) // [!code focus] * // @log: Uint8Array [128, 3, 131, ...] * ``` * * @param publicKey - The public key to serialize. * @returns The serialized public key. */ export function toBytes(publicKey, options = {}) { return Bytes.fromHex(toHex(publicKey, options)); } /** * Serializes a {@link ox#PublicKey.PublicKey} to {@link ox#Hex.Hex}. * * @example * ```ts twoslash * import { PublicKey } from 'ox' * * const publicKey = PublicKey.from({ * prefix: 4, * x: '0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75', * y: '0x3547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5' * }) * * const hex = PublicKey.toHex(publicKey) // [!code focus] * // @log: '0x048318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5' * ``` * * @param publicKey - The public key to serialize. * @returns The serialized public key. */ export function toHex(publicKey, options = {}) { assert(publicKey); const { prefix, x, y } = publicKey; const { includePrefix = true } = options; const publicKey_ = Hex.concat(includePrefix ? Hex.fromNumber(prefix, { size: 1 }) : '0x', x, // If the public key is not compressed, add the y coordinate. typeof y === 'string' ? y : '0x'); return publicKey_; } /** * Validates a {@link ox#PublicKey.PublicKey}. Returns `true` if valid, `false` * otherwise. * * @example * ```ts twoslash * import { PublicKey } from 'ox' * * const valid = PublicKey.validate({ * prefix: 4, * y: '0x6e1c1f59ee1cf25b75a8d57b3c89e7e6b3b1da823df8b3b89497f30c1f000000' * }) * // @log: false * ``` * * @param publicKey - The public key object to assert. */ export function validate(publicKey, options = {}) { try { assert(publicKey, options); return true; } catch { return false; } } /** * Thrown when a public key is invalid. * * @example * ```ts twoslash * import { PublicKey } from 'ox' * * PublicKey.assert({ y: '0x01' }) * // @error: PublicKey.InvalidError: Value `{"y":"0x01"}` is not a valid public key. * // @error: Public key must contain: * // @error: - an `x` and `prefix` value (compressed) * // @error: - an `x`, `y`, and `prefix` value (uncompressed) * ``` */ export class InvalidError extends Errors.BaseError { name = 'PublicKey.InvalidError'; constructor({ publicKey }) { super(`Value \`${Json.stringify(publicKey)}\` is not a valid public key.`, { metaMessages: [ 'Public key must contain:', '- an `x` and `prefix` value (compressed)', '- an `x`, `y`, and `prefix` value (uncompressed)', ], }); } } /** Thrown when a public key has an invalid prefix. */ export class InvalidPrefixError extends Errors.BaseError { name = 'PublicKey.InvalidPrefixError'; constructor({ prefix, cause }) { super(`Prefix "${prefix}" is invalid.`, { cause, }); } } /** Thrown when the public key has an invalid prefix for a compressed public key. */ export class InvalidCompressedPrefixError extends Errors.BaseError { name = 'PublicKey.InvalidCompressedPrefixError'; constructor() { super('Prefix must be 2 or 3 for compressed public keys.'); } } /** * Thrown when the public key has an invalid prefix for an uncompressed public * key. */ export class InvalidUncompressedPrefixError extends Errors.BaseError { name = 'PublicKey.InvalidUncompressedPrefixError'; constructor() { super('Prefix must be 4 for uncompressed public keys.'); } } /** Thrown when the public key has an invalid serialized size. */ export class InvalidSerializedSizeError extends Errors.BaseError { name = 'PublicKey.InvalidSerializedSizeError'; constructor({ publicKey }) { super(`Value \`${publicKey}\` is an invalid public key size.`, { metaMessages: [ 'Expected: 33 bytes (compressed + prefix), 64 bytes (uncompressed) or 65 bytes (uncompressed + prefix).', `Received ${Hex.size(Hex.from(publicKey))} bytes.`, ], }); } } //# sourceMappingURL=PublicKey.js.map