import * as Address from '../core/Address.js'
import type * as Bytes from '../core/Bytes.js'
import * as Errors from '../core/Errors.js'
import * as Hex from '../core/Hex.js'
import type {
  Assign,
  Compute,
  IsNarrowable,
  OneOf,
  PartialBy,
  UnionPartialBy,
} from '../core/internal/types.js'
import * as Json from '../core/Json.js'
import * as ox_P256 from '../core/P256.js'
import type * as PublicKey from '../core/PublicKey.js'
import * as Rlp from '../core/Rlp.js'
import * as ox_Secp256k1 from '../core/Secp256k1.js'
import * as Signature from '../core/Signature.js'
import type * as WebAuthnP256 from '../core/WebAuthnP256.js'
import * as ox_WebAuthnP256 from '../core/WebAuthnP256.js'
import * as MultisigConfig from './MultisigConfig.js'

/** Signature type identifiers for encoding/decoding */
const serializedP256Type = '0x01'
const serializedWebAuthnType = '0x02'
const serializedKeychainType = '0x03'
const serializedKeychainV2Type = '0x04'
const serializedMultisigType = '0x05'

/** Serialized magic identifier for Tempo signature envelopes. */
export const magicBytes =
  '0x7777777777777777777777777777777777777777777777777777777777777777' // 32 "T"s

/**
 * Statically determines the signature type of an envelope at compile time.
 *
 * @example
 * ```ts twoslash
 * import type { SignatureEnvelope } from 'ox/tempo'
 *
 * type Type = SignatureEnvelope.GetType<{
 *   r: `0x${string}`
 *   s: `0x${string}`
 *   yParity: number
 * }>
 * // @log: 'secp256k1'
 * ```
 */
export type GetType<
  envelope extends PartialBy<SignatureEnvelope, 'type'> | unknown,
> = unknown extends envelope
  ? envelope extends unknown
    ? Type
    : never
  : envelope extends { type: infer T extends Type }
    ? T
    : envelope extends {
          signature: { r: `0x${string}`; s: `0x${string}` }
          prehash: boolean
          publicKey: PublicKey.PublicKey
        }
      ? 'p256'
      : envelope extends {
            signature: { r: `0x${string}`; s: `0x${string}` }
            metadata: any
            publicKey: PublicKey.PublicKey
          }
        ? 'webAuthn'
        : envelope extends {
              r: `0x${string}`
              s: `0x${string}`
              yParity: number
            }
          ? 'secp256k1'
          : envelope extends {
                signature: {
                  r: `0x${string}`
                  s: `0x${string}`
                  yParity: number
                }
              }
            ? 'secp256k1'
            : envelope extends {
                  userAddress: Address.Address
                }
              ? 'keychain'
              : envelope extends
                    | {
                        account: Address.Address
                        signatures: any
                      }
                    | {
                        init: MultisigConfig.Config
                        signatures: any
                      }
                ? 'multisig'
                : never

/**
 * Represents a signature envelope that can contain different signature types.
 *
 * Tempo transactions support multiple signature types, each with different wire formats:
 *
 * - **secp256k1** (no type prefix, 65 bytes): Standard Ethereum ECDSA signature. The sender
 *   address is recovered via `ecrecover`. Base transaction cost: 21,000 gas.
 *
 * - **p256** (type `0x01`, 130 bytes): P256/secp256r1 curve signature for passkey accounts.
 *   Includes embedded public key (64 bytes) and prehash flag. Enables native WebCrypto
 *   key support. Additional gas cost: +5,000 gas over secp256k1.
 *
 * - **webAuthn** (type `0x02`, 129-2049 bytes): WebAuthn signature with authenticator data
 *   and clientDataJSON. Enables browser passkey authentication. The signature is also
 *   charged as calldata (16 gas/non-zero byte, 4 gas/zero byte).
 *
 * - **keychain** (type `0x03` V1, `0x04` V2): Access key signature that wraps an inner signature
 *   (secp256k1, p256, or webAuthn). Format: type byte + user_address (20 bytes) + inner signature.
 *   V2 binds the signature to the user account via `keccak256(sigHash || userAddress)`.
 *   The protocol validates the access key authorization via the AccountKeychain precompile.
 *
 * [Signature Types Specification](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
 */
export type SignatureEnvelope<numberType = number> = OneOf<
  | Secp256k1<numberType>
  | P256<numberType>
  | WebAuthn<numberType>
  | Keychain<numberType>
  | Multisig<numberType>
>

/**
 * RPC-formatted signature envelope.
 */
export type SignatureEnvelopeRpc = OneOf<
  Secp256k1Rpc | P256Rpc | WebAuthnRpc | KeychainRpc | MultisigRpc
>

/** Primitive signature envelope accepted by protocol sidecars. */
export type Primitive<numberType = number> = OneOf<
  Secp256k1<numberType> | P256<numberType> | WebAuthn<numberType>
>

/** RPC-formatted primitive signature envelope. */
export type PrimitiveRpc = OneOf<Secp256k1Rpc | P256Rpc | WebAuthnRpc>

/**
 * Keychain signature version.
 *
 * - `'v1'`: Legacy format. Inner signature signs the raw `sig_hash` directly. Deprecated at T1C.
 * - `'v2'`: Inner signature signs `keccak256(sig_hash || user_address)`, binding the signature
 *   to the specific user account.
 */
export type KeychainVersion = 'v1' | 'v2'

export type Keychain<numberType = number> = {
  /** Root account address that this transaction is being executed for */
  userAddress: Address.Address
  /** The actual signature from the access key (can be Secp256k1, P256, or WebAuthn) */
  inner: SignatureEnvelope<numberType>
  /** The access key address (recovered address of the access key signer). */
  keyId?: Address.Address | undefined
  type: 'keychain'
  /** Keychain signature version. @default 'v1' */
  version?: KeychainVersion | undefined
}

export type KeychainRpc = {
  type: 'keychain'
  userAddress: Address.Address
  keyId?: Address.Address | undefined
  signature: SignatureEnvelopeRpc
  version?: KeychainVersion | undefined
}

/**
 * Native multisig signature (type `0x05`).
 *
 * Wraps a set of owner approvals (secp256k1, p256, webAuthn, or nested
 * multisig) over the multisig owner approval digest. The transaction sender is
 * the derived `account`, authorized once the recovered owner weights meet the
 * configured threshold.
 *
 * [TIP-1061](https://tips.sh/1061)
 */
export type Multisig<numberType = number> = {
  type: 'multisig'
  /** Native multisig account address. */
  account: Address.Address
  /**
   * Owner approvals over the multisig owner approval digest. Each approval is
   * either a primitive signature or a nested multisig signature (keychain
   * approvals are invalid).
   */
  signatures: readonly SignatureEnvelope<numberType>[]
  /**
   * Initial native multisig config for bootstrapping this account. Present only on
   * the first (bootstrap) transaction from the derived account; absent on every
   * subsequent transaction.
   */
  init?: MultisigConfig.Config<numberType> | undefined
}

/** RPC-formatted native multisig signature. */
export type MultisigRpc = OneOf<
  | {
      /** Existing native multisig account. */
      account: Address.Address
      /** Structured owner approvals. */
      signatures: readonly SignatureEnvelopeRpc[]
      /** Multisig RPC signatures are untagged. */
      type?: undefined
    }
  | {
      /** Initial config for bootstrapping a native multisig account. */
      init: MultisigConfig.Config
      /** Structured owner approvals. */
      signatures: readonly SignatureEnvelopeRpc[]
      /** Multisig RPC signatures are untagged. */
      type?: undefined
    }
>

