[#792] zcash_primitives: implement Seed Signature section of ZIP-32

Implements a type to enclose a Seed Fingerprint byte sequence
called `SeedFingerprint`

implements a public function to create a `SeedFingerprint` from
seed bytes

Closes #792

Credits for PR Suggestions by Daira and Kris

Edit: Added suggestions from Daira Emma and a test for returning `None`

Added test refactor suggestions as well

removed reference to test vectors
This commit is contained in:
Francisco Gindre 2023-03-15 19:30:35 -03:00
parent 3d65c481d7
commit e3f09ba66a
No known key found for this signature in database
GPG Key ID: 6B61CD8DAA2862B4
2 changed files with 81 additions and 1 deletions

View File

@ -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.")]

View File

@ -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
/// <https://zips.z.cash/zip-0032#seed-fingerprints> or None
/// if the length of `seed_bytes` is less than 32 or
/// greater than 252.
pub fn from_seed(seed_bytes: &[u8]) -> Option<SeedFingerprint> {
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<u8>,
fingerprint: Vec<u8>,
}
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`"
);
}