Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memo type, existing EncryptedCiphertext types for Sprout and Sapling #249

Merged
merged 12 commits into from Feb 13, 2020
1 change: 1 addition & 0 deletions zebra-chain/src/lib.rs
Expand Up @@ -7,6 +7,7 @@ mod sha256d_writer;
pub mod block;
pub mod equihash_solution;
pub mod note_commitment_tree;
pub mod note_encryption;
pub mod proofs;
pub mod serialization;
pub mod transaction;
Expand Down
4 changes: 4 additions & 0 deletions zebra-chain/src/note_encryption.rs
@@ -0,0 +1,4 @@
//! Note encryption types.
mod memo;
mod sapling;
mod sprout;
83 changes: 83 additions & 0 deletions zebra-chain/src/note_encryption/memo.rs
@@ -0,0 +1,83 @@
use std::{cmp, convert::TryFrom, fmt};

/// A 512-byte _Memo_ field associated with a note, as described in
/// [protocol specification §5.5][ps].
///
/// The _Memo_ field of a note is a plaintext type; the parent note is
/// what is encrypted and stored on the blockchain. The underlying
/// usage of the memo field is by agreement between the sender and
/// recipient of the note.
///
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#notept
#[derive(Clone)]
pub struct Memo(Box<[u8; 512]>);

impl<'a> TryFrom<&'a [u8]> for Memo {
type Error = &'static str;

fn try_from(input: &'a [u8]) -> Result<Self, Self::Error> {
let mut full_bytes = [0; 512];

match input.len().cmp(&512) {
cmp::Ordering::Less => {
full_bytes[0..input.len()].copy_from_slice(input);
Ok(Memo(Box::new(full_bytes)))
}
cmp::Ordering::Equal => {
full_bytes[..].copy_from_slice(input);
Ok(Memo(Box::new(full_bytes)))
}
cmp::Ordering::Greater => Err("Memos have a max length of 512 bytes."),
}
}
}

impl fmt::Debug for Memo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let output: String;

// This saves work but if the 'valid utf8 string' is just a
// bunch of numbers, it prints them out like
// 'Memo("\u{0}\u{0}..")', so. ¯\_(ツ)_/¯
match std::str::from_utf8(&self.0[..]) {
Ok(memo) => output = String::from(memo),
_ => output = hex::encode(&self.0[..]),
}

f.debug_tuple("Memo").field(&output).finish()
}
}

#[test]
fn memo_fmt() {
let memo = Memo(Box::new(
*b"thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryyyyyyyyyyyyyyyyyyyyyyyyyy \
looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong \
meeeeeeeeeeeeeeeeeeemooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo \
but it's just short enough",
));

assert_eq!(format!("{:?}", memo),
"Memo(\"thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryyyyyyyyyyyyyyyyyyyyyyyyyy looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong meeeeeeeeeeeeeeeeeeemooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo but it\\\'s just short enough\")"
);

let mut some_bytes = [0u8; 512];
some_bytes[0] = 0xF6;

assert_eq!(format!("{:?}", Memo(Box::new(some_bytes))),
"Memo(\"f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\")"
);
}

#[test]
fn memo_from_string() {
let memo = Memo::try_from("foo bar baz".as_ref()).unwrap();

let mut bytes = [0; 512];
bytes[0..11].copy_from_slice(&[102, 111, 111, 32, 98, 97, 114, 32, 98, 97, 122]);

assert!(memo.0.iter().eq(bytes.iter()));
}
160 changes: 160 additions & 0 deletions zebra-chain/src/note_encryption/sapling.rs
@@ -0,0 +1,160 @@
use std::{fmt, io};

#[cfg(test)]
use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};

use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize};

/// A ciphertext component for encrypted output notes.
pub struct EncryptedCiphertext(pub [u8; 580]);

impl fmt::Debug for EncryptedCiphertext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("EncryptedCiphertext")
.field(&hex::encode(&self.0[..]))
.finish()
}
}

// These impls all only exist because of array length restrictions.

impl Copy for EncryptedCiphertext {}

impl Clone for EncryptedCiphertext {
fn clone(&self) -> Self {
let mut bytes = [0; 580];
bytes[..].copy_from_slice(&self.0[..]);
Self(bytes)
}
}

impl PartialEq for EncryptedCiphertext {
fn eq(&self, other: &Self) -> bool {
self.0[..] == other.0[..]
}
}

impl Eq for EncryptedCiphertext {}

impl ZcashSerialize for EncryptedCiphertext {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
writer.write_all(&self.0[..])?;
Ok(())
}
}

impl ZcashDeserialize for EncryptedCiphertext {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let mut bytes = [0; 580];
reader.read_exact(&mut bytes[..])?;
Ok(Self(bytes))
}
}

