hip-0087

HIP-87: PQ Permit (replaces EIP-2612). Status Active. Hanzo's own standard — read this before implementing against it.

HIP-0087: PQ Permit (replaces EIP-2612)

Abstract

HIP-0087 specifies the post-quantum replacement for EIP-2612 permit. A PQ Permit is a typed off-chain authorization signed by the token owner's ML-DSA-65 key over a TupleHash256 transcript bound by the cust string PERMIT-V1 (SP 800-185). The transcript commits to (profile_id, chain_id, verifying_contract, owner_account_id, spender_account_id, value, nonce, deadline). A contract verifies the permit via the Z-Chain auth precompile (HIP-0104) or directly via the ML-DSA.Verify precompile. The replay protection follows EIP-2612's per-owner nonce pattern. The whole flow is profile-gated; a strict-PQ chain refuses any classical permit.

Specification

Canonical reference: luxfi/consensus/protocol/auth/permit.go (auth-pq-surface branch).

PermitMessage {
    version              uint8    = 0x01
    profile_id           uint8
    identity_scheme_id   uint8    = 0x42
    hash_suite_id        uint8    = 0x01
    chain_id             uint64
    verifying_contract   [20]byte // EVM-form
    owner_account_id     [48]byte
    spender_account_id   [48]byte
    value                [32]byte // u256 big-endian
    nonce                uint64
    deadline             uint64
}

transcript = TupleHash256(
    "PERMIT-V1",
    [ version, profile_id, identity_scheme_id, hash_suite_id,
      chain_id_be8, verifying_contract, owner_account_id,
      spender_account_id, value, nonce_be8, deadline_be8 ],
    384
)

signature = ML-DSA.Sign(owner_account_key, transcript)

The owner publishes (PermitMessage, owner_pubkey, signature). The contract calls a thin verifier (either the Z-Chain proof precompile in HIP-0104 or a direct ML-DSA.Verify precompile at address 0x14) and on success increments the owner's nonce and updates allowance[owner][spender] = value.

Acceptance:

  1. profile_id matches chain pin; otherwise reject.
  2. chain_id matches.
  3. verifying_contract == msg.sender.
  4. now <= deadline.
  5. nonce == nonces[owner].
  6. AccountID == SHA3-384("LUX-ACCOUNT-V1" || owner_pubkey).
  7. ML-DSA.Verify(owner_pubkey, transcript, signature) == true.

Failure of any check is a hard revert; no recovery.

Backwards compatibility

None. EIP-2612 permits are refused under strict-PQ. Token contracts deployed on strict-PQ chains expose only the PQ Permit interface; contracts that need both expose two separate methods and operators disable the classical method on profile activation.

Reference implementation

luxfi/consensus/protocol/auth/permit.go (auth-pq-surface). EVM precompile binding: luxfi/coreth/core/vm/contracts_pq.go. KAT vectors: luxfi/consensus/protocol/auth/testdata/permit_v1.json.

Security considerations

deadline bounds the permit's validity; off-chain leakage past the deadline is harmless. Per-owner nonce prevents replay within validity window. Cross-chain replay prevented by chain_id. Cross-contract replay prevented by verifying_contract. Cross-profile replay prevented by profile_id. ML-DSA-65 signature size (~3309 B) makes the permit unsuitable for tightly gas-constrained chains but well within Lux gas budget; profile activation should be accompanied by gas-schedule update (HIP-0104 §"Gas schedule").

References

Copyright

CC0.