export type P256<numberType = number> = {
  prehash: boolean
  publicKey: PublicKey.PublicKey
  signature: Signature.Signature<false, numberType>
  type: 'p256'
}

export type P256Rpc = {
  preHash: boolean
  pubKeyX: Hex.Hex
  pubKeyY: Hex.Hex
  r: Hex.Hex
  s: Hex.Hex
  type: 'p256'
}

export type Secp256k1<numberType = number> = {
  signature: Signature.Signature<true, numberType>
  type: 'secp256k1'
}

export type Secp256k1Rpc = Compute<
  Signature.Rpc<true> & {
    v?: Hex.Hex | undefined
    type: 'secp256k1'
  }
>

export type Secp256k1Flat<numberType = number> = Signature.Signature<
  true,
  numberType
> & {
  type?: 'secp256k1' | undefined
}

export type WebAuthn<numberType = number> = {
  metadata: Pick<
    WebAuthnP256.SignMetadata,
    'authenticatorData' | 'clientDataJSON'
  >
  signature: Signature.Signature<false, numberType>
  publicKey: PublicKey.PublicKey
  type: 'webAuthn'
}

export type WebAuthnRpc = {
  pubKeyX: Hex.Hex
  pubKeyY: Hex.Hex
  r: Hex.Hex
  s: Hex.Hex
  type: 'webAuthn'
  webauthnData: Hex.Hex
}

/** Hex-encoded serialized signature envelope. */
export type Serialized = Hex.Hex

/** List of supported signature types. */
export const types = ['secp256k1', 'p256', 'webAuthn'] as const

/** Union type of supported signature types. */
export type Type = (typeof types)[number]

/**
 * Asserts that a {@link ox#SignatureEnvelope.SignatureEnvelope} is valid.
 *
 * @example
 * ```ts twoslash
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * SignatureEnvelope.assert({
 *   type: 'secp256k1',
 *   signature: {
 *     r: '0x0000000000000000000000000000000000000000000000000000000000000000',
 *     s: '0x0000000000000000000000000000000000000000000000000000000000000000',
 *     yParity: 0
 *   }
 * })
 * ```
 *
 * @param envelope - The signature envelope to assert.
 * @throws `CoercionError` if the envelope type cannot be determined.
 */
export function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void {
  const type = getType(envelope)

  if (type === 'secp256k1') {
    const secp256k1 = envelope as Secp256k1
    Signature.assert(secp256k1.signature)
    return
  }

  if (type === 'p256') {
    const p256 = envelope as P256
    const missing: string[] = []

    if (typeof p256.signature?.r !== 'string') missing.push('signature.r')
    if (typeof p256.signature?.s !== 'string') missing.push('signature.s')
    if (typeof p256.prehash !== 'boolean') missing.push('prehash')
    if (!p256.publicKey) missing.push('publicKey')
    else {
      if (typeof p256.publicKey.x !== 'string') missing.push('publicKey.x')
      if (typeof p256.publicKey.y !== 'string') missing.push('publicKey.y')
    }

    if (missing.length > 0)
      throw new MissingPropertiesError({ envelope, missing, type: 'p256' })
    return
  }

  if (type === 'webAuthn') {
    const webauthn = envelope as WebAuthn
    const missing: string[] = []

    if (typeof webauthn.signature?.r !== 'string') missing.push('signature.r')
    if (typeof webauthn.signature?.s !== 'string') missing.push('signature.s')
    if (!webauthn.metadata) missing.push('metadata')
    else {
      if (!webauthn.metadata.authenticatorData)
        missing.push('metadata.authenticatorData')
      if (!webauthn.metadata.clientDataJSON)
        missing.push('metadata.clientDataJSON')
    }
    if (!webauthn.publicKey) missing.push('publicKey')
    else {
      if (typeof webauthn.publicKey.x !== 'string') missing.push('publicKey.x')
      if (typeof webauthn.publicKey.y !== 'string') missing.push('publicKey.y')
    }

    if (missing.length > 0)
      throw new MissingPropertiesError({ envelope, missing, type: 'webAuthn' })
    return
  }

  if (type === 'keychain') {
    const keychain = envelope as Keychain
    assert(keychain.inner)
    return
  }

  if (type === 'multisig') {
    const multisig = envelope as Multisig
    assertMultisig(multisig, 1)
    return
  }
}

export declare namespace assert {
  type ErrorType =
    | CoercionError
    | InvalidMultisigApprovalError
    | MissingPropertiesError
    | MultisigConfig.assert.ErrorType
    | MultisigConfig.getAddress.ErrorType
    | Signature.assert.ErrorType
    | Errors.GlobalErrorType
}

function assertMultisig(envelope: Multisig, depth: number): void {
  const missing: string[] = []
  if (!envelope.account) missing.push('account')
  if (!Array.isArray(envelope.signatures)) missing.push('signatures')
  if (missing.length > 0)
    throw new MissingPropertiesError({
      envelope,
      missing,
      type: 'multisig',
    })
  if (depth > MultisigConfig.maxNestingDepth)
    throw new InvalidMultisigApprovalError({
      reason: `multisig nesting depth exceeds ${MultisigConfig.maxNestingDepth}`,
    })
  if (!Address.validate(envelope.account))
    throw new InvalidMultisigApprovalError({
      reason: 'multisig account is invalid',
    })
  if (Hex.toBigInt(envelope.account) === 0n)
    throw new InvalidMultisigApprovalError({
      reason: 'multisig account cannot be zero',
    })
  if (envelope.signatures.length === 0)
    throw new InvalidMultisigApprovalError({
      reason: 'multisig signatures cannot be empty',
    })
  if (envelope.signatures.length > MultisigConfig.maxSignatures)
    throw new InvalidMultisigApprovalError({
      reason: `multisig signatures exceed ${MultisigConfig.maxSignatures}`,
    })

  if (envelope.init) {
    MultisigConfig.assert(envelope.init)
    if (
      !Address.isEqual(
        MultisigConfig.getAddress(envelope.init),
        envelope.account,
      )
    )
      throw new InvalidMultisigApprovalError({
        reason: 'multisig init does not derive account',
      })
  }

  for (const inner of envelope.signatures) {
    const type = getType(inner)
    if (type === 'keychain')
      throw new InvalidMultisigApprovalError({
        reason: 'keychain owner approvals are not allowed',
      })
    if (type === 'multisig') {
      const multisig = inner as Multisig
      if (multisig.init)
        throw new InvalidMultisigApprovalError({
          reason: 'nested multisig owner approvals cannot carry `init`',
        })
      assertMultisig(multisig, depth + 1)
    } else assert(inner)

    if (Hex.size(serialize(inner)) > MultisigConfig.maxOwnerSignatureBytes)
      throw new InvalidMultisigApprovalError({
        reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
      })
  }
}

/**
 * Extracts the address of the signer from a {@link ox#SignatureEnvelope.SignatureEnvelope}.
 *
 * - **secp256k1**: Recovers the address from the payload via `ecrecover`.
 * - **p256** / **webAuthn**: Derives the address from the embedded public key.
 * - **keychain**: Extracts from the inner signature (or returns `userAddress` if `user` is `true`).
 *
 * @example
 * ```ts twoslash
 * import { Secp256k1 } from 'ox'
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * const payload = '0xdeadbeef'
 * const signature = Secp256k1.sign({
 *   payload,
 *   privateKey: '0x...'
 * })
 * const envelope = SignatureEnvelope.from(signature)
 *
 * const address = SignatureEnvelope.extractAddress({
 *   // [!code focus]
 *   payload, // [!code focus]
 *   signature: envelope // [!code focus]
 * }) // [!code focus]
 * ```
 *
 * @param options - The extraction options.
 * @returns The signer address.
 */
