Skip to content

Cross-Chain Stealth Addresses

When performing cross-chain swaps with stealth addresses, understanding curve compatibility is essential. Different blockchains use different elliptic curves, which affects how refund addresses are generated.

SIP supports two elliptic curves to enable stealth addresses across blockchain ecosystems:

CurveChainsAddress Format
secp256k1Ethereum, Bitcoin, Zcash, Polygon, Arbitrum, BaseKeccak256/Hash160 derived
ed25519Solana, NEARRaw 32-byte public key
From → ToCurvesAutomatic Refund?
Ethereum → Polygonsecp256k1 → secp256k1Yes
Solana → NEARed25519 → ed25519Yes
Ethereum → Solanasecp256k1 → ed25519No - requires senderAddress
Solana → Ethereumed25519 → secp256k1No - requires senderAddress

When both chains use the same curve, SIP can automatically generate a stealth refund address from your meta-address.

import { createShieldedIntent, PrivacyLevel } from '@sip-protocol/sdk'
// Ethereum to Polygon - both use secp256k1
const intent = await createShieldedIntent({
input: {
asset: { chain: 'ethereum', symbol: 'ETH', address: null, decimals: 18 },
amount: 1_000_000_000_000_000_000n, // 1 ETH
},
output: {
asset: { chain: 'polygon', symbol: 'MATIC', address: null, decimals: 18 },
minAmount: 0n,
maxSlippage: 0.01, // 1%
},
privacy: PrivacyLevel.SHIELDED,
recipientMetaAddress, // secp256k1 meta-address
// senderAddress NOT required - automatically derived (same-curve)
})
// Solana to NEAR - both use ed25519
const intent = await createShieldedIntent({
input: {
asset: { chain: 'solana', symbol: 'SOL', address: null, decimals: 9 },
amount: 10_000_000_000n, // 10 SOL
},
output: {
asset: { chain: 'near', symbol: 'NEAR', address: null, decimals: 24 },
minAmount: 0n,
maxSlippage: 0.01, // 1%
},
privacy: PrivacyLevel.SHIELDED,
recipientMetaAddress, // ed25519 meta-address
// senderAddress NOT required - automatically derived (same-curve)
})

When chains use different curves, you must provide a senderAddress for refunds because:

  1. secp256k1 keys cannot derive ed25519 addresses (and vice versa)
  2. Mathematical incompatibility - the curves have different properties
  3. No key derivation path exists between the two curve types
// Cross-curve: secp256k1 → ed25519
// MUST provide senderAddress (in the options arg) for refunds
const intent = await createShieldedIntent(
{
input: {
asset: { chain: 'ethereum', symbol: 'ETH', address: null, decimals: 18 },
amount: 1_000_000_000_000_000_000n, // 1 ETH
},
output: {
asset: { chain: 'solana', symbol: 'SOL', address: null, decimals: 9 },
minAmount: 0n,
maxSlippage: 0.01, // 1%
},
privacy: PrivacyLevel.SHIELDED,
recipientMetaAddress, // ed25519 meta-address for Solana
},
{
senderAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD11', // Your Ethereum address for refunds
},
)
// Cross-curve: ed25519 → secp256k1
// MUST provide senderAddress (in the options arg) for refunds
const intent = await createShieldedIntent(
{
input: {
asset: { chain: 'solana', symbol: 'SOL', address: null, decimals: 9 },
amount: 10_000_000_000n, // 10 SOL
},
output: {
asset: { chain: 'ethereum', symbol: 'ETH', address: null, decimals: 18 },
minAmount: 0n,
maxSlippage: 0.01, // 1%
},
privacy: PrivacyLevel.SHIELDED,
recipientMetaAddress, // secp256k1 meta-address for Ethereum
},
{
senderAddress: 'DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK', // Your Solana address for refunds
},
)

If you attempt a cross-curve swap without providing senderAddress, you’ll receive one of these errors:

secp256k1 Meta-Address + ed25519 Input Chain

Section titled “secp256k1 Meta-Address + ed25519 Input Chain”
ValidationError: Cross-curve refunds not supported: input chain solana
requires ed25519 but meta-address uses secp256k1. Please provide a
senderAddress for refunds, or use matching curves for input/output chains.

ed25519 Meta-Address + secp256k1 Input Chain

Section titled “ed25519 Meta-Address + secp256k1 Input Chain”
ValidationError: Cross-curve refunds not supported: input chain ethereum
requires secp256k1 but meta-address uses ed25519. Please provide a
senderAddress for refunds, or use matching curves for input/output chains.

The two curve types are mathematically incompatible:

Propertysecp256k1ed25519
Curve typeWeierstrassTwisted Edwards
Field prime2^256 - 2^32 - 9772^255 - 19
Group order~2^256~2^252
Signature schemeECDSAEdDSA

There is no mathematical transformation to convert a secp256k1 public key into a valid ed25519 public key (or vice versa). They operate in completely different mathematical spaces.

SIP stealth addresses are derived from the meta-address public keys:

Stealth Address = SpendingKey + hash(SharedSecret) × G

Where G is the generator point of the specific curve. Using a secp256k1 generator with ed25519 arithmetic (or vice versa) produces invalid addresses.

For the best UX, prefer swaps between chains using the same curve:

  • EVM ecosystem: Ethereum, Polygon, Arbitrum, Base, Optimism
  • Non-EVM ecosystem: Solana, NEAR

2. Always Provide senderAddress for Cross-Curve

Section titled “2. Always Provide senderAddress for Cross-Curve”

When building UIs, detect cross-curve scenarios and prompt users for their refund address:

function needsSenderAddress(inputChain: string, outputChain: string): boolean {
const secp256k1Chains = ['ethereum', 'polygon', 'arbitrum', 'base', 'bitcoin', 'zcash']
const ed25519Chains = ['solana', 'near']
const inputIsSecp = secp256k1Chains.includes(inputChain)
const outputIsSecp = secp256k1Chains.includes(outputChain)
return inputIsSecp !== outputIsSecp // Different curves
}

Ensure the senderAddress matches the input chain’s expected format:

The SDK exposes chain-specific validators. Dispatch on the input chain to validate the refund address:

import {
isValidSolanaAddress,
isValidNearImplicitAddress,
isValidNearAccountId,
} from '@sip-protocol/sdk'
function isValidAddress(address: string, chain: string): boolean {
switch (chain) {
case 'solana':
return isValidSolanaAddress(address)
case 'near':
// NEAR accepts implicit (64-hex) accounts and named accounts (alice.near)
return isValidNearImplicitAddress(address) || isValidNearAccountId(address)
default:
// EVM chains (ethereum, polygon, arbitrum, base, ...): 0x + 40 hex chars
return /^0x[0-9a-fA-F]{40}$/.test(address)
}
}
if (!isValidAddress(senderAddress, inputChain)) {
throw new Error(`Invalid ${inputChain} address format`)
}
ScenariosenderAddress Required?Refund Address
EVM → EVMNoAuto-generated stealth address
Solana → NEARNoAuto-generated stealth address
EVM → SolanaYesUser-provided address
Solana → EVMYesUser-provided address

Cross-curve swaps trade off some privacy (using a known refund address) for cross-ecosystem compatibility. Same-curve swaps maintain full privacy with automatically generated stealth refund addresses.