dup_crypto/keys/ed25519/
bip32.rs

1//  Copyright (C) 2020 Éloïs SANCHEZ.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16//! Implement [BIP32-Ed25519 specifications](https://drive.google.com/file/d/0ByMtMw2hul0EMFJuNnZORDR2NDA/view).
17//!
18//! # Generate an HD wallet
19//!
20//! ```
21//! use dup_crypto::seeds::Seed32;
22//! use dup_crypto::keys::KeyPair as _;
23//! use dup_crypto::keys::ed25519::bip32::KeyPair;
24//!
25//! let seed = Seed32::random().expect("fail to generate random seed");
26//!
27//! let master_key_pair = KeyPair::from_seed(seed);
28//!
29//! let master_public_key = master_key_pair.public_key();
30//! ```
31//!
32//! `dup_crypto::keys::ed25519::bip32::KeyPair` implement the `dup_crypto::keys::KeyPair` trait, so sign and verify like a classic ed25519 keypair.
33//!
34//! # Derive private key and public key
35//!
36//! ```
37//! use dup_crypto::seeds::Seed32;
38//! use dup_crypto::keys::KeyPair as _;
39//! use dup_crypto::keys::ed25519::bip32::{PrivateDerivationPath, KeyPair, PublicKeyWithChainCode};
40//! use dup_crypto::utils::U31;
41//!
42//! let master_keypair = KeyPair::from_seed(Seed32::random().expect("fail to generate random seed"));
43//!
44//! let account_index = U31::new(2)?;
45//! let address_index = U31::new(3)?;
46//!
47//! // Derive master external keypair
48//! let derivation_path = PrivateDerivationPath::opaque(account_index, true, None)?;
49//! let external_keypair = master_keypair.derive(derivation_path);
50//!
51//! // Get master external public key and chain code
52//! let external_public_key = external_keypair.public_key();
53//! let external_chain_code = external_keypair.chain_code();
54//!
55//! // Derive a specific address with public derivation
56//! let address = PublicKeyWithChainCode {
57//!     public_key: external_public_key,
58//!     chain_code: external_chain_code,
59//! }
60//! .derive(address_index)?
61//! .public_key;
62//!
63//! // Verify that the private derivation give us the same address
64//! assert_eq!(
65//!     address,
66//!     master_keypair
67//!         .derive(PrivateDerivationPath::opaque(
68//!                 account_index,
69//!                 true,
70//!                 Some(address_index)
71//!         )?).public_key()
72//! );
73//! # Ok::<(), Box<dyn std::error::Error>>(())
74//! ```
75//!
76
77use crate::{
78    bases::b58::ToBase58,
79    hashs::{Hash, Hash64},
80    keys::{inner::KeyPairInner, KeyPair as KeyPairTrait, PublicKey as _},
81    mnemonic::Mnemonic,
82    seeds::Seed32,
83    utils::U31,
84};
85use arrayvec::ArrayVec;
86use std::fmt::Display;
87use thiserror::Error;
88use zeroize::Zeroize;
89
90const CHAIN_CODE_SIZE: usize = 32;
91const EXTENDED_SECRET_KEY_SIZE: usize = 64;
92
93/// BIP32 Chain code
94pub type ChainCode = [u8; 32];
95
96#[derive(Clone, Copy, Debug, Error)]
97#[error("The account index is not compatible with the account type.")]
98/// Invalid account index
99pub struct InvalidAccountIndex;
100
101#[derive(Clone, Copy, Debug, Error)]
102/// Derivation error
103pub enum PublicDerivationError {
104    /// Invalid addition
105    #[error("Invalid addition")]
106    InvalidAddition,
107    /// Expected soft derivation
108    #[error("Expected soft derivation")]
109    ExpectedSoftDerivation,
110}
111
112impl From<ed25519_bip32::DerivationError> for PublicDerivationError {
113    fn from(e: ed25519_bip32::DerivationError) -> Self {
114        match e {
115            ed25519_bip32::DerivationError::InvalidAddition => Self::InvalidAddition,
116            ed25519_bip32::DerivationError::ExpectedSoftDerivation => Self::ExpectedSoftDerivation,
117        }
118    }
119}
120
121#[derive(Clone, Copy, Debug, Eq, PartialEq)]
122/// BIP32 Derivation index
123struct DerivationIndex(u32);
124
125impl From<DerivationIndex> for u32 {
126    fn from(val: DerivationIndex) -> Self {
127        val.0
128    }
129}
130
131impl DerivationIndex {
132    /// Derivation 0'
133    pub const HARD_ZERO: Self = DerivationIndex(0x80000000);
134    /// Derivation 1'
135    pub const HARD_ONE: Self = DerivationIndex(0x80000001);
136
137    /// Hardened derivation
138    fn hard(index: U31) -> Self {
139        Self(index.into_u32() | 0x80000000)
140    }
141    /// Soft
142    fn soft(index: U31) -> Self {
143        Self(index.into_u32())
144    }
145}
146
147#[derive(Clone, Debug)]
148/// Private Derivation path
149pub struct PrivateDerivationPath(ArrayVec<DerivationIndex, 3>);
150
151impl PrivateDerivationPath {
152    /// Derive transparent account
153    pub fn transparent(account_index: U31) -> Result<Self, InvalidAccountIndex> {
154        if account_index.into_u32() % 3 == 0 {
155            let mut avec = ArrayVec::new();
156            avec.push(DerivationIndex::hard(account_index));
157            Ok(Self(avec))
158        } else {
159            Err(InvalidAccountIndex)
160        }
161    }
162    /// Derive internal keypair for semi-opaque account
163    pub fn semi_opaque_internal(
164        account_index: U31,
165        address_index_opt: Option<U31>,
166    ) -> Result<Self, InvalidAccountIndex> {
167        if account_index.into_u32() % 3 == 1 {
168            let mut avec = ArrayVec::new();
169            avec.push(DerivationIndex::hard(account_index));
170            avec.push(DerivationIndex::HARD_ONE);
171            if let Some(address_index) = address_index_opt {
172                avec.push(DerivationIndex::soft(address_index))
173            }
174            Ok(Self(avec))
175        } else {
176            Err(InvalidAccountIndex)
177        }
178    }
179    /// Derive external chain keypair for semi-opaque account
180    pub fn semi_opaque_external(account_index: U31) -> Result<Self, InvalidAccountIndex> {
181        if account_index.into_u32() % 3 == 1 {
182            let mut avec = ArrayVec::new();
183            avec.push(DerivationIndex::hard(account_index));
184            avec.push(DerivationIndex::HARD_ZERO);
185            Ok(Self(avec))
186        } else {
187            Err(InvalidAccountIndex)
188        }
189    }
190    /// Derive opaque account
191    pub fn opaque(
192        account_index: U31,
193        external: bool,
194        address_index_opt: Option<U31>,
195    ) -> Result<Self, InvalidAccountIndex> {
196        if account_index.into_u32() % 3 == 2 {
197            let mut avec = ArrayVec::new();
198            avec.push(DerivationIndex::hard(account_index));
199            if external {
200                avec.push(DerivationIndex::HARD_ZERO);
201            } else {
202                avec.push(DerivationIndex::HARD_ONE);
203            }
204            if let Some(address_index) = address_index_opt {
205                avec.push(DerivationIndex::soft(address_index))
206            }
207            Ok(Self(avec))
208        } else {
209            Err(InvalidAccountIndex)
210        }
211    }
212    fn into_iter(self) -> impl Iterator<Item = DerivationIndex> {
213        self.0.into_iter()
214    }
215}
216
217/// HDWallet extended public key (Ed25519 public key + BIP32 ChainCode)
218///
219#[derive(Clone, Copy, Debug, PartialEq)]
220pub struct PublicKeyWithChainCode {
221    /// Ed25519 public key
222    pub public_key: super::PublicKey,
223    /// BIP32 ChainCode
224    pub chain_code: ChainCode,
225}
226
227impl PublicKeyWithChainCode {
228    /// BIP32 Derivation
229    ///
230    /// May fail in 2 cases :
231    ///
232    /// * The derivation is of the hardened type
233    /// * The public key is not issued from a private key of HD wallet type
234    pub fn derive(&self, derivation_index: U31) -> Result<Self, PublicDerivationError> {
235        let xpub =
236            ed25519_bip32::XPub::from_pk_and_chaincode(&self.public_key.datas, &self.chain_code);
237        let xpub_derived = xpub.derive(
238            ed25519_bip32::DerivationScheme::V2,
239            derivation_index.into_u32(),
240        )?;
241        Ok(Self {
242            public_key: super::PublicKey::from_32_bytes_array(xpub_derived.public_key()),
243            chain_code: xpub_derived.chain_code(),
244        })
245    }
246}
247
248/// HDWallet extended key pair (Ed25519 extended private key + BIP32 ChainCode)
249#[derive(Clone, Debug, PartialEq, Eq, Zeroize)]
250#[zeroize(drop)]
251pub struct KeyPair {
252    chain_code: [u8; CHAIN_CODE_SIZE],
253    extended_secret_key: [u8; EXTENDED_SECRET_KEY_SIZE],
254}
255
256impl Display for KeyPair {
257    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258        write!(f, "({}, hidden)", self.public_key().to_base58())
259    }
260}
261
262impl KeyPair {
263    /// Get BIP32 chain code
264    pub fn chain_code(&self) -> ChainCode {
265        self.chain_code
266    }
267    /// BIP32 derivation
268    pub fn derive(&self, derivation_path: PrivateDerivationPath) -> Self {
269        let mut kp = self.to_owned();
270        for derivation_index in derivation_path.into_iter() {
271            kp = kp.derive_inner(derivation_index);
272        }
273        kp
274    }
275    /// Create key-pair from a mnemonic
276    pub fn from_mnemonic(mnemonic: &Mnemonic) -> Self {
277        Self::from_seed(crate::mnemonic::mnemonic_to_seed(&mnemonic))
278    }
279    fn derive_inner(&self, derivation_index: DerivationIndex) -> Self {
280        let xprv = ed25519_bip32::XPrv::from_extended_and_chaincode(
281            &self.extended_secret_key,
282            &self.chain_code,
283        );
284        let xprv_derived =
285            xprv.derive(ed25519_bip32::DerivationScheme::V2, derivation_index.into());
286        Self {
287            chain_code: xprv_derived.chain_code(),
288            extended_secret_key: xprv_derived.extended_secret_key(),
289        }
290    }
291}
292
293impl KeyPairInner for KeyPair {
294    fn scalar_bytes_without_normalization(&self) -> [u8; 32] {
295        let mut scalar_bytes = [0; 32];
296        scalar_bytes.copy_from_slice(&self.extended_secret_key[..32]);
297        scalar_bytes
298    }
299}
300
301impl KeyPairTrait for KeyPair {
302    type Seed = Seed32;
303    type Signator = Signator;
304
305    fn generate_signator(&self) -> Self::Signator {
306        Signator {
307            extended_secret_key: self.extended_secret_key,
308        }
309    }
310
311    // Generate an HDWallet extended key pair from 32 bytes seed
312    fn from_seed(seed: Seed32) -> Self {
313        let digest = Hash64::sha512(seed.as_ref());
314        let mut extended_secret_key = [0u8; EXTENDED_SECRET_KEY_SIZE];
315        extended_secret_key.copy_from_slice(digest.as_ref());
316        normalize_bytes_ed25519_force3rd(&mut extended_secret_key);
317
318        Self {
319            chain_code: gen_root_chain_code(&seed),
320            extended_secret_key,
321        }
322    }
323
324    fn public_key(&self) -> super::PublicKey {
325        super::PublicKey::from_32_bytes_array(cryptoxide::ed25519::to_public(
326            &self.extended_secret_key,
327        ))
328    }
329
330    fn verify(
331        &self,
332        message: &[u8],
333        signature: &super::Signature,
334    ) -> Result<(), crate::keys::SigError> {
335        self.public_key().verify(message, signature)
336    }
337
338    fn upcast(self) -> super::super::KeyPairEnum {
339        super::super::KeyPairEnum::Bip32Ed25519(self)
340    }
341}
342
343/// HDWallet signator
344#[derive(Clone, Debug, PartialEq, Eq, Zeroize)]
345#[zeroize(drop)]
346pub struct Signator {
347    extended_secret_key: [u8; EXTENDED_SECRET_KEY_SIZE],
348}
349
350impl super::super::Signator for Signator {
351    type PublicKey = super::PublicKey;
352
353    fn public_key(&self) -> Self::PublicKey {
354        super::PublicKey::from_32_bytes_array(cryptoxide::ed25519::to_public(
355            &self.extended_secret_key,
356        ))
357    }
358
359    fn sign(&self, message: &[u8]) -> <Self::PublicKey as super::super::PublicKey>::Signature {
360        super::Signature(cryptoxide::ed25519::signature_extended(
361            message,
362            &self.extended_secret_key,
363        ))
364    }
365}
366
367/// Generate root chain code as specified in the paper BIP32-Ed25519 section V.
368///
369/// > "Derive c ← H256(0x01||~k), where H256 is SHA-256, and call it the root chain code."
370fn gen_root_chain_code(seed: &Seed32) -> ChainCode {
371    Hash::compute_multipart(&[&[0x01], seed.as_ref()]).0
372}
373
374/// takes the given raw bytes and perform some modifications to normalize
375/// to a valid Ed25519 extended key, but it does also force
376/// the 3rd highest bit to be cleared too.
377fn normalize_bytes_ed25519_force3rd(bytes: &mut [u8; EXTENDED_SECRET_KEY_SIZE]) {
378    bytes[0] &= 0b1111_1000;
379    bytes[31] &= 0b0001_1111;
380    bytes[31] |= 0b0100_0000;
381}
382
383#[cfg(test)]
384mod tests {
385    use crate::keys::{PublicKey, Signator};
386
387    use super::*;
388    use unwrap::unwrap;
389
390    #[test]
391    fn test_derivation_index() {
392        let u31_zero = unwrap!(U31::new(0));
393        let index: u32 = DerivationIndex::hard(u31_zero).into();
394        assert_eq!(index, 0x80_00_00_00);
395
396        let u31_max = unwrap!(U31::new(0x7F_FF_FF_FF));
397        let index: u32 = DerivationIndex::hard(u31_max).into();
398        assert_eq!(index, u32::MAX);
399    }
400
401    #[test]
402    fn test_public_key_derivation() -> Result<(), InvalidAccountIndex> {
403        let mnemonic = unwrap!(crate::mnemonic::Mnemonic::from_phrase(
404            "acquire flat utility climb filter device liberty beyond matrix satisfy metal essence",
405            crate::mnemonic::Language::English
406        ));
407
408        println!("mnemonic={:?}", mnemonic.phrase());
409        let seed = crate::mnemonic::mnemonic_to_seed(&mnemonic);
410        println!("seed={:?}", hex::encode(seed.as_ref()));
411        let master_kp = KeyPair::from_seed(seed);
412
413        let account_index = unwrap!(U31::new(2));
414        let address_index = unwrap!(U31::new(3));
415
416        let external_chain_kp =
417            master_kp.derive(PrivateDerivationPath::opaque(account_index, true, None)?);
418        let external_chain_public_key = external_chain_kp.public_key();
419        let external_chain_code = external_chain_kp.chain_code();
420        println!(
421            "external_chain_public_key={:?}",
422            external_chain_public_key.to_base58()
423        );
424        println!(
425            "external_chain_code={:?}",
426            bs58::encode(external_chain_code).into_string()
427        );
428
429        println!(
430            "address(m/2'/0'/3)= {}",
431            master_kp
432                .derive(PrivateDerivationPath::opaque(
433                    account_index,
434                    true,
435                    Some(address_index)
436                )?)
437                .public_key()
438        );
439
440        assert_eq!(
441            unwrap!(PublicKeyWithChainCode {
442                public_key: external_chain_public_key,
443                chain_code: external_chain_code,
444            }
445            .derive(address_index))
446            .public_key,
447            master_kp
448                .derive(PrivateDerivationPath::opaque(
449                    account_index,
450                    true,
451                    Some(address_index)
452                )?)
453                .public_key()
454        );
455
456        Ok(())
457    }
458
459    #[test]
460    fn test_sign_and_verify() {
461        let seed = unwrap!(Seed32::from_base58(
462            "DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV"
463        ));
464        let kp = KeyPair::from_seed(seed);
465        let public_key = kp.public_key();
466
467        let message = "toto";
468        let wrong_message = "titi";
469        let sig = kp.generate_signator().sign(message.as_bytes());
470        let wrong_sig = kp.generate_signator().sign(wrong_message.as_bytes());
471
472        assert!(public_key.verify(message.as_bytes(), &sig).is_ok());
473        assert!(public_key.verify(wrong_message.as_bytes(), &sig).is_err());
474        assert!(public_key.verify(message.as_bytes(), &wrong_sig).is_err());
475    }
476
477    #[test]
478    fn test_derivation_index_consts() -> Result<(), crate::utils::U31Error> {
479        assert_eq!(
480            DerivationIndex::HARD_ZERO,
481            DerivationIndex::hard(U31::new(0)?)
482        );
483        assert_eq!(
484            DerivationIndex::HARD_ONE,
485            DerivationIndex::hard(U31::new(1)?)
486        );
487        Ok(())
488    }
489}