Skip to content

EIP-5564 Implementation

This document details SIP Protocol’s implementation of EIP-5564 (Stealth Addresses) and ERC-6538 (Stealth Meta-Address Registry), including technical decisions, security considerations, and integration guides.

EIP-5564 defines a standardized approach to stealth addresses on Ethereum. SIP Protocol implements the secp256k1 scheme (scheme id 1) for full compatibility with the Ethereum ecosystem.

PropertySIP Implementation
Scheme1 (secp256k1)
Curvesecp256k1
Key formatCompressed (33 bytes)
Hash functionSHA-256
View tagFirst byte of shared secret hash
Address derivationKeccak-256 of uncompressed public key

A stealth meta-address encodes two public keys needed to generate stealth addresses:

sip:<chain>:<spendingKey>:<viewingKey>
ComponentSizeDescription
chainvariableChain identifier (e.g., ethereum, polygon)
spendingKey33 bytesCompressed secp256k1 public key for spending
viewingKey33 bytesCompressed secp256k1 public key for scanning

Compressed secp256k1 public keys are 33 bytes:

  • First byte: 0x02 (y is even) or 0x03 (y is odd)
  • Remaining 32 bytes: x-coordinate
// Valid compressed keys
'0x02' + 64 hex chars // 33 bytes total, y even
'0x03' + 64 hex chars // 33 bytes total, y odd
// Invalid formats
'0x04' + 128 hex chars // Uncompressed (65 bytes)
'0x' + 64 hex chars // Missing prefix (32 bytes)
import { generateStealthMetaAddress } from '@sip-protocol/sdk'
const { metaAddress, spendingPrivateKey, viewingPrivateKey } =
generateStealthMetaAddress('ethereum')
// metaAddress.spendingKey: 0x02abc...
// metaAddress.viewingKey: 0x03def...
import {
encodeStealthMetaAddress,
decodeStealthMetaAddress,
} from '@sip-protocol/sdk'
// Encode for sharing
const encoded = encodeStealthMetaAddress(metaAddress)
// "sip:ethereum:0x02abc...:0x03def..."
// Decode from string
const decoded = decodeStealthMetaAddress(encoded)

The protocol follows EIP-5564’s cryptographic scheme:

Given:

  • Recipient spending public key: K
  • Recipient viewing public key: V
  • Ephemeral private key: r (randomly generated)
  • Ephemeral public key: R = r * G

The stealth address is derived as:

1. Compute shared secret point: S = r * K
2. Hash the shared secret: h = SHA256(S)
3. Derive stealth public key: P = V + h * G
4. Derive Ethereum address: addr = keccak256(decompress(P))[12:32]
5. View tag: vt = h[0]
import { generateStealthAddress } from '@sip-protocol/sdk'
const { stealthAddress, sharedSecret } = generateStealthAddress(recipientMetaAddress)
// stealthAddress.address: 0x02xyz... (compressed public key)
// stealthAddress.ephemeralPublicKey: 0x03abc... (must be published)
// stealthAddress.viewTag: 42 (0-255, first byte of hash)

Converting the stealth public key to an Ethereum address:

import { publicKeyToEthAddress } from '@sip-protocol/sdk'
// Compressed public key → Ethereum address
const ethAddress = publicKeyToEthAddress(stealthAddress.address)
// "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"

Steps:

  1. Decompress the public key (33 bytes → 65 bytes)
  2. Remove the 04 prefix (65 → 64 bytes)
  3. Keccak-256 hash (64 → 32 bytes)
  4. Take last 20 bytes
  5. Apply EIP-55 checksum

The ephemeral key is generated per-payment and must be published for the recipient to claim funds:

// Sender generates ephemeral key
const { stealthAddress } = generateStealthAddress(recipientMetaAddress)
// Ephemeral public key must be published (announcer contract or off-chain)
console.log('Publish:', stealthAddress.ephemeralPublicKey)

Sender computes:

S = r * K (ephemeral private × spending public)

Recipient computes:

S = k * R (spending private × ephemeral public)

Both arrive at the same point due to ECDH properties.

The view tag enables efficient scanning by allowing quick rejection of non-matching payments.

  1. View tag = first byte of SHA256(shared_secret_point)
  2. Range: 0-255
  3. Expected false positive rate: ~1/256 (0.39%)
import { checkViewTag } from '@sip-protocol/sdk'
// For each potential payment
for (const payment of potentialPayments) {
// Quick check using view tag
const viewTagMatch = checkViewTag(
payment.ephemeralPublicKey,
mySpendingPrivateKey,
payment.viewTag
)
if (!viewTagMatch) {
continue // Skip - not our payment (~99.6% of payments)
}
// View tag matches - do full computation
const stealthPrivateKey = deriveStealthPrivateKey(...)
const computedAddress = privateKeyToAddress(stealthPrivateKey)
if (computedAddress === payment.toAddress) {
// This is our payment!
console.log('Found payment:', payment)
}
}

