Works best when
- Recipient is a constrained signer that cannot evaluate ECDH over the curve in real time
- Sender does not need to know the recipient's identity (settlement contract just transfers to a supplied destination)
- Recipient generates destinations rarely enough that re-derivation cost is acceptable
Avoid when
- Auditor needs view-only access to incoming transfers (no view-key split; use EIP-5564)
- Sender wants to push to a recipient who has not pre-derived a destination (use stealth addresses)
- Long-lived recipient secret cannot be tolerated under the threat model
I2I vs I2U — context differences
Institution to institution
I2ICounterparty payment account derives a fresh destination per settlement event under a long-lived treasury secret.
Institution to end user
I2UUser wallet derives a fresh receive destination per receive context under a long-lived recipient secret.
Post-quantum exposure
Risk · high- Vector
- secp256k1 keys broken by Shor. HNDL is high: address-to-recipient mapping is preserved on-chain forever.
- Mitigation
- Migrate the curve and the recipient-side derivation to a post-quantum primitive
Components
- Long-lived recipient secret: 32 bytes provisioned at enrollment; held on a constrained device.
- PRF: HMAC-SHA256 (universal in conservative crypto APIs). Any PRF with 256-bit output suffices.
- Curve operation: secp256k1 scalar multiplication. Performed by the recipient or by an auxiliary device that learns nothing else; need not happen on the constrained device itself if it lacks the cycles.
- Address hash: keccak256, producing the 20-byte Ethereum address from the public key.
Protocol
The construction is a PRF-derived deterministic key per (recipientSecret, context) pair:
- recipient
derivedScalar = HMAC-SHA256(recipientSecret, DOMAIN_TAG || context...). - recipient
derivedPrivkey = derivedScalarused as a secp256k1 scalar in[1, n-1]; on rare>= noutputs, RFC 6979-style reject-and-rehash. - recipient
derivedPubkey = derivedPrivkey * G(secp256k1 scalar multiplication). - recipient
destination = keccak256(derivedPubkey)[-20:]. - sender Transfer funds to
destination(recipient supplied it under their authority). - recipient Spend from
destinationlater by re-derivingderivedPrivkeyfrom the same(recipientSecret, context).
DOMAIN_TAG MUST be a domain-separated, application-specific constant. context MUST include all parameters that distinguish destinations within the recipient's lifetime (e.g., event identifier, contract address, sequence number).
Guarantees & threat model
- Unlinkability against external observers: HMAC-SHA256 is a PRF. Without the recipient secret, two destinations look uniformly random and uncorrelated. Address-space collisions are bounded by birthday probability over 160-bit addresses (about 2^-80 per pair).
- Spendability: the recipient (with the secret and the public context) re-derives
derivedPrivkeyexactly. No chain scanning, no published view key. - Non-interactivity: the sender does not need a view pubkey, ephemeral keypair, or per-recipient state.
- Threat model: adversary observes the chain and may compromise non-recipient parties. Out of scope: device seizure (long-lived secret residual); post-quantum adversary (recoverable curve).
Trade-offs
- No view / spend split. Whoever holds the recipient secret both generates and spends. EIP-5564 supports a view-only auditor; this pattern does not. Audit access requires sharing the secret (full spend authority) or a separate disclosure mechanism.
- Long-lived recipient secret. Held on the device for its full lifetime. Device seizure exposes all past and future destinations. Mitigations are operational (frequent re-enrollment).
- No detection of unsolicited inbound transfers. Unlike EIP-5564 chain-scanning, the recipient knows about transfers only to destinations they themselves derived.
- Constrained-device limitations. The curve operation and keccak256 are not always available on conservative crypto APIs. Some deployments offload steps 3 and 4 to a companion device.
- Sloppy notation in spec writeups. The shorthand
destination = truncate(HMAC(secret, context))is broken if read literally: a 20-byte truncation produces an unspendable address. The four-step construction above is required for spendability. Audit production code for the full path.
Example
A privacy-preserving payroll system. Each employee's wallet holds a long-lived recipient secret. For each monthly payroll cycle, the wallet derives a fresh receive address via HMAC-SHA256 over (recipientSecret, monthIdentifier, employerId), then computes the secp256k1 public key and the keccak256 address. The employee provides this address to the employer's payroll system; the employer disburses salary to the address. Observers see a fresh address each month with no public linkage between an employee's monthly receipts. The wallet later re-derives the private key from the same secret and month identifier to spend.