export function extractAddress(
  options: extractAddress.Options,
): extractAddress.ReturnType {
  const { signature, root } = options
  if (signature.type === 'keychain') {
    if (root) return signature.userAddress
    return extractAddress({ ...options, signature: signature.inner })
  }
  // Native multisig signatures have no single signer; the recovered sender is the
  // derived multisig account address.
  if (signature.type === 'multisig') return signature.account
  return Address.fromPublicKey(extractPublicKey(options))
}

export declare namespace extractAddress {
  type Options = {
    /** The sign payload that was signed (only required for secp256k1 signatures). */
    payload: Hex.Hex | Bytes.Bytes
    /** The signature envelope. */
    signature: SignatureEnvelope
    /** Whether to return the root `userAddress` for keychain signatures instead of extracting from the inner signature. */
    root?: boolean | undefined
  }

  type ReturnType = Address.Address

  type ErrorType =
    | Address.fromPublicKey.ErrorType
    | extractPublicKey.ErrorType
    | Errors.GlobalErrorType
}

/**
 * Extracts the public key of the signer from a {@link ox#SignatureEnvelope.SignatureEnvelope}.
 *
 * - **secp256k1**: Recovers the public key from the payload via `ecrecover`.
 * - **p256** / **webAuthn**: Returns the embedded public key.
 * - **keychain**: Extracts from the inner signature.
 *
 * @example
 * ```ts twoslash
 * import { Secp256k1 } from 'ox'
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * const payload = '0xdeadbeef'
 * const signature = Secp256k1.sign({
 *   payload,
 *   privateKey: '0x...'
 * })
 * const envelope = SignatureEnvelope.from(signature)
 *
 * const publicKey = SignatureEnvelope.extractPublicKey({
 *   // [!code focus]
 *   payload, // [!code focus]
 *   signature: envelope // [!code focus]
 * }) // [!code focus]
 * ```
 *
 * @param options - The extraction options.
 * @returns The signer's public key.
 */
export function extractPublicKey(
  options: extractPublicKey.Options,
): extractPublicKey.ReturnType {
  const { payload, signature } = options

  switch (signature.type) {
    case 'secp256k1':
      return ox_Secp256k1.recoverPublicKey({
        payload,
        signature: signature.signature,
      })
    case 'p256':
    case 'webAuthn':
      return signature.publicKey
    case 'keychain':
      return extractPublicKey({ payload, signature: signature.inner })
    case 'multisig':
      // A multisig signature aggregates multiple owner approvals and has no
      // single public key; recover the multisig account via `extractAddress`.
      throw new CoercionError({ envelope: signature })
  }
}

export declare namespace extractPublicKey {
  type Options = {
    /** The sign payload that was signed (only required for secp256k1 signatures). */
    payload: Hex.Hex | Bytes.Bytes
    /** The signature envelope. */
    signature: SignatureEnvelope
  }

  type ReturnType = PublicKey.PublicKey

  type ErrorType =
    | CoercionError
    | ox_Secp256k1.recoverPublicKey.ErrorType
    | Errors.GlobalErrorType
}

/**
 * Deserializes a hex-encoded signature envelope into a typed signature object.
 *
 * Wire format detection:
 * - 65 bytes (no prefix): secp256k1 signature
 * - Type `0x01` + 129 bytes: P256 signature (r, s, pubKeyX, pubKeyY, prehash)
 * - Type `0x02` + variable: WebAuthn signature (webauthnData, r, s, pubKeyX, pubKeyY)
 * - Type `0x03` + 20 bytes + inner: Keychain V1 signature (userAddress + inner signature)
 * - Type `0x04` + 20 bytes + inner: Keychain V2 signature (userAddress + inner signature)
 *
 * [Signature Types](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
 *
 * @example
 * ```ts twoslash
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * const envelope = SignatureEnvelope.deserialize('0x...')
 * ```
 *
 * @param serialized - The hex-encoded signature envelope to deserialize.
 * @returns The deserialized signature envelope.
 * @throws `CoercionError` if the serialized value cannot be coerced to a valid signature envelope.
 */
export function deserialize(value: Serialized): SignatureEnvelope {
  return deserialize_(value, 0)
}

