chains-sdk
Unified, secure multi-chain signing SDK for Rust. Supports ECDSA (secp256k1, P-256), EdDSA (Ed25519), BLS12-381 (including threshold), Schnorr (BIP-340), FROST threshold signatures (RFC 9591), and MuSig2 multi-party signatures (BIP-327) — with BIP-32/39/44 HD key derivation, address generation, full serde support, and a CLI tool.
Supported Algorithms
| Algorithm | Curve / Scheme | Chains | Standard |
|---|---|---|---|
| ECDSA | secp256k1 | Ethereum, Bitcoin, XRP | RFC 6979 |
| ECDSA | P-256 (secp256r1) | NEO | FIPS 186-4 |
| EdDSA | Ed25519 | Solana, XRP | RFC 8032 |
| Schnorr | secp256k1 | Bitcoin (Taproot) | BIP-340 |
| BLS | BLS12-381 | Beacon chain | — |
| BLS Threshold | BLS12-381 | Any (t-of-n) | — |
| FROST | secp256k1 + SHA-256 | Any (threshold) | RFC 9591 |
| MuSig2 | secp256k1 | Any (multi-party) | BIP-327 |
Quick Start
[]
= "1.0"
Ethereum (secp256k1 ECDSA)
use EthereumSigner;
use ;
use ecrecover;
// Generate a new key pair
let signer = generate?;
println!; // 0x...
// Sign a message (EIP-191 personal_sign)
let personal_sig = signer.sign?;
println!;
println!;
println!; // 27 or 28
// EIP-155 chain-aware signing (chain_id must be non-zero)
let mainnet_sig = signer.sign_with_chain_id?; // Mainnet
let polygon_sig = signer.sign_with_chain_id?; // Polygon
assert!;
assert!;
// ecrecover (recover address from signature)
let recovered = ecrecover?;
assert_eq!;
Gnosis Safe Multisig
use ;
use EthereumSigner;
use KeyPair;
let signer = generate?;
let domain = safe_domain_separator;
let tx = SafeTransaction ;
// Sign the Safe transaction (EIP-712)
let sig = tx.sign?;
// Build execTransaction calldata
let calldata = tx.encode_exec_transaction?;
// Owner management
use safe;
let add = encode_add_owner;
let remove = encode_remove_owner;
UUPS / Proxy Contracts (EIP-1967)
use proxy;
// UUPS upgrade
let calldata = encode_upgrade_to;
let calldata = encode_upgrade_to_and_call;
// EIP-1967 storage slots
assert_eq!;
// Multicall3 batch
let calls = vec!;
let batch = encode_multicall;
Smart Wallet / Account Abstraction (EIP-4337 v0.7)
use ;
// Smart wallet execute
let calldata = encode_execute;
// Batch execution
let calls = vec!;
let batch = encode_execute_batch;
// EIP-4337 v0.7 PackedUserOperation
let op = PackedUserOperation ;
let hash = op.hash;
// ERC-1271 contract signature validation
use ;
let calldata = encode_is_valid_signature;
Bitcoin (secp256k1 ECDSA + BIP-340 Schnorr)
use BitcoinSigner;
use SchnorrSigner;
use ;
// ECDSA signer — Legacy + SegWit addresses
let signer = generate?;
println!; // 1...
println!; // bc1q...
println!; // tb1q...
// BIP-137 message signing
let sig = signer.sign_message?;
// WIF import/export
let wif = signer.to_wif;
let restored = from_wif?;
// Schnorr / Taproot (BIP-340)
let schnorr = generate?;
println!; // bc1p...
let sig = schnorr.sign?;
Solana (Ed25519)
use SolanaSigner;
use ;
let signer = generate?;
println!; // Base58
let sig = signer.sign?;
XRP (secp256k1 ECDSA + Ed25519)
use ;
use ;
// ECDSA variant
let ecdsa = generate?;
println!; // r...
let sig = ecdsa.sign?;
// Ed25519 variant
let eddsa = generate?;
println!;
NEO (P-256 ECDSA)
use NeoSigner;
use ;
let signer = generate?;
println!; // A...
let sig = signer.sign?;
BLS (BLS12-381 Aggregated Signatures)
use ;
use ;
let signer1 = generate?;
let signer2 = generate?;
let sig1 = signer1.sign?;
let sig2 = signer2.sign?;
// Aggregate signatures, then verify (N signers, 1 verify call)
let agg_sig = aggregate_signatures?;
let pks = vec!;
assert!;
BIP-39 Mnemonic → HD Keys (BIP-32/44)
use Mnemonic;
use ;
// Generate 24-word mnemonic
let mnemonic = generate?;
println!;
// Derive seed → master key → chain-specific paths
let seed = mnemonic.to_seed;
let master = from_seed?;
let eth_key = master.derive_path?; // m/44'/60'/0'/0/0
let btc_key = master.derive_path?; // m/44'/0'/0'/0/0
let sol_key = master.derive_path?; // m/44'/501'/0'/0'
let xrp_key = master.derive_path?; // m/44'/144'/0'/0/0
// Export as xprv/xpub (BIP-32 serialization)
println!;
println!;
// One-step: mnemonic → chain signer
let eth = to_ethereum_signer?;
let btc = to_bitcoin_signer?;
BIP-85 — Deterministic Entropy Derivation
Derive unlimited child mnemonics, WIF keys, and xprv keys from a single master.
use ExtendedPrivateKey;
use bip85;
let seed = ;
let master = from_seed?;
// Derive child BIP-39 mnemonics (deterministic & reproducible)
let mnemonic_12 = derive_bip39?; // 12 words
let mnemonic_24 = derive_bip39?; // 24 words
// Derive WIF private key
let wif = derive_wif?; // starts with K or L
// Derive child xprv
let child = derive_xprv?;
println!;
// Raw hex entropy (16-64 bytes)
let entropy = derive_hex?;
FROST — T-of-N Threshold Schnorr (RFC 9591)
Any t of n participants can collaboratively sign. No single party holds the full key.
use ;
// 1. Trusted dealer generates 2-of-3 key shares
let secret = ; // group secret key
let kgen = trusted_dealer_keygen?;
// kgen.key_shares(): 3 shares, any 2 can sign
// 2. Verify shares against VSS commitments
for pkg in kgen.key_shares
// 3. Round 1 — Participants 1 and 3 generate nonce commitments
let nonce1 = commit?;
let nonce3 = commit?;
let commitments = vec!;
// 4. Round 2 — Each participant produces a partial signature share
let msg = b"threshold signed message";
let share1 = sign?;
let share3 = sign?;
// 5. Coordinator aggregates shares into a standard Schnorr signature
let sig = aggregate?;
// 6. Anyone can verify — indistinguishable from single-signer Schnorr
assert!;
Identifiable abort — detect which participant sent a bad share:
let pk1 = kgen.key_shares.public_key;
let is_valid = verify_share?;
assert!;
MuSig2 — N-of-N Multi-Party Schnorr (BIP-327)
All signers must participate. Produces a standard BIP-340 Schnorr signature.
use musig2;
// 1. Each signer has their own key pair
let sk1 = ;
let sk2 = ;
let pk1 = individual_pubkey?;
let pk2 = individual_pubkey?;
// 2. Key aggregation — combine into a single public key
let key_agg = key_agg?;
println!;
// 3. Round 1 — Nonce generation
let msg = b"multi-party signed";
let = nonce_gen?;
let = nonce_gen?;
// 4. Nonce aggregation
let agg_nonce = nonce_agg?;
// 5. Round 2 — Partial signing
let psig1 = sign?;
let psig2 = sign?;
// 6. Aggregate into a 64-byte BIP-340 Schnorr signature
let sig = partial_sig_agg?;
assert_eq!;
// 7. Standard BIP-340 verification
assert!;
Address Validation
use validate_address;
use validate_address as validate_eth;
use validate_address as validate_sol;
assert!; // BTC P2PKH
assert!; // BTC P2WPKH
assert!; // ETH EIP-55
assert!; // Solana
BIP-322 Message Signing & Verification
use ;
use SchnorrSigner;
use ;
// ── P2WPKH (SegWit) ──
let signer = generate?;
let proof = sign_simple_p2wpkh?;
// Verify (pass the raw proof bytes from sign)
let pubkey = signer.public_key_bytes;
let mut pk33 = ;
pk33.copy_from_slice;
let valid = verify_simple_p2wpkh?;
// ── P2TR (Taproot / Schnorr) ──
let schnorr_signer = generate?;
let proof = sign_simple_p2tr?;
PSBT (Partially Signed Bitcoin Transactions)
use BitcoinSigner;
use Psbt;
use SighashType;
use KeyPair;
// Deserialize a PSBT
let mut psbt = deserialize?;
// Auto-sign a SegWit input (computes BIP-143 sighash internally)
let signer = from_wif?;
psbt.sign_segwit_input?;
// Auto-sign a Taproot input (computes BIP-341 sighash internally)
// psbt.sign_taproot_input(0, &schnorr_signer, SighashType::Default)?;
// Round-trip: serialize → deserialize
let reserialized = psbt.serialize;
let restored = deserialize?;
Output Descriptors (BIP-380-386)
use descriptor;
// Parse and derive addresses from output descriptors
let desc = parse;
let addr = derive_address?;
Features
All modules are enabled by default. Disable unused ones to reduce compile time:
[]
= { = "1.0", = false, = ["ethereum", "frost"] }
| Feature | Description |
|---|---|
ethereum |
Ethereum ECDSA + EIP-191/712/155 + ecrecover |
bitcoin |
Bitcoin ECDSA + Schnorr + P2PKH/P2WPKH/P2TR + BIP-137 + WIF |
solana |
Solana Ed25519 |
xrp |
XRP ECDSA + Ed25519 + r-address |
neo |
NEO P-256 ECDSA + A-address |
bls |
BLS12-381 aggregated signatures (requires C compiler for blst) |
hd_key |
BIP-32/44 HD key derivation + xpub/xprv serialization |
mnemonic |
BIP-39 seed phrases (12/15/18/21/24 words) |
frost |
FROST T-of-N threshold Schnorr (RFC 9591, secp256k1-SHA256) |
musig2 |
MuSig2 N-of-N multi-party Schnorr (BIP-327) |
bip85 |
BIP-85 deterministic entropy (child mnemonics, WIF, xprv) |
serde |
Serialization support for keys and signatures |
custom_rng |
Pluggable TRNG source for TEE/enclave environments |
Benchmarks
Run with cargo bench --all-features. Covers all chains + threshold signing:
| Benchmark | What it measures |
|---|---|
frost_2of3_full_sign |
FROST keygen → commit → sign → aggregate |
musig2_2of2_full_sign |
MuSig2 nonce → sign → aggregate |
bls_threshold_2of3_full |
BLS threshold keygen → sign → aggregate |
eip2333_derive_master |
EIP-2333 master key derivation from seed |
eip2333_derive_child |
EIP-2333 child key derivation |
eip2333_validator_path |
EIP-2333 full validator path (5 levels) |
Security
#![deny(unsafe_code)]— zero unsafe blocks in default builds (mlock feature has 3 justified exceptions)#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]— zero panic surface- All ECDSA uses RFC 6979 deterministic nonces (secp256k1 + P-256)
- All key material wrapped in
Zeroizing/ZeroizeOnDrop - Constant-time comparisons via
subtle::ConstantTimeEq - FROST nonces are single-use
Zeroizing<Scalar>with drop guards cargo audit: 0 vulnerabilities across 175+ dependencies- 1,800+ tests including NIST SHA-256, BIP-32, BIP-39, BIP-85, BIP-137, BIP-143, BIP-174, BIP-322, BIP-327, BIP-340, BIP-341, BIP-342, RFC 6979, RFC 8032, RFC 9591, EIP-2333, EIP-2612, EIP-4337, ERC-4337, and FIPS 186-4 vectors
Enclave / Confidential Computing
chains-sdk is hardened for SGX, Nitro, TDX, and SEV-SNP environments:
use ;
// Zeroize-on-drop memory for sensitive data
let mut guard = new;
secure_random?; // Fill from OS/hardware TRNG
// Constant-time hex encoding (prevents timing side-channels)
let hex = ct_hex_encode;
// Debug output is redacted: GuardedMemory { len: 32, data: "[REDACTED]" }
println!;
Pluggable RNG for enclaves (requires custom_rng feature):
// Replace getrandom with hardware TRNG
set_custom_rng;
CLI Tool
# Generate keys
# Sign a message
# Derive address from key
# Verify a signature
Architecture
src/
├── crypto.rs # Shared: tagged_hash, double_sha256, hash160, sha256
├── encoding.rs # Shared: compact_size, bech32, base58check
├── error.rs # Unified SignerError enum
├── traits.rs # KeyPair, Signer, Verifier traits
├── atomic_swap.rs # Cross-chain HTLC (Bitcoin P2WSH + Ethereum ABI)
├── bin/ # CLI tool (chains-sdk keygen/sign/verify/address)
├── bitcoin/
│ ├── mod.rs # ECDSA signer, WIF, P2PKH/P2WPKH, BIP-137
│ ├── schnorr.rs # BIP-340 Schnorr, P2TR addresses
│ ├── taproot.rs # BIP-341/342 Taproot scripts
│ ├── tapscript.rs # Tapscript builder, BIP-342 leaf hashing
│ ├── sighash.rs # BIP-143/341/342 sighash computation
│ ├── transaction.rs # Transaction serialization, txid, vsize
│ ├── message.rs # BIP-322 sign + verify (P2WPKH / P2TR)
│ ├── musig2_tx.rs # MuSig2 Taproot transaction signing
│ ├── miniscript.rs # Miniscript compiler (policy → script)
│ ├── silent_payments.rs # BIP-352 Silent Payments
│ ├── ordinals.rs # Bitcoin Ordinals inscriptions (Tapscript)
│ ├── psbt/ # BIP-174 PSBT with auto-signing
│ ├── descriptor.rs # BIP-380-386 output descriptors
│ ├── helpers.rs # OP_RETURN, RBF, CPFP
│ └── scripts.rs # HTLC, CLTV/CSV timelock, coin selection
├── ethereum/
│ ├── mod.rs # ECDSA signer, EIP-191/712/155, ecrecover
│ ├── abi.rs # ABI encoding/decoding
│ ├── eips.rs # EIP-2612/3009/4494/6492/7702 helpers
│ ├── rlp.rs # RLP encoding/decoding
│ ├── transaction.rs # EIP-1559/2930 transaction builder
│ ├── keystore.rs # EIP-2335 keystore (scrypt + AES-128-CTR)
│ ├── safe.rs # Gnosis Safe multisig (EIP-712)
│ ├── proxy.rs # UUPS/EIP-1967 proxy, Multicall3
│ ├── smart_wallet.rs # Account abstraction (EIP-4337 v0.7)
│ ├── siwe.rs # Sign-In with Ethereum (EIP-4361)
│ ├── userop.rs # ERC-4337 UserOperation encoding
│ ├── permit2.rs # Uniswap Permit2 (EIP-712 signatures)
│ ├── uniswap_v4.rs # Uniswap V4 swap/pool encoding
│ └── bls/ # BLS12-381 Beacon Chain (PoS)
│ ├── mod.rs # BLS signing + aggregation
│ ├── threshold.rs # BLS threshold (t-of-n) keygen + signing
│ ├── eip2333.rs # EIP-2333 key derivation + EIP-2334 paths
│ └── keystore.rs # EIP-2335 keystore (scrypt + AES-128-CTR)
├── solana/
│ ├── mod.rs # EdDSA signer, Base58 address
│ ├── transaction.rs # SPL Token, System, Compute Budget
│ ├── programs.rs # ATA, Memo v2, Stake, Durable Nonce
│ ├── dex.rs # Jupiter/Raydium DEX routing
│ ├── token_extensions.rs # SPL Token-2022 extensions
│ ├── metaplex.rs # Metaplex NFT minting/metadata
│ ├── staking.rs # Native staking + Marinade Finance
│ ├── governance.rs # SPL Governance (DAO proposals/voting)
│ └── jupiter_dca.rs # Jupiter DCA (Dollar-Cost Averaging)
├── xrp/
│ ├── transaction.rs # Binary codec, Payment, TrustSet, multisign
│ └── advanced.rs # IOU amounts, DEX orders, Escrow
├── neo/
│ ├── transaction.rs # NeoVM scripts, NEP-17, tx builder
│ └── witness.rs # Witness serialization, NEP-11 NFT, GAS claim
├── threshold/
│ ├── frost/ # RFC 9591 T-of-N + identifiable abort + proactive refresh
│ └── musig2/ # BIP-327 N-of-N + adaptor sigs + tweaks + nested trees
├── security.rs # Enclave: GuardedMemory, ct_hex, secure_random, custom_rng
├── hd_key.rs # BIP-32/44 HD key derivation + xpub/xprv
├── mnemonic.rs # BIP-39 seed phrases
└── bip85.rs # BIP-85 deterministic entropy
fuzz/ # cargo-fuzz harness (ABI, RLP, CT-hex, BIP-39, PSBT, Permit2)
License
Licensed under either of Apache License, Version 2.0 or MIT License at your option.