Signer
no_std-compatible Rust toolkit for multi-chain transaction signing — thirteen networks, zero hand-written cryptography, cross-implementation KATs.
Signer turns 32-byte private keys into standards-compliant signatures for Aptos, Bitcoin, Ethereum, Solana, Cosmos, Tron, Sui, TON, Filecoin, Spark, XRP Ledger, Nostr, and Casper. It layers thin wrappers around k256 (secp256k1 ECDSA and BIP-340 Schnorr) and ed25519-dalek on a capability-split trait surface (SignDigest, optional SignMessage, chain-inherent sign_transaction); every library crate builds under no_std + alloc, private keys wrap in Zeroizing / ZeroizeOnDrop and redact in Debug, and chain outputs are pinned against independent references (RFC 6979 / 8032, BIP-137 / 340, EIP-191 / 712, @noble/curves, …).
See also
kobe— the companion HD-wallet derivation toolkit that feedsSigner::from_derivedwith BIP-39 / BIP-32 / SLIP-10 accounts.
Quick Start
Install the CLI
Shell (macOS / Linux):
|
PowerShell (Windows):
irm https://sh.qntx.fun/signer/ps | iex
Or via Cargo:
CLI Usage
# Prefer stdin / file for keys on shared hosts (shell history / process listings)
| | | | | | |
|
# Agent-friendly JSON (global flag before the chain subcommand)
# Self-upgrade (sh.qntx.fun install path; same as re-running the install script)
CLI verbs match the library: sign-digest, sign-message, sign-tx, address. Chains without a canonical personal-message scheme omit sign-message. Private keys accept hex, - (stdin), or @path. JSON sign results include a stable scheme field (ecdsa_recoverable, ed25519, schnorr, …). Cargo installs under .cargo/bin are not overwritten; the command prints a cargo install signer-cli --force hint instead.
Library Usage
# Default is `std` + `mainstream` (btc, evm, svm). Opt into more chains or presets explicitly.
= { = "3", = ["std", "mainstream"] }
# All chains
= { = "3", = ["std", "all-chains"] }
# Single chain + optional kobe HD bridge
= { = "3", = ["std", "evm", "kobe"] }
= { = "3.2", = ["std", "evm"] }
kobe may resolve k256 0.13 while signer uses k256 0.14; the bridge is 32-byte secrets only, not shared signing-key types. See SECURITY.md and docs/KAT_MATRIX.md.
use ;
let signer = from_hex?;
let raw = signer.sign_digest?; // v = 0 | 1 (raw parity → RLP)
let msg = signer.sign_message?; // v = 27 | 28 (EIP-191)
println!; // identity of this key only (not HD)
println!;
println!; // ecdsa_recoverable
SignOutput is a discriminated enum — Ecdsa { signature, v }, EcdsaDer, Ed25519, Ed25519WithPubkey, Schnorr — so callers pattern-match wire shape instead of juggling optional metadata. v semantics are documented per producer (raw parity, EIP-191, BIP-137 ranges); they collide across chains, so verifiers must already know the scheme.
Chains without a canonical off-chain message scheme do not implement SignMessage. Build the domain preimage externally and pass it to the chain’s inherent sign_transaction (not a shared trait — byte semantics differ by protocol):
// Cosmos: build ADR-036 StdSignDoc bytes externally (e.g. via kobe / app layer)
use Signer;
let signer = from_hex?;
let signature = signer.sign_transaction?;
Bitcoin message signing selects the BIP-137 header for the target address type; the default matches Bitcoin Core signmessage (compressed P2PKH):
use ;
let signer = from_hex?;
let compressed = signer.sign_message?; // v = 31 | 32
let bech32 = signer.sign_message_with?;
Architecture
L0 signer-primitives curve engines + SignDigest / SignOutput / v_encoding
L1 signer-{chain} protocol preimage + wire (EIP-191, BIP-137, intent, …)
L2 signer-cli ops UX (sign-digest / sign-message / sign-tx)
── kobe (companion) HD only — FromDerived over 32-byte secret material
Two key materials (secp256k1 scalar, Ed25519 seed) and three schemes (ECDSA, BIP-340, Ed25519) cover all thirteen chains. Framing is product value, not noise: digests and headers are where multi-chain correctness lives.
Kobe HD wallet integration
Enable the kobe feature to construct signers from kobe accounts:
use Wallet;
use Deriver;
use ;
let wallet = from_mnemonic?;
let account = new.derive?;
let signer = from_derived?;
let sig = signer.sign_message?;
Supported Chains
| Chain | Crate | Curve | Sighash / digest | Off-chain message |
|---|---|---|---|---|
| Bitcoin | signer-btc |
secp256k1 | double-SHA-256 | BIP-137 (four header variants) |
| Ethereum | signer-evm |
secp256k1 | Keccak-256 | EIP-191, EIP-712 |
| Cosmos | signer-cosmos |
secp256k1 | SHA-256 | ADR-036 StdSignDoc (external) |
| Tron | signer-tron |
secp256k1 | SHA-256 (raw_data txID) |
TRON prefix, wire v = 27/28 |
| Filecoin | signer-fil |
secp256k1 | BLAKE2b-256 over CID bytes | use sign-tx / digest |
| Spark | signer-spark |
secp256k1 | double-SHA-256 | BIP-137 (compressed P2PKH) |
| TON | signer-ton |
Ed25519 | raw | caller-owned preimage |
| XRP Ledger | signer-xrpl |
secp256k1 | STX\0 + SHA-512-half, DER |
none (no canonical personal-message) |
| Solana | signer-svm |
Ed25519 | raw | raw Ed25519 (nacl.sign.detached) |
| Sui | signer-sui |
Ed25519 | BLAKE2b-256 intent + BCS | PersonalMessage intent |
| Aptos | signer-aptos |
Ed25519 | SHA3-256 domain + BCS | use sign-tx / sign_raw |
| Nostr | signer-nostr |
Schnorr BIP-340 | SHA-256 (NIP-01 event id) | raw BIP-340 (optional) |
| Casper | signer-casper |
secp256k1 / Ed25519 | deploy digest (caller BLAKE2b) | dual-curve digest / raw bytes |
| Arweave | signer-arweave |
secp256k1 ECDSA | deep-hash → SHA-256 | format=2 recoverable 65B; owner empty |
* Address helpers on chain crates are identity of this key only (not multi-path HD). Full derivation lives in kobe.
Design
- 14 chains — Aptos, Bitcoin, Casper, Arweave (ECDSA), Ethereum, Solana, Cosmos, Tron, Sui, TON, Filecoin, Spark, XRP Ledger, Nostr
- Mature crypto dependencies —
k256for secp256k1 ECDSA and BIP-340 Schnorr,ed25519-dalekfor Ed25519; hashing viasha2/sha3/blake2/ripemd; encoding viabech32/bs58 - Capability-split traits — mandatory
SignDigest::sign_digest(&[u8; 32]→SignOutput); optionalSignMessage,ExtractSignableBytes,EncodeSignedTransaction; protocolsign_transactionis inherent per chain (no false universal trait) - Scheme-honest semantics — ECDSA digests are prehashes; Ed25519 / BIP-340 treat 32-byte inputs as messages where applicable (documented on the trait)
- Discriminated
SignOutput— wire variants +scheme()/ CLI JSONscheme;voffsets live inv_encoding(EIP-191, BIP-137, …) - Cross-implementation KATs — RFC 6979, RFC 8032, BIP-340 CSV, EIP-712 Mail, BIP-137 headers, recover/verify round-trips — strength graded in
docs/KAT_MATRIX.md no_std+alloc— library crates compile onthumbv7m-none-eabiunder CI- Security hardened —
ZeroizeOnDrop/Zeroizing, redactedDebug, CLI key ingress via stdin or@path - Kobe integration — optional
kobefeature:FromDerivedover kobe account types - Strict linting — Clippy
pedantic+nursery+correctness(deny),rust_2018_idiomsdeny, zero warnings on nightly;just check-namesbans legacy dual names
Crates
See crates/README.md for the full crate table, dependency graph, and feature flag reference.
Contributing
See CONTRIBUTING.md for development setup, the chain sign contract, PR expectations, and the release checklist.
Security
This library has not been independently audited. Use at your own risk. Full policy: SECURITY.md.
- Private keys wrapped in
zeroize— wiped on drop; secret exports returnZeroizing<…> Debugfor secret-bearing types redacts material ([REDACTED]); do not rely on{:?}for secrets- CLI prefers
-k -/-k @pathover argv on shared hosts - Random generation uses OS CSPRNG via
getrandom; prefertry_random()where entropy can fail SignDigest/SignMessagerequireSend + Syncfor async executors- No key material is logged or persisted by the workspace
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE-MIT or https://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project shall be dual-licensed as above, without any additional terms or conditions.
A QuantX open-source project.
Code is law. We write both.