function deserialize_(
  value: Serialized,
  multisigDepth: number,
): SignatureEnvelope {
  const serialized = value.endsWith(magicBytes.slice(2))
    ? Hex.slice(value, 0, -Hex.size(magicBytes))
    : value

  const size = Hex.size(serialized)

  // Backward compatibility: 65 bytes means secp256k1 without type identifier
  if (size === 65) {
    const signature = Signature.fromHex(serialized)
    Signature.assert(signature)
    return { signature, type: 'secp256k1' } satisfies Secp256k1
  }

  // For all other lengths, first byte is the type identifier
  const typeId = Hex.slice(serialized, 0, 1)
  const data = Hex.slice(serialized, 1)
  const dataSize = Hex.size(data)

  if (typeId === serializedP256Type) {
    // P256: 32 (r) + 32 (s) + 32 (pubKeyX) + 32 (pubKeyY) + 1 (prehash) = 129 bytes
    if (dataSize !== 129)
      throw new InvalidSerializedError({
        reason: `Invalid P256 signature envelope size: expected 129 bytes, got ${dataSize} bytes`,
        serialized,
      })

    return {
      publicKey: {
        prefix: 4,
        x: Hex.slice(data, 64, 96),
        y: Hex.slice(data, 96, 128),
      },
      prehash: Hex.toNumber(Hex.slice(data, 128, 129)) !== 0,
      signature: {
        r: Hex.slice(data, 0, 32),
        s: Hex.slice(data, 32, 64),
      },
      type: 'p256',
    } satisfies P256
  }

  if (typeId === serializedWebAuthnType) {
    // WebAuthn: variable (webauthnData) + 32 (r) + 32 (s) + 32 (pubKeyX) + 32 (pubKeyY)
    // Minimum: 128 bytes (at least some authenticator data + signature components)
    if (dataSize < 128)
      throw new InvalidSerializedError({
        reason: `Invalid WebAuthn signature envelope size: expected at least 128 bytes, got ${dataSize} bytes`,
        serialized,
      })

    const webauthnDataSize = dataSize - 128
    const webauthnData = Hex.slice(data, 0, webauthnDataSize)

    // Parse webauthnData into authenticatorData and clientDataJSON
    // According to the Rust code, it's authenticatorData || clientDataJSON
    // We need to find the split point (minimum authenticatorData is 37 bytes)
    let authenticatorData: Hex.Hex | undefined
    let clientDataJSON: string | undefined

    // Try to find the JSON start (clientDataJSON should start with '{')
    for (let split = 37; split < webauthnDataSize; split++) {
      const potentialJson = Hex.toString(Hex.slice(webauthnData, split))
      if (potentialJson.startsWith('{') && potentialJson.endsWith('}')) {
        try {
          JSON.parse(potentialJson)
          authenticatorData = Hex.slice(webauthnData, 0, split)
          clientDataJSON = potentialJson
          break
        } catch {}
      }
    }

    if (!authenticatorData || !clientDataJSON)
      throw new InvalidSerializedError({
        reason:
          'Unable to parse WebAuthn metadata: could not extract valid authenticatorData and clientDataJSON',
        serialized,
      })

    return {
      publicKey: {
        prefix: 4,
        x: Hex.slice(data, webauthnDataSize + 64, webauthnDataSize + 96),
        y: Hex.slice(data, webauthnDataSize + 96, webauthnDataSize + 128),
      },
      metadata: {
        authenticatorData,
        clientDataJSON,
      },
      signature: {
        r: Hex.slice(data, webauthnDataSize, webauthnDataSize + 32),
        s: Hex.slice(data, webauthnDataSize + 32, webauthnDataSize + 64),
      },
      type: 'webAuthn',
    } satisfies WebAuthn
  }

  if (
    typeId === serializedKeychainType ||
    typeId === serializedKeychainV2Type
  ) {
    const userAddress = Hex.slice(data, 0, 20)
    const inner = deserialize_(Hex.slice(data, 20), multisigDepth)

    return {
      userAddress,
      inner,
      type: 'keychain',
      version: typeId === serializedKeychainV2Type ? 'v2' : 'v1',
    } satisfies Keychain
  }

  if (typeId === serializedMultisigType) {
    const depth = multisigDepth + 1
    if (depth > MultisigConfig.maxNestingDepth)
      throw new InvalidSerializedError({
        reason: `multisig nesting depth exceeds ${MultisigConfig.maxNestingDepth}`,
        serialized,
      })

    // The first field distinguishes the static wire shapes: a bootstrap init
    // config is an RLP list, while an initialized account is a 20-byte string.
    const decoded = Rlp.toHex(data)
    if (!Array.isArray(decoded) || decoded.length !== 2)
      throw new InvalidSerializedError({
        reason: 'invalid multisig wire shape: expected exactly two fields',
        serialized,
      })

    const [address, signatures] = decoded
    if (!Array.isArray(signatures) || signatures.some(Array.isArray))
      throw new InvalidSerializedError({
        reason: 'invalid multisig signatures list',
        serialized,
      })
    if (signatures.length === 0)
      throw new InvalidSerializedError({
        reason: 'multisig signatures cannot be empty',
        serialized,
      })
    if (signatures.length > MultisigConfig.maxSignatures)
      throw new InvalidSerializedError({
        reason: `multisig signatures exceed ${MultisigConfig.maxSignatures}`,
        serialized,
      })
    for (const signature of signatures)
      if (
        Hex.size(signature as Hex.Hex) > MultisigConfig.maxOwnerSignatureBytes
      )
        throw new InvalidSerializedError({
          reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
          serialized,
        })

    if (!Array.isArray(address) && !Address.validate(address))
      throw new InvalidSerializedError({
        reason: 'invalid multisig account',
        serialized,
      })
    if (Array.isArray(address)) {
      const [salt, threshold, owners] = address
      if (
        address.length !== 3 ||
        Array.isArray(salt) ||
        Hex.size(salt) !== 32 ||
        Array.isArray(threshold) ||
        Hex.size(threshold) > 1 ||
        !Array.isArray(owners) ||
        owners.some(
          (owner) =>
            !Array.isArray(owner) ||
            owner.length !== 2 ||
            owner.some(Array.isArray) ||
            Hex.size(owner[1] as Hex.Hex) > 1,
        )
      )
        throw new InvalidSerializedError({
          reason: 'invalid multisig init config',
          serialized,
        })
    }

    const init = Array.isArray(address)
      ? MultisigConfig.fromTuple(address as unknown as MultisigConfig.Tuple)
      : undefined
    const account = init
      ? MultisigConfig.getAddress(init)
      : (address as Address.Address)
    const envelope = {
      type: 'multisig',
      account,
      signatures: signatures.map((signature) =>
        deserialize_(signature as Hex.Hex, depth),
      ),
      ...(init ? { init } : {}),
    } satisfies Multisig
    assertMultisig(envelope, depth)
    return envelope
  }

  throw new InvalidSerializedError({
    reason: `Unknown signature type identifier: ${typeId}. Expected ${serializedP256Type} (P256), ${serializedWebAuthnType} (WebAuthn), ${serializedKeychainType} (Keychain V1), ${serializedKeychainV2Type} (Keychain V2), or ${serializedMultisigType} (Multisig)`,
    serialized,
  })
}

/**
 * Coerces a value to a signature envelope.
 *
 * Accepts either a serialized hex string or an existing signature envelope object.
 * Use this to wrap raw signatures from {@link ox#Secp256k1.(sign:function)}, {@link ox#P256.(sign:function)},
 * {@link ox#WebCryptoP256.(sign:function)}, or {@link ox#WebAuthnP256.(sign:function)} into the envelope format
 * required by Tempo transactions.
 *
 * [Signature Types](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
 *
 * @example
 * ### Secp256k1
 *
 * Standard Ethereum ECDSA signature using the secp256k1 curve.
 *
 * ```ts twoslash
 * import { Secp256k1 } from 'ox'
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * const privateKey = Secp256k1.randomPrivateKey()
 * const signature = Secp256k1.sign({
 *   payload: '0xdeadbeef',
 *   privateKey
 * })
 *
 * const envelope = SignatureEnvelope.from(signature)
 * ```
 *
 * @example
 * ### P256
 *
 * ECDSA signature using the P-256 (secp256r1) curve. Requires embedding the
 * public key.
 *
 * ```ts twoslash
 * import { P256 } from 'ox'
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * const { privateKey, publicKey } = P256.createKeyPair()
 * const signature = P256.sign({
 *   payload: '0xdeadbeef',
 *   privateKey
 * })
 *
 * const envelope = SignatureEnvelope.from({
 *   signature,
 *   publicKey
 * })
 * ```
 *
 * @example
 * ### P256 (WebCrypto)
 *
 * When using WebCrypto keys, `prehash` must be `true` since WebCrypto always
 * SHA256 hashes the digest before signing.
 *
 * ```ts twoslash
 * // @noErrors
 * import { WebCryptoP256 } from 'ox'
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * const { privateKey, publicKey } =
 *   await WebCryptoP256.createKeyPair()
 * const signature = await WebCryptoP256.sign({
 *   payload: '0xdeadbeef',
 *   privateKey
 * })
 *
 * const envelope = SignatureEnvelope.from({
 *   signature,
 *   publicKey,
 *   prehash: true
 * })
 * ```
 *
 * @example
 * ### WebAuthn
 *
 * Passkey-based signature using WebAuthn. Includes authenticator metadata
 * (authenticatorData and clientDataJSON) along with the P-256 signature and
 * public key.
 *
 * ```ts twoslash
 * // @noErrors
 * import { WebAuthnP256 } from 'ox'
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * const credential = await WebAuthnP256.createCredential({
 *   name: 'Example'
 * })
 *
 * const { metadata, signature } = await WebAuthnP256.sign({
 *   challenge: '0xdeadbeef',
 *   credentialId: credential.id
 * })
 *
 * const envelope = SignatureEnvelope.from({
 *   signature,
 *   publicKey: credential.publicKey,
 *   metadata
 * })
 * ```
 *
 * @example
 * ### Keychain
 *
 * Wraps another signature type with a user address, used for delegated signing
 * via access keys on behalf of a root account.
 *
 * ```ts twoslash
 * import { Secp256k1 } from 'ox'
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * const privateKey = Secp256k1.randomPrivateKey()
 * const signature = Secp256k1.sign({
 *   payload: '0xdeadbeef',
 *   privateKey
 * })
 *
 * const envelope = SignatureEnvelope.from({
 *   userAddress: '0x1234567890123456789012345678901234567890',
 *   inner: SignatureEnvelope.from(signature)
 * })
 * ```
 *
 * @example
 * ### Multisig (from genesis config)
 *
 * Pass `genesisConfig` to derive `account` automatically. Set `init: true` to
 * opt into bootstrap (uses `genesisConfig` as the bootstrap `init`); omit
 * `init` for subsequent (non-bootstrap) transactions.
 *
 * ```ts twoslash
 * import { Secp256k1 } from 'ox'
 * import { MultisigConfig, SignatureEnvelope } from 'ox/tempo'
 *
 * const genesisConfig = MultisigConfig.from({
 *   threshold: 1,
 *   owners: [
 *     {
 *       owner: '0x1111111111111111111111111111111111111111',
 *       weight: 1
 *     }
 *   ]
 * })
 *
 * const privateKey = Secp256k1.randomPrivateKey()
 * const signature = SignatureEnvelope.from(
 *   Secp256k1.sign({ payload: '0xdeadbeef', privateKey })
 * )
 *
 * // Bootstrap transaction
 * const bootstrap = SignatureEnvelope.from({
 *   genesisConfig,
 *   signatures: [signature],
 *   init: true
 * })
 *
 * // Subsequent (non-bootstrap) transactions
 * const subsequent = SignatureEnvelope.from({
 *   genesisConfig,
 *   signatures: [signature]
 * })
 * ```
 *
 * @param value - The value to coerce (either a hex string or signature envelope).
 * @returns The signature envelope.
 */
