diff --git a/src/tree.rs b/src/tree.rs index 02f7e12b..943e1789 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -13,7 +13,7 @@ use pasta_curves::{arithmetic::FieldExt, pallas}; use ff::{Field, PrimeField, PrimeFieldBits}; use lazy_static::lazy_static; use rand::RngCore; -use serde::de::Deserializer; +use serde::de::{Deserializer, Error}; use serde::ser::Serializer; use serde::{Deserialize, Serialize}; use std::iter; @@ -164,6 +164,11 @@ fn hash_with_l(l: usize, pair: Pair) -> CtOption { /// A newtype wrapper for leaves and internal nodes in the Orchard /// incremental note commitment tree. +/// +/// This wraps a CtOption because Sinsemilla hashes +/// can produce a bottom value which needs to be accounted for in +/// the production of a Merkle root. Leaf nodes are always wrapped +/// with the `Some` constructor. #[derive(Clone, Debug)] pub struct OrchardIncrementalTreeDigest(CtOption); @@ -181,8 +186,12 @@ impl OrchardIncrementalTreeDigest { /// Parses a incremental tree leaf digest from the bytes of /// a note commitment. - pub fn from_bytes(bytes: &[u8; 32]) -> Self { - OrchardIncrementalTreeDigest(pallas::Base::from_bytes(bytes)) + /// + /// Returns the empty `CtOption` if the provided bytes represent + /// a non-canonical encoding. + pub fn from_bytes(bytes: &[u8; 32]) -> CtOption { + pallas::Base::from_bytes(bytes) + .map(|b| OrchardIncrementalTreeDigest(CtOption::new(b, 1.into()))) } } @@ -232,7 +241,9 @@ impl Serialize for OrchardIncrementalTreeDigest { impl<'de> Deserialize<'de> for OrchardIncrementalTreeDigest { fn deserialize>(deserializer: D) -> Result { let parsed = <[u8; 32]>::deserialize(deserializer)?; - Ok(Self::from_bytes(&parsed)) + >::from(Self::from_bytes(&parsed)).ok_or(Error::custom( + "Attempted to deserialize non-canonical representaion of a Pallas base field element.", + )) } }