Skip to main content

det_keygen/
lib.rs

1//! # det-keygen
2//!
3//! This crate implements an *experimental* port of the Python [reference implementation](https://github.com/C2SP/C2SP/blob/main/det-keygen/ecdsa.py) of [C2SP](https://github.com/C2SP/C2SP)'s ECDSA [Deterministic Key Generation](https://c2sp.org/det-keygen).
4//!
5//! This specification enables the derivation of ECDSA private keys from arbitrary seeds (using FIPS 186-5 methods only) which
6//! should contain at least 192 bits of entropy.
7//! # Examples
8//!
9//! ```
10//! // Generate a P-256 private key based on a 160-bit seed
11//!
12//! use det_keygen::{Keygen, P256};
13//!
14//! let seed = b"When in doubt, don't";
15//!
16//! let keygen = Keygen::<P256>::new(seed);
17//! let key = keygen.generate();
18//! ```
19
20use crypto_bigint::{U256, Uint, Zero};
21
22/// Tag represents a 256-bit hmac tag.
23type Tag = [u8; 256 / 8];
24
25/// Curve describes an implementation independent description of an elliptic curve.
26pub trait Curve {
27    /// Length of the curve's field element in bytes.
28    const LEN: usize;
29
30    /// Personalization string used in the key generation process.
31    const PERSONALIZATION_STRING: &'static [u8];
32
33    // FIXME: replace with with hybrid-array as soon as RFC2532 becomes stable
34    /// Private key output of this curve.
35    type Output;
36
37    /// Generate a key pair from a seed.
38    fn generate(temp: &[u8]) -> Option<Self::Output>;
39}
40
41/// Keygen derives a key for a curve following the c2sp.org/det-keygen.
42pub struct Keygen<C: Curve> {
43    k: Tag,
44    v: Tag,
45    _phantom: std::marker::PhantomData<C>,
46}
47
48impl<C: Curve> Keygen<C> {
49    /// Instantiate the key generation process. Seed must be at least 128-bits long with 160-bit of entropy preferred.
50    pub fn new(seed: &[u8]) -> Self {
51        let k = [0; 256 / 8];
52        let v = [1; 256 / 8];
53
54        let k = hmac256(
55            k.as_ref(),
56            [v.as_slice(), &[0u8], seed, C::PERSONALIZATION_STRING]
57                .concat()
58                .as_ref(),
59        );
60
61        let v = hmac256(k.as_ref(), &v);
62
63        let k = hmac256(
64            k.as_ref(),
65            [v.as_slice(), &[1u8], seed, C::PERSONALIZATION_STRING]
66                .concat()
67                .as_ref(),
68        );
69
70        let v = hmac256(k.as_ref(), v.as_ref());
71
72        Self {
73            k,
74            v,
75            _phantom: std::marker::PhantomData,
76        }
77    }
78
79    #[doc(hidden)]
80    fn candidate(&mut self) -> Vec<u8> {
81        let mut temp = vec![];
82
83        while temp.len() < C::LEN {
84            self.v = hmac256(self.k.as_ref(), self.v.as_ref());
85            temp.extend_from_slice(self.v.as_ref());
86        }
87
88        temp
89    }
90
91    /// Generate the Curve output.
92    pub fn generate(mut self) -> C::Output {
93        let temp = self.candidate();
94        let res = C::generate(&temp);
95
96        res.unwrap_or_else(|| {
97            // try once more
98            self.k = hmac256(
99                self.k.as_ref(),
100                [self.v.as_slice(), &[0u8]].concat().as_ref(),
101            );
102            self.v = hmac256(self.k.as_slice(), self.v.as_ref());
103
104            let temp = self.candidate();
105            C::generate(&temp).unwrap()
106        })
107    }
108}
109
110/// P-256 Curve implementation.
111pub struct P256 {}
112
113impl Curve for P256 {
114    const PERSONALIZATION_STRING: &'static [u8] = b"det ECDSA key gen P-256";
115    const LEN: usize = 32;
116
117    type Output = [u8; Self::LEN];
118
119    fn generate(temp: &[u8]) -> Option<Self::Output> {
120        const N: U256 =
121            U256::from_be_hex("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551");
122
123        let d = bits2int(temp, 256);
124
125        if d.is_zero().into() || d >= N {
126            None
127        } else {
128            Some(d.to_be_bytes())
129        }
130    }
131}
132
133fn bits2int<const LIMBS: usize>(data: &[u8], qlen: usize) -> Uint<LIMBS> {
134    let mut x = Uint::<LIMBS>::from_be_slice(data);
135
136    let blen = data.len() * 8;
137
138    if blen > qlen {
139        let shift = blen - qlen;
140        x >>= shift;
141    }
142
143    x
144}
145
146#[cfg(feature = "ring")]
147fn hmac256(k: &[u8], v: &[u8]) -> Tag {
148    use ring::hmac;
149
150    let k = hmac::Key::new(hmac::HMAC_SHA256, k);
151    hmac::sign(&k, v)
152        .as_ref()
153        .try_into()
154        .expect("tag larger then 256 bits")
155}
156
157#[cfg(test)]
158mod test {
159    use super::*;
160
161    use std::fs;
162
163    use base64::{Engine as _, engine::general_purpose};
164    use p256::SecretKey;
165    use pkcs8::EncodePrivateKey;
166    use proptest::prelude::*;
167    use ring::{
168        rand::SystemRandom,
169        signature::{
170            ECDSA_P256_SHA256_FIXED, ECDSA_P256_SHA256_FIXED_SIGNING, EcdsaKeyPair, KeyPair,
171            UnparsedPublicKey,
172        },
173    };
174    use serde::{Deserialize, Deserializer};
175
176    fn deserialize_base64<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
177    where
178        D: Deserializer<'de>,
179    {
180        let s: String = Deserialize::deserialize(deserializer)?;
181        general_purpose::STANDARD
182            .decode(&s)
183            .map_err(serde::de::Error::custom)
184    }
185
186    #[derive(Deserialize, Debug)]
187    struct TestVector {
188        curve: String,
189        #[serde(deserialize_with = "deserialize_base64")]
190        seed: Vec<u8>,
191        private_key_pkcs8: String,
192    }
193
194    proptest! {
195    #[test]
196    fn test_fuzz_p256(seed: Vec<u8>) {
197        prop_assume!(!seed.is_empty());
198
199        const MSG: &[u8] = b"hello world";
200
201            let rng = SystemRandom::new();
202
203            let g = Keygen::<P256>::new(&seed);
204            let key = g.generate();
205
206            let key = SecretKey::from_bytes(&key.into()).unwrap();
207            let key = key.to_pkcs8_der().unwrap();
208            let keys =
209                EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, key.as_bytes(), &rng)
210                    .unwrap();
211
212            let signature = keys.sign(&rng, MSG).unwrap();
213
214            let public_key = UnparsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, keys.public_key());
215
216            let _sig = public_key.verify(MSG, signature.as_ref()).unwrap();
217        }
218    }
219
220    #[test]
221    fn test_vectors() {
222        let rng = SystemRandom::new();
223
224        let vectors = fs::read_to_string("src/escdsa.json").unwrap();
225        let vectors: Vec<TestVector> = serde_json::from_str(&vectors).unwrap();
226
227        for vector in vectors.iter().filter(|v| v.curve == "secp256r1") {
228            let g = Keygen::<P256>::new(&vector.seed);
229
230            let key = g.generate();
231            let key = SecretKey::from_bytes(&key.into()).unwrap();
232            let key = key.to_pkcs8_der().unwrap();
233            let key = key.as_bytes();
234
235            let _keys = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, key, &rng);
236
237            let key = general_purpose::STANDARD.encode(key);
238            assert_eq!(
239                key, vector.private_key_pkcs8,
240                "testing with key {:?}",
241                vector.private_key_pkcs8
242            );
243        }
244    }
245}