G... address encoding. Import from @wraith-protocol/sdk/chains/stellar.
Most developers should use the agent client instead. These primitives are for power users building custom Stellar stealth address integrations without the managed platform.
Installation
The Stellar primitives depend on@stellar/stellar-sdk as an optional peer dependency. It is not installed automatically — add it explicitly alongside the main SDK.
Key differences from EVM
The Stellar module uses a fundamentally different cryptographic stack. If you are familiar with the EVM primitives, the table below summarizes what changes.| Aspect | EVM | Stellar |
|---|---|---|
| Curve | secp256k1 | ed25519 |
| Key format | HexString (0x-prefixed) | Uint8Array (raw bytes) |
| ECDH method | secp256k1 getSharedSecret | X25519 (Montgomery form conversion) |
| Address format | 0x... (20 bytes) | G... (56-char Stellar address) |
| Public key size | 33 bytes (compressed) | 32 bytes |
| Meta-address prefix | st:eth:0x | st:xlm: |
| Hash function | keccak256 | SHA-256 (domain-separated) |
| Private key output | HexString | bigint scalar |
| Signing | secp256k1 ECDSA | ed25519 with raw scalar |
Keypair.fromRawEd25519Seed() does not accept a derived scalar directly — you must use signWithScalar() or signStellarTransaction() from this module.
Import
Constants
Core types
Functions
deriveStealthKeys(signature)
Derive spending and viewing key pairs from a 64-byte ed25519 signature. The same signature always produces the same keys.
A 64-byte ed25519 signature from
stellarKeypair.sign(Buffer.from(STEALTH_SIGNING_MESSAGE)).StealthKeys
How it works:
spendingKey = SHA-256("wraith:spending:" || signature)— 32-byte seedviewingKey = SHA-256("wraith:viewing:" || signature)— 32-byte seed- Each seed is expanded to a clamped scalar via
seedToScalar()(SHA-512 + bit clamping) - Public keys are derived via
ed25519.getPublicKey(seed)
r/s components, because ed25519 signature components do not have the same independence guarantees.
generateStealthAddress(spendingPubKey, viewingPubKey, ephemeralSeed?)
Generate a one-time Stellar stealth address for a recipient. Call this on the sender’s side.
The recipient’s 32-byte ed25519 spending public key.
The recipient’s 32-byte ed25519 viewing public key.
Override the randomly generated ephemeral seed. Use only for deterministic testing.
GeneratedStealthAddress
How it works:
- Generate a random ephemeral ed25519 seed
- Compute ECDH shared secret via X25519 (keys converted to Montgomery form)
viewTag = computeViewTag(sharedSecret)=SHA-256("wraith:tag:" || sharedSecret)[0]hScalar = hashToScalar(sharedSecret)= SHA-256 output reduced mod LstealthPoint = spendingPubKey + hScalar × G(ed25519 point addition)- Encode as Stellar
G...address viaStrKey.encodeEd25519PublicKey
scanAnnouncements(announcements, viewingKey, spendingPubKey, spendingScalar)
Scan Soroban contract event announcements and return only those that belong to you. Call this on the recipient’s side.
Array of announcements from Soroban contract events.
Your 32-byte ed25519 viewing private key seed (from
deriveStealthKeys).Your 32-byte ed25519 spending public key (from
deriveStealthKeys).Your clamped spending scalar (from
keys.spendingScalar). Note: this is spendingScalar, not spendingKey. This differs from the EVM module, which takes the raw private key bytes.MatchedAnnouncement[]
deriveStealthPrivateScalar(spendingScalar, viewingKey, ephemeralPubKey)
Derive the private scalar for a specific stealth address. Use this when you need the scalar directly without running a full scan.
Your clamped spending scalar (
keys.spendingScalar).Your 32-byte viewing private key seed (
keys.viewingKey).The 32-byte ephemeral public key from the announcement.
bigint — (spendingScalar + hashScalar) mod L
Signing from a stealth address
Stealth private keys on Stellar are derived scalars. They are not valid ed25519 seeds, so you cannot useKeypair.fromRawEd25519Seed() with them. Use the signing functions from this module instead.
signStellarTransaction(txHash, stealthScalar, stealthPubKey)
Sign a Stellar transaction hash with a stealth private scalar.
The 32-byte transaction hash from the Stellar SDK.
The stealth private scalar from
deriveStealthPrivateScalar or MatchedAnnouncement.stealthPrivateScalar.The 32-byte stealth public key from
MatchedAnnouncement.stealthPubKeyBytes.Uint8Array — 64-byte ed25519 signature
signWithScalar(message, scalar, publicKey)
Sign arbitrary message bytes with a raw scalar. Use when you need to sign something other than a transaction hash.
Meta-address utilities
encodeStealthMetaAddress(spendingPubKey, viewingPubKey)
Encode two 32-byte ed25519 public keys into a Stellar stealth meta-address string.
decodeStealthMetaAddress(metaAddress)
Decode a Stellar meta-address back into its component public keys. Use this on the sender’s side before calling generateStealthAddress.
pubKeyToStellarAddress(publicKey)
Convert a 32-byte ed25519 public key to a Stellar G... address.
Helper utilities
bytesToHex(bytes) / hexToBytes(hex)
Convert between Uint8Array and hex strings. Useful when interacting with systems that expect hex-encoded keys.
seedToScalar(seed)
Convert a 32-byte ed25519 seed to its clamped scalar. This mirrors standard ed25519 private key expansion.
SHA-512(seed) → take the lower 32 bytes → apply bit clamping (a[0] &= 248; a[31] &= 127; a[31] |= 64) → interpret as little-endian bigint.
hashToScalar(sharedSecret)
Hash a shared secret to a scalar for stealth address derivation.
computeSharedSecret(privateKey, publicKey)
Compute an X25519 ECDH shared secret from ed25519 keys. Ed25519 keys are converted to Montgomery form before the Diffie-Hellman exchange.
End-to-end example
Stellar-specific considerations
Account creation: Stellar requires accounts to exist with a minimum balance of 1 XLM. When sending to a new stealth address for the first time, useOperation.createAccount rather than Operation.payment.
Announcement source: Announcements come from Soroban contract events via sorobanServer.getEvents(), not a subgraph. The event structure matches the Announcement interface above.
Signing stealth addresses: You must use signStellarTransaction() or signWithScalar() from this module. Stealth scalars are derived values that are not valid seeds for Keypair.fromRawEd25519Seed() — the standard Stellar SDK signing path will not work.
