Note: For background information and conceptual explanations of stealth transactions, privacy on Solana, and the motivations behind KION Stealth, please visit the official documentation at docs.kion.sh. This README focuses on the technical details of this Anchor program.
KION Stealth leverages secp256k1-based keys and ECDH operations to create stealth addresses on Solana.
- Users register a stealth meta-address (comprised of a spending key and a viewing key).
- Senders generate an ephemeral keypair and derive a one-time stealth address using the recipient’s meta-address.
- Recipients detect incoming transactions by scanning on-chain announcements using their viewing key.
- Recipients derive a unique stealth private key on-the-fly, enabling them to spend funds delivered to that stealth address.
The code in this repository demonstrates how to implement these workflows using Anchor’s Solana Rust framework.
Initializes a global configuration account for the program.
-
Purpose:
Store top-level settings and the program’s authority. -
Signature:
pub fn initialize(ctx: Context<Initialize>, bump: u8) -> Result<()>;
-
Key Data:
GlobalConfigaccount withauthorityandbump.
Registers a user’s stealth meta-address on-chain. This associates:
-
spending_pubkey(Secp256k1 compressed, 33 bytes) -
viewing_pubkey(Secp256k1 compressed, 33 bytes) -
Signature:
pub fn register_user( ctx: Context<RegisterUser>, spending_pubkey: [u8; 33], viewing_pubkey: [u8; 33], ) -> Result<()>;
-
Result:
Writes user’s data into aUserDataaccount keyed by their authority.
Generates an ephemeral keypair directly on-chain.
-
Randomness Source:
- Recent blockhash
- Current time (
unix_timestamp) - Authority’s public key
-
Signature:
pub fn generate_ephemeral_keypair(ctx: Context<GenerateEphemeralKeypair>) -> Result<()>;
-
Result:
Stores the ephemeral secret and its corresponding secp256k1 compressed public key in anEphemeralKeysaccount.
Note: Storing ephemeral secrets on-chain is primarily for demonstration/testing. In a production system, ephemeral keys are often generated off-chain.
Publishes an on-chain announcement for a stealth transaction.
-
Parameters:
ephemeral_pubkey_external: Optional[u8; 33]. If provided, the ephemeral public key is directly used without on-chain ephemeral data.
-
Core Steps:
- Retrieve ephemeral pubkey (either from
ephemeral_pubkey_externalor anEphemeralKeysaccount). - Compute a shared secret (
ECDH(ephemeral_sk, recipient.viewing_pubkey)), then hash it. - Derive the stealth address =
recipient.spending_pubkey + (hashed_secret * G). - Extract a single-byte
view_tagfor quick scanning. - Store all relevant data in a
StealthAnnouncementaccount and emit aStealthAnnouncementEvent.
- Retrieve ephemeral pubkey (either from
-
Signature:
pub fn announce_stealth( ctx: Context<AnnounceStealth>, ephemeral_pubkey_external: Option<[u8; 33]>, ) -> Result<()>;
Allows recipients to verify if an announcement is relevant to their viewing key.
-
Process:
- Read the ephemeral public key from the
StealthAnnouncement. - Perform ECDH with the recipient’s
viewing_private_key. - Hash the result, check if the first byte (
view_tag) matches the stored tag. - If it does not match, return
NotRelevant. Otherwise, it belongs to the scanning recipient.
- Read the ephemeral public key from the
-
Signature:
pub fn scan_announcement( ctx: Context<ScanAnnouncement>, viewing_private_key: [u8; 32], ) -> Result<()>;
Computes the final stealth private key for spending from the stealth address.
-
Formula:
[ \text{stealth_privkey} = s + H(\text{ephemeral_pubkey} * v) \mod n ]
Where:- ( s ) = recipient’s spending private key
- ( v ) = recipient’s viewing private key
- ( H(\cdots) ) = keccak256 hash of the ECDH result
-
Signature:
pub fn derive_stealth_private_key( ctx: Context<DeriveStealthPrivateKey>, spending_private_key: [u8; 32], ephemeral_pubkey_compressed: [u8; 33], viewing_private_key: [u8; 32], ) -> Result<()>;
-
Result:
The derived key is logged as hex (for demonstration). In a production environment, you’d typically handle this off-chain or with secure key management.
Stores top-level program settings.
#[account]
pub struct GlobalConfig {
pub authority: Pubkey,
pub bump: u8,
}- Size:
8 + 32 + 1
Represents a user’s stealth meta-address.
#[account]
pub struct UserData {
pub authority: Pubkey,
pub spending_pubkey: [u8; 33],
pub viewing_pubkey: [u8; 33],
}- Size:
8 + 32 + 33 + 33
Holds ephemeral private/public key pairs.
Warning: Storing private keys on-chain is only recommended for demonstration or testing.
#[account]
pub struct EphemeralKeys {
pub authority: Pubkey,
pub ephemeral_private_key: [u8; 32],
pub ephemeral_pubkey_compressed: [u8; 33],
}- Size:
8 + 32 + 32 + 33
Defines one stealth transaction announcement.
#[account]
pub struct StealthAnnouncement {
pub authority: Pubkey,
pub ephemeral_pubkey_compressed: [u8; 33],
pub recipient_spending_pubkey: [u8; 33],
pub recipient_viewing_pubkey: [u8; 33],
pub stealth_address_compressed: [u8; 33],
pub view_tag: u8,
pub timestamp: i64,
}- Size:
8 + 32 + (33 * 4) + 1 + 8
Emitted upon a successful stealth announcement. Off-chain indexers can listen for:
ephemeral_pubkeystealth_addressview_tagtimestamp
Handles cryptographic and domain-specific errors:
InvalidPrivateKeyInvalidEphemeralPubkeyInvalidViewingKeyInvalidSpendingKeyCannotCombinePointsNotRelevant
-
Install Dependencies
yarn install
or
npm install
-
Compile the Program
anchor build
-
Run Tests
anchor testThis uses a local validator, deploys the program, and runs the test suite.
-
Deploy
Update yourAnchor.tomlwith the target cluster (devnet/mainnet) and run:anchor deploy
Ensure your wallet is funded with enough SOL for transaction fees.
Below is a hypothetical flow. Adapt to your environment as needed.
// 1. Initialize program
await program.methods
.initialize(bump)
.accounts({
globalConfig: globalConfigPda,
authority: user.publicKey,
systemProgram: SystemProgram.programId,
})
.signers([user])
.rpc();
// 2. Register user meta-address
await program.methods
.registerUser(spendingPubkey, viewingPubkey)
.accounts({
authority: user.publicKey,
userData: userDataPda,
systemProgram: SystemProgram.programId,
})
.signers([user])
.rpc();
// 3. Generate ephemeral keypair on-chain
await program.methods
.generateEphemeralKeypair()
.accounts({
authority: user.publicKey,
ephemeralKeys: ephemeralKeysPda,
sysvarRecentBlockhashes: SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
systemProgram: SystemProgram.programId,
})
.signers([user])
.rpc();
// 4. Announce stealth transaction
await program.methods
.announceStealth(null) // or pass ephemeralPubkeyExternal
.accounts({
authority: user.publicKey,
ephemeralKeys: ephemeralKeysPda,
recipientUserData: recipientDataPda,
announcement: announcementPda,
systemProgram: SystemProgram.programId,
})
.signers([user])
.rpc();
// 5. Scan for announcement with viewing key
await program.methods
.scanAnnouncement(viewingPrivateKey)
.accounts({
announcement: announcementPda,
})
.rpc();
// 6. Derive stealth private key
await program.methods
.deriveStealthPrivateKey(spendingPrivateKey, ephemeralPubkeyCompressed, viewingPrivateKey)
.accounts({
authority: user.publicKey,
})
.signers([user])
.rpc();
// Program logs your stealth private key hex for demonstration.- Storing ephemeral secrets on-chain is not recommended for real-world usage.
- Users should protect their private keys (spending, viewing) diligently.
- False positives are possible with the 1-byte
view_tag. Recipients handle mismatches gracefully. - Before production, audit the program and cryptographic logic.
For more details, FAQs, and conceptual overviews, please visit
docs.kion.sh