export function from<const value extends from.Value>(
  value: value | from.Value,
  options?: from.Options,
): from.ReturnValue<value> {
  if (typeof value === 'string') return deserialize(value) as never

  if (
    typeof value === 'object' &&
    value !== null &&
    'r' in value &&
    's' in value &&
    'yParity' in value
  )
    return { signature: value, type: 'secp256k1' } as never

  const type = getType(value)

  if (type === 'multisig') {
    const multisig = value as Multisig & {
      genesisConfig?: MultisigConfig.Config | undefined
      init?: MultisigConfig.Config | boolean | undefined
    }
    const { genesisConfig, init, ...rest } = multisig
    // Derive `account` from `genesisConfig` when not provided explicitly.
    const account = (() => {
      if (rest.account) return rest.account
      if (genesisConfig) return MultisigConfig.getAddress(genesisConfig)
      return rest.account
    })()
    // `init: true` opts into bootstrap using the supplied `genesisConfig`.
    // Otherwise, `init` is treated as the explicit bootstrap config (or
    // omitted).
    const initSource = init === true ? genesisConfig : init || undefined
    return {
      ...rest,
      account,
      signatures: rest.signatures.map((signature) => from(signature)),
      // Normalize the bootstrap config (sorts owners, defaults the salt) so the
      // in-memory envelope matches what `deserialize` reconstructs.
      ...(initSource ? { init: MultisigConfig.from(initSource) } : {}),
      type,
    } as never
  }

  return {
    ...value,
    ...(type === 'p256' ? { prehash: (value as P256).prehash } : {}),
    ...(type === 'keychain'
      ? {
          ...(!(
            typeof value === 'object' &&
            value !== null &&
            'version' in value &&
            value.version
          )
            ? { version: 'v2' }
            : {}),
          ...(!(typeof value === 'object' && 'keyId' in value && value.keyId)
            ? (() => {
                const inner = (value as Keychain).inner
                if (inner.type === 'p256' || inner.type === 'webAuthn')
                  return { keyId: Address.fromPublicKey(inner.publicKey) }
                if (inner.type === 'secp256k1' && options?.payload)
                  return {
                    keyId: Address.fromPublicKey(
                      ox_Secp256k1.recoverPublicKey({
                        payload: options.payload,
                        signature: inner.signature,
                      }),
                    ),
                  }
                return {}
              })()
            : {}),
        }
      : {}),
    type,
  } as never
}

export declare namespace from {
  type Options = {
    /** Payload that was signed. Used to recover `keyId` for keychain envelopes with secp256k1 inner signatures. */
    payload?: Hex.Hex | Bytes.Bytes | undefined
  }

  /**
   * Multisig envelope input variant where `account` is derived from the
   * supplied `genesisConfig`. Pass `init: true` to opt into bootstrap (uses
   * `genesisConfig` as the bootstrap `init`); omit `init` for subsequent
   * (non-bootstrap) transactions.
   */
  type MultisigFromGenesisConfig = {
    type?: 'multisig' | undefined
    genesisConfig: MultisigConfig.Config
    signatures: readonly SignatureEnvelope[]
    init?: MultisigConfig.Config | boolean | undefined
  }

  type Value =
    | UnionPartialBy<SignatureEnvelope, 'prehash' | 'type'>
    | Secp256k1Flat
    | Serialized
    | MultisigFromGenesisConfig

  type ReturnValue<value extends Value> = Compute<
    OneOf<
      value extends Serialized
        ? SignatureEnvelope
        : value extends Secp256k1Flat
          ? Secp256k1
          : value extends MultisigFromGenesisConfig
            ? Multisig
            : IsNarrowable<value, SignatureEnvelope> extends true
              ? SignatureEnvelope
              : Assign<
                  value,
                  {
                    readonly type: GetType<value>
                  } & (GetType<value> extends 'keychain'
                    ? { keyId?: Address.Address | undefined }
                    : {})
                >
    >
  >
}

/**
 * Converts an RPC-formatted signature envelope to a typed signature envelope.
 *
 * @example
 * ```ts twoslash
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * const envelope = SignatureEnvelope.fromRpc({
 *   r: '0x0',
 *   s: '0x0',
 *   yParity: '0x0',
 *   type: 'secp256k1'
 * })
 * ```
 *
 * @param envelope - The RPC signature envelope to convert.
 * @returns The signature envelope with bigint values.
 */
export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
  if (envelope.type === 'secp256k1')
    return {
      signature: Signature.fromRpc(envelope),
      type: 'secp256k1',
    }

  if (envelope.type === 'p256') {
    return {
      prehash: envelope.preHash,
      publicKey: {
        prefix: 4,
        x: Hex.padLeft(envelope.pubKeyX, 32),
        y: Hex.padLeft(envelope.pubKeyY, 32),
      },
      signature: {
        r: Hex.padLeft(envelope.r, 32),
        s: Hex.padLeft(envelope.s, 32),
      },
      type: 'p256',
    }
  }

  if (envelope.type === 'webAuthn') {
    const webauthnData = envelope.webauthnData
    const webauthnDataSize = Hex.size(webauthnData)

    // Parse webauthnData into authenticatorData and clientDataJSON
    let authenticatorData: Hex.Hex | undefined
    let clientDataJSON: string | undefined

    // Try to find the JSON start (clientDataJSON should start with '{')
    for (let split = 37; split < webauthnDataSize; split++) {
      const potentialJson = Hex.toString(Hex.slice(webauthnData, split))
      if (potentialJson.startsWith('{') && potentialJson.endsWith('}')) {
        try {
          JSON.parse(potentialJson)
          authenticatorData = Hex.slice(webauthnData, 0, split)
          clientDataJSON = potentialJson
          break
        } catch {}
      }
    }

    if (!authenticatorData || !clientDataJSON)
      throw new InvalidSerializedError({
        reason:
          'Unable to parse WebAuthn metadata: could not extract valid authenticatorData and clientDataJSON',
        serialized: webauthnData,
      })

    return {
      metadata: {
        authenticatorData,
        clientDataJSON,
      },
      publicKey: {
        prefix: 4,
        x: Hex.padLeft(envelope.pubKeyX, 32),
        y: Hex.padLeft(envelope.pubKeyY, 32),
      },
      signature: {
        r: Hex.padLeft(envelope.r, 32),
        s: Hex.padLeft(envelope.s, 32),
      },
      type: 'webAuthn',
    }
  }

  if (
    envelope.type === 'keychain' ||
    ('userAddress' in envelope && 'signature' in envelope)
  ) {
    const keychain = envelope as KeychainRpc
    return {
      type: 'keychain',
      userAddress: keychain.userAddress,
      inner: fromRpc(keychain.signature),
      ...(keychain.keyId ? { keyId: keychain.keyId } : {}),
      ...(keychain.version ? { version: keychain.version } : {}),
    }
  }

  if (
    (envelope as { type?: string | undefined }).type === 'multisig' ||
    ('signatures' in envelope && ('account' in envelope || 'init' in envelope))
  ) {
    const multisig = envelope as MultisigRpc
    const hasAccount = typeof multisig.account !== 'undefined'
    const hasInit = typeof multisig.init !== 'undefined'
    if (hasAccount === hasInit)
      throw new InvalidMultisigApprovalError({
        reason: 'RPC multisig must contain exactly one of `account` or `init`',
      })
    const init = hasInit
      ? MultisigConfig.from(multisig.init as MultisigConfig.Config)
      : undefined
    const account = init ? MultisigConfig.getAddress(init) : multisig.account
    const result = {
      type: 'multisig',
      account: account as Address.Address,
      signatures: multisig.signatures.map((signature) => fromRpc(signature)),
      ...(init ? { init } : {}),
    } satisfies Multisig
    assert(result)
    return result
  }

  throw new CoercionError({ envelope })
}

