diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index d2778523e..8e60ac629 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -6,7 +6,7 @@ use memuse::{self, DynamicUsage}; use subtle::{Choice, ConditionallySelectable}; use crate::sapling::{Diversifier, NullifierDerivingKey, PaymentAddress, ViewingKey}; - +pub mod fingerprint; pub mod sapling; #[deprecated(note = "Please use the types exported from the `zip32::sapling` module instead.")] diff --git a/zcash_primitives/src/zip32/fingerprint.rs b/zcash_primitives/src/zip32/fingerprint.rs new file mode 100644 index 000000000..8c4cffb9a --- /dev/null +++ b/zcash_primitives/src/zip32/fingerprint.rs @@ -0,0 +1,80 @@ +//! Seed Fingerprints according to ZIP 32 +//! +//! Implements section `Seed Fingerprints` of Shielded Hierarchical Deterministic Wallets (ZIP 32) +//! +//! [Section Seed Fingerprints]: https://zips.z.cash/zip-0032#seed-fingerprints +use blake2b_simd::Params as Blake2bParams; + +pub const ZIP32_SEED_FP_PERSONALIZATION: &[u8; 16] = b"Zcash_HD_Seed_FP"; +pub struct SeedFingerprint([u8; 32]); + +impl SeedFingerprint { + /// Return the seed fingerprint of the wallet as defined in + /// or None + /// if the length of `seed_bytes` is less than 32 or + /// greater than 252. + pub fn from_seed(seed_bytes: &[u8]) -> Option { + let seed_len = seed_bytes.len(); + + if (32..=252).contains(&seed_len) { + let seed_len: u8 = seed_len.try_into().unwrap(); + Some(SeedFingerprint( + Blake2bParams::new() + .hash_length(32) + .personal(ZIP32_SEED_FP_PERSONALIZATION) + .to_state() + .update(&[seed_len]) + .update(seed_bytes) + .finalize() + .as_bytes() + .try_into() + .expect("hash length should be 32 bytes"), + )) + } else { + None + } + } + + /// Returns the fingerprint as a byte array. + pub fn to_bytes(&self) -> [u8; 32] { + self.0 + } +} + +#[test] +fn test_seed_fingerprint() { + struct TestVector { + root_seed: Vec, + fingerprint: Vec, + } + + let test_vectors = vec![TestVector { + root_seed: vec![ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, + ], + fingerprint: vec![ + 0xde, 0xff, 0x60, 0x4c, 0x24, 0x67, 0x10, 0xf7, 0x17, 0x6d, 0xea, 0xd0, 0x2a, 0xa7, + 0x46, 0xf2, 0xfd, 0x8d, 0x53, 0x89, 0xf7, 0x7, 0x25, 0x56, 0xdc, 0xb5, 0x55, 0xfd, + 0xbe, 0x5e, 0x3a, 0xe3, + ], + }]; + + for tv in test_vectors { + let fp = SeedFingerprint::from_seed(&tv.root_seed).expect("root_seed has valid length"); + assert_eq!(&fp.to_bytes(), &tv.fingerprint[..]); + } +} +#[test] +fn test_seed_fingerprint_is_none() { + let odd_seed = vec![ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, + ]; + + assert!( + SeedFingerprint::from_seed(&odd_seed).is_none(), + "fingerprint from short seed should be `None`" + ); +}