Skip to main content

kobe_casper/
key_algo.rs

1//! Casper signature algorithm / derivation-path style.
2
3use alloc::format;
4use alloc::string::String;
5use core::fmt;
6use core::str::FromStr;
7
8use kobe_primitives::ParseDerivationStyleError;
9
10/// Signature algorithm and matching HD path layout for Casper.
11///
12/// Implements [`kobe_primitives::DerivationStyle`] so CLI / generic helpers
13/// can enumerate algorithms the same way they enumerate EVM styles.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
15#[non_exhaustive]
16pub enum KeyAlgo {
17    /// secp256k1 — Ledger / casper-cli default path `m/44'/506'/0'/0/{index}`.
18    ///
19    /// This is Kobe's **default** for Casper interop with hardware wallets.
20    #[default]
21    Secp256k1,
22    /// Ed25519 — SLIP-10 full-hardened path `m/44'/506'/0'/0'/{index}'`.
23    ///
24    /// Matches `casper-client keygen` default algorithm (different path
25    /// convention from Ledger secp).
26    Ed25519,
27}
28
29/// Every variant — returned by [`kobe_primitives::DerivationStyle::all`].
30const ALL_ALGOS: &[KeyAlgo] = &[KeyAlgo::Secp256k1, KeyAlgo::Ed25519];
31
32/// Tokens accepted by [`KeyAlgo::from_str`].
33const ACCEPTED_TOKENS: &[&str] = &["secp256k1", "secp", "ecdsa", "ed25519", "ed", "eddsa"];
34
35impl kobe_primitives::DerivationStyle for KeyAlgo {
36    fn path(self, index: u32) -> String {
37        match self {
38            Self::Secp256k1 => format!("m/44'/506'/0'/0/{index}"),
39            Self::Ed25519 => format!("m/44'/506'/0'/0'/{index}'"),
40        }
41    }
42
43    fn name(self) -> &'static str {
44        match self {
45            Self::Secp256k1 => "secp256k1",
46            Self::Ed25519 => "ed25519",
47        }
48    }
49
50    fn all() -> &'static [Self] {
51        ALL_ALGOS
52    }
53}
54
55impl fmt::Display for KeyAlgo {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.write_str(<Self as kobe_primitives::DerivationStyle>::name(*self))
58    }
59}
60
61impl FromStr for KeyAlgo {
62    type Err = ParseDerivationStyleError;
63
64    fn from_str(s: &str) -> Result<Self, Self::Err> {
65        match s.to_lowercase().as_str() {
66            "secp256k1" | "secp" | "ecdsa" => Ok(Self::Secp256k1),
67            "ed25519" | "ed" | "eddsa" => Ok(Self::Ed25519),
68            _ => Err(ParseDerivationStyleError::new("casper", s, ACCEPTED_TOKENS)),
69        }
70    }
71}
72
73#[cfg(test)]
74#[allow(clippy::unwrap_used, reason = "unit tests")]
75mod tests {
76    use kobe_primitives::DerivationStyle as _;
77
78    use super::*;
79
80    #[test]
81    fn paths() {
82        assert_eq!(KeyAlgo::Secp256k1.path(0), "m/44'/506'/0'/0/0");
83        assert_eq!(KeyAlgo::Secp256k1.path(7), "m/44'/506'/0'/0/7");
84        assert_eq!(KeyAlgo::Ed25519.path(0), "m/44'/506'/0'/0'/0'");
85        assert_eq!(KeyAlgo::Ed25519.path(3), "m/44'/506'/0'/0'/3'");
86    }
87
88    #[test]
89    fn from_str_aliases() {
90        assert_eq!("secp256k1".parse::<KeyAlgo>().unwrap(), KeyAlgo::Secp256k1);
91        assert_eq!("SECP".parse::<KeyAlgo>().unwrap(), KeyAlgo::Secp256k1);
92        assert_eq!("ed25519".parse::<KeyAlgo>().unwrap(), KeyAlgo::Ed25519);
93        assert_eq!("ed".parse::<KeyAlgo>().unwrap(), KeyAlgo::Ed25519);
94        assert!("rsa".parse::<KeyAlgo>().is_err());
95    }
96
97    #[test]
98    fn default_is_secp() {
99        assert_eq!(KeyAlgo::default(), KeyAlgo::Secp256k1);
100    }
101}