UNPKG

ox

Version:

Ethereum Standard Library

294 lines 10.2 kB
/* eslint-disable jsdoc-js/require-jsdoc, jsdoc-js/require-description, jsdoc-js/require-example */ import * as core_SignatureEnvelope from '../../tempo/SignatureEnvelope.js'; import * as core_Hex from '../../core/Hex.js'; import * as core_MultisigConfig from '../../tempo/MultisigConfig.js'; import * as z_Address from '../Address.js'; import * as z_Hex from '../Hex.js'; import * as z_MultisigConfig from './MultisigConfig.js'; import * as z from 'zod/mini'; /** Signature envelope key type schema. */ export const Type = z.union([ z.literal('secp256k1'), z.literal('p256'), z.literal('webAuthn'), ]); /** Keychain signature version schema. */ export const KeychainVersion = z.union([z.literal('v1'), z.literal('v2')]); /** Uncompressed public key schema. */ export const PublicKey = z.object({ prefix: z .number() .check(z.refine((value) => value === 4, 'expected uncompressed prefix')), x: z_Hex.Hex32, y: z_Hex.Hex32, }); const RecoveredSignature = z.object({ r: z_Hex.Hex32, s: z_Hex.Hex32, yParity: z .number() .check(z.refine((value) => value === 0 || value === 1, 'expected yParity')), }); const Signature = z.object({ r: z_Hex.Hex32, s: z_Hex.Hex32, }); /** RPC secp256k1 signature envelope schema. */ export const Secp256k1Rpc = z.object({ r: z_Hex.Hex, s: z_Hex.Hex, type: z.literal('secp256k1'), v: z.optional(z_Hex.Hex), yParity: z.optional(z_Hex.Hex), }); /** RPC P256 signature envelope schema. */ export const P256Rpc = z.object({ preHash: z.boolean(), pubKeyX: z_Hex.Hex, pubKeyY: z_Hex.Hex, r: z_Hex.Hex, s: z_Hex.Hex, type: z.literal('p256'), }); /** RPC WebAuthn signature envelope schema. */ export const WebAuthnRpc = z.object({ pubKeyX: z_Hex.Hex, pubKeyY: z_Hex.Hex, r: z_Hex.Hex, s: z_Hex.Hex, type: z.literal('webAuthn'), webauthnData: z_Hex.Hex, }); /** RPC primitive signature envelope schema. */ export const PrimitiveRpc = z.union([Secp256k1Rpc, P256Rpc, WebAuthnRpc]); /** RPC keychain signature envelope schema. */ export const KeychainRpc = z.object({ keyId: z.optional(z_Address.Address), // `signature` is recursive; type the getter concretely to break the cycle. signature: z.lazy(() => Rpc), type: z.literal('keychain'), userAddress: z_Address.Address, version: z.optional(KeychainVersion), }); /** RPC native multisig signature envelope schema. */ export const MultisigRpc = z .union([ z.strictObject({ account: z_Address.Address, signatures: z.lazy(() => z.readonly(z .array(Rpc) .check(z.minLength(1), z.maxLength(core_MultisigConfig.maxSignatures)))), }), z.strictObject({ init: z_MultisigConfig.Config, signatures: z.lazy(() => z.readonly(z .array(Rpc) .check(z.minLength(1), z.maxLength(core_MultisigConfig.maxSignatures)))), }), ]) // Keep invalid recursive approvals inside Zod's issue path. .check(z.refine((value) => { try { core_SignatureEnvelope.fromRpc(value); return true; } catch { return false; } }, 'expected valid native multisig signature')); /** RPC signature envelope schema. */ export const Rpc = z.union([ Secp256k1Rpc, P256Rpc, WebAuthnRpc, KeychainRpc, MultisigRpc, ]); /** secp256k1 signature envelope schema. */ export const Secp256k1 = z.object({ signature: RecoveredSignature, type: z.literal('secp256k1'), }); /** P256 signature envelope schema. */ export const P256 = z.object({ prehash: z.boolean(), publicKey: PublicKey, signature: Signature, type: z.literal('p256'), }); /** WebAuthn signature envelope schema. */ export const WebAuthn = z.object({ metadata: z.object({ authenticatorData: z_Hex.Hex, clientDataJSON: z.string(), }), publicKey: PublicKey, signature: Signature, type: z.literal('webAuthn'), }); /** Primitive signature envelope schema. */ export const Primitive = z.union([Secp256k1, P256, WebAuthn]); /** Keychain signature envelope schema. */ export const Keychain = z.object({ // `inner` is recursive; type the getter concretely to break the cycle. inner: z.lazy(() => Domain), keyId: z.optional(z_Address.Address), type: z.literal('keychain'), userAddress: z_Address.Address, version: z.optional(KeychainVersion), }); /** Native multisig signature envelope schema. */ export const Multisig = z .object({ account: z_Address.Address, init: z.optional(z_MultisigConfig.Config), // `signatures` is recursive; type the getter concretely to break the cycle. signatures: z.lazy(() => z.readonly(z .array(Domain) .check(z.minLength(1), z.maxLength(core_MultisigConfig.maxSignatures)))), type: z.literal('multisig'), }) // Keep invalid recursive approvals inside Zod's issue path. .check(z.refine((value) => core_SignatureEnvelope.validate(value), 'expected valid native multisig signature')); /** Decoded signature envelope schema. */ export const Domain = z.union([Secp256k1, P256, WebAuthn, Keychain, Multisig]); /** Codec decoding an RPC signature envelope into a signature envelope. */ export const SignatureEnvelope = z.codec(Rpc, Domain, { decode: (value) => fromRpc(value), encode: (value) => toRpc(value), }); /** Hex-encoded serialized signature envelope schema. */ export const Serialized = z_Hex.Hex; /** Recursively decodes an RPC signature envelope into a signature envelope. */ function fromRpc(value) { if (value.type === 'secp256k1') { const secp256k1 = value; return { signature: { r: core_Hex.padLeft(secp256k1.r, 32), s: core_Hex.padLeft(secp256k1.s, 32), yParity: toYParity(secp256k1.v, secp256k1.yParity), }, type: 'secp256k1', }; } if (value.type === 'p256') { const p256 = value; return { prehash: p256.preHash, publicKey: { prefix: 4, x: core_Hex.padLeft(p256.pubKeyX, 32), y: core_Hex.padLeft(p256.pubKeyY, 32), }, signature: { r: core_Hex.padLeft(p256.r, 32), s: core_Hex.padLeft(p256.s, 32), }, type: 'p256', }; } if (value.type === 'webAuthn') { const webAuthn = value; return { metadata: parseWebauthnData(webAuthn.webauthnData), publicKey: { prefix: 4, x: core_Hex.padLeft(webAuthn.pubKeyX, 32), y: core_Hex.padLeft(webAuthn.pubKeyY, 32), }, signature: { r: core_Hex.padLeft(webAuthn.r, 32), s: core_Hex.padLeft(webAuthn.s, 32), }, type: 'webAuthn', }; } if ('signatures' in value && ('account' in value || 'init' in value)) return core_SignatureEnvelope.fromRpc(value); const keychain = value; return { inner: fromRpc(keychain.signature), type: 'keychain', userAddress: keychain.userAddress, ...(keychain.keyId ? { keyId: keychain.keyId } : {}), ...(keychain.version ? { version: keychain.version } : {}), }; } /** Recursively encodes a signature envelope into an RPC signature envelope. */ function toRpc(value) { if (value.type === 'secp256k1') { const secp256k1 = value; return { r: secp256k1.signature.r, s: secp256k1.signature.s, type: 'secp256k1', yParity: secp256k1.signature.yParity === 0 ? '0x0' : '0x1', }; } if (value.type === 'p256') { const p256 = value; return { preHash: p256.prehash, pubKeyX: p256.publicKey.x, pubKeyY: p256.publicKey.y, r: p256.signature.r, s: p256.signature.s, type: 'p256', }; } if (value.type === 'webAuthn') { const webAuthn = value; return { pubKeyX: webAuthn.publicKey.x, pubKeyY: webAuthn.publicKey.y, r: webAuthn.signature.r, s: webAuthn.signature.s, type: 'webAuthn', webauthnData: core_Hex.concat(webAuthn.metadata.authenticatorData, core_Hex.fromString(webAuthn.metadata.clientDataJSON)), }; } if (value.type === 'multisig') return core_SignatureEnvelope.toRpc(value); const keychain = value; return { signature: toRpc(keychain.inner), type: 'keychain', userAddress: keychain.userAddress, ...(keychain.keyId ? { keyId: keychain.keyId } : {}), ...(keychain.version ? { version: keychain.version } : {}), }; } /** Computes a `yParity` value from RPC `v`/`yParity` fields. */ function toYParity(v, yParity) { const v_ = v ? Number(v) : undefined; let yParity_ = yParity ? Number(yParity) : undefined; if (typeof v_ === 'number' && typeof yParity_ !== 'number') { if (v_ === 0 || v_ === 27) yParity_ = 0; else if (v_ === 1 || v_ === 28) yParity_ = 1; else if (v_ >= 35) yParity_ = v_ % 2 === 0 ? 1 : 0; } return yParity_; } /** Parses concatenated WebAuthn metadata into its component parts. */ function parseWebauthnData(webauthnData) { const size = core_Hex.size(webauthnData); for (let split = 37; split < size; split++) { const potentialJson = core_Hex.toString(core_Hex.slice(webauthnData, split)); if (potentialJson.startsWith('{') && potentialJson.endsWith('}')) { try { JSON.parse(potentialJson); return { authenticatorData: core_Hex.slice(webauthnData, 0, split), clientDataJSON: potentialJson, }; } catch { } } } throw new Error('Unable to parse WebAuthn metadata: could not extract valid authenticatorData and clientDataJSON'); } //# sourceMappingURL=SignatureEnvelope.js.map