#[cfg(test)]
impl Arbitrary for EncryptedCiphertext {
type Parameters = ();

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(vec(any::<u8>(), 580))
.prop_map(|v| {
let mut bytes = [0; 580];
bytes.copy_from_slice(v.as_slice());
return Self(bytes);
})
.boxed()
}

type Strategy = BoxedStrategy<Self>;
}

/// A ciphertext component for encrypted output notes.
pub struct OutCiphertext(pub [u8; 80]);

impl fmt::Debug for OutCiphertext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("OutCiphertext")
.field(&hex::encode(&self.0[..]))
.finish()
}
}

// These impls all only exist because of array length restrictions.

impl Copy for OutCiphertext {}

impl Clone for OutCiphertext {
fn clone(&self) -> Self {
let mut bytes = [0; 80];
bytes[..].copy_from_slice(&self.0[..]);
Self(bytes)
}
}

impl PartialEq for OutCiphertext {
fn eq(&self, other: &Self) -> bool {
self.0[..] == other.0[..]
}
}

impl Eq for OutCiphertext {}

impl ZcashSerialize for OutCiphertext {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
writer.write_all(&self.0[..])?;
Ok(())
}
}

impl ZcashDeserialize for OutCiphertext {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let mut bytes = [0; 80];
reader.read_exact(&mut bytes[..])?;
Ok(Self(bytes))
}
}

#[cfg(test)]
impl Arbitrary for OutCiphertext {
type Parameters = ();

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(vec(any::<u8>(), 80))
.prop_map(|v| {
let mut bytes = [0; 80];
bytes.copy_from_slice(v.as_slice());
return Self(bytes);
})
.boxed()
}

type Strategy = BoxedStrategy<Self>;
}

#[cfg(test)]
proptest! {

#[test]
fn encrypted_ciphertext_roundtrip(ec in any::<EncryptedCiphertext>()) {

let mut data = Vec::new();

ec.zcash_serialize(&mut data).expect("EncryptedCiphertext should serialize");

let ec2 = EncryptedCiphertext::zcash_deserialize(&data[..]).expect("randomized EncryptedCiphertext should deserialize");

prop_assert_eq![ec, ec2];
}

#[test]
fn out_ciphertext_roundtrip(oc in any::<OutCiphertext>()) {

let mut data = Vec::new();

oc.zcash_serialize(&mut data).expect("OutCiphertext should serialize");

let oc2 = OutCiphertext::zcash_deserialize(&data[..]).expect("randomized OutCiphertext should deserialize");

prop_assert_eq![oc, oc2];
}
}
88 changes: 88 additions & 0 deletions zebra-chain/src/note_encryption/sprout.rs
@@ -0,0 +1,88 @@
use std::{
fmt,
io::{self},
};

#[cfg(test)]
use proptest::{collection::vec, prelude::*};

use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize};

/// A ciphertext component for encrypted output notes.
pub struct EncryptedCiphertext(pub [u8; 601]);

impl fmt::Debug for EncryptedCiphertext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("EncryptedCiphertext")
.field(&hex::encode(&self.0[..]))
.finish()
}
}

// These impls all only exist because of array length restrictions.

impl Copy for EncryptedCiphertext {}

impl Clone for EncryptedCiphertext {
fn clone(&self) -> Self {
let mut bytes = [0; 601];
bytes[..].copy_from_slice(&self.0[..]);
Self(bytes)
}
}

impl PartialEq for EncryptedCiphertext {
fn eq(&self, other: &Self) -> bool {
self.0[..] == other.0[..]
}
}

impl Eq for EncryptedCiphertext {}

impl ZcashSerialize for EncryptedCiphertext {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
writer.write_all(&self.0[..])?;
Ok(())
}
}

impl ZcashDeserialize for EncryptedCiphertext {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let mut bytes = [0; 601];
reader.read_exact(&mut bytes[..])?;
Ok(Self(bytes))
}
}

#[cfg(test)]
impl Arbitrary for EncryptedCiphertext {
type Parameters = ();

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(vec(any::<u8>(), 601))
.prop_map(|v| {
let mut bytes = [0; 601];
bytes.copy_from_slice(v.as_slice());
return Self(bytes);
})
.boxed()
}

type Strategy = BoxedStrategy<Self>;
}

#[cfg(test)]
proptest! {

#[test]
fn encrypted_ciphertext_roundtrip(ec in any::<EncryptedCiphertext>()) {

let mut data = Vec::new();

ec.zcash_serialize(&mut data).expect("EncryptedCiphertext should serialize");

let ec2 = EncryptedCiphertext::zcash_deserialize(&data[..]).expect("randomized EncryptedCiphertext should deserialize");

prop_assert_eq![ec, ec2];
}
}
Empty file.
Empty file.