Skip to main content

xrpl_mithril_wallet/
algorithm.rs

1//! XRPL key algorithms and low-level cryptographic operations.
2//!
3//! This module defines the two signature algorithms supported by the XRPL and
4//! encapsulates all direct calls to cryptographic crates behind feature gates.
5//! No other module in this crate should import `k256`, `secp256k1`, or
6//! `ed25519_dalek` directly.
7
8// Require at least one secp256k1 backend.
9#[cfg(not(any(feature = "pure-rust-crypto", feature = "native-crypto")))]
10compile_error!(
11    "xrpl-wallet: enable either `pure-rust-crypto` (default) or `native-crypto` feature \
12     to provide a secp256k1 backend"
13);
14
15use crate::error::WalletError;
16
17/// The two signature algorithms supported by the XRPL.
18///
19/// Most XRPL accounts use [`Secp256k1`](Algorithm::Secp256k1). Choose
20/// [`Ed25519`](Algorithm::Ed25519) for faster signing and smaller keys.
21///
22/// # Examples
23///
24/// ```
25/// use xrpl_mithril_wallet::{Wallet, Algorithm};
26///
27/// // secp256k1 is the most widely used on the XRPL
28/// let secp_wallet = Wallet::generate(Algorithm::Secp256k1).unwrap();
29/// assert_eq!(secp_wallet.algorithm(), Algorithm::Secp256k1);
30///
31/// // Ed25519 produces public keys prefixed with 0xED
32/// let ed_wallet = Wallet::generate(Algorithm::Ed25519).unwrap();
33/// assert_eq!(ed_wallet.public_key()[0], 0xED);
34///
35/// // Display shows the algorithm name
36/// assert_eq!(format!("{}", Algorithm::Secp256k1), "secp256k1");
37/// assert_eq!(format!("{}", Algorithm::Ed25519), "ed25519");
38/// ```
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40#[non_exhaustive]
41pub enum Algorithm {
42    /// ECDSA on the secp256k1 curve. The default and most common algorithm.
43    Secp256k1,
44    /// Ed25519 (EdDSA). Used by accounts whose seed was derived with the Ed25519 family.
45    Ed25519,
46}
47
48impl core::fmt::Display for Algorithm {
49    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50        match self {
51            Self::Secp256k1 => write!(f, "secp256k1"),
52            Self::Ed25519 => write!(f, "ed25519"),
53        }
54    }
55}
56
57// ---------------------------------------------------------------------------
58// secp256k1 operations — pure Rust backend (k256)
59// ---------------------------------------------------------------------------
60
61#[cfg(all(feature = "pure-rust-crypto", not(feature = "native-crypto")))]
62pub(crate) mod secp256k1_impl {
63    use super::*;
64    use k256::ecdsa::signature::hazmat::{PrehashSigner, PrehashVerifier};
65    use k256::ecdsa::signature::SignatureEncoding;
66    use k256::ecdsa::{SigningKey, VerifyingKey};
67    use k256::elliptic_curve::ops::Reduce;
68    use k256::{Scalar, U256};
69
70    /// Check whether `bytes` is a valid secp256k1 secret key (non-zero, < curve order).
71    pub(crate) fn is_valid_secret(bytes: &[u8; 32]) -> bool {
72        SigningKey::from_slice(bytes).is_ok()
73    }
74
75    /// Derive the 33-byte compressed public key from a 32-byte secret.
76    pub(crate) fn public_key(secret: &[u8; 32]) -> Result<[u8; 33], WalletError> {
77        let sk = SigningKey::from_slice(secret)
78            .map_err(|e| WalletError::InvalidSecretKey(e.to_string()))?;
79        let vk = sk.verifying_key();
80        let point = vk.to_encoded_point(true);
81        let mut pubkey = [0u8; 33];
82        pubkey.copy_from_slice(point.as_bytes());
83        Ok(pubkey)
84    }
85
86    /// Add two 32-byte scalars modulo the secp256k1 curve order.
87    ///
88    /// Used during XRPL account key derivation: `account_key = root_key + intermediate`.
89    pub(crate) fn scalar_add(a: &[u8; 32], b: &[u8; 32]) -> Result<[u8; 32], WalletError> {
90        let sa = <Scalar as Reduce<U256>>::reduce_bytes(&(*a).into());
91        let sb = <Scalar as Reduce<U256>>::reduce_bytes(&(*b).into());
92        let sum = sa + sb;
93        if sum.is_zero().into() {
94            return Err(WalletError::KeyDerivationFailed(
95                "scalar addition produced zero".into(),
96            ));
97        }
98        Ok(sum.to_bytes().into())
99    }
100
101    /// Sign a 32-byte pre-hashed message with ECDSA secp256k1.
102    ///
103    /// Returns the DER-encoded signature. The `hash` must be a SHA-512/Half
104    /// signing hash — this function does **not** hash the input again.
105    pub(crate) fn sign(secret: &[u8; 32], hash: &[u8; 32]) -> Result<Vec<u8>, WalletError> {
106        let sk = SigningKey::from_slice(secret)
107            .map_err(|e| WalletError::InvalidSecretKey(e.to_string()))?;
108        let sig: k256::ecdsa::Signature = sk
109            .sign_prehash(hash)
110            .map_err(|e| WalletError::SigningFailed(e.to_string()))?;
111        Ok(sig.to_der().to_vec())
112    }
113
114    /// Verify a DER-encoded ECDSA secp256k1 signature against a 32-byte hash.
115    pub(crate) fn verify(
116        pubkey: &[u8],
117        hash: &[u8; 32],
118        signature: &[u8],
119    ) -> Result<(), WalletError> {
120        let vk = VerifyingKey::from_sec1_bytes(pubkey)
121            .map_err(|e| WalletError::InvalidPublicKey(e.to_string()))?;
122        let sig = k256::ecdsa::Signature::from_der(signature)
123            .map_err(|e| WalletError::InvalidSignature(e.to_string()))?;
124        vk.verify_prehash(hash, &sig)
125            .map_err(|e| WalletError::VerificationFailed(e.to_string()))
126    }
127}
128
129// ---------------------------------------------------------------------------
130// secp256k1 operations — native C backend (rust-secp256k1)
131// ---------------------------------------------------------------------------
132
133#[cfg(feature = "native-crypto")]
134pub(crate) mod secp256k1_impl {
135    use super::*;
136    use secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
137
138    /// Check whether `bytes` is a valid secp256k1 secret key.
139    pub(crate) fn is_valid_secret(bytes: &[u8; 32]) -> bool {
140        SecretKey::from_byte_array(*bytes).is_ok()
141    }
142
143    /// Derive the 33-byte compressed public key from a 32-byte secret.
144    pub(crate) fn public_key(secret: &[u8; 32]) -> Result<[u8; 33], WalletError> {
145        let secp = Secp256k1::new();
146        let sk = SecretKey::from_byte_array(*secret)
147            .map_err(|e| WalletError::InvalidSecretKey(e.to_string()))?;
148        let pk = PublicKey::from_secret_key(&secp, &sk);
149        Ok(pk.serialize())
150    }
151
152    /// Add two 32-byte scalars modulo the secp256k1 curve order.
153    pub(crate) fn scalar_add(a: &[u8; 32], b: &[u8; 32]) -> Result<[u8; 32], WalletError> {
154        let mut sk = SecretKey::from_byte_array(*a)
155            .map_err(|e| WalletError::KeyDerivationFailed(e.to_string()))?;
156        let tweak = secp256k1::Scalar::from_be_bytes(*b)
157            .map_err(|e| WalletError::KeyDerivationFailed(e.to_string()))?;
158        sk = sk
159            .add_tweak(&tweak)
160            .map_err(|e| WalletError::KeyDerivationFailed(e.to_string()))?;
161        Ok(sk.secret_bytes())
162    }
163
164    /// Sign a 32-byte pre-hashed message with ECDSA secp256k1 (DER-encoded).
165    pub(crate) fn sign(secret: &[u8; 32], hash: &[u8; 32]) -> Result<Vec<u8>, WalletError> {
166        let secp = Secp256k1::new();
167        let sk = SecretKey::from_byte_array(*secret)
168            .map_err(|e| WalletError::InvalidSecretKey(e.to_string()))?;
169        let msg = Message::from_digest(*hash);
170        let sig = secp.sign_ecdsa(msg, &sk);
171        Ok(sig.serialize_der().to_vec())
172    }
173
174    /// Verify a DER-encoded ECDSA secp256k1 signature against a 32-byte hash.
175    pub(crate) fn verify(
176        pubkey: &[u8],
177        hash: &[u8; 32],
178        signature: &[u8],
179    ) -> Result<(), WalletError> {
180        let secp = Secp256k1::new();
181        let pk = PublicKey::from_slice(pubkey)
182            .map_err(|e| WalletError::InvalidPublicKey(e.to_string()))?;
183        let msg = Message::from_digest(*hash);
184        let sig = secp256k1::ecdsa::Signature::from_der(signature)
185            .map_err(|e| WalletError::InvalidSignature(e.to_string()))?;
186        secp.verify_ecdsa(msg, &sig, &pk)
187            .map_err(|e| WalletError::VerificationFailed(e.to_string()))
188    }
189}
190
191// Re-export the active secp256k1 implementation under a unified namespace.
192pub(crate) use secp256k1_impl as secp256k1_ops;
193
194// ---------------------------------------------------------------------------
195// Ed25519 operations — always uses ed25519-dalek
196// ---------------------------------------------------------------------------
197
198pub(crate) mod ed25519_ops {
199    use super::*;
200    use ed25519_dalek::{Signer, Verifier};
201
202    /// Derive the 32-byte Ed25519 public key from a 32-byte secret.
203    pub(crate) fn public_key(secret: &[u8; 32]) -> [u8; 32] {
204        let sk = ed25519_dalek::SigningKey::from_bytes(secret);
205        sk.verifying_key().to_bytes()
206    }
207
208    /// Sign a message with Ed25519.
209    ///
210    /// Unlike secp256k1, Ed25519 signs the **raw signing data** (prefix +
211    /// serialized transaction), not a pre-computed hash. Ed25519's internal
212    /// algorithm applies its own SHA-512 hashing.
213    pub(crate) fn sign(secret: &[u8; 32], message: &[u8]) -> Vec<u8> {
214        let sk = ed25519_dalek::SigningKey::from_bytes(secret);
215        let sig = sk.sign(message);
216        sig.to_bytes().to_vec()
217    }
218
219    /// Verify an Ed25519 signature against a message.
220    pub(crate) fn verify(
221        pubkey: &[u8; 32],
222        message: &[u8],
223        signature: &[u8],
224    ) -> Result<(), WalletError> {
225        let vk = ed25519_dalek::VerifyingKey::from_bytes(pubkey)
226            .map_err(|e| WalletError::InvalidPublicKey(e.to_string()))?;
227        let sig = ed25519_dalek::Signature::from_slice(signature)
228            .map_err(|e| WalletError::InvalidSignature(e.to_string()))?;
229        vk.verify(message, &sig)
230            .map_err(|e| WalletError::VerificationFailed(e.to_string()))
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237
238    #[test]
239    fn secp256k1_public_key_is_33_bytes() {
240        // A known-valid 32-byte secret (not zero, not >= curve order)
241        let secret = [1u8; 32];
242        let pubkey = secp256k1_ops::public_key(&secret).expect("valid secret");
243        assert_eq!(pubkey.len(), 33);
244        // Compressed keys start with 0x02 or 0x03
245        assert!(pubkey[0] == 0x02 || pubkey[0] == 0x03);
246    }
247
248    #[test]
249    fn secp256k1_sign_verify_round_trip() {
250        let secret = [42u8; 32];
251        let hash = [0xABu8; 32];
252
253        let pubkey = secp256k1_ops::public_key(&secret).expect("valid secret");
254        let signature = secp256k1_ops::sign(&secret, &hash).expect("sign");
255        secp256k1_ops::verify(&pubkey, &hash, &signature).expect("verify should succeed");
256    }
257
258    #[test]
259    fn secp256k1_verify_wrong_hash_fails() {
260        let secret = [42u8; 32];
261        let hash = [0xABu8; 32];
262        let wrong_hash = [0xCDu8; 32];
263
264        let pubkey = secp256k1_ops::public_key(&secret).expect("valid secret");
265        let signature = secp256k1_ops::sign(&secret, &hash).expect("sign");
266        assert!(secp256k1_ops::verify(&pubkey, &wrong_hash, &signature).is_err());
267    }
268
269    #[test]
270    fn secp256k1_scalar_add_non_zero() {
271        let a = [1u8; 32];
272        let b = [2u8; 32];
273        let sum = secp256k1_ops::scalar_add(&a, &b).expect("scalar add");
274        // Sum should differ from both inputs
275        assert_ne!(sum, a);
276        assert_ne!(sum, b);
277    }
278
279    #[test]
280    fn secp256k1_zero_secret_rejected() {
281        let zero = [0u8; 32];
282        assert!(!secp256k1_ops::is_valid_secret(&zero));
283    }
284
285    #[test]
286    fn ed25519_public_key_is_32_bytes() {
287        let secret = [1u8; 32];
288        let pubkey = ed25519_ops::public_key(&secret);
289        assert_eq!(pubkey.len(), 32);
290    }
291
292    #[test]
293    fn ed25519_sign_verify_round_trip() {
294        let secret = [42u8; 32];
295        let message = b"test message for Ed25519 signing";
296
297        let pubkey = ed25519_ops::public_key(&secret);
298        let signature = ed25519_ops::sign(&secret, message);
299        assert_eq!(signature.len(), 64);
300        ed25519_ops::verify(&pubkey, message, &signature).expect("verify should succeed");
301    }
302
303    #[test]
304    fn ed25519_verify_wrong_message_fails() {
305        let secret = [42u8; 32];
306        let message = b"correct message";
307        let wrong_message = b"wrong message";
308
309        let pubkey = ed25519_ops::public_key(&secret);
310        let signature = ed25519_ops::sign(&secret, message);
311        assert!(ed25519_ops::verify(&pubkey, wrong_message, &signature).is_err());
312    }
313
314    #[test]
315    fn algorithm_display() {
316        assert_eq!(format!("{}", Algorithm::Secp256k1), "secp256k1");
317        assert_eq!(format!("{}", Algorithm::Ed25519), "ed25519");
318    }
319}