From bd4e5e1f38af104856230fd924fcc912449d3a27 Mon Sep 17 00:00:00 2001 From: Deirdre Connolly Date: Thu, 11 Mar 2021 08:24:45 -0500 Subject: [PATCH] Cleanup --- Cargo.lock | 1 + zebra-chain/Cargo.toml | 1 + zebra-chain/src/orchard/keys.rs | 87 ++++++++++++++++++--------- zebra-chain/src/orchard/sinsemilla.rs | 34 +++++------ 4 files changed, 79 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c0cb0630..19c720403 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4448,6 +4448,7 @@ dependencies = [ "fpe", "funty", "futures 0.3.14", + "group 0.9.0", "halo2", "hex", "itertools 0.10.0", diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index d09ca6c71..8658f2797 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -27,6 +27,7 @@ displaydoc = "0.2.1" equihash = "0.1" fpe = "0.4" futures = "0.3" +group = "0.9" halo2 = { git = "https://github.com/zcash/halo2.git", branch = "main" } hex = "0.4" jubjub = "0.6.0" diff --git a/zebra-chain/src/orchard/keys.rs b/zebra-chain/src/orchard/keys.rs index 017016123..2e11f161b 100644 --- a/zebra-chain/src/orchard/keys.rs +++ b/zebra-chain/src/orchard/keys.rs @@ -9,7 +9,7 @@ mod tests; use std::{ - convert::{From, Into, TryFrom}, + convert::{From, Into, TryFrom, TryInto}, fmt, io::{self, Write}, str::FromStr, @@ -18,7 +18,8 @@ use std::{ use aes::Aes256; use bech32::{self, FromBase32, ToBase32, Variant}; use fpe::ff1::{BinaryNumeralString, FF1}; -use halo2::pasta::pallas; +use group::GroupEncoding; +use halo2::{arithmetic::FieldExt, pasta::pallas}; use rand_core::{CryptoRng, RngCore}; use crate::{ @@ -42,13 +43,15 @@ use super::sinsemilla::*; #[allow(non_snake_case)] fn prp_d(K: [u8; 32], d: [u8; 11]) -> [u8; 11] { let radix = 2; - let tweak = ""; + let tweak = b""; let ff = FF1::::new(&K, radix).expect("valid radix"); - let enc = ff - .encrypt(tweak.into(), &BinaryNumeralString::from_bytes_le(&d)) - .unwrap(); + ff.encrypt(tweak, &BinaryNumeralString::from_bytes_le(&d)) + .unwrap() + .to_bytes_le() + .try_into() + .unwrap() } /// Invokes Blake2b-512 as PRF^expand with parameter t. @@ -59,16 +62,17 @@ fn prp_d(K: [u8; 32], d: [u8; 11]) -> [u8; 11] { // TODO: This is basically a duplicate of the one in our sapling module, its // definition in the draft NU5 spec is incomplete so I'm putting it here in case // it changes. -fn prf_expand(sk: [u8; 32], t: &[u8]) -> [u8; 64] { - let hash = blake2b_simd::Params::new() +fn prf_expand(sk: [u8; 32], t: &[&[u8]]) -> [u8; 64] { + let state = blake2b_simd::Params::new() .hash_length(64) .personal(b"Zcash_ExpandSeed") - .to_state() - .update(&sk[..]) - .update(t) - .finalize(); + .to_state(); - *hash.as_array() + state.update(&sk[..]); + + t.iter().for_each(|t_i| state.update(t_i)); + + *state.finalize().as_array() } /// Used to derive the outgoing cipher key _ock_ used to encrypt an encrypted @@ -77,15 +81,15 @@ fn prf_expand(sk: [u8; 32], t: &[u8]) -> [u8; 64] { /// PRF^ock(ovk, cv, cm_x, ephemeralKey) := BLAKE2b-256(“Zcash_Orchardock”, ovk || cv || cm_x || ephemeralKey) /// /// https://zips.z.cash/protocol/nu5.pdf#concreteprfs -fn prf_ock(ovk: [u8; 32], cv: [u8; 32], cm_x: [u8; 32], ephemeral_key: [u8; 32]) -> [u8; 32] { +fn prf_ock(ovk: [u8; 32], cv: [u8; 32], cm_x: [u8; 32], ephemeral_key: [u8; 32]) -> [u8; 64] { let hash = blake2b_simd::Params::new() .hash_length(32) .personal(b"Zcash_Orchardock") .to_state() - .update(ovk) - .update(cv) - .update(cm_x) - .update(ephemeral_key) + .update(&ovk) + .update(&cv) + .update(&cm_x) + .update(&ephemeral_key) .finalize(); *hash.as_array() @@ -348,8 +352,8 @@ pub struct NullifierDerivingKey(pub pallas::Base); impl fmt::Debug for NullifierDerivingKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("NullifierDerivingKey") - .field("u", &hex::encode(self.0.get_u().to_bytes())) - .field("v", &hex::encode(self.0.get_v().to_bytes())) + .field("x", &hex::encode(self.0.get_x().to_bytes())) + .field("y", &hex::encode(self.0.get_y().to_bytes())) .finish() } } @@ -406,7 +410,7 @@ impl From for IvkCommitRandomness { /// /// https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents fn from(sk: SpendingKey) -> Self { - Self(pallas::Scalar::from_bytes_wide(prf_expand(sk, [8]))) + Self(pallas::Scalar::from_bytes_wide(prf_expand(sk.into(), &[8]))) } } @@ -416,6 +420,20 @@ impl From for [u8; 32] { } } +impl TryFrom<[u8; 32]> for IvkCommitRandomness { + type Error = &'static str; + + fn try_from(bytes: [u8; 32]) -> Result { + let possible_scalar = pallas::Scalar::from_bytes(&bytes); + + if possible_scalar.is_some().into() { + Ok(Self(possible_scalar.unwrap())) + } else { + Err("Invalid pallas::Scalar value") + } + } +} + /// Magic human-readable strings used to identify what networks Orchard incoming /// viewing keys are associated with when encoded/decoded with bech32. /// @@ -476,10 +494,18 @@ impl From for IncomingViewingKey { /// https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents /// https://zips.z.cash/protocol/protocol.pdf#concreteprfs fn from(fvk: FullViewingKey) -> Self { - let M = (fvk.ak.into(), fvk.nk.into()).concat(); + let M = ( + fvk.spend_validating_key.into(), + fvk.nullifier_deriving_key.into(), + ) + .concat(); // Commit^ivk_rivk - let scalar = sinsemilla_short_commit(fvk.ivk.into(), "z.cash:Orchard-CommitIvk", M); + let scalar = sinsemilla_short_commit( + fvk.ivk_commit_randomness.into(), + "z.cash:Orchard-CommitIvk", + M, + ); Self { network: Network::default(), @@ -594,7 +620,7 @@ impl FromStr for FullViewingKey { }, spend_validating_key: SpendValidatingKey::from(ak_bytes), nullifier_deriving_key: NullifierDerivingKey::from(nk_bytes), - ivk_commit_randomness: IvkCommitRandomness::from(rivk_bytes), + ivk_commit_randomness: IvkCommitRandomness::try_from(rivk_bytes).unwrap(), }) } _ => Err(SerializationError::Parse("bech32 decoding error")), @@ -605,12 +631,19 @@ impl FromStr for FullViewingKey { impl FullViewingKey { /// [4.2.3]: https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents #[allow(non_snake_case)] - pub fn to_R(&self) -> [u8; 32] { + pub fn to_R(&self) -> [u8; 64] { // let K = I2LEBSP_l_sk(rivk) let K: [u8; 32] = self.ivk_commit_randomness.into(); // let R = PRF^expand_K( [0x82] || I2LEOSP256(ak) || I2LEOSP256(nk) ) - prf_expand(K, ([0x82], self.ak.into(), self.nk.into()).concat()) + prf_expand( + K, + [ + [0x82u8], + self.spend_validating_key.into(), + self.nullifier_deriving_key.into(), + ], + ) } } @@ -688,7 +721,7 @@ impl From for pallas::Point { /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents fn from(d: Diversifier) -> Self { - diversify_hash(d.0) + diversify_hash(&d.0) } } diff --git a/zebra-chain/src/orchard/sinsemilla.rs b/zebra-chain/src/orchard/sinsemilla.rs index 085058680..81fe844c0 100644 --- a/zebra-chain/src/orchard/sinsemilla.rs +++ b/zebra-chain/src/orchard/sinsemilla.rs @@ -4,6 +4,20 @@ use bitvec::prelude::*; use halo2::pasta::pallas; +/// [Hash Extractor for Pallas][concreteextractorpallas] +/// +/// P → B^[l^Orchard_Merkle] +/// +/// [concreteextractorpallas]: https://zips.z.cash/protocol/nu5.pdf#concreteextractorpallas +// TODO: should this return the basefield element type, or the bytes? +pub fn extract_p(point: pallas::Point) -> pallas::Base { + match pallas::Affine::from(point).get_xy().into() { + // If Some, it's not the identity. + Some((x, _)) => x, + _ => pallas::Base::zero(), + } +} + /// GroupHash into Pallas, aka _GroupHash^P_ /// /// Produces a random point in the Pallas curve. The first input element acts @@ -21,7 +35,7 @@ pub fn pallas_group_hash(D: &[u8], M: &[u8]) -> pallas::Point { /// https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash #[allow(non_snake_case)] fn Q(D: &[u8]) -> pallas::Point { - pallas_group_hash("z.cash:SinsemillaQ", D) + pallas_group_hash(b"z.cash:SinsemillaQ", D) } /// S(j) := GroupHash^P(︀“z.cash:SinsemillaS”, LEBS2OSP32(I2LEBSP32(j))) @@ -32,7 +46,7 @@ fn Q(D: &[u8]) -> pallas::Point { // XXX: should j be strictly limited to k=10 bits? #[allow(non_snake_case)] fn S(j: [u8; 2]) -> pallas::Point { - pallas_group_hash("z.cash:SinsemillaS", &j) + pallas_group_hash(b"z.cash:SinsemillaS", &j) } /// "...an algebraic hash function with collision resistance (for fixed input @@ -76,20 +90,6 @@ pub fn sinsemilla_hash_to_point(D: &[u8], M: &BitVec) -> pallas::Point acc } -/// [Hash Extractor for Pallas][concreteextractorpallas] -/// -/// P → B^[l^Orchard_Merkle] -/// -/// [concreteextractorpallas]: https://zips.z.cash/protocol/nu5.pdf#concreteextractorpallas -// TODO: should this return the basefield element type, or the bytes? -pub fn extract_p(point: pallas::Point) -> pallas::Base { - match pallas::Affine::from(point).get_xy().into() { - // If Some, it's not the identity. - Some((x, _)) => x, - _ => pallas::Base::zero(), - } -} - /// Sinsemilla Hash Function /// /// "SinsemillaHash is an algebraic hash function with collision resistance (for @@ -117,7 +117,7 @@ pub fn sinsemilla_hash(D: &[u8], M: &BitVec) -> pallas::Base { #[allow(non_snake_case)] pub fn sinsemilla_commit(r: pallas::Scalar, D: &[u8], M: &BitVec) -> pallas::Point { sinsemilla_hash_to_point((D, b"-M").concat(), M) - + r * pallas_group_hash((D, b"r").concat(), b"") + + pallas_group_hash((D, b"r").concat(), b"") * r } /// SinsemillaShortCommit_r(D, M) := Extract_P(SinsemillaCommit_r(D, M))