UNPKG

ox

Version:

Ethereum Standard Library

203 lines 7.3 kB
import * as AbiParameters from '../core/AbiParameters.js'; import * as Authorization from '../core/Authorization.js'; import * as Errors from '../core/Errors.js'; import * as Hex from '../core/Hex.js'; import * as Secp256k1 from '../core/Secp256k1.js'; import * as Signature from '../core/Signature.js'; /** * Magic bytes used to identify ERC-8010 wrapped signatures. */ export const magicBytes = '0x8010801080108010801080108010801080108010801080108010801080108010'; /** Suffix ABI parameters for the ERC-8010 wrapped signature. */ export const suffixParameters = AbiParameters.from('(uint256 chainId, address delegation, uint256 nonce, uint8 yParity, uint256 r, uint256 s), address to, bytes data'); /** * Asserts that the wrapped signature is valid. * * @example * ```ts twoslash * import { SignatureErc8010 } from 'ox/erc8010' * * SignatureErc8010.assert('0xdeadbeef') * // @error: InvalidWrappedSignatureError: Value `0xdeadbeef` is an invalid ERC-8010 wrapped signature. * ``` * * @param value - The value to assert. */ export function assert(value) { if (typeof value === 'string') { if (Hex.slice(value, -32) !== magicBytes) throw new InvalidWrappedSignatureError(value); return; } if (value === null || typeof value !== 'object') throw new InvalidUnwrappedSignatureError(value); const { authorization, data, signature, to } = value; if (typeof signature !== 'string') throw new InvalidUnwrappedSignatureError(value); if (data !== undefined && typeof data !== 'string') throw new InvalidUnwrappedSignatureError(value); if (to !== undefined && typeof to !== 'string') throw new InvalidUnwrappedSignatureError(value); if (authorization === null || typeof authorization !== 'object') throw new InvalidUnwrappedSignatureError(value); Signature.assert(authorization); } /** * Parses an [ERC-8010 wrapped signature](https://github.com/jxom/ERCs/blob/16f7e3891fff2e1e9c25dea0485497739db8a816/ERCS/erc-8010.md) into its constituent parts. * * @example * ```ts twoslash * // @noErrors * import { Secp256k1 } from 'ox' * import { SignatureErc8010 } from 'ox/erc8010' // [!code focus] * * const signature = Secp256k1.sign({ * payload: '0x...', * privateKey: '0x...', * }) * * // Instantiate from serialized format. // [!code focus] * const wrapped = SignatureErc8010.from('0x...') // [!code focus] * // @log: { authorization: { ... }, data: '0x...', signature: { ... } } // [!code focus] * * // Instantiate from constituent parts. // [!code focus] * const wrapped = SignatureErc8010.from({ // [!code focus] * authorization: { ... }, // [!code focus] * data: '0x...', // [!code focus] * signature, // [!code focus] * }) * // @log: { authorization: { ... }, data: '0x...', signature: { ... } } * ``` * * @param value - Value to parse. * @returns Parsed value. */ export function from(value) { if (typeof value === 'string') return unwrap(value); assert(value); return value; } /** * Unwraps an [ERC-8010 wrapped signature](https://github.com/jxom/ERCs/blob/16f7e3891fff2e1e9c25dea0485497739db8a816/ERCS/erc-8010.md) into its constituent parts. * * @example * ```ts twoslash * import { SignatureErc8010 } from 'ox/erc8010' * * const { authorization, data, signature } = * SignatureErc8010.unwrap('0x...') * ``` * * @param wrapped - Wrapped signature to unwrap. * @returns Unwrapped signature. */ export function unwrap(wrapped) { assert(wrapped); const suffixLengthBig = Hex.toBigInt(Hex.slice(wrapped, -64, -32)); const wrappedSize = Hex.size(wrapped); // Suffix + length word + magic bytes must fit inside the wrapped value. if (suffixLengthBig < 0n || suffixLengthBig > BigInt(wrappedSize - 64)) throw new InvalidWrappedSignatureError(wrapped); const suffixLength = Number(suffixLengthBig); const suffix = Hex.slice(wrapped, -suffixLength - 64, -64); const signature = Hex.slice(wrapped, 0, -suffixLength - 64); const [auth, to, data] = AbiParameters.decode(suffixParameters, suffix); const authorization = Authorization.from({ address: auth.delegation, chainId: Number(auth.chainId), nonce: auth.nonce, yParity: auth.yParity, r: Hex.fromNumber(auth.r, { size: 32 }), s: Hex.fromNumber(auth.s, { size: 32 }), }); return { authorization, signature, ...(data && data !== '0x' ? { data, to } : {}), }; } /** * Wraps a signature into [ERC-8010 format](https://github.com/jxom/ERCs/blob/16f7e3891fff2e1e9c25dea0485497739db8a816/ERCS/erc-8010.md). * * @example * ```ts twoslash * // @noErrors * import { Secp256k1, Signature } from 'ox' * import { SignatureErc8010 } from 'ox/erc8010' // [!code focus] * * const signature = Secp256k1.sign({ * payload: '0x...', * privateKey: '0x...', * }) * * const wrapped = SignatureErc8010.wrap({ // [!code focus] * authorization: { ... }, // [!code focus] * data: '0xdeadbeef', // [!code focus] * signature: Signature.toHex(signature), // [!code focus] * }) // [!code focus] * ``` * * @param value - Values to wrap. * @returns Wrapped signature. */ export function wrap(value) { const { data, signature } = value; assert(value); const to = value.to ?? Secp256k1.recoverAddress({ payload: Authorization.getSignPayload(value.authorization), signature: Signature.from(value.authorization), }); const suffix = AbiParameters.encode(suffixParameters, [ { ...value.authorization, delegation: value.authorization.address, chainId: BigInt(value.authorization.chainId), r: BigInt(value.authorization.r ?? 0n), s: BigInt(value.authorization.s ?? 0n), }, to, data ?? '0x', ]); const suffixLength = Hex.fromNumber(Hex.size(suffix), { size: 32 }); return Hex.concat(signature, suffix, suffixLength, magicBytes); } /** * Validates a wrapped signature. Returns `true` if the wrapped signature is valid, `false` otherwise. * * @example * ```ts twoslash * import { SignatureErc8010 } from 'ox/erc8010' * * const valid = SignatureErc8010.validate('0xdeadbeef') * // @log: false * ``` * * @param value - The value to validate. * @returns `true` if the value is valid, `false` otherwise. */ export function validate(value) { try { assert(value); return true; } catch { return false; } } /** Thrown when the ERC-8010 wrapped signature is invalid. */ export class InvalidWrappedSignatureError extends Errors.BaseError { name = 'SignatureErc8010.InvalidWrappedSignatureError'; constructor(wrapped) { super(`Value \`${wrapped}\` is an invalid ERC-8010 wrapped signature.`); } } /** Thrown when an ERC-8010 unwrapped signature object is malformed. */ export class InvalidUnwrappedSignatureError extends Errors.BaseError { name = 'SignatureErc8010.InvalidUnwrappedSignatureError'; constructor(value) { super(`Value \`${JSON.stringify(value, (_, v) => typeof v === 'bigint' ? v.toString() : v)}\` is an invalid ERC-8010 unwrapped signature.`); } } //# sourceMappingURL=SignatureErc8010.js.map