Without view tag: O(n × full_computation) With view tag: O(n × (cheap_check + 0.004 × full_computation))

For 1 million transactions, this reduces full computations from 1M to ~4K.

EIP-5564 defines an announcer contract for publishing ephemeral keys on-chain.

interface IERC5564Announcer {
event Announcement(
uint256 indexed schemeId,
address indexed stealthAddress,
address indexed caller,
bytes ephemeralPubKey,
bytes metadata
);
function announce(
uint256 schemeId,
address stealthAddress,
bytes memory ephemeralPubKey,
bytes memory metadata
) external;
}
import { createAnnouncerClient } from '@sip-protocol/sdk'
const announcer = createAnnouncerClient({
address: '0x...', // EIP-5564 announcer contract
provider: ethersProvider,
})
// After sending to stealth address
await announcer.announce({
schemeId: 1, // secp256k1
stealthAddress: ethAddress,
ephemeralPubKey: stealthAddress.ephemeralPublicKey,
metadata: '0x', // Optional: encrypted note
})
// Listen for announcements
const announcements = await announcer.getAnnouncements({
fromBlock: 19000000,
schemeId: 1,
})
// Filter using view tag
const myPayments = announcements.filter(a =>
checkViewTag(a.ephemeralPubKey, mySpendingPrivateKey, a.viewTag)
)

ERC-6538 defines a registry for storing stealth meta-addresses on-chain.

interface IERC6538Registry {
event StealthMetaAddressSet(
address indexed registrant,
uint256 indexed schemeId,
bytes stealthMetaAddress
);
function registerKeys(
uint256 schemeId,
bytes memory stealthMetaAddress
) external;
function registerKeysOnBehalf(
address registrant,
uint256 schemeId,
bytes memory signature,
bytes memory stealthMetaAddress
) external;
function stealthMetaAddressOf(
address registrant,
uint256 schemeId
) external view returns (bytes memory);
}
import { createRegistryClient } from '@sip-protocol/sdk'
const registry = createRegistryClient({
address: '0x...', // ERC-6538 registry
signer: ethers.signer,
})
// Register your meta-address
await registry.registerKeys({
schemeId: 1, // secp256k1
stealthMetaAddress: encodedMetaAddress,
})
// Look up recipient's meta-address
const recipientMeta = await registry.stealthMetaAddressOf(
recipientEthAddress,
1 // schemeId
)
if (!recipientMeta) {
throw new Error('Recipient has no registered stealth meta-address')
}
KeySensitivityStorage Recommendation
Spending privateCriticalHardware wallet, encrypted vault
Viewing privateHighSecure storage, may share with auditors
Ephemeral privateTemporarySecurely wipe after use
ThreatMitigation
Spending key compromiseFull loss of funds - treat as seed phrase
Viewing key compromisePrivacy loss only - can regenerate
Ephemeral key reuseNever reuse - always generate fresh
View tag collision~0.4% false positives, full derivation required
  1. Key generation: Use cryptographically secure random number generator
  2. Key storage: Encrypt at rest, never log or transmit unencrypted
  3. Ephemeral keys: Generate fresh for each payment, securely wipe after
  4. View tags: Always check before full computation
  5. Registry: Verify on-chain meta-address matches expected format

SIP SDK uses secure memory practices:

import { secureWipe } from '@sip-protocol/sdk'
const privateKey = generatePrivateKey()
try {
// Use key
} finally {
secureWipe(privateKey) // Overwrite memory
}
FeatureSIP ProtocolUmbraFluidkey
Schemesecp256k1secp256k1secp256k1
View tagYes (1 byte)Yes (1 byte)Yes (1 byte)
Multi-chainYesEthereum onlyEthereum L2s
ERC-6538PlannedNoPartial
ComplianceViewing keysNoNo
Cross-chainNEAR IntentsNoNo
  1. Cross-chain: Works across 15+ chains via NEAR Intents
  2. Compliance: Viewing keys for selective disclosure
  3. Multi-curve: secp256k1 (EVM) and ed25519 (Solana/NEAR)
  4. SDK: Comprehensive TypeScript SDK with React hooks

SIP Protocol is fully compatible with EIP-5564 scheme 1, with these extensions:

ExtensionDescription
Ed25519 supportAdditional scheme for Solana/NEAR (not on-chain)
Cross-chainSettlement via NEAR Intents
Viewing keysAdditional layer for compliance
SIP encodingsip:chain:spending:viewing format

Before deploying to production:

  • Private keys never logged or transmitted
  • Ephemeral keys generated fresh per payment
  • Secure random number generator used
  • Keys wiped from memory after use
  • View tag checked before full derivation
  • Registry contract address verified
  • Announcer contract address verified
  • Chain IDs validated
  • Input validation on all parameters