export declare namespace fromRpc {
  type ErrorType =
    | assert.ErrorType
    | CoercionError
    | InvalidSerializedError
    | MultisigConfig.getAddress.ErrorType
    | Signature.fromRpc.ErrorType
    | Errors.GlobalErrorType
}

/**
 * Determines the signature type of an envelope.
 *
 * @example
 * ```ts twoslash
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * const type = SignatureEnvelope.getType({
 *   signature: {
 *     r: '0x0000000000000000000000000000000000000000000000000000000000000000',
 *     s: '0x0000000000000000000000000000000000000000000000000000000000000000',
 *     yParity: 0
 *   }
 * })
 * // @log: 'secp256k1'
 * ```
 *
 * @param envelope - The signature envelope to inspect.
 * @returns The signature type ('secp256k1', 'p256', or 'webAuthn').
 * @throws `CoercionError` if the envelope type cannot be determined.
 */
export function getType<
  envelope extends
    | PartialBy<SignatureEnvelope, 'type'>
    | Secp256k1Flat
    | unknown,
>(envelope: envelope): GetType<envelope> {
  if (typeof envelope !== 'object' || envelope === null)
    throw new CoercionError({ envelope })

  if ('type' in envelope && envelope.type) return envelope.type as never

  // Detect secp256k1 signature (backwards compatibility: also support flat structure)
  if (
    'signature' in envelope &&
    !('publicKey' in envelope) &&
    typeof envelope.signature === 'object' &&
    envelope.signature !== null &&
    'r' in envelope.signature &&
    's' in envelope.signature &&
    'yParity' in envelope.signature
  )
    return 'secp256k1' as never

  // Detect secp256k1 signature (flat structure)
  if ('r' in envelope && 's' in envelope && 'yParity' in envelope)
    return 'secp256k1' as never

  // Detect P256 signature
  if (
    'signature' in envelope &&
    'prehash' in envelope &&
    'publicKey' in envelope &&
    typeof envelope.prehash === 'boolean'
  )
    return 'p256' as never

  // Detect WebAuthn signature
  if (
    'signature' in envelope &&
    'metadata' in envelope &&
    'publicKey' in envelope
  )
    return 'webAuthn' as never

  // Detect Keychain signature
  if ('userAddress' in envelope && 'inner' in envelope)
    return 'keychain' as never

  // Detect Multisig signature
  if (
    ('account' in envelope ||
      'genesisConfig' in envelope ||
      'init' in envelope) &&
    'signatures' in envelope
  )
    return 'multisig' as never

  throw new CoercionError({
    envelope,
  })
}

/**
 * Serializes a signature envelope to a hex-encoded string.
 *
 * Wire format:
 * - secp256k1: 65 bytes (no type prefix, for backward compatibility)
 * - P256: `0x01` + r (32) + s (32) + pubKeyX (32) + pubKeyY (32) + prehash (1) = 130 bytes
 * - WebAuthn: `0x02` + webauthnData (variable) + r (32) + s (32) + pubKeyX (32) + pubKeyY (32)
 * - Keychain V1: `0x03` + userAddress (20) + inner signature (recursive)
 * - Keychain V2: `0x04` + userAddress (20) + inner signature (recursive)
 * - Multisig: `0x05` + RLP `[account | init, signatures]`
 *
 * [Signature Types](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
 *
 * @example
 * ```ts twoslash
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * const serialized = SignatureEnvelope.serialize({
 *   signature: {
 *     r: '0x0000000000000000000000000000000000000000000000000000000000000000',
 *     s: '0x0000000000000000000000000000000000000000000000000000000000000000',
 *     yParity: 0
 *   },
 *   type: 'secp256k1'
 * })
 * ```
 *
 * @param envelope - The signature envelope to serialize.
 * @returns The hex-encoded serialized signature.
 * @throws `CoercionError` if the envelope cannot be serialized.
 */
export function serialize(
  envelope: UnionPartialBy<SignatureEnvelope, 'prehash'>,
  options: serialize.Options = {},
): Serialized {
  const type = getType(envelope)

  // Backward compatibility: no type identifier for secp256k1
  if (type === 'secp256k1') {
    const secp256k1 = envelope as Secp256k1
    return Hex.concat(
      Signature.toHex(secp256k1.signature),
      options.magic ? magicBytes : '0x',
    )
  }

  if (type === 'p256') {
    const p256 = envelope as P256
    // Format: 1 byte (type) + 32 (r) + 32 (s) + 32 (pubKeyX) + 32 (pubKeyY) + 1 (prehash)
    return Hex.concat(
      serializedP256Type,
      p256.signature.r,
      p256.signature.s,
      p256.publicKey.x,
      p256.publicKey.y as Hex.Hex,
      Hex.fromNumber(p256.prehash ? 1 : 0, { size: 1 }),
      options.magic ? magicBytes : '0x',
    )
  }

  if (type === 'webAuthn') {
    const webauthn = envelope as WebAuthn
    // Format: 1 byte (type) + variable (authenticatorData || clientDataJSON) + 32 (r) + 32 (s) + 32 (pubKeyX) + 32 (pubKeyY)
    const webauthnData = Hex.concat(
      webauthn.metadata.authenticatorData,
      Hex.fromString(webauthn.metadata.clientDataJSON),
    )

    return Hex.concat(
      serializedWebAuthnType,
      webauthnData,
      webauthn.signature.r,
      webauthn.signature.s,
      webauthn.publicKey.x,
      webauthn.publicKey.y as Hex.Hex,
      options.magic ? magicBytes : '0x',
    )
  }

  if (type === 'keychain') {
    const keychain = envelope as Keychain
    const keychainTypeId =
      keychain.version === 'v1'
        ? serializedKeychainType
        : serializedKeychainV2Type
    return Hex.concat(
      keychainTypeId,
      keychain.userAddress,
      serialize(keychain.inner),
      options.magic ? magicBytes : '0x',
    )
  }

  if (type === 'multisig') {
    const multisig = envelope as Multisig
    assert(multisig)
    // The first field is either the initialized account or the bootstrap init
    // config. Each owner approval is an encoded signature.
    return Hex.concat(
      serializedMultisigType,
      Rlp.fromHex([
        multisig.init
          ? MultisigConfig.toTuple(multisig.init)
          : multisig.account,
        multisig.signatures.map((signature) => serialize(signature)),
      ]),
      options.magic ? magicBytes : '0x',
    )
  }

  throw new CoercionError({ envelope })
}

export declare namespace serialize {
  type Options = {
    /**
     * Whether to serialize the signature envelope with the Tempo magic identifier.
     * This is useful for being able to distinguish between Tempo and non-Tempo (e.g. ERC-1271) signatures.
     */
    magic?: boolean | undefined
  }

