From 6b13ce3e8354d72a118bf9695f01fc32f1d90666 Mon Sep 17 00:00:00 2001 From: Deirdre Connolly Date: Wed, 12 Aug 2020 01:43:57 -0400 Subject: [PATCH] Add Sapling EphemeralPublicKey type that wraps jubjub::AffinePoint --- zebra-chain/src/keys/sapling.rs | 61 ++++++++++++++++++- zebra-chain/src/keys/sapling/arbitrary.rs | 17 ++++++ zebra-chain/src/transaction/serialize.rs | 6 +- zebra-chain/src/transaction/shielded_data.rs | 5 +- .../src/transaction/tests/arbitrary.rs | 10 ++- 5 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 zebra-chain/src/keys/sapling/arbitrary.rs diff --git a/zebra-chain/src/keys/sapling.rs b/zebra-chain/src/keys/sapling.rs index 860e7d0c..9657062f 100644 --- a/zebra-chain/src/keys/sapling.rs +++ b/zebra-chain/src/keys/sapling.rs @@ -9,6 +9,8 @@ //! [3.1]: https://zips.z.cash/protocol/protocol.pdf#addressesandkeys #![allow(clippy::unit_arg)] +#[cfg(test)] +mod arbitrary; #[cfg(test)] mod test_vectors; #[cfg(test)] @@ -29,7 +31,8 @@ use proptest_derive::Arbitrary; use crate::{ redjubjub::{self, SpendAuth}, - serialization::{ReadZcashExt, SerializationError}, + serde_helpers, + serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}, Network, }; @@ -860,3 +863,59 @@ impl FromStr for FullViewingKey { } } } + +/// An ephemeral public key for Sapling key agreement. +/// +/// https://zips.z.cash/protocol/canopy.pdf#concretesaplingkeyagreement +#[derive(Copy, Clone, Deserialize, PartialEq, Serialize)] +pub struct EphemeralPublicKey(#[serde(with = "serde_helpers::AffinePoint")] jubjub::AffinePoint); + +impl fmt::Debug for EphemeralPublicKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("EphemeralPublicKey") + .field("u", &hex::encode(self.0.get_u().to_bytes())) + .field("v", &hex::encode(self.0.get_v().to_bytes())) + .finish() + } +} + +impl Eq for EphemeralPublicKey {} + +impl From<&EphemeralPublicKey> for [u8; 32] { + fn from(nk: &EphemeralPublicKey) -> [u8; 32] { + nk.0.to_bytes() + } +} + +impl PartialEq<[u8; 32]> for EphemeralPublicKey { + fn eq(&self, other: &[u8; 32]) -> bool { + <[u8; 32]>::from(self) == *other + } +} + +impl TryFrom<[u8; 32]> for EphemeralPublicKey { + type Error = &'static str; + + fn try_from(bytes: [u8; 32]) -> Result { + let possible_point = jubjub::AffinePoint::from_bytes(bytes); + + if possible_point.is_some().into() { + Ok(Self(possible_point.unwrap())) + } else { + Err("Invalid jubjub::AffinePoint value") + } + } +} + +impl ZcashSerialize for EphemeralPublicKey { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&<[u8; 32]>::from(self)[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for EphemeralPublicKey { + fn zcash_deserialize(mut reader: R) -> Result { + Self::try_from(reader.read_32_bytes()?).map_err(|e| SerializationError::Parse(e)) + } +} diff --git a/zebra-chain/src/keys/sapling/arbitrary.rs b/zebra-chain/src/keys/sapling/arbitrary.rs new file mode 100644 index 00000000..611d1780 --- /dev/null +++ b/zebra-chain/src/keys/sapling/arbitrary.rs @@ -0,0 +1,17 @@ +use std::convert::TryFrom; + +use proptest::{arbitrary::any, array, prelude::*}; + +use crate::keys::sapling; + +impl Arbitrary for sapling::EphemeralPublicKey { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + array::uniform32(any::()) + .prop_filter_map("Valid jubjub::AffinePoint", |b| Self::try_from(b).ok()) + .boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index 5e2a9ce1..b48a69f2 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -9,7 +9,7 @@ use std::{ }; use crate::{ - commitments, notes, + commitments, keys, notes, proofs::ZkSnarkProof, serialization::{ ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize, @@ -352,7 +352,7 @@ impl ZcashSerialize for Output { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { self.cv.zcash_serialize(&mut writer)?; writer.write_all(&self.cm_u.to_bytes())?; - writer.write_all(&self.ephemeral_key.to_bytes())?; + self.ephemeral_key.zcash_serialize(&mut writer)?; self.enc_ciphertext.zcash_serialize(&mut writer)?; self.out_ciphertext.zcash_serialize(&mut writer)?; self.zkproof.zcash_serialize(&mut writer)?; @@ -365,7 +365,7 @@ impl ZcashDeserialize for Output { Ok(Output { cv: commitments::sapling::ValueCommitment::zcash_deserialize(&mut reader)?, cm_u: jubjub::Fq::from_bytes(&reader.read_32_bytes()?).unwrap(), - ephemeral_key: jubjub::AffinePoint::from_bytes(reader.read_32_bytes()?).unwrap(), + ephemeral_key: keys::sapling::EphemeralPublicKey::zcash_deserialize(&mut reader)?, enc_ciphertext: notes::sapling::EncryptedCiphertext::zcash_deserialize(&mut reader)?, out_ciphertext: notes::sapling::OutCiphertext::zcash_deserialize(&mut reader)?, zkproof: Groth16Proof::zcash_deserialize(&mut reader)?, diff --git a/zebra-chain/src/transaction/shielded_data.rs b/zebra-chain/src/transaction/shielded_data.rs index a7a7927a..50342426 100644 --- a/zebra-chain/src/transaction/shielded_data.rs +++ b/zebra-chain/src/transaction/shielded_data.rs @@ -1,5 +1,5 @@ use crate::{ - commitments, notes, + commitments, keys, notes, proofs::Groth16Proof, redjubjub::{self, Binding, SpendAuth}, serde_helpers, @@ -37,8 +37,7 @@ pub struct Output { #[serde(with = "serde_helpers::Fq")] pub cm_u: jubjub::Fq, /// An encoding of an ephemeral Jubjub public key. - #[serde(with = "serde_helpers::AffinePoint")] - pub ephemeral_key: jubjub::AffinePoint, + pub ephemeral_key: keys::sapling::EphemeralPublicKey, /// A ciphertext component for the encrypted output note. pub enc_ciphertext: notes::sapling::EncryptedCiphertext, /// A ciphertext component for the encrypted output note. diff --git a/zebra-chain/src/transaction/tests/arbitrary.rs b/zebra-chain/src/transaction/tests/arbitrary.rs index b6cff647..1216f757 100644 --- a/zebra-chain/src/transaction/tests/arbitrary.rs +++ b/zebra-chain/src/transaction/tests/arbitrary.rs @@ -1,5 +1,5 @@ use crate::{ - commitments, + commitments, keys, notes::{sapling, sprout}, proofs::{Groth16Proof, ZkSnarkProof}, transaction::{ @@ -97,18 +97,16 @@ impl Arbitrary for Output { ( any::(), any::(), - array::uniform32(any::()).prop_filter("Valid jubjub::AffinePoint", |b| { - jubjub::AffinePoint::from_bytes(*b).is_some().unwrap_u8() == 1 - }), + any::(), any::(), any::(), any::(), ) .prop_map( - |(cv, cm, ephemeral_key_bytes, enc_ciphertext, out_ciphertext, zkproof)| Self { + |(cv, cm, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof)| Self { cv, cm_u: cm.extract_u(), - ephemeral_key: jubjub::AffinePoint::from_bytes(ephemeral_key_bytes).unwrap(), + ephemeral_key, enc_ciphertext, out_ciphertext, zkproof,