Skip to main content

dig_keystore/scheme/
mod.rs

1//! Key schemes: the typed layer that defines how raw secret bytes become
2//! usable keys, public keys, and signatures.
3//!
4//! # Concept
5//!
6//! A *scheme* answers three questions for any key family:
7//!
8//! 1. **What is stored?** — Typically a 32-byte seed; sometimes a full keypair
9//!    or curve scalar.
10//! 2. **How is a public key derived?** — Pure function of the stored bytes.
11//! 3. **How is a signature produced?** — Pure function of the stored bytes and
12//!    a message.
13//!
14//! Each [`KeyScheme`] implementation also pins two on-disk identifiers:
15//!
16//! - A 6-byte **magic prefix** (e.g., `b"DIGVK1"`) — the first bytes of every
17//!   file of this scheme. Human-readable for support ("what is this file?"),
18//!   machine-enforceable for type safety.
19//! - A 2-byte **scheme id** (e.g., `0x0001`) — included in the header to
20//!   guard against MAGIC-collisions if we ever need to distinguish minor
21//!   variants without bumping the magic.
22//!
23//! # Supplied schemes
24//!
25//! | Scheme | Magic | ID | Curve | Role |
26//! |---|---|---|---|---|
27//! | [`BlsSigning`] | `DIGVK1` | 0x0001 | BLS12-381 G1/G2 | DIG L2 validator signing key |
28//! | [`L1WalletBls`] | `DIGLW1` | 0x0003 | BLS12-381 G1/G2 | Chia L1 wallet master seed |
29//!
30//! Additional schemes (e.g., `L1WalletSecp256k1` for hypothetical Ethereum L1
31//! wallets, or hardware-signer wrappers) can be added by implementing the
32//! trait in a fresh module.
33//!
34//! # Why separate types and not a runtime enum
35//!
36//! A runtime enum (`enum KeyType { Bls, L1Wallet, ... }`) would force every
37//! caller into a `match` at every sign call. The trait-based approach:
38//!
39//! - Makes `Keystore<BlsSigning>` and `Keystore<L1WalletBls>` distinct types;
40//!   you cannot accidentally feed a validator key where a wallet key was
41//!   expected at compile time.
42//! - Lets each scheme choose its own `PublicKey` and `Signature` associated
43//!   types. `BlsSigning` produces `chia_bls::Signature`; a future secp256k1
44//!   scheme would produce `k256::ecdsa::Signature`.
45//! - Keeps the cryptographic code of each scheme localised for auditing.
46//!
47//! # References
48//!
49//! - [`chia-bls` crate](https://crates.io/crates/chia-bls) 0.26 — BLS12-381
50//!   primitives used by all shipped schemes.
51//! - [IETF draft-irtf-cfrg-bls-signature-05](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05)
52//!   — the augmented BLS scheme (`BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_AUG_`)
53//!   `chia-bls::sign` implements.
54//! - [EIP-2333](https://eips.ethereum.org/EIPS/eip-2333) — master-key
55//!   derivation from a seed (what `SecretKey::from_seed` follows).
56
57use rand_core::{CryptoRng, RngCore};
58use zeroize::Zeroizing;
59
60use crate::error::Result;
61
62mod bls_signing;
63mod l1_wallet_bls;
64
65pub use bls_signing::BlsSigning;
66pub use l1_wallet_bls::L1WalletBls;
67
68/// Trait implemented by every supported key scheme.
69///
70/// Implementors define:
71/// - how to generate fresh secret bytes (typically a 32-byte seed);
72/// - how to derive a public key from the secret bytes;
73/// - how to sign a byte message with the secret bytes;
74/// - the 6-byte on-disk magic prefix and 2-byte scheme id.
75///
76/// # Contract
77///
78/// - `public_key` and `sign` must be **pure functions** of the secret bytes
79///   and message — no external state, no RNG calls. Determinism is required so
80///   signatures are reproducible across runs given identical inputs.
81/// - `generate` must use the provided `CryptoRng` — no `OsRng::default()`
82///   shortcuts, so tests can inject a deterministic RNG.
83/// - Implementors must not panic on malformed input. Return an
84///   [`Err`](crate::error::KeystoreError) instead.
85///
86/// # Safety
87///
88/// Implementations are expected to handle secret bytes through zeroizing
89/// wrappers and never log / Debug-print them. The [`crate::Keystore`]
90/// orchestration and [`crate::SignerHandle`] guarantee that secret bytes
91/// passed to `public_key` / `sign` live inside a [`Zeroizing`] buffer for the
92/// duration of the call.
93pub trait KeyScheme: Send + Sync + 'static {
94    /// Public key type returned by [`public_key`](Self::public_key).
95    ///
96    /// For BLS schemes, this is `chia_bls::PublicKey` (48-byte G1 compressed).
97    type PublicKey: Clone + core::fmt::Debug + Send + Sync;
98
99    /// Signature type returned by [`sign`](Self::sign).
100    ///
101    /// For BLS schemes, this is `chia_bls::Signature` (96-byte G2 compressed).
102    type Signature: Clone + Send + Sync;
103
104    /// 6-byte file magic. Must be unique per scheme.
105    ///
106    /// Recognized values: `DIGVK1` (validator key), `DIGLW1` (L1 wallet).
107    /// New schemes must register their magic in [`crate::format::is_known_magic`]
108    /// so the decoder accepts it.
109    const MAGIC: [u8; 6];
110
111    /// Human-readable name for error messages (e.g., `"BlsSigning"`).
112    ///
113    /// Shown to users in [`crate::KeystoreError::SchemeMismatch`] so they can
114    /// tell why e.g. loading a wallet file as a validator key fails.
115    const NAME: &'static str;
116
117    /// Scheme id stored in the file header.
118    ///
119    /// Allocation:
120    /// - `0x0001` — [`BlsSigning`]
121    /// - `0x0002` — *(reserved)* `L1WalletSecp256k1` (not implemented)
122    /// - `0x0003` — [`L1WalletBls`]
123    const SCHEME_ID: u16;
124
125    /// Length (in bytes) of the canonical secret.
126    ///
127    /// [`crate::Keystore::create`] passes exactly this many bytes from
128    /// [`generate`](Self::generate) into the encryption layer; [`crate::Keystore::unlock`]
129    /// validates the decrypted plaintext length equals this constant.
130    const SECRET_LEN: usize;
131
132    /// Generate fresh secret bytes.
133    ///
134    /// Must return **exactly** [`SECRET_LEN`](Self::SECRET_LEN) bytes.
135    /// Implementations fill the buffer from `rng` — the caller-supplied RNG
136    /// is used directly, not a hidden `OsRng`, so tests with deterministic
137    /// seeds work.
138    fn generate<R: RngCore + CryptoRng>(rng: &mut R) -> Zeroizing<Vec<u8>>;
139
140    /// Derive the public key from the given secret bytes.
141    ///
142    /// Returns [`Err`](crate::error::KeystoreError) if `secret.len() != SECRET_LEN`,
143    /// or if the bytes are malformed for the scheme (e.g., invalid secp256k1
144    /// scalar). [`BlsSigning`] derives via `from_seed`, which accepts any 32
145    /// bytes; [`L1WalletBls`] deserializes an already-derived canonical
146    /// scalar via `from_bytes` and rejects out-of-range values (rare, but
147    /// possible — see its module docs).
148    fn public_key(secret: &[u8]) -> Result<Self::PublicKey>;
149
150    /// Sign `msg` using the given secret bytes.
151    ///
152    /// Returns [`Err`](crate::error::KeystoreError) if `secret.len() != SECRET_LEN`.
153    /// For BLS schemes, the underlying call is
154    /// [`chia_bls::sign`](https://docs.rs/chia-bls/latest/chia_bls/fn.sign.html),
155    /// which uses Chia's augmented scheme (AUG) — each signature incorporates
156    /// the signer's pubkey into the signed message to foreclose rogue-key
157    /// attacks.
158    fn sign(secret: &[u8], msg: &[u8]) -> Result<Self::Signature>;
159}