  type ErrorType =
    | assert.ErrorType
    | CoercionError
    | Hex.concat.ErrorType
    | Hex.fromNumber.ErrorType
    | Hex.fromString.ErrorType
    | Rlp.fromHex.ErrorType
    | Signature.toHex.ErrorType
    | Errors.GlobalErrorType
}

/**
 * Orders native multisig owner approvals into the strictly-ascending
 * recovered-owner order the Tempo node requires for the multisig `signatures`
 * array (the node enforces "recovered owners must be strictly ascending").
 *
 * Each approval is signed over the multisig owner approval digest
 * ({@link ox#MultisigConfig.(getSignPayload:function)}), so the signer of
 * every approval is recovered against that digest and the list is sorted by the
 * recovered owner address. Works for any owner key type (secp256k1, p256,
 * webAuthn).
 *
 * Config updates never change `account`, so the genesis config is the correct
 * input even for post-update transactions.
 *
 * @example
 * ```ts twoslash
 * import { Secp256k1 } from 'ox'
 * import {
 *   MultisigConfig,
 *   SignatureEnvelope,
 *   TxEnvelopeTempo
 * } from 'ox/tempo'
 *
 * const genesisConfig = MultisigConfig.from({
 *   threshold: 2,
 *   owners: [
 *     {
 *       owner: '0x1111111111111111111111111111111111111111',
 *       weight: 1
 *     },
 *     {
 *       owner: '0x2222222222222222222222222222222222222222',
 *       weight: 1
 *     }
 *   ]
 * })
 *
 * const tx = TxEnvelopeTempo.from({ chainId: 1, calls: [] })
 * const payload = TxEnvelopeTempo.getSignPayload(tx)
 *
 * const privateKeys = [
 *   Secp256k1.randomPrivateKey(),
 *   Secp256k1.randomPrivateKey()
 * ]
 * const digest = MultisigConfig.getSignPayload({
 *   payload,
 *   genesisConfig
 * })
 * const signatures = privateKeys.map((privateKey) =>
 *   SignatureEnvelope.from(
 *     Secp256k1.sign({ payload: digest, privateKey })
 *   )
 * )
 *
 * const ordered = SignatureEnvelope.sortMultisigApprovals({
 *   // [!code focus]
 *   genesisConfig, // [!code focus]
 *   payload, // [!code focus]
 *   signatures // [!code focus]
 * }) // [!code focus]
 * ```
 *
 * @param value - The approval ordering parameters.
 * @returns The owner approvals ordered ascending by recovered owner address.
 */
export function sortMultisigApprovals(
  value: sortMultisigApprovals.Value,
): readonly SignatureEnvelope[] {
  const { payload, signatures } = value
  const digest = MultisigConfig.getSignPayload(
    'genesisConfig' in value && value.genesisConfig
      ? { payload, genesisConfig: value.genesisConfig }
      : { payload, account: (value as { account: Address.Address }).account },
  )
  // Recover each signer once (decorate–sort–undecorate) rather than inside the
  // comparator.
  return signatures
    .map((signature) => ({
      key: Hex.toBigInt(extractAddress({ payload: digest, signature })),
      signature,
    }))
    .sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
    .map((entry) => entry.signature)
}

export declare namespace sortMultisigApprovals {
  type Value = {
    /** The inner transaction sign payload (`tx.signature_hash()`). */
    payload: Hex.Hex | Bytes.Bytes
    /** The owner approvals to order. */
    signatures: readonly SignatureEnvelope[]
  } & OneOf<
    | {
        /** The native multisig account address. */
        account: Address.Address
      }
    | {
        /**
         * The initial multisig config (the bootstrap config that derived the
         * permanent `account`). Used to derive the account automatically.
         */
        genesisConfig: MultisigConfig.Config
      }
  >

  type ErrorType =
    | MultisigConfig.getSignPayload.ErrorType
    | extractAddress.ErrorType
    | Errors.GlobalErrorType
}

/**
 * Converts a signature envelope to RPC format.
 *
 * @example
 * ```ts twoslash
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * const rpc = SignatureEnvelope.toRpc({
 *   signature: {
 *     r: '0x0000000000000000000000000000000000000000000000000000000000000000',
 *     s: '0x0000000000000000000000000000000000000000000000000000000000000000',
 *     yParity: 0
 *   },
 *   type: 'secp256k1'
 * })
 * ```
 *
 * @param envelope - The signature envelope to convert.
 * @returns The RPC signature envelope with hex values.
 */
export function toRpc<const envelope extends toRpc.Input>(
  envelope: envelope,
): toRpc.ReturnType<envelope> {
  const type = getType(envelope)

  if (type === 'secp256k1') {
    const secp256k1 = envelope as Secp256k1
    return {
      ...Signature.toRpc(secp256k1.signature),
      type: 'secp256k1',
    } as never
  }

  if (type === 'p256') {
    const p256 = envelope as P256
    return {
      preHash: p256.prehash,
      pubKeyX: p256.publicKey.x,
      pubKeyY: p256.publicKey.y as Hex.Hex,
      r: p256.signature.r,
      s: p256.signature.s,
      type: 'p256',
    } as never
  }

  if (type === 'webAuthn') {
    const webauthn = envelope as WebAuthn
    const webauthnData = Hex.concat(
      webauthn.metadata.authenticatorData,
      Hex.fromString(webauthn.metadata.clientDataJSON),
    )

    return {
      pubKeyX: webauthn.publicKey.x,
      pubKeyY: webauthn.publicKey.y as Hex.Hex,
      r: webauthn.signature.r,
      s: webauthn.signature.s,
      type: 'webAuthn',
      webauthnData,
    } as never
  }

  if (type === 'keychain') {
    const keychain = envelope as Keychain
    return {
      type: 'keychain',
      userAddress: keychain.userAddress,
      signature: toRpc(keychain.inner),
      ...(keychain.keyId ? { keyId: keychain.keyId } : {}),
      ...(keychain.version ? { version: keychain.version } : {}),
    } as never
  }

  if (type === 'multisig') {
    const multisig = envelope as Multisig
    assert(multisig)
    const signatures = multisig.signatures.map((signature) => toRpc(signature))
    if (multisig.init) {
      const init = {
        ...multisig.init,
        salt: multisig.init.salt ?? MultisigConfig.zeroSalt,
        threshold: Number(multisig.init.threshold),
        owners: multisig.init.owners.map((owner) => ({
          ...owner,
          weight: Number(owner.weight),
        })),
      }
      return {
        init,
        signatures,
      } as never
    }
    return {
      account: multisig.account,
      signatures,
    } as never
  }

  throw new CoercionError({ envelope })
}

export declare namespace toRpc {
  /** Numberish input accepted by {@link ox#SignatureEnvelope.(toRpc:function)}. */
  type Input = SignatureEnvelope<Hex.Hex | number>

  /** RPC signature envelope inferred from the input type. */
  type ReturnType<envelope extends Input = Input> =
    GetType<envelope> extends 'secp256k1'
      ? Secp256k1Rpc
      : GetType<envelope> extends 'p256'
        ? P256Rpc
        : GetType<envelope> extends 'webAuthn'
          ? WebAuthnRpc
          : GetType<envelope> extends 'keychain'
            ? KeychainRpc
            : GetType<envelope> extends 'multisig'
              ? MultisigRpc
              : SignatureEnvelopeRpc

  type ErrorType =
    | assert.ErrorType
    | CoercionError
    | Signature.toRpc.ErrorType
    | Errors.GlobalErrorType
}

