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.
Overview
Section titled “Overview”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.
Key Properties
Section titled “Key Properties”| Property | SIP Implementation |
|---|---|
| Scheme | 1 (secp256k1) |
| Curve | secp256k1 |
| Key format | Compressed (33 bytes) |
| Hash function | SHA-256 |
| View tag | First byte of shared secret hash |
| Address derivation | Keccak-256 of uncompressed public key |
Stealth Meta-Address Format
Section titled “Stealth Meta-Address Format”A stealth meta-address encodes two public keys needed to generate stealth addresses:
sip:<chain>:<spendingKey>:<viewingKey>Components
Section titled “Components”| Component | Size | Description |
|---|---|---|
chain | variable | Chain identifier (e.g., ethereum, polygon) |
spendingKey | 33 bytes | Compressed secp256k1 public key for spending |
viewingKey | 33 bytes | Compressed secp256k1 public key for scanning |
Key Format
Section titled “Key Format”Compressed secp256k1 public keys are 33 bytes:
- First byte:
0x02(y is even) or0x03(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)Generation
Section titled “Generation”import { generateStealthMetaAddress } from '@sip-protocol/sdk'
const { metaAddress, spendingPrivateKey, viewingPrivateKey } = generateStealthMetaAddress('ethereum')
// metaAddress.spendingKey: 0x02abc...// metaAddress.viewingKey: 0x03def...Encoding/Decoding
Section titled “Encoding/Decoding”import { encodeStealthMetaAddress, decodeStealthMetaAddress,} from '@sip-protocol/sdk'
// Encode for sharingconst encoded = encodeStealthMetaAddress(metaAddress)// "sip:ethereum:0x02abc...:0x03def..."
// Decode from stringconst decoded = decodeStealthMetaAddress(encoded)Stealth Address Generation
Section titled “Stealth Address Generation”The protocol follows EIP-5564’s cryptographic scheme:
Mathematical Description
Section titled “Mathematical Description”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 (canonical EIP-5564 — shared secret from the VIEWING key, stealth public key offset from the SPENDING key):
1. Compute shared secret point: S = r * V2. Hash the shared secret: h = SHA256(S)3. Derive stealth public key: P = K + h * G4. Derive Ethereum address: addr = keccak256(decompress(P))[12:32]5. View tag: vt = h[0]Implementation
Section titled “Implementation”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)Address Derivation
Section titled “Address Derivation”Converting the stealth public key to an Ethereum address:
import { publicKeyToEthAddress } from '@sip-protocol/sdk'
// Compressed public key → Ethereum addressconst ethAddress = publicKeyToEthAddress(stealthAddress.address)// "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"Steps:
- Decompress the public key (33 bytes → 65 bytes)
- Remove the
04prefix (65 → 64 bytes) - Keccak-256 hash (64 → 32 bytes)
- Take last 20 bytes
- Apply EIP-55 checksum
Ephemeral Key and Shared Secret
Section titled “Ephemeral Key and Shared Secret”Ephemeral Key
Section titled “Ephemeral Key”The ephemeral key is generated per-payment and must be published for the recipient to claim funds:
// Sender generates ephemeral keyconst { stealthAddress } = generateStealthAddress(recipientMetaAddress)
// Ephemeral public key must be published (announcer contract or off-chain)console.log('Publish:', stealthAddress.ephemeralPublicKey)Shared Secret Derivation
Section titled “Shared Secret Derivation”Sender computes:
S = r * V (ephemeral private × viewing public)Recipient computes:
S = v * R (viewing private × ephemeral public)Both arrive at the same point due to ECDH properties. Because the shared secret derives from the VIEWING key, the recipient can detect payments with only the viewing private key plus the spending PUBLIC key — the spending private key is needed solely to derive the spendable stealth key.
View Tag Optimization
Section titled “View Tag Optimization”The view tag enables efficient scanning by allowing quick rejection of non-matching payments.
How It Works
Section titled “How It Works”- View tag = first byte of
SHA256(shared_secret_point) - Range: 0-255
- Expected false positive rate: ~1/256 (0.39%)
Scanning Process
Section titled “Scanning Process”checkStealthAddress performs the view-tag check internally (it returns early on a tag
mismatch before doing the full elliptic-curve derivation), so scanning is a single call
per candidate:
Scanning is view-only — it needs the recipient’s viewing PRIVATE key and their
spending PUBLIC key (the meta-address spendingKey), never the spending private
key (canonical EIP-5564):
import { checkStealthAddress } from '@sip-protocol/sdk'
// For each potential paymentfor (const payment of potentialPayments) { // View tag is checked first inside checkStealthAddress; the // expensive derivation only runs when the tag matches (~0.4% of the time) const isMine = checkStealthAddress( payment.stealthAddress, myViewingPrivateKey, mySpendingPublicKey )
if (isMine) { // This is our payment! console.log('Found payment:', payment) }}Performance Impact
Section titled “Performance Impact”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.
Announcer Contract Integration
Section titled “Announcer Contract Integration”EIP-5564 defines an announcer contract for publishing ephemeral keys on-chain.
Standard Interface
Section titled “Standard Interface”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;}SIP Integration
Section titled “SIP Integration”// Planned API — illustrative, not yet exported by @sip-protocol/sdkconst announcer = createAnnouncerClient({ address: '0x...', // EIP-5564 announcer contract provider: ethersProvider,})
// After sending to stealth addressawait announcer.announce({ schemeId: 1, // secp256k1 stealthAddress: ethAddress, ephemeralPubKey: stealthAddress.ephemeralPublicKey, metadata: '0x', // Optional: encrypted note})Scanning Announcements
Section titled “Scanning Announcements”// Planned API — illustrative, not yet exported by @sip-protocol/sdkconst announcements = await announcer.getAnnouncements({ fromBlock: 19000000, schemeId: 1,})
// Each candidate is then checked with checkStealthAddress (view tag handled// internally; view-only — viewing PRIVATE key + spending PUBLIC key)const myPayments = announcements.filter(a => checkStealthAddress(a.stealthAddress, myViewingPrivateKey, mySpendingPublicKey))Registry Contract (ERC-6538)
Section titled “Registry Contract (ERC-6538)”ERC-6538 defines a registry for storing stealth meta-addresses on-chain.
Standard Interface
Section titled “Standard Interface”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);}Registration
Section titled “Registration”// Planned API — illustrative, not yet exported by @sip-protocol/sdkconst registry = createRegistryClient({ address: '0x...', // ERC-6538 registry signer: ethers.signer,})
// Register your meta-addressawait registry.registerKeys({ schemeId: 1, // secp256k1 stealthMetaAddress: encodedMetaAddress,})Lookup
Section titled “Lookup”// Planned API — illustrative, not yet exported by @sip-protocol/sdkconst recipientMeta = await registry.stealthMetaAddressOf( recipientEthAddress, 1 // schemeId)
if (!recipientMeta) { throw new Error('Recipient has no registered stealth meta-address')}Security Considerations
Section titled “Security Considerations”Private Key Protection
Section titled “Private Key Protection”| Key | Sensitivity | Storage Recommendation |
|---|---|---|
| Spending private | Critical | Hardware wallet, encrypted vault |
| Viewing private | High | Secure storage, may share with auditors |
| Ephemeral private | Temporary | Securely wipe after use |
Threat Model
Section titled “Threat Model”| Threat | Mitigation |
|---|---|
| Spending key compromise | Full loss of funds - treat as seed phrase |
| Viewing key compromise | Privacy loss only - can regenerate |
| Ephemeral key reuse | Never reuse - always generate fresh |
| View tag collision | ~0.4% false positives, full derivation required |
Best Practices
Section titled “Best Practices”- Key generation: Use cryptographically secure random number generator
- Key storage: Encrypt at rest, never log or transmit unencrypted
- Ephemeral keys: Generate fresh for each payment, securely wipe after
- View tags: Always check before full computation
- Registry: Verify on-chain meta-address matches expected format
Secure Memory
Section titled “Secure Memory”SIP SDK uses secure memory practices:
import { secureWipe } from '@sip-protocol/sdk'
const privateKey = generatePrivateKey()try { // Use key} finally { secureWipe(privateKey) // Overwrite memory}Comparison with Other Implementations
Section titled “Comparison with Other Implementations”| Feature | SIP Protocol | Umbra | Fluidkey |
|---|---|---|---|
| Scheme | secp256k1 | secp256k1 | secp256k1 |
| View tag | Yes (1 byte) | Yes (1 byte) | Yes (1 byte) |
| Multi-chain | Yes | Ethereum only | Ethereum L2s |
| ERC-6538 | Planned | No | Partial |
| Compliance | Viewing keys | No | No |
| Cross-chain | NEAR Intents | No | No |
SIP Advantages
Section titled “SIP Advantages”- Cross-chain: Works across 15+ chains via NEAR Intents
- Compliance: Viewing keys for selective disclosure
- Multi-curve: secp256k1 (EVM) and ed25519 (Solana/NEAR)
- SDK: Comprehensive TypeScript SDK with React hooks
Deviations from Standard
Section titled “Deviations from Standard”SIP Protocol is fully compatible with EIP-5564 scheme 1, with these extensions:
| Extension | Description |
|---|---|
| Ed25519 support | Additional scheme for Solana/NEAR (not on-chain) |
| Cross-chain | Settlement via NEAR Intents |
| Viewing keys | Additional layer for compliance |
| SIP encoding | sip:chain:spending:viewing format |
Audit Checklist
Section titled “Audit Checklist”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