signer 3.2.0

Multi-chain transaction signer — umbrella crate re-exporting chain-specific signers.
Documentation
# Signer

[![Crates.io][crates-badge]][crates-url]
[![Docs.rs][docs-badge]][docs-url]
[![CI][ci-badge]][ci-url]
[![License][license-badge]][license-url]
[![Rust][rust-badge]][rust-url]

[crates-badge]: https://img.shields.io/crates/v/signer.svg
[crates-url]: https://crates.io/crates/signer
[docs-badge]: https://img.shields.io/docsrs/signer.svg
[docs-url]: https://docs.rs/signer
[ci-badge]: https://github.com/qntx/signer/actions/workflows/ci.yml/badge.svg
[ci-url]: https://github.com/qntx/signer/actions/workflows/ci.yml
[license-badge]: https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg
[license-url]: LICENSE-MIT
[rust-badge]: https://img.shields.io/badge/rust-edition%202024-orange.svg
[rust-url]: https://doc.rust-lang.org/edition-guide/

**`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`](https://docs.rs/k256) (secp256k1 ECDSA and BIP-340 Schnorr) and [`ed25519-dalek`](https://docs.rs/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`](https://github.com/qntx/kobe) — the companion HD-wallet derivation toolkit that feeds `Signer::from_derived` with BIP-39 / BIP-32 / SLIP-10 accounts.

<p align="center">
  <img src="demo.gif" alt="Signer CLI Demo"/>
</p>

## Quick Start

### Install the CLI

**Shell** (macOS / Linux):

```bash
curl -fsSL https://sh.qntx.fun/signer | sh
```

**PowerShell** (Windows):

```powershell
irm https://sh.qntx.fun/signer/ps | iex
```

Or via Cargo:

```bash
cargo install signer-cli
```

### CLI Usage

```bash
# Prefer stdin / file for keys on shared hosts (shell history / process listings)
echo "$KEY" | signer evm    sign-message -k - -m "Hello, Ethereum!"   # EIP-191
echo "$KEY" | signer btc    sign-message -k - -m "Hello, Bitcoin!"    # BIP-137
echo "$KEY" | signer sui    sign-tx      -k - -t "0000..."            # BLAKE2b intent
echo "$KEY" | signer cosmos sign-tx      -k - -t "<SignDoc hex>"      # ADR-036 input
echo "$KEY" | signer xrpl   sign-tx      -k - -t "<tx fields hex>"    # STX\0 + SHA-512/2 + DER
echo "$KEY" | signer nostr  sign-digest  -k - -x "5e6ea04f..."        # NIP-01 event id
echo "$KEY" | signer casper sign-digest  -k - -x "<32-byte deploy hash>" --algo secp256k1
signer      casper sign-digest -k @./key.hex -x "<digest>" --algo ed25519
echo "$KEY" | signer arweave sign-digest -k - -x "<32-byte SHA-256(deep-hash)>"
signer      arweave sign-payload -k "$KEY" -d "<deep-hash hex>"
signer      evm    address     -k @./key.hex                          # EIP-55 (this key only)

# Agent-friendly JSON (global flag before the chain subcommand)
signer --json evm sign-message -k - -m "test"

# Self-upgrade (sh.qntx.fun install path; same as re-running the install script)
signer upgrade              # install latest if newer (`update` is an alias)
signer upgrade --check      # report only
signer upgrade --force      # reinstall even when up to date
signer --json upgrade --check
```

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

```toml
# Default is `std` + `mainstream` (btc, evm, svm). Opt into more chains or presets explicitly.
signer = { version = "3", features = ["std", "mainstream"] }

# All chains
signer = { version = "3", features = ["std", "all-chains"] }

# Single chain + optional kobe HD bridge
signer = { version = "3", features = ["std", "evm", "kobe"] }
kobe   = { version = "3.2", features = ["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](SECURITY.md) and [docs/KAT_MATRIX.md](docs/KAT_MATRIX.md).

```rust
use signer::evm::{SignDigest, SignMessage, Signer};

let signer = Signer::from_hex(
    "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318",
)?;

let raw = signer.sign_digest(&[0x42u8; 32])?; // v = 0 | 1  (raw parity → RLP)
let msg = signer.sign_message(b"hello")?;     // v = 27 | 28 (EIP-191)

println!("Address:   {}", signer.address());  // identity of this key only (not HD)
println!("Signature: {}", msg.to_hex());
println!("Scheme:    {}", msg.scheme());      // 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):

```rust
// Cosmos: build ADR-036 StdSignDoc bytes externally (e.g. via kobe / app layer)
use signer::cosmos::Signer;

let signer = Signer::from_hex("4c0883a6...")?;
let signature = signer.sign_transaction(sign_doc_bytes)?;
```

Bitcoin message signing selects the BIP-137 header for the target address type; the default matches Bitcoin Core `signmessage` (compressed P2PKH):

```rust
use signer::btc::{BitcoinMessageAddressType, SignMessage, Signer};

let signer = Signer::from_hex("4c0883a6...")?;
let compressed = signer.sign_message(b"Hi")?; // v = 31 | 32
let bech32 = signer.sign_message_with(BitcoinMessageAddressType::SegwitBech32, b"Hi")?;
```

### Architecture

```text
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](https://github.com/qntx/kobe) accounts:

```rust
use kobe::Wallet;
use kobe::evm::Deriver;
use signer::evm::{FromDerived, Signer};

let wallet = Wallet::from_mnemonic(
    "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
    None,
)?;
let account = Deriver::new(&wallet).derive(0)?;
let signer = Signer::from_derived(&account)?;
let sig = signer.sign_message(b"hello")?;
```

## 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** — `k256` for secp256k1 ECDSA and BIP-340 Schnorr, `ed25519-dalek` for Ed25519; hashing via `sha2` / `sha3` / `blake2` / `ripemd`; encoding via `bech32` / `bs58`
- **Capability-split traits** — mandatory `SignDigest::sign_digest` (`&[u8; 32]` → `SignOutput`); optional `SignMessage`, `ExtractSignableBytes`, `EncodeSignedTransaction`; protocol `sign_transaction` is 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 JSON `scheme`; `v` offsets live in `v_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`](docs/KAT_MATRIX.md)
- **`no_std` + `alloc`** — library crates compile on `thumbv7m-none-eabi` under CI
- **Security hardened** — `ZeroizeOnDrop` / `Zeroizing`, redacted `Debug`, CLI key ingress via stdin or `@path`
- **Kobe integration** — optional `kobe` feature: `FromDerived` over kobe account types
- **Strict linting** — Clippy `pedantic` + `nursery` + `correctness` (deny), `rust_2018_idioms` deny, zero warnings on nightly; `just check-names` bans legacy dual names

## Crates

See **[`crates/README.md`](crates/README.md)** for the full crate table, dependency graph, and feature flag reference.

## Contributing

See **[`CONTRIBUTING.md`](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`](SECURITY.md)**.

- Private keys wrapped in [`zeroize`](https://docs.rs/zeroize) — wiped on drop; secret exports return `Zeroizing<…>`
- `Debug` for secret-bearing types redacts material (`[REDACTED]`); do not rely on `{:?}` for secrets
- CLI prefers `-k -` / `-k @path` over argv on shared hosts
- Random generation uses OS CSPRNG via [`getrandom`](https://docs.rs/getrandom); prefer `try_random()` where entropy can fail
- `SignDigest` / `SignMessage` require `Send + Sync` for async executors
- No key material is logged or persisted by the workspace

## License

Licensed under either of:

- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
- MIT License ([LICENSE-MIT](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.

---

<div align="center">

A **[QuantX](https://qntx.fun)** open-source project.

<a href="https://qntx.fun"><img alt="QuantX" width="369" src="https://raw.githubusercontent.com/qntx/.github/main/profile/qntx.svg" /></a>

Code is law. We write both.

</div>