/**
 * Validates a signature envelope. Returns `true` if the envelope is valid, `false` otherwise.
 *
 * @example
 * ```ts twoslash
 * import { SignatureEnvelope } from 'ox/tempo'
 *
 * const valid = SignatureEnvelope.validate({
 *   signature: {
 *     r: '0x0000000000000000000000000000000000000000000000000000000000000000',
 *     s: '0x0000000000000000000000000000000000000000000000000000000000000000',
 *     yParity: 0
 *   },
 *   type: 'secp256k1'
 * })
 * // @log: true
 * ```
 *
 * @param envelope - The signature envelope to validate.
 * @returns `true` if valid, `false` otherwise.
 */
export function validate(
  envelope: PartialBy<SignatureEnvelope, 'type'>,
): boolean {
  try {
    assert(envelope)
    return true
  } catch {
    return false
  }
}

export declare namespace validate {
  type ErrorType = Errors.GlobalErrorType
}

/**
 * Verifies a signature envelope against a digest/payload.
 *
 * Supports `secp256k1`, `p256`, and `webAuthn` signature types.
 *
 * :::warning
 * `keychain` signatures are not supported and will throw an error.
 * :::
 *
 * @example
 * ### Secp256k1
 *
 * ```ts twoslash
 * import { SignatureEnvelope } from 'ox/tempo'
 * import { Secp256k1 } from 'ox'
 *
 * const privateKey = Secp256k1.randomPrivateKey()
 * const publicKey = Secp256k1.getPublicKey({ privateKey })
 * const payload = '0xdeadbeef'
 *
 * const signature = Secp256k1.sign({ payload, privateKey })
 * const envelope = SignatureEnvelope.from(signature)
 *
 * const valid = SignatureEnvelope.verify(envelope, {
 *   payload,
 *   publicKey
 * })
 * // @log: true
 * ```
 *
 * @example
 * ### P256
 *
 * For P256 signatures, the `address` or `publicKey` must match the embedded
 * public key in the signature envelope.
 *
 * ```ts twoslash
 * import { SignatureEnvelope } from 'ox/tempo'
 * import { P256 } from 'ox'
 *
 * const privateKey = P256.randomPrivateKey()
 * const publicKey = P256.getPublicKey({ privateKey })
 * const payload = '0xdeadbeef'
 *
 * const signature = P256.sign({ payload, privateKey })
 * const envelope = SignatureEnvelope.from({
 *   prehash: false,
 *   publicKey,
 *   signature
 * })
 *
 * const valid = SignatureEnvelope.verify(envelope, {
 *   payload,
 *   publicKey
 * })
 * // @log: true
 * ```
 *
 * @example
 * ### WebCryptoP256
 *
 * ```ts twoslash
 * import { SignatureEnvelope } from 'ox/tempo'
 * import { WebCryptoP256 } from 'ox'
 *
 * const { privateKey, publicKey } =
 *   await WebCryptoP256.createKeyPair()
 * const payload = '0xdeadbeef'
 *
 * const signature = await WebCryptoP256.sign({
 *   payload,
 *   privateKey
 * })
 * const envelope = SignatureEnvelope.from({
 *   prehash: true,
 *   publicKey,
 *   signature
 * })
 *
 * const valid = SignatureEnvelope.verify(envelope, {
 *   payload,
 *   publicKey
 * })
 * // @log: true
 * ```
 *
 * @example
 * ### WebAuthnP256
 *
 * ```ts twoslash
 * import { SignatureEnvelope } from 'ox/tempo'
 * import { WebAuthnP256 } from 'ox'
 *
 * const credential = await WebAuthnP256.createCredential({
 *   name: 'Example'
 * })
 * const payload = '0xdeadbeef'
 *
 * const { metadata, signature } = await WebAuthnP256.sign({
 *   challenge: payload,
 *   credentialId: credential.id
 * })
 * const envelope = SignatureEnvelope.from({
 *   metadata,
 *   signature,
 *   publicKey: credential.publicKey
 * })
 *
 * const valid = SignatureEnvelope.verify(envelope, {
 *   payload,
 *   publicKey: credential.publicKey
 * })
 * // @log: true
 * ```
 *
 * @param parameters - Verification parameters.
 * @returns `true` if the signature is valid, `false` otherwise.
 */
export function verify(
  signature: SignatureEnvelope,
  parameters: verify.Parameters,
): boolean {
  const { payload } = parameters

  const address = (() => {
    if (parameters.address) return parameters.address
    if (parameters.publicKey) return Address.fromPublicKey(parameters.publicKey)
    return undefined
  })()
  if (!address) return false

  const envelope = from(signature)

  if (envelope.type === 'secp256k1') {
    if (!address) return false
    return ox_Secp256k1.verify({
      address,
      payload,
      signature: envelope.signature,
    })
  }

  if (envelope.type === 'p256') {
    const envelopeAddress = Address.fromPublicKey(envelope.publicKey)
    if (!Address.isEqual(envelopeAddress, address)) return false
    return ox_P256.verify({
      hash: envelope.prehash,
      publicKey: envelope.publicKey,
      payload,
      signature: envelope.signature,
    })
  }

  if (envelope.type === 'webAuthn') {
    const envelopeAddress = Address.fromPublicKey(envelope.publicKey)
    if (!Address.isEqual(envelopeAddress, address)) return false
    return ox_WebAuthnP256.verify({
      challenge: Hex.from(payload),
      metadata: envelope.metadata,
      publicKey: envelope.publicKey,
      signature: envelope.signature,
    })
  }

  throw new VerificationError(
    `Unable to verify signature envelope of type "${envelope.type}".`,
  )
}

export declare namespace verify {
  type Parameters = {
    /** Payload that was signed. */
    payload: Hex.Hex | Bytes.Bytes
  } & OneOf<
    | {
        /** Public key that signed the payload. */
        publicKey: PublicKey.PublicKey
      }
    | {
        /** Address that signed the payload. */
        address: Address.Address
      }
  >
}

/**
 * Error thrown when a signature envelope cannot be coerced to a valid type.
 */
export class CoercionError extends Errors.BaseError {
  override readonly name = 'SignatureEnvelope.CoercionError'
  constructor({ envelope }: { envelope: unknown }) {
    super(
      `Unable to coerce value (\`${Json.stringify(envelope)}\`) to a valid signature envelope.`,
    )
  }
}

/**
 * Error thrown when a signature envelope is missing required properties.
 */
export class MissingPropertiesError extends Errors.BaseError {
  override readonly name = 'SignatureEnvelope.MissingPropertiesError'
  constructor({
    envelope,
    missing,
    type,
  }: {
    envelope: unknown
    missing: string[]
    type: Type | 'keychain' | 'multisig'
  }) {
    super(
      `Signature envelope of type "${type}" is missing required properties: ${missing.map((m) => `\`${m}\``).join(', ')}.\n\nProvided: ${Json.stringify(envelope)}`,
    )
  }
}

/**
 * Error thrown when a serialized signature envelope cannot be deserialized.
 */
export class InvalidSerializedError extends Errors.BaseError {
  override readonly name = 'SignatureEnvelope.InvalidSerializedError'
  constructor({ reason, serialized }: { reason: string; serialized: Hex.Hex }) {
    super(`Unable to deserialize signature envelope: ${reason}`, {
      metaMessages: [`Serialized: ${serialized}`],
    })
  }
}

/**
 * Error thrown when a native multisig owner approval is invalid.
 */
export class InvalidMultisigApprovalError extends Errors.BaseError {
  override readonly name = 'SignatureEnvelope.InvalidMultisigApprovalError'
  constructor({ reason }: { reason: string }) {
    super(`Invalid native multisig owner approval: ${reason}.`)
  }
}

/**
 * Error thrown when a signature envelope fails to verify.
 */
export class VerificationError extends Errors.BaseError {
  override readonly name = 'SignatureEnvelope.VerificationError'
}
