diff --git a/zebra-chain/src/orchard/arbitrary.rs b/zebra-chain/src/orchard/arbitrary.rs index 0147b01a1..5f1cedcf7 100644 --- a/zebra-chain/src/orchard/arbitrary.rs +++ b/zebra-chain/src/orchard/arbitrary.rs @@ -4,7 +4,7 @@ use proptest::{arbitrary::any, array, collection::vec, prelude::*}; use crate::primitives::redpallas::{Signature, SpendAuth, VerificationKeyBytes}; -use super::{keys, note, Action, AuthorizedAction, Flags, NoteCommitment, ValueCommitment}; +use super::{keys, note, tree, Action, AuthorizedAction, Flags, NoteCommitment, ValueCommitment}; use std::{ convert::{TryFrom, TryInto}, @@ -96,3 +96,21 @@ impl Arbitrary for Flags { type Strategy = BoxedStrategy; } + +impl Arbitrary for tree::Root { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + use halo2::arithmetic::FieldExt; + + (vec(any::(), 64)) + .prop_map(|bytes| { + let bytes = bytes.try_into().expect("vec is the correct length"); + Self::try_from(pallas::Base::from_bytes_wide(&bytes).to_bytes()) + .expect("a valid generated Orchard note commitment tree root") + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/zebra-chain/src/orchard/commitment.rs b/zebra-chain/src/orchard/commitment.rs index f1602b090..14cc9bff1 100644 --- a/zebra-chain/src/orchard/commitment.rs +++ b/zebra-chain/src/orchard/commitment.rs @@ -161,7 +161,10 @@ impl NoteCommitment { Some(( rcm, - NoteCommitment::from(sinsemilla_commit(rcm.0, b"z.cash:Orchard-NoteCommit", &s)), + NoteCommitment::from( + sinsemilla_commit(rcm.0, b"z.cash:Orchard-NoteCommit", &s) + .expect("valid orchard note commitment, not ⊥ "), + ), )) } diff --git a/zebra-chain/src/orchard/keys.rs b/zebra-chain/src/orchard/keys.rs index 38bc710c6..864ed6df2 100644 --- a/zebra-chain/src/orchard/keys.rs +++ b/zebra-chain/src/orchard/keys.rs @@ -562,7 +562,8 @@ impl From for IncomingViewingKey { fvk.ivk_commit_randomness.into(), b"z.cash:Orchard-CommitIvk", &M, - ); + ) + .expect("deriving orchard commit^ivk should not output ⊥ "); Self { network: fvk.network, @@ -966,8 +967,6 @@ impl Eq for TransmissionKey {} impl From<[u8; 32]> for TransmissionKey { /// Attempts to interpret a byte representation of an affine point, failing /// if the element is not on the curve or non-canonical. - /// - /// fn from(bytes: [u8; 32]) -> Self { Self(pallas::Affine::from_bytes(&bytes).unwrap()) } diff --git a/zebra-chain/src/orchard/note/nullifiers.rs b/zebra-chain/src/orchard/note/nullifiers.rs index 831ea161b..16b9b908c 100644 --- a/zebra-chain/src/orchard/note/nullifiers.rs +++ b/zebra-chain/src/orchard/note/nullifiers.rs @@ -1,6 +1,11 @@ #![allow(clippy::unit_arg)] #![allow(dead_code)] +use std::{ + convert::TryFrom, + hash::{Hash, Hasher}, +}; + use halo2::{arithmetic::FieldExt, pasta::pallas}; use crate::serialization::{serde_helpers, SerializationError}; @@ -9,11 +14,6 @@ use super::super::{ commitment::NoteCommitment, keys::NullifierDerivingKey, note::Note, sinsemilla::*, }; -use std::{ - convert::TryFrom, - hash::{Hash, Hasher}, -}; - /// A cryptographic permutation, defined in [poseidonhash]. /// /// PoseidonHash(x, y) = f([x, y, 0])_1 (using 1-based indexing). diff --git a/zebra-chain/src/orchard/sinsemilla.rs b/zebra-chain/src/orchard/sinsemilla.rs index ff81efa6c..25f96ccc2 100644 --- a/zebra-chain/src/orchard/sinsemilla.rs +++ b/zebra-chain/src/orchard/sinsemilla.rs @@ -1,15 +1,18 @@ //! Sinsemilla hash functions and helpers. use bitvec::prelude::*; - +use group::Group; use halo2::{ arithmetic::{Coordinates, CurveAffine, CurveExt}, pasta::pallas, }; -/// [Hash Extractor for Pallas][concreteextractorpallas] +/// [Coordinate Extractor for Pallas][concreteextractorpallas] /// -/// P → B^[l^Orchard_Merkle] +/// ExtractP: P → P𝑥 such that ExtractP(𝑃) = 𝑥(𝑃) mod 𝑞P. +/// +/// ExtractP returns the type P𝑥 which is precise for its range, unlike +/// ExtractJ(𝑟) which returns a bit sequence. /// /// [concreteextractorpallas]: https://zips.z.cash/protocol/nu5.pdf#concreteextractorpallas pub fn extract_p(point: pallas::Point) -> pallas::Base { @@ -23,13 +26,24 @@ pub fn extract_p(point: pallas::Point) -> pallas::Base { } } +/// Extract⊥ P: P ∪ {⊥} → P𝑥 ∪ {⊥} such that +/// +/// Extract⊥ P(︀⊥)︀ = ⊥ +/// Extract⊥ P(︀𝑃: P)︀ = ExtractP(𝑃). +/// +/// +pub fn extract_p_bottom(maybe_point: Option) -> Option { + // Maps an Option to Option by applying a function to a contained value. + maybe_point.map(extract_p) +} + /// GroupHash into Pallas, aka _GroupHash^P_ /// /// Produces a random point in the Pallas curve. The first input element acts /// as a domain separator to distinguish uses of the group hash for different /// purposes; the second input element is the message. /// -/// https://zips.z.cash/protocol/nu5.pdf#concretegrouphashpallasandvesta +/// #[allow(non_snake_case)] pub fn pallas_group_hash(D: &[u8], M: &[u8]) -> pallas::Point { let domain_separator = std::str::from_utf8(D).unwrap(); @@ -39,7 +53,7 @@ pub fn pallas_group_hash(D: &[u8], M: &[u8]) -> pallas::Point { /// Q(D) := GroupHash^P(︀“z.cash:SinsemillaQ”, D) /// -/// https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash +/// #[allow(non_snake_case)] fn Q(D: &[u8]) -> pallas::Point { pallas_group_hash(b"z.cash:SinsemillaQ", D) @@ -49,14 +63,40 @@ fn Q(D: &[u8]) -> pallas::Point { /// /// S: {0 .. 2^k - 1} -> P^*, aka 10 bits hashed into the group /// -/// https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash +/// #[allow(non_snake_case)] fn S(j: &BitSlice) -> pallas::Point { // The value of j is a 10-bit value, therefore must never exceed 2^10 in // value. assert_eq!(j.len(), 10); - pallas_group_hash(b"z.cash:SinsemillaS", j.as_raw_slice()) + // I2LEOSP_32(𝑗) + let mut leosp_32_j = [0u8; 4]; + leosp_32_j[..2].copy_from_slice(j.as_raw_slice()); + + pallas_group_hash(b"z.cash:SinsemillaS", &leosp_32_j) +} + +/// Incomplete addition on the Pallas curve. +/// +/// P ∪ {⊥} × P ∪ {⊥} → P ∪ {⊥} +/// +/// +fn incomplete_addition( + left: Option, + right: Option, +) -> Option { + let identity = pallas::Point::identity(); + + match (left, right) { + (None, _) | (_, None) => None, + (Some(l), _) if l == identity => None, + (_, Some(r)) if r == identity => None, + (Some(l), Some(r)) if l == r => None, + // The inverse of l, (x, -y) + (Some(l), Some(r)) if l == -r => None, + (Some(l), Some(r)) => Some(l + r), + } } /// "...an algebraic hash function with collision resistance (for fixed input @@ -67,19 +107,21 @@ fn S(j: &BitSlice) -> pallas::Point { /// the Sinsemilla hash for the Orchard incremental Merkle tree (§ 5.4.1.3 /// ‘MerkleCRH^Orchard Hash Function’). /// -/// https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash +/// SinsemillaHashToPoint(𝐷: B^Y^[N] , 𝑀 : B ^[{0 .. 𝑘·𝑐}] ) → P ∪ {⊥} +/// +/// /// /// # Panics /// /// If `M` is greater than `k*c = 2530` bits. #[allow(non_snake_case)] -pub fn sinsemilla_hash_to_point(D: &[u8], M: &BitVec) -> pallas::Point { +pub fn sinsemilla_hash_to_point(D: &[u8], M: &BitVec) -> Option { let k = 10; let c = 253; assert!(M.len() <= k * c); - let mut acc = Q(D); + let mut acc = Some(Q(D)); // Split M into n segments of k bits, where k = 10 and c = 253, padding // the last segment with zeros. @@ -92,7 +134,7 @@ pub fn sinsemilla_hash_to_point(D: &[u8], M: &BitVec) -> pallas::Point BitSlice::::from_slice_mut(&mut store).expect("must work for small slices"); bits[..chunk.len()].copy_from_bitslice(chunk); - acc = acc + acc + S(&bits[..k]); + acc = incomplete_addition(incomplete_addition(acc, Some(S(&bits[..k]))), acc); } acc @@ -107,14 +149,16 @@ pub fn sinsemilla_hash_to_point(D: &[u8], M: &BitVec) -> pallas::Point /// PedersenHash) is to make efcient use of the lookups available in recent /// proof systems including Halo 2." /// -/// https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash +/// SinsemillaHash: B^Y^[N] × B[{0 .. 𝑘·𝑐}] → P_𝑥 ∪ {⊥} +/// +/// /// /// # Panics /// /// If `M` is greater than `k*c = 2530` bits in `sinsemilla_hash_to_point`. #[allow(non_snake_case)] -pub fn sinsemilla_hash(D: &[u8], M: &BitVec) -> pallas::Base { - extract_p(sinsemilla_hash_to_point(D, M)) +pub fn sinsemilla_hash(D: &[u8], M: &BitVec) -> Option { + extract_p_bottom(sinsemilla_hash_to_point(D, M)) } /// Sinsemilla commit @@ -126,19 +170,68 @@ pub fn sinsemilla_hash(D: &[u8], M: &BitVec) -> pallas::Base { /// /// https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit #[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) - + pallas_group_hash(&[D, b"-r"].concat(), b"") * r +pub fn sinsemilla_commit( + r: pallas::Scalar, + D: &[u8], + M: &BitVec, +) -> Option { + incomplete_addition( + sinsemilla_hash_to_point(&[D, b"-M"].concat(), M), + Some(pallas_group_hash(&[D, b"-r"].concat(), b"") * r), + ) } -/// SinsemillaShortCommit_r(D, M) := Extract_P(SinsemillaCommit_r(D, M)) +/// SinsemillaShortCommit_r(D, M) := Extract⊥ P(SinsemillaCommit_r(D, M)) /// /// https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit #[allow(non_snake_case)] -pub fn sinsemilla_short_commit(r: pallas::Scalar, D: &[u8], M: &BitVec) -> pallas::Base { - extract_p(sinsemilla_commit(r, D, M)) +pub fn sinsemilla_short_commit( + r: pallas::Scalar, + D: &[u8], + M: &BitVec, +) -> Option { + extract_p_bottom(sinsemilla_commit(r, D, M)) } // TODO: test the above correctness and compatibility with the zcash-hackworks test vectors // https://github.com/ZcashFoundation/zebra/issues/2079 // https://github.com/zcash-hackworks/zcash-test-vectors/pulls + +#[cfg(test)] +mod tests { + + use super::*; + + fn x_from_str(s: &str) -> pallas::Base { + use group::ff::PrimeField; + + pallas::Base::from_str(s).unwrap() + } + + #[test] + #[allow(non_snake_case)] + fn single_test_vector() { + use group::Curve; + + let D = b"z.cash:test-Sinsemilla"; + let M = bitvec![ + Lsb0, u8; 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, + 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, + ]; + + let test_vector = pallas::Affine::from_xy( + x_from_str( + "19681977528872088480295086998934490146368213853811658798708435106473481753752", + ), + x_from_str( + "14670850419772526047574141291705097968771694788047376346841674072293161339903", + ), + ) + .unwrap(); + + assert_eq!( + sinsemilla_hash_to_point(&D[..], &M).expect("").to_affine(), + test_vector + ) + } +} diff --git a/zebra-chain/src/orchard/tests.rs b/zebra-chain/src/orchard/tests.rs index a7ac2ac35..829ffe5d5 100644 --- a/zebra-chain/src/orchard/tests.rs +++ b/zebra-chain/src/orchard/tests.rs @@ -1,2 +1,3 @@ mod preallocate; mod prop; +pub mod test_vectors; diff --git a/zebra-chain/src/orchard/tests/test_vectors.rs b/zebra-chain/src/orchard/tests/test_vectors.rs new file mode 100644 index 000000000..1de762524 --- /dev/null +++ b/zebra-chain/src/orchard/tests/test_vectors.rs @@ -0,0 +1,169 @@ +// Produced by https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_empty_roots.py +// Reproduced by https://github.com/zcash/orchard/blob/main/src/tree.rs +pub const EMPTY_ROOTS: [[u8; 32]; 33] = [ + [ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ], + [ + 0xd1, 0xab, 0x25, 0x07, 0xc8, 0x09, 0xc2, 0x71, 0x3c, 0x00, 0x0f, 0x52, 0x5e, 0x9f, 0xbd, + 0xcb, 0x06, 0xc9, 0x58, 0x38, 0x4e, 0x51, 0xb9, 0xcc, 0x7f, 0x79, 0x2d, 0xde, 0x6c, 0x97, + 0xf4, 0x11, + ], + [ + 0xc7, 0x41, 0x3f, 0x46, 0x14, 0xcd, 0x64, 0x04, 0x3a, 0xbb, 0xab, 0x7c, 0xc1, 0x09, 0x5c, + 0x9b, 0xb1, 0x04, 0x23, 0x1c, 0xea, 0x89, 0xe2, 0xc3, 0xe0, 0xdf, 0x83, 0x76, 0x95, 0x56, + 0xd0, 0x30, + ], + [ + 0x21, 0x11, 0xfc, 0x39, 0x77, 0x53, 0xe5, 0xfd, 0x50, 0xec, 0x74, 0x81, 0x6d, 0xf2, 0x7d, + 0x6a, 0xda, 0x7e, 0xd2, 0xa9, 0xac, 0x38, 0x16, 0xaa, 0xb2, 0x57, 0x3c, 0x8f, 0xac, 0x79, + 0x42, 0x04, + ], + [ + 0x80, 0x6a, 0xfb, 0xfe, 0xb4, 0x5c, 0x64, 0xd4, 0xf2, 0x38, 0x4c, 0x51, 0xef, 0xf3, 0x07, + 0x64, 0xb8, 0x45, 0x99, 0xae, 0x56, 0xa7, 0xab, 0x3d, 0x4a, 0x46, 0xd9, 0xce, 0x3a, 0xea, + 0xb4, 0x31, + ], + [ + 0x87, 0x3e, 0x41, 0x57, 0xf2, 0xc0, 0xf0, 0xc6, 0x45, 0xe8, 0x99, 0x36, 0x00, 0x69, 0xfc, + 0xc9, 0xd2, 0xed, 0x9b, 0xc1, 0x1b, 0xf5, 0x98, 0x27, 0xaf, 0x02, 0x30, 0xed, 0x52, 0xed, + 0xab, 0x18, + ], + [ + 0x27, 0xab, 0x13, 0x20, 0x95, 0x3a, 0xe1, 0xad, 0x70, 0xc8, 0xc1, 0x5a, 0x12, 0x53, 0xa0, + 0xa8, 0x6f, 0xbc, 0x8a, 0x0a, 0xa3, 0x6a, 0x84, 0x20, 0x72, 0x93, 0xf8, 0xa4, 0x95, 0xff, + 0xc4, 0x02, + ], + [ + 0x4e, 0x14, 0x56, 0x3d, 0xf1, 0x91, 0xa2, 0xa6, 0x5b, 0x4b, 0x37, 0x11, 0x3b, 0x52, 0x30, + 0x68, 0x05, 0x55, 0x05, 0x1b, 0x22, 0xd7, 0x4a, 0x8e, 0x1f, 0x1d, 0x70, 0x6f, 0x90, 0xf3, + 0x13, 0x3b, + ], + [ + 0xb3, 0xbb, 0xe4, 0xf9, 0x93, 0xd1, 0x8a, 0x0f, 0x4e, 0xb7, 0xf4, 0x17, 0x4b, 0x1d, 0x85, + 0x55, 0xce, 0x33, 0x96, 0x85, 0x5d, 0x04, 0x67, 0x6f, 0x1c, 0xe4, 0xf0, 0x6d, 0xda, 0x07, + 0x37, 0x1f, + ], + [ + 0x4e, 0xf5, 0xbd, 0xe9, 0xc6, 0xf0, 0xd7, 0x6a, 0xeb, 0x9e, 0x27, 0xe9, 0x3f, 0xba, 0x28, + 0xc6, 0x79, 0xdf, 0xcb, 0x99, 0x1c, 0xbc, 0xb8, 0x39, 0x5a, 0x2b, 0x57, 0x92, 0x4c, 0xbd, + 0x17, 0x0e, + ], + [ + 0xa3, 0xc0, 0x25, 0x68, 0xac, 0xeb, 0xf5, 0xca, 0x1e, 0xc3, 0x0d, 0x6a, 0x7d, 0x7c, 0xd2, + 0x17, 0xa4, 0x7d, 0x6a, 0x1b, 0x83, 0x11, 0xbf, 0x94, 0x62, 0xa5, 0xf9, 0x39, 0xc6, 0xb7, + 0x43, 0x07, + ], + [ + 0x3e, 0xf9, 0xb3, 0x0b, 0xae, 0x61, 0x22, 0xda, 0x16, 0x05, 0xba, 0xd6, 0xec, 0x5d, 0x49, + 0xb4, 0x1d, 0x4d, 0x40, 0xca, 0xa9, 0x6c, 0x1c, 0xf6, 0x30, 0x2b, 0x66, 0xc5, 0xd2, 0xd1, + 0x0d, 0x39, + ], + [ + 0x22, 0xae, 0x28, 0x00, 0xcb, 0x93, 0xab, 0xe6, 0x3b, 0x70, 0xc1, 0x72, 0xde, 0x70, 0x36, + 0x2d, 0x98, 0x30, 0xe5, 0x38, 0x00, 0x39, 0x88, 0x84, 0xa7, 0xa6, 0x4f, 0xf6, 0x8e, 0xd9, + 0x9e, 0x0b, + ], + [ + 0x18, 0x71, 0x10, 0xd9, 0x26, 0x72, 0xc2, 0x4c, 0xed, 0xb0, 0x97, 0x9c, 0xdf, 0xc9, 0x17, + 0xa6, 0x05, 0x3b, 0x31, 0x0d, 0x14, 0x5c, 0x03, 0x1c, 0x72, 0x92, 0xbb, 0x1d, 0x65, 0xb7, + 0x66, 0x1b, + ], + [ + 0x3f, 0x98, 0xad, 0xbe, 0x36, 0x4f, 0x14, 0x8b, 0x0c, 0xc2, 0x04, 0x2c, 0xaf, 0xc6, 0xbe, + 0x11, 0x66, 0xfa, 0xe3, 0x90, 0x90, 0xab, 0x4b, 0x35, 0x4b, 0xfb, 0x62, 0x17, 0xb9, 0x64, + 0x45, 0x3b, + ], + [ + 0x63, 0xf8, 0xdb, 0xd1, 0x0d, 0xf9, 0x36, 0xf1, 0x73, 0x49, 0x73, 0xe0, 0xb3, 0xbd, 0x25, + 0xf4, 0xed, 0x44, 0x05, 0x66, 0xc9, 0x23, 0x08, 0x59, 0x03, 0xf6, 0x96, 0xbc, 0x63, 0x47, + 0xec, 0x0f, + ], + [ + 0x21, 0x82, 0x16, 0x3e, 0xac, 0x40, 0x61, 0x88, 0x5a, 0x31, 0x35, 0x68, 0x14, 0x8d, 0xfa, + 0xe5, 0x64, 0xe4, 0x78, 0x06, 0x6d, 0xcb, 0xe3, 0x89, 0xa0, 0xdd, 0xb1, 0xec, 0xb7, 0xf5, + 0xdc, 0x34, + ], + [ + 0xbd, 0x9d, 0xc0, 0x68, 0x19, 0x18, 0xa3, 0xf3, 0xf9, 0xcd, 0x1f, 0x9e, 0x06, 0xaa, 0x1a, + 0xd6, 0x89, 0x27, 0xda, 0x63, 0xac, 0xc1, 0x3b, 0x92, 0xa2, 0x57, 0x8b, 0x27, 0x38, 0xa6, + 0xd3, 0x31, + ], + [ + 0xca, 0x2c, 0xed, 0x95, 0x3b, 0x7f, 0xb9, 0x5e, 0x3b, 0xa9, 0x86, 0x33, 0x3d, 0xa9, 0xe6, + 0x9c, 0xd3, 0x55, 0x22, 0x3c, 0x92, 0x97, 0x31, 0x09, 0x4b, 0x6c, 0x21, 0x74, 0xc7, 0x63, + 0x8d, 0x2e, + ], + [ + 0x55, 0x35, 0x4b, 0x96, 0xb5, 0x6f, 0x9e, 0x45, 0xaa, 0xe1, 0xe0, 0x09, 0x4d, 0x71, 0xee, + 0x24, 0x8d, 0xab, 0xf6, 0x68, 0x11, 0x77, 0x78, 0xbd, 0xc3, 0xc1, 0x9c, 0xa5, 0x33, 0x1a, + 0x4e, 0x1a, + ], + [ + 0x70, 0x97, 0xb0, 0x4c, 0x2a, 0xa0, 0x45, 0xa0, 0xde, 0xff, 0xca, 0xca, 0x41, 0xc5, 0xac, + 0x92, 0xe6, 0x94, 0x46, 0x65, 0x78, 0xf5, 0x90, 0x9e, 0x72, 0xbb, 0x78, 0xd3, 0x33, 0x10, + 0xf7, 0x05, + ], + [ + 0xe8, 0x1d, 0x68, 0x21, 0xff, 0x81, 0x3b, 0xd4, 0x10, 0x86, 0x7a, 0x3f, 0x22, 0xe8, 0xe5, + 0xcb, 0x7a, 0xc5, 0x59, 0x9a, 0x61, 0x0a, 0xf5, 0xc3, 0x54, 0xeb, 0x39, 0x28, 0x77, 0x36, + 0x2e, 0x01, + ], + [ + 0x15, 0x7d, 0xe8, 0x56, 0x7f, 0x7c, 0x49, 0x96, 0xb8, 0xc4, 0xfd, 0xc9, 0x49, 0x38, 0xfd, + 0x80, 0x8c, 0x3b, 0x2a, 0x5c, 0xcb, 0x79, 0xd1, 0xa6, 0x38, 0x58, 0xad, 0xaa, 0x9a, 0x6d, + 0xd8, 0x24, + ], + [ + 0xfe, 0x1f, 0xce, 0x51, 0xcd, 0x61, 0x20, 0xc1, 0x2c, 0x12, 0x46, 0x95, 0xc4, 0xf9, 0x8b, + 0x27, 0x59, 0x18, 0xfc, 0xea, 0xe6, 0xeb, 0x20, 0x98, 0x73, 0xed, 0x73, 0xfe, 0x73, 0x77, + 0x5d, 0x0b, + ], + [ + 0x1f, 0x91, 0x98, 0x29, 0x12, 0x01, 0x26, 0x69, 0xf7, 0x4d, 0x0c, 0xfa, 0x10, 0x30, 0xff, + 0x37, 0xb1, 0x52, 0x32, 0x4e, 0x5b, 0x83, 0x46, 0xb3, 0x33, 0x5a, 0x0a, 0xae, 0xb6, 0x3a, + 0x0a, 0x2d, + ], + [ + 0x5d, 0xec, 0x15, 0xf5, 0x2a, 0xf1, 0x7d, 0xa3, 0x93, 0x13, 0x96, 0x18, 0x3c, 0xbb, 0xbf, + 0xbe, 0xa7, 0xed, 0x95, 0x07, 0x14, 0x54, 0x0a, 0xec, 0x06, 0xc6, 0x45, 0xc7, 0x54, 0x97, + 0x55, 0x22, + ], + [ + 0xe8, 0xae, 0x2a, 0xd9, 0x1d, 0x46, 0x3b, 0xab, 0x75, 0xee, 0x94, 0x1d, 0x33, 0xcc, 0x58, + 0x17, 0xb6, 0x13, 0xc6, 0x3c, 0xda, 0x94, 0x3a, 0x4c, 0x07, 0xf6, 0x00, 0x59, 0x1b, 0x08, + 0x8a, 0x25, + ], + [ + 0xd5, 0x3f, 0xde, 0xe3, 0x71, 0xce, 0xf5, 0x96, 0x76, 0x68, 0x23, 0xf4, 0xa5, 0x18, 0xa5, + 0x83, 0xb1, 0x15, 0x82, 0x43, 0xaf, 0xe8, 0x97, 0x00, 0xf0, 0xda, 0x76, 0xda, 0x46, 0xd0, + 0x06, 0x0f, + ], + [ + 0x15, 0xd2, 0x44, 0x4c, 0xef, 0xe7, 0x91, 0x4c, 0x9a, 0x61, 0xe8, 0x29, 0xc7, 0x30, 0xec, + 0xeb, 0x21, 0x62, 0x88, 0xfe, 0xe8, 0x25, 0xf6, 0xb3, 0xb6, 0x29, 0x8f, 0x6f, 0x6b, 0x6b, + 0xd6, 0x2e, + ], + [ + 0x4c, 0x57, 0xa6, 0x17, 0xa0, 0xaa, 0x10, 0xea, 0x7a, 0x83, 0xaa, 0x6b, 0x6b, 0x0e, 0xd6, + 0x85, 0xb6, 0xa3, 0xd9, 0xe5, 0xb8, 0xfd, 0x14, 0xf5, 0x6c, 0xdc, 0x18, 0x02, 0x1b, 0x12, + 0x25, 0x3f, + ], + [ + 0x3f, 0xd4, 0x91, 0x5c, 0x19, 0xbd, 0x83, 0x1a, 0x79, 0x20, 0xbe, 0x55, 0xd9, 0x69, 0xb2, + 0xac, 0x23, 0x35, 0x9e, 0x25, 0x59, 0xda, 0x77, 0xde, 0x23, 0x73, 0xf0, 0x6c, 0xa0, 0x14, + 0xba, 0x27, + ], + [ + 0x87, 0xd0, 0x63, 0xcd, 0x07, 0xee, 0x49, 0x44, 0x22, 0x2b, 0x77, 0x62, 0x84, 0x0e, 0xb9, + 0x4c, 0x68, 0x8b, 0xec, 0x74, 0x3f, 0xa8, 0xbd, 0xf7, 0x71, 0x5c, 0x8f, 0xe2, 0x9f, 0x10, + 0x4c, 0x2a, + ], + [ + 0xae, 0x29, 0x35, 0xf1, 0xdf, 0xd8, 0xa2, 0x4a, 0xed, 0x7c, 0x70, 0xdf, 0x7d, 0xe3, 0xa6, + 0x68, 0xeb, 0x7a, 0x49, 0xb1, 0x31, 0x98, 0x80, 0xdd, 0xe2, 0xbb, 0xd9, 0x03, 0x1a, 0xe5, + 0xd8, 0x2f, + ], +]; diff --git a/zebra-chain/src/orchard/tree.rs b/zebra-chain/src/orchard/tree.rs index 82c813bf5..0bc2db85b 100644 --- a/zebra-chain/src/orchard/tree.rs +++ b/zebra-chain/src/orchard/tree.rs @@ -11,19 +11,26 @@ //! A root of a note commitment tree is associated with each treestate. #![allow(clippy::unit_arg)] +#![allow(clippy::derive_hash_xor_eq)] #![allow(dead_code)] -use std::{collections::VecDeque, fmt, io}; +use std::{ + collections::VecDeque, + convert::TryFrom, + fmt, + hash::{Hash, Hasher}, + io, +}; use bitvec::prelude::*; -use halo2::arithmetic::FieldExt; +use halo2::{arithmetic::FieldExt, pasta::pallas}; use lazy_static::lazy_static; -#[cfg(any(test, feature = "proptest-impl"))] -use proptest_derive::Arbitrary; use super::{commitment::NoteCommitment, sinsemilla::*}; -use crate::serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}; +use crate::serialization::{ + serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, +}; const MERKLE_DEPTH: usize = 32; @@ -31,34 +38,50 @@ const MERKLE_DEPTH: usize = 32; /// /// Used to hash incremental Merkle tree hash values for Orchard. /// +/// MerkleCRH^Orchard: {0..MerkleDepth^Orchard − 1} × P𝑥 ∪ {⊥} × P𝑥 ∪ {⊥} → P𝑥 ∪ {⊥} +/// /// MerkleCRH^Orchard(layer, left, right) := SinsemillaHash("z.cash:Orchard-MerkleCRH", l || left || right), /// -/// where l = I2LEBSP_10(MerkleDepth^Orchard − 1 − layer) and left, right, and -/// the output are all technically 255 bits (l_MerkleOrchard), not 256. +/// where l = I2LEBSP_10(MerkleDepth^Orchard − 1 − layer), and left, right, and +/// the output are the x-coordinates of Pallas affine points. /// -/// https://zips.z.cash/protocol/nu5.pdf#merklecrh -/// https://zips.z.cash/protocol/nu5.pdf#constants -fn merkle_crh_orchard(layer: u8, left: [u8; 32], right: [u8; 32]) -> [u8; 32] { - let mut s = bitvec![Lsb0, u8;]; +/// https://zips.z.cash/protocol/protocol.pdf#orchardmerklecrh +/// https://zips.z.cash/protocol/protocol.pdf#constants +fn merkle_crh_orchard( + layer: u8, + maybe_left: Option, + maybe_right: Option, +) -> Option { + match (maybe_left, maybe_right) { + (None, _) | (_, None) => None, + (Some(left), Some(right)) => { + let mut s = bitvec![Lsb0, u8;]; - // Prefix: l = I2LEBSP_10(MerkleDepth^Orchard − 1 − layer) - s.extend_from_bitslice(&BitArray::::from([layer, 0])[0..10]); - s.extend_from_bitslice(&BitArray::::from(left)[0..255]); - s.extend_from_bitslice(&BitArray::::from(right)[0..255]); + // Prefix: l = I2LEBSP_10(MerkleDepth^Orchard − 1 − layer) + let l = MERKLE_DEPTH - 1 - layer as usize; + s.extend_from_bitslice(&BitArray::::from([l, 0])[0..10]); + s.extend_from_bitslice(&BitArray::::from(left.to_bytes())[0..255]); + s.extend_from_bitslice(&BitArray::::from(right.to_bytes())[0..255]); - sinsemilla_hash(b"z.cash:Orchard-MerkleCRH", &s).to_bytes() + sinsemilla_hash(b"z.cash:Orchard-MerkleCRH", &s) + } + } } lazy_static! { /// Orchard note commitment trees have a max depth of 32. /// /// https://zips.z.cash/protocol/nu5.pdf#constants - static ref EMPTY_ROOTS: Vec<[u8; 32]> = { - // Uncommitted^Orchard = I2LEBSP_l_MerkleOrchard(1) - let mut v = vec![jubjub::Fq::one().to_bytes()]; + static ref EMPTY_ROOTS: Vec = { - for d in 0..MERKLE_DEPTH { - let next = merkle_crh_orchard(d as u8, v[d], v[d]); + // The empty leaf node, Uncommitted^Orchard = I2LEBSP_l_MerkleOrchard(2) + let mut v = vec![NoteCommitmentTree::uncommitted()]; + + // Starting with layer 31 (the first internal layer, after the leaves), + // generate the empty roots up to layer 0, the root. + for d in 0..MERKLE_DEPTH + { + let next = merkle_crh_orchard((MERKLE_DEPTH - 1 - d) as u8, Some(v[d]), Some(v[d])).unwrap(); v.push(next); } @@ -71,7 +94,6 @@ lazy_static! { /// `NoteCommitmentTree`. /// /// https://zips.z.cash/protocol/nu5.pdf#merkletree -// XXX: dedupe with sapling? pub struct Position(pub(crate) u64); /// Orchard note commitment tree root node hash. @@ -79,26 +101,42 @@ pub struct Position(pub(crate) u64); /// The root hash in LEBS2OSP256(rt) encoding of the Orchard note commitment /// tree corresponding to the final Orchard treestate of this block. A root of a /// note commitment tree is associated with each treestate. -#[derive(Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] -#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] -// XXX: dedupe with sapling? -pub struct Root(pub [u8; 32]); +#[derive(Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)] +pub struct Root(#[serde(with = "serde_helpers::Base")] pub(crate) pallas::Base); impl fmt::Debug for Root { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("Root").field(&hex::encode(&self.0)).finish() - } -} - -impl From<[u8; 32]> for Root { - fn from(bytes: [u8; 32]) -> Root { - Self(bytes) + f.debug_tuple("Root") + .field(&hex::encode(&self.0.to_bytes())) + .finish() } } impl From for [u8; 32] { fn from(root: Root) -> Self { - root.0 + root.0.into() + } +} + +impl Hash for Root { + fn hash(&self, state: &mut H) { + self.0.to_bytes().hash(state) + } +} + +impl TryFrom<[u8; 32]> for Root { + type Error = SerializationError; + + fn try_from(bytes: [u8; 32]) -> Result { + let possible_point = pallas::Base::from_bytes(&bytes); + + if possible_point.is_some().into() { + Ok(Self(possible_point.unwrap())) + } else { + Err(SerializationError::Parse( + "Invalid pallas::Base value for Orchard note commitment tree root", + )) + } } } @@ -112,9 +150,7 @@ impl ZcashSerialize for Root { impl ZcashDeserialize for Root { fn zcash_deserialize(mut reader: R) -> Result { - let anchor = reader.read_32_bytes()?.into(); - - Ok(anchor) + Self::try_from(reader.read_32_bytes()?) } } @@ -135,8 +171,8 @@ impl From> for NoteCommitmentTree { } } -impl From> for NoteCommitmentTree { - fn from(values: Vec) -> Self { +impl From> for NoteCommitmentTree { + fn from(values: Vec) -> Self { if values.is_empty() { return NoteCommitmentTree { root: Root::default(), @@ -147,8 +183,7 @@ impl From> for NoteCommitmentTree { let count = values.len() as u32; let mut height = 0u8; - let mut current_layer: VecDeque<[u8; 32]> = - values.into_iter().map(|cm_u| cm_u.to_bytes()).collect(); + let mut current_layer: VecDeque = values.into_iter().collect(); while usize::from(height) < MERKLE_DEPTH { let mut next_layer_up = vec![]; @@ -161,7 +196,7 @@ impl From> for NoteCommitmentTree { } else { right = current_layer.pop_front().unwrap(); } - next_layer_up.push(merkle_crh_orchard(height, left, right)); + next_layer_up.push(merkle_crh_orchard(height, Some(left), Some(right)).unwrap()); } height += 1; @@ -179,11 +214,36 @@ impl From> for NoteCommitmentTree { } impl NoteCommitmentTree { - /// Get the Pallas-based Pedersen hash of root node of this merkle tree of - /// commitment notes. + /// Get the Pallas-based Sinsemilla hash of root node of this merkle tree of + /// note commitments. pub fn hash(&self) -> [u8; 32] { - self.root.0 + self.root.into() + } + + /// An as-yet unused Orchard note commitment tree leaf node. + /// + /// Distinct for Orchard, a distinguished hash value of: + /// + /// Uncommitted^Orchard = I2LEBSP_l_MerkleOrchard(2) + pub fn uncommitted() -> pallas::Base { + pallas::Base::one().double() } } // TODO: check empty roots, incremental roots, as part of https://github.com/ZcashFoundation/zebra/issues/1287 + +#[cfg(test)] +mod tests { + + use super::*; + use crate::orchard::tests::test_vectors; + + #[test] + fn empty_roots() { + zebra_test::init(); + + for i in 0..EMPTY_ROOTS.len() { + assert_eq!(EMPTY_ROOTS[i].to_bytes(), test_vectors::EMPTY_ROOTS[i]); + } + } +}