Merge pull request #177 from str4d/175-memo-enum

Implement ZIP 302 memos
This commit is contained in:
str4d 2021-03-25 10:24:43 +13:00 committed by GitHub
commit ebadc8c44c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 538 additions and 317 deletions

View File

@ -22,8 +22,10 @@ and this library adheres to Rust's notion of
decryption APIs: decryption APIs:
- `zcash_client_backend::proto::compact_formats::CompactOutput::epk()` - `zcash_client_backend::proto::compact_formats::CompactOutput::epk()`
- The `epk` field of `zcash_client_backend::wallet::WalletShieldedOutput`. - The `epk` field of `zcash_client_backend::wallet::WalletShieldedOutput`.
- `zcash_client_backend::decrypt::decrypt_transaction` now takes a variable with - `zcash_client_backend::decrypt`:
type `P: zcash_primitives::consensus::Parameters`. - `decrypt_transaction` now takes a variable with type
`P: zcash_primitives::consensus::Parameters`.
- The `memo` field of `DecryptedOutput` now has type `MemoBytes`.
- `zcash_client_backend::wallet`: - `zcash_client_backend::wallet`:
- The `nf` property of `WalletShieldedSpend` now has the type `Nullifier`. - The `nf` property of `WalletShieldedSpend` now has the type `Nullifier`.
- The `account` property of `WalletShieldedSpend` and `WalletShieldedOutput` - The `account` property of `WalletShieldedSpend` and `WalletShieldedOutput`

View File

@ -7,8 +7,8 @@ use std::fmt::Debug;
use zcash_primitives::{ use zcash_primitives::{
block::BlockHash, block::BlockHash,
consensus::BlockHeight, consensus::BlockHeight,
memo::{Memo, MemoBytes},
merkle_tree::{CommitmentTree, IncrementalWitness}, merkle_tree::{CommitmentTree, IncrementalWitness},
note_encryption::Memo,
primitives::{Nullifier, PaymentAddress}, primitives::{Nullifier, PaymentAddress},
sapling::Node, sapling::Node,
transaction::{components::Amount, Transaction, TxId}, transaction::{components::Amount, Transaction, TxId},
@ -137,11 +137,11 @@ pub trait WalletRead {
anchor_height: BlockHeight, anchor_height: BlockHeight,
) -> Result<Amount, Self::Error>; ) -> Result<Amount, Self::Error>;
/// Returns the memo for a note, if it is known and a valid UTF-8 string. /// Returns the memo for a note.
/// ///
/// This will return `Ok(None)` if the note identifier does not appear in the /// Implementations of this method must return an error if the note identifier
/// database as a known note ID. /// does not appear in the backing data store.
fn get_memo_as_utf8(&self, id_note: Self::NoteRef) -> Result<Option<String>, Self::Error>; fn get_memo(&self, id_note: Self::NoteRef) -> Result<Memo, Self::Error>;
/// Returns the note commitment tree at the specified block height. /// Returns the note commitment tree at the specified block height.
fn get_commitment_tree( fn get_commitment_tree(
@ -199,7 +199,7 @@ pub struct SentTransaction<'a> {
pub account: AccountId, pub account: AccountId,
pub recipient_address: &'a RecipientAddress, pub recipient_address: &'a RecipientAddress,
pub value: Amount, pub value: Amount,
pub memo: Option<Memo>, pub memo: Option<MemoBytes>,
} }
/// This trait encapsulates the write capabilities required to update stored /// This trait encapsulates the write capabilities required to update stored
@ -259,6 +259,7 @@ pub mod testing {
use zcash_primitives::{ use zcash_primitives::{
block::BlockHash, block::BlockHash,
consensus::BlockHeight, consensus::BlockHeight,
memo::Memo,
merkle_tree::{CommitmentTree, IncrementalWitness}, merkle_tree::{CommitmentTree, IncrementalWitness},
primitives::{Nullifier, PaymentAddress}, primitives::{Nullifier, PaymentAddress},
sapling::Node, sapling::Node,
@ -342,8 +343,8 @@ pub mod testing {
Ok(Amount::zero()) Ok(Amount::zero())
} }
fn get_memo_as_utf8(&self, _id_note: Self::NoteRef) -> Result<Option<String>, Self::Error> { fn get_memo(&self, _id_note: Self::NoteRef) -> Result<Memo, Self::Error> {
Ok(None) Ok(Memo::Empty)
} }
fn get_commitment_tree( fn get_commitment_tree(

View File

@ -3,7 +3,7 @@ use std::fmt::Debug;
use zcash_primitives::{ use zcash_primitives::{
consensus::{self, BranchId, NetworkUpgrade}, consensus::{self, BranchId, NetworkUpgrade},
note_encryption::Memo, memo::MemoBytes,
prover::TxProver, prover::TxProver,
transaction::{ transaction::{
builder::Builder, builder::Builder,
@ -155,7 +155,7 @@ pub fn create_spend_to_address<E, N, P, D, R>(
extsk: &ExtendedSpendingKey, extsk: &ExtendedSpendingKey,
to: &RecipientAddress, to: &RecipientAddress,
value: Amount, value: Amount,
memo: Option<Memo>, memo: Option<MemoBytes>,
ovk_policy: OvkPolicy, ovk_policy: OvkPolicy,
) -> Result<R, E> ) -> Result<R, E>
where where

View File

@ -2,7 +2,8 @@ use std::collections::HashMap;
use zcash_primitives::{ use zcash_primitives::{
consensus::{self, BlockHeight}, consensus::{self, BlockHeight},
note_encryption::{try_sapling_note_decryption, try_sapling_output_recovery, Memo}, memo::MemoBytes,
note_encryption::{try_sapling_note_decryption, try_sapling_output_recovery},
primitives::{Note, PaymentAddress}, primitives::{Note, PaymentAddress},
transaction::Transaction, transaction::Transaction,
zip32::ExtendedFullViewingKey, zip32::ExtendedFullViewingKey,
@ -22,8 +23,8 @@ pub struct DecryptedOutput {
pub account: AccountId, pub account: AccountId,
/// The address the note was sent to. /// The address the note was sent to.
pub to: PaymentAddress, pub to: PaymentAddress,
/// The memo included with the note. /// The memo bytes included with the note.
pub memo: Memo, pub memo: MemoBytes,
/// True if this output was recovered using an [`OutgoingViewingKey`], meaning that /// True if this output was recovered using an [`OutgoingViewingKey`], meaning that
/// this is a logical output of the transaction. /// this is a logical output of the transaction.
/// ///

View File

@ -275,8 +275,9 @@ mod tests {
use zcash_primitives::{ use zcash_primitives::{
consensus::{BlockHeight, Network}, consensus::{BlockHeight, Network},
constants::SPENDING_KEY_GENERATOR, constants::SPENDING_KEY_GENERATOR,
memo::MemoBytes,
merkle_tree::CommitmentTree, merkle_tree::CommitmentTree,
note_encryption::{Memo, SaplingNoteEncryption}, note_encryption::SaplingNoteEncryption,
primitives::{Note, Nullifier, SaplingIvk}, primitives::{Note, Nullifier, SaplingIvk},
transaction::components::Amount, transaction::components::Amount,
util::generate_random_rseed, util::generate_random_rseed,
@ -344,7 +345,7 @@ mod tests {
Some(extfvk.fvk.ovk), Some(extfvk.fvk.ovk),
note.clone(), note.clone(),
to, to,
Memo::default(), MemoBytes::empty(),
&mut rng, &mut rng,
); );
let cmu = note.cmu().to_repr().as_ref().to_owned(); let cmu = note.cmu().to_repr().as_ref().to_owned();

View File

@ -36,7 +36,7 @@ pub enum SqliteClientError {
Io(std::io::Error), Io(std::io::Error),
/// A received memo cannot be interpreted as a UTF-8 string. /// A received memo cannot be interpreted as a UTF-8 string.
InvalidMemo(std::str::Utf8Error), InvalidMemo(zcash_primitives::memo::Error),
/// Wrapper for errors from zcash_client_backend /// Wrapper for errors from zcash_client_backend
BackendError(data_api::error::Error<NoteId>), BackendError(data_api::error::Error<NoteId>),
@ -98,6 +98,12 @@ impl From<bs58::decode::Error> for SqliteClientError {
} }
} }
impl From<zcash_primitives::memo::Error> for SqliteClientError {
fn from(e: zcash_primitives::memo::Error) -> Self {
SqliteClientError::InvalidMemo(e)
}
}
impl From<data_api::error::Error<NoteId>> for SqliteClientError { impl From<data_api::error::Error<NoteId>> for SqliteClientError {
fn from(e: data_api::error::Error<NoteId>) -> Self { fn from(e: data_api::error::Error<NoteId>) -> Self {
SqliteClientError::BackendError(e) SqliteClientError::BackendError(e)

View File

@ -33,6 +33,7 @@ use rusqlite::{Connection, Statement, NO_PARAMS};
use zcash_primitives::{ use zcash_primitives::{
block::BlockHash, block::BlockHash,
consensus::{self, BlockHeight}, consensus::{self, BlockHeight},
memo::Memo,
merkle_tree::{CommitmentTree, IncrementalWitness}, merkle_tree::{CommitmentTree, IncrementalWitness},
primitives::{Nullifier, PaymentAddress}, primitives::{Nullifier, PaymentAddress},
sapling::Node, sapling::Node,
@ -205,10 +206,10 @@ impl<P: consensus::Parameters> WalletRead for WalletDB<P> {
wallet::get_balance_at(self, account, anchor_height) wallet::get_balance_at(self, account, anchor_height)
} }
fn get_memo_as_utf8(&self, id_note: Self::NoteRef) -> Result<Option<String>, Self::Error> { fn get_memo(&self, id_note: Self::NoteRef) -> Result<Memo, Self::Error> {
match id_note { match id_note {
NoteId::SentNoteId(id_note) => wallet::get_sent_memo_as_utf8(self, id_note), NoteId::SentNoteId(id_note) => wallet::get_sent_memo(self, id_note),
NoteId::ReceivedNoteId(id_note) => wallet::get_received_memo_as_utf8(self, id_note), NoteId::ReceivedNoteId(id_note) => wallet::get_received_memo(self, id_note),
} }
} }
@ -317,8 +318,8 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> {
self.wallet_db.get_balance_at(account, anchor_height) self.wallet_db.get_balance_at(account, anchor_height)
} }
fn get_memo_as_utf8(&self, id_note: Self::NoteRef) -> Result<Option<String>, Self::Error> { fn get_memo(&self, id_note: Self::NoteRef) -> Result<Memo, Self::Error> {
self.wallet_db.get_memo_as_utf8(id_note) self.wallet_db.get_memo(id_note)
} }
fn get_commitment_tree( fn get_commitment_tree(
@ -485,7 +486,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
sent_tx.account, sent_tx.account,
sent_tx.recipient_address, sent_tx.recipient_address,
sent_tx.value, sent_tx.value,
&sent_tx.memo, sent_tx.memo.as_ref(),
)?; )?;
// Return the row number of the transaction, so the caller can fetch it for sending. // Return the row number of the transaction, so the caller can fetch it for sending.
@ -545,7 +546,8 @@ mod tests {
use zcash_primitives::{ use zcash_primitives::{
block::BlockHash, block::BlockHash,
consensus::{BlockHeight, Network, NetworkUpgrade, Parameters}, consensus::{BlockHeight, Network, NetworkUpgrade, Parameters},
note_encryption::{Memo, SaplingNoteEncryption}, memo::MemoBytes,
note_encryption::SaplingNoteEncryption,
primitives::{Note, Nullifier, PaymentAddress}, primitives::{Note, Nullifier, PaymentAddress},
transaction::components::Amount, transaction::components::Amount,
util::generate_random_rseed, util::generate_random_rseed,
@ -601,7 +603,7 @@ mod tests {
Some(extfvk.fvk.ovk), Some(extfvk.fvk.ovk),
note.clone(), note.clone(),
to, to,
Memo::default(), MemoBytes::empty(),
&mut rng, &mut rng,
); );
let cmu = note.cmu().to_repr().as_ref().to_vec(); let cmu = note.cmu().to_repr().as_ref().to_vec();
@ -661,7 +663,7 @@ mod tests {
Some(extfvk.fvk.ovk), Some(extfvk.fvk.ovk),
note.clone(), note.clone(),
to, to,
Memo::default(), MemoBytes::empty(),
&mut rng, &mut rng,
); );
let cmu = note.cmu().to_repr().as_ref().to_vec(); let cmu = note.cmu().to_repr().as_ref().to_vec();
@ -689,7 +691,7 @@ mod tests {
Some(extfvk.fvk.ovk), Some(extfvk.fvk.ovk),
note.clone(), note.clone(),
change_addr, change_addr,
Memo::default(), MemoBytes::empty(),
&mut rng, &mut rng,
); );
let cmu = note.cmu().to_repr().as_ref().to_vec(); let cmu = note.cmu().to_repr().as_ref().to_vec();

View File

@ -3,12 +3,13 @@
use ff::PrimeField; use ff::PrimeField;
use rusqlite::{params, OptionalExtension, ToSql, NO_PARAMS}; use rusqlite::{params, OptionalExtension, ToSql, NO_PARAMS};
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryFrom;
use zcash_primitives::{ use zcash_primitives::{
block::BlockHash, block::BlockHash,
consensus::{self, BlockHeight, NetworkUpgrade}, consensus::{self, BlockHeight, NetworkUpgrade},
memo::{Memo, MemoBytes},
merkle_tree::{CommitmentTree, IncrementalWitness}, merkle_tree::{CommitmentTree, IncrementalWitness},
note_encryption::Memo,
primitives::{Note, Nullifier, PaymentAddress}, primitives::{Note, Nullifier, PaymentAddress},
sapling::Node, sapling::Node,
transaction::{components::Amount, Transaction, TxId}, transaction::{components::Amount, Transaction, TxId},
@ -41,7 +42,7 @@ pub trait ShieldedOutput {
fn account(&self) -> AccountId; fn account(&self) -> AccountId;
fn to(&self) -> &PaymentAddress; fn to(&self) -> &PaymentAddress;
fn note(&self) -> &Note; fn note(&self) -> &Note;
fn memo(&self) -> Option<&Memo>; fn memo(&self) -> Option<&MemoBytes>;
fn is_change(&self) -> Option<bool>; fn is_change(&self) -> Option<bool>;
fn nullifier(&self) -> Option<Nullifier>; fn nullifier(&self) -> Option<Nullifier>;
} }
@ -59,7 +60,7 @@ impl ShieldedOutput for WalletShieldedOutput<Nullifier> {
fn note(&self) -> &Note { fn note(&self) -> &Note {
&self.note &self.note
} }
fn memo(&self) -> Option<&Memo> { fn memo(&self) -> Option<&MemoBytes> {
None None
} }
fn is_change(&self) -> Option<bool> { fn is_change(&self) -> Option<bool> {
@ -84,7 +85,7 @@ impl ShieldedOutput for DecryptedOutput {
fn note(&self) -> &Note { fn note(&self) -> &Note {
&self.note &self.note
} }
fn memo(&self) -> Option<&Memo> { fn memo(&self) -> Option<&MemoBytes> {
Some(&self.memo) Some(&self.memo)
} }
fn is_change(&self) -> Option<bool> { fn is_change(&self) -> Option<bool> {
@ -260,7 +261,7 @@ pub fn get_balance_at<P>(
} }
} }
/// Returns the memo for a received note, if it is known and a valid UTF-8 string. /// Returns the memo for a received note.
/// ///
/// The note is identified by its row index in the `received_notes` table within the wdb /// The note is identified by its row index in the `received_notes` table within the wdb
/// database. /// database.
@ -273,35 +274,27 @@ pub fn get_balance_at<P>(
/// use zcash_client_sqlite::{ /// use zcash_client_sqlite::{
/// NoteId, /// NoteId,
/// WalletDB, /// WalletDB,
/// wallet::get_received_memo_as_utf8, /// wallet::get_received_memo,
/// }; /// };
/// ///
/// let data_file = NamedTempFile::new().unwrap(); /// let data_file = NamedTempFile::new().unwrap();
/// let db = WalletDB::for_path(data_file, Network::TestNetwork).unwrap(); /// let db = WalletDB::for_path(data_file, Network::TestNetwork).unwrap();
/// let memo = get_received_memo_as_utf8(&db, 27); /// let memo = get_received_memo(&db, 27);
/// ``` /// ```
pub fn get_received_memo_as_utf8<P>( pub fn get_received_memo<P>(wdb: &WalletDB<P>, id_note: i64) -> Result<Memo, SqliteClientError> {
wdb: &WalletDB<P>, let memo_bytes: Vec<_> = wdb.conn.query_row(
id_note: i64,
) -> Result<Option<String>, SqliteClientError> {
let memo: Vec<_> = wdb.conn.query_row(
"SELECT memo FROM received_notes "SELECT memo FROM received_notes
WHERE id_note = ?", WHERE id_note = ?",
&[id_note], &[id_note],
|row| row.get(0), |row| row.get(0),
)?; )?;
match Memo::from_bytes(&memo) { MemoBytes::from_bytes(&memo_bytes)
Some(memo) => match memo.to_utf8() { .and_then(Memo::try_from)
Some(Ok(res)) => Ok(Some(res)), .map_err(SqliteClientError::from)
Some(Err(e)) => Err(SqliteClientError::InvalidMemo(e)),
None => Ok(None),
},
None => Ok(None),
}
} }
/// Returns the memo for a sent note, if it is known and a valid UTF-8 string. /// Returns the memo for a sent note.
/// ///
/// The note is identified by its row index in the `sent_notes` table within the wdb /// The note is identified by its row index in the `sent_notes` table within the wdb
/// database. /// database.
@ -314,32 +307,24 @@ pub fn get_received_memo_as_utf8<P>(
/// use zcash_client_sqlite::{ /// use zcash_client_sqlite::{
/// NoteId, /// NoteId,
/// WalletDB, /// WalletDB,
/// wallet::get_sent_memo_as_utf8, /// wallet::get_sent_memo,
/// }; /// };
/// ///
/// let data_file = NamedTempFile::new().unwrap(); /// let data_file = NamedTempFile::new().unwrap();
/// let db = WalletDB::for_path(data_file, Network::TestNetwork).unwrap(); /// let db = WalletDB::for_path(data_file, Network::TestNetwork).unwrap();
/// let memo = get_sent_memo_as_utf8(&db, 12); /// let memo = get_sent_memo(&db, 12);
/// ``` /// ```
pub fn get_sent_memo_as_utf8<P>( pub fn get_sent_memo<P>(wdb: &WalletDB<P>, id_note: i64) -> Result<Memo, SqliteClientError> {
wdb: &WalletDB<P>, let memo_bytes: Vec<_> = wdb.conn.query_row(
id_note: i64,
) -> Result<Option<String>, SqliteClientError> {
let memo: Vec<_> = wdb.conn.query_row(
"SELECT memo FROM sent_notes "SELECT memo FROM sent_notes
WHERE id_note = ?", WHERE id_note = ?",
&[id_note], &[id_note],
|row| row.get(0), |row| row.get(0),
)?; )?;
match Memo::from_bytes(&memo) { MemoBytes::from_bytes(&memo_bytes)
Some(memo) => match memo.to_utf8() { .and_then(Memo::try_from)
Some(Ok(res)) => Ok(Some(res)), .map_err(SqliteClientError::from)
Some(Err(e)) => Err(SqliteClientError::InvalidMemo(e)),
None => Ok(None),
},
None => Ok(None),
}
} }
pub fn block_height_extrema<P>( pub fn block_height_extrema<P>(
@ -605,7 +590,7 @@ pub fn put_received_note<'a, P, T: ShieldedOutput>(
let diversifier = output.to().diversifier().0.to_vec(); let diversifier = output.to().diversifier().0.to_vec();
let value = output.note().value as i64; let value = output.note().value as i64;
let rcm = rcm.as_ref(); let rcm = rcm.as_ref();
let memo = output.memo().map(|m| m.as_bytes()); let memo = output.memo().map(|m| m.as_slice());
let is_change = output.is_change(); let is_change = output.is_change();
let tx = tx_ref; let tx = tx_ref;
let output_index = output.index() as i64; let output_index = output.index() as i64;
@ -694,7 +679,7 @@ pub fn put_sent_note<'a, P: consensus::Parameters>(
account, account,
to_str, to_str,
value, value,
&output.memo.as_bytes(), &output.memo.as_slice(),
tx_ref, tx_ref,
output_index output_index
])? == 0 ])? == 0
@ -708,7 +693,7 @@ pub fn put_sent_note<'a, P: consensus::Parameters>(
&RecipientAddress::Shielded(output.to.clone()), &RecipientAddress::Shielded(output.to.clone()),
Amount::from_u64(output.note.value) Amount::from_u64(output.note.value)
.map_err(|_| SqliteClientError::CorruptedData("Note value invalid.".to_string()))?, .map_err(|_| SqliteClientError::CorruptedData("Note value invalid.".to_string()))?,
&Some(output.memo.clone()), Some(&output.memo),
)? )?
} }
@ -722,7 +707,7 @@ pub fn insert_sent_note<'a, P: consensus::Parameters>(
account: AccountId, account: AccountId,
to: &RecipientAddress, to: &RecipientAddress,
value: Amount, value: Amount,
memo: &Option<Memo>, memo: Option<&MemoBytes>,
) -> Result<(), SqliteClientError> { ) -> Result<(), SqliteClientError> {
let to_str = to.encode(&stmts.wallet_db.params); let to_str = to.encode(&stmts.wallet_db.params);
let ivalue: i64 = value.into(); let ivalue: i64 = value.into();
@ -732,7 +717,7 @@ pub fn insert_sent_note<'a, P: consensus::Parameters>(
account.0, account.0,
to_str, to_str,
ivalue, ivalue,
memo.as_ref().map(|m| m.as_bytes().to_vec()), memo.map(|m| m.as_slice().to_vec()),
])?; ])?;
Ok(()) Ok(())

View File

@ -25,6 +25,13 @@ and this library adheres to Rust's notion of
- `b58_script_address_prefix` - `b58_script_address_prefix`
- The `Network` enum, which enables code to be generic over the network type - The `Network` enum, which enables code to be generic over the network type
at runtime. at runtime.
- `zcash_primitives::memo`:
- `MemoBytes`, a minimal wrapper around the memo bytes, that only imposes
the existence of null-padding for shorter memos. `MemoBytes` is guaranteed
to be round-trip encodable (modulo null padding).
- `Memo`, an enum that implements the memo field format defined in
[ZIP 302](https://zips.z.cash/zip-0302). It can be converted to and from
`MemoBytes`.
- `zcash_primitives::primitives::Nullifier` struct. - `zcash_primitives::primitives::Nullifier` struct.
- `zcash_primitives::transaction`: - `zcash_primitives::transaction`:
- `TxVersion` enum, representing the set of valid transaction format - `TxVersion` enum, representing the set of valid transaction format
@ -90,6 +97,12 @@ and this library adheres to Rust's notion of
`&self`. `&self`.
- `zcash_primitives::merkle_tree::CommitmentTree::new` has been renamed to - `zcash_primitives::merkle_tree::CommitmentTree::new` has been renamed to
`CommitmentTree::empty`. `CommitmentTree::empty`.
- `zcash_primitives::note_encryption`:
- `SaplingNoteEncryption::new` now takes `MemoBytes`.
- The following APIs now return `MemoBytes`:
- `try_sapling_note_decryption`
- `try_sapling_output_recovery`
- `try_sapling_output_recovery_with_ock`
- `zcash_primitives::primitives::Note::nf` now returns `Nullifier`. - `zcash_primitives::primitives::Note::nf` now returns `Nullifier`.
- `zcash_primitives::transaction`: - `zcash_primitives::transaction`:
- The `overwintered`, `version`, and `version_group_id` properties of the - The `overwintered`, `version`, and `version_group_id` properties of the
@ -101,6 +114,12 @@ and this library adheres to Rust's notion of
`Nullifier`. `Nullifier`.
- `signature_hash` and `signature_hash_data` now take a `SignableInput` - `signature_hash` and `signature_hash_data` now take a `SignableInput`
argument instead of a `transparent_input` argument. argument instead of a `transparent_input` argument.
- `builder::SaplingOutput::new` and `builder::Builder::add_sapling_output` now
take `Option<MemoBytes>`.
### Removed
- `zcash_primitives::note_encryption::Memo` (replaced by
`zcash_primitives::memo::{Memo, MemoBytes}`).
## [0.4.0] - 2020-09-09 ## [0.4.0] - 2020-09-09
### Added ### Added

View File

@ -3,7 +3,8 @@ use ff::Field;
use rand_core::OsRng; use rand_core::OsRng;
use zcash_primitives::{ use zcash_primitives::{
consensus::{NetworkUpgrade::Canopy, Parameters, TEST_NETWORK}, consensus::{NetworkUpgrade::Canopy, Parameters, TEST_NETWORK},
note_encryption::{try_sapling_note_decryption, Memo, SaplingNoteEncryption}, memo::MemoBytes,
note_encryption::{try_sapling_note_decryption, SaplingNoteEncryption},
primitives::{Diversifier, PaymentAddress, SaplingIvk, ValueCommitment}, primitives::{Diversifier, PaymentAddress, SaplingIvk, ValueCommitment},
transaction::components::{OutputDescription, GROTH_PROOF_SIZE}, transaction::components::{OutputDescription, GROTH_PROOF_SIZE},
util::generate_random_rseed, util::generate_random_rseed,
@ -35,7 +36,7 @@ fn bench_note_decryption(c: &mut Criterion) {
let note = pa.create_note(value, rseed).unwrap(); let note = pa.create_note(value, rseed).unwrap();
let cmu = note.cmu(); let cmu = note.cmu();
let mut ne = SaplingNoteEncryption::new(None, note, pa, Memo::default(), &mut rng); let mut ne = SaplingNoteEncryption::new(None, note, pa, MemoBytes::empty(), &mut rng);
let ephemeral_key = ne.epk().clone().into(); let ephemeral_key = ne.epk().clone().into();
let enc_ciphertext = ne.encrypt_note_plaintext(); let enc_ciphertext = ne.encrypt_note_plaintext();
let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu); let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu);

View File

@ -13,6 +13,7 @@ pub mod constants;
pub mod group_hash; pub mod group_hash;
pub mod keys; pub mod keys;
pub mod legacy; pub mod legacy;
pub mod memo;
pub mod merkle_tree; pub mod merkle_tree;
pub mod note_encryption; pub mod note_encryption;
pub mod pedersen_hash; pub mod pedersen_hash;

View File

@ -0,0 +1,412 @@
//! Structs for handling encrypted memos.
use std::cmp::Ordering;
use std::convert::{TryFrom, TryInto};
use std::error;
use std::fmt;
use std::ops::Deref;
use std::str;
/// Format a byte array as a colon-delimited hex string.
///
/// Source: https://github.com/tendermint/signatory
/// License: MIT / Apache 2.0
fn fmt_colon_delimited_hex<B>(f: &mut fmt::Formatter<'_>, bytes: B) -> fmt::Result
where
B: AsRef<[u8]>,
{
let len = bytes.as_ref().len();
for (i, byte) in bytes.as_ref().iter().enumerate() {
write!(f, "{:02x}", byte)?;
if i != len - 1 {
write!(f, ":")?;
}
}
Ok(())
}
/// Errors that may result from attempting to construct an invalid memo.
#[derive(Debug, PartialEq)]
pub enum Error {
InvalidUtf8(std::str::Utf8Error),
TooLong(usize),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InvalidUtf8(e) => write!(f, "Invalid UTF-8: {}", e),
Error::TooLong(n) => write!(f, "Memo length {} is larger than maximum of 512", n),
}
}
}
impl error::Error for Error {}
/// The unencrypted memo bytes received alongside a shielded note in a Zcash transaction.
#[derive(Clone)]
pub struct MemoBytes(pub(crate) Box<[u8; 512]>);
impl fmt::Debug for MemoBytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MemoBytes(")?;
fmt_colon_delimited_hex(f, &self.0[..])?;
write!(f, ")")
}
}
impl PartialEq for MemoBytes {
fn eq(&self, rhs: &MemoBytes) -> bool {
self.0[..] == rhs.0[..]
}
}
impl Eq for MemoBytes {}
impl PartialOrd for MemoBytes {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MemoBytes {
fn cmp(&self, rhs: &Self) -> Ordering {
self.0[..].cmp(&rhs.0[..])
}
}
impl MemoBytes {
/// Creates a `MemoBytes` indicating that no memo is present.
pub fn empty() -> Self {
let mut bytes = [0u8; 512];
bytes[0] = 0xF6;
MemoBytes(Box::new(bytes))
}
/// Creates a `MemoBytes` from a slice, exactly as provided.
///
/// Returns an error if the provided slice is longer than 512 bytes. Slices shorter
/// than 512 bytes are padded with null bytes.
///
/// Note that passing an empty slice to this API (or an all-zeroes slice) will result
/// in a memo representing an empty string. What you almost certainly want in this
/// case is [`MemoBytes::empty`], which uses a specific encoding to indicate that no
/// memo is present.
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() > 512 {
return Err(Error::TooLong(bytes.len()));
}
let mut memo = [0u8; 512];
memo[..bytes.len()].copy_from_slice(bytes);
Ok(MemoBytes(Box::new(memo)))
}
/// Returns the raw byte array containing the memo bytes, including null padding.
pub fn as_array(&self) -> &[u8; 512] {
&self.0
}
/// Returns a slice of the raw bytes, excluding null padding.
pub fn as_slice(&self) -> &[u8] {
let first_null = self
.0
.iter()
.enumerate()
.rev()
.find(|(_, &b)| b != 0)
.map(|(i, _)| i + 1)
.unwrap_or_default();
&self.0[..first_null]
}
}
/// Type-safe wrapper around String to enforce memo length requirements.
#[derive(Clone, PartialEq)]
pub struct TextMemo(String);
impl From<TextMemo> for String {
fn from(memo: TextMemo) -> String {
memo.0
}
}
impl Deref for TextMemo {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.0.deref()
}
}
/// An unencrypted memo received alongside a shielded note in a Zcash transaction.
#[derive(Clone)]
pub enum Memo {
/// An empty memo field.
Empty,
/// A memo field containing a UTF-8 string.
Text(TextMemo),
/// Some unknown memo format from ✨*the future*✨ that we can't parse.
Future(MemoBytes),
/// A memo field containing arbitrary bytes.
Arbitrary(Box<[u8; 511]>),
}
impl fmt::Debug for Memo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Memo::Empty => write!(f, "Memo::Empty"),
Memo::Text(memo) => write!(f, "Memo::Text(\"{}\")", memo.0),
Memo::Future(bytes) => write!(f, "Memo::Future({:0x})", bytes.0[0]),
Memo::Arbitrary(bytes) => {
write!(f, "Memo::Arbitrary(")?;
fmt_colon_delimited_hex(f, &bytes[..])?;
write!(f, ")")
}
}
}
}
impl Default for Memo {
fn default() -> Self {
Memo::Empty
}
}
impl PartialEq for Memo {
fn eq(&self, rhs: &Memo) -> bool {
match (self, rhs) {
(Memo::Empty, Memo::Empty) => true,
(Memo::Text(a), Memo::Text(b)) => a == b,
(Memo::Future(a), Memo::Future(b)) => a.0[..] == b.0[..],
(Memo::Arbitrary(a), Memo::Arbitrary(b)) => a[..] == b[..],
_ => false,
}
}
}
impl TryFrom<MemoBytes> for Memo {
type Error = Error;
/// Parses a `Memo` from its ZIP 302 serialization.
///
/// Returns an error if the provided slice does not represent a valid `Memo` (for
/// example, if the slice is not 512 bytes, or the encoded `Memo` is non-canonical).
fn try_from(bytes: MemoBytes) -> Result<Self, Self::Error> {
match bytes.0[0] {
0xF6 if bytes.0.iter().skip(1).all(|&b| b == 0) => Ok(Memo::Empty),
0xFF => Ok(Memo::Arbitrary(Box::new(bytes.0[1..].try_into().unwrap()))),
b if b <= 0xF4 => str::from_utf8(bytes.as_slice())
.map(|r| Memo::Text(TextMemo(r.to_owned())))
.map_err(Error::InvalidUtf8),
_ => Ok(Memo::Future(bytes)),
}
}
}
impl From<Memo> for MemoBytes {
/// Serializes the `Memo` per ZIP 302.
fn from(memo: Memo) -> Self {
match memo {
// Small optimisation to avoid a clone
Memo::Future(memo) => memo,
memo => (&memo).into(),
}
}
}
impl From<&Memo> for MemoBytes {
/// Serializes the `Memo` per ZIP 302.
fn from(memo: &Memo) -> Self {
match memo {
Memo::Empty => MemoBytes::empty(),
Memo::Text(s) => {
let mut bytes = [0u8; 512];
let s_bytes = s.0.as_bytes();
// s_bytes.len() is guaranteed to be <= 512
bytes[..s_bytes.len()].copy_from_slice(s_bytes);
MemoBytes(Box::new(bytes))
}
Memo::Future(memo) => memo.clone(),
Memo::Arbitrary(arb) => {
let mut bytes = [0u8; 512];
bytes[0] = 0xFF;
bytes[1..].copy_from_slice(arb.as_ref());
MemoBytes(Box::new(bytes))
}
}
}
}
impl Memo {
/// Parses a `Memo` from its ZIP 302 serialization.
///
/// Returns an error if the provided slice does not represent a valid `Memo` (for
/// example, if the slice is not 512 bytes, or the encoded `Memo` is non-canonical).
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
MemoBytes::from_bytes(bytes).and_then(TryFrom::try_from)
}
/// Serializes the `Memo` per ZIP 302.
pub fn encode(&self) -> MemoBytes {
self.into()
}
}
impl str::FromStr for Memo {
type Err = Error;
/// Returns a `Memo` containing the given string, or an error if the string is too long.
fn from_str(memo: &str) -> Result<Self, Self::Err> {
if memo.is_empty() {
Ok(Memo::Empty)
} else if memo.len() <= 512 {
Ok(Memo::Text(TextMemo(memo.to_owned())))
} else {
Err(Error::TooLong(memo.len()))
}
}
}
#[cfg(test)]
mod tests {
use std::convert::TryInto;
use std::str::FromStr;
use super::{Error, Memo, MemoBytes};
#[test]
fn memo_from_str() {
assert_eq!(
Memo::from_str("").unwrap().encode(),
MemoBytes(Box::new([
0xf6, 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, 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, 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, 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, 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, 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, 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, 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, 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,
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, 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, 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, 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, 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]))
);
assert_eq!(
Memo::from_str(
"thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryyyyyyyyyyyyyyyyyyyyyyyyyy \
looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong \
meeeeeeeeeeeeeeeeeeemooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo \
but it's just short enough"
)
.unwrap()
.encode(),
MemoBytes(Box::new([
0x74, 0x68, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x73, 0x20, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x73, 0x20, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x20, 0x76, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x72, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
0x79, 0x20, 0x6c, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6e, 0x67, 0x20, 0x6d,
0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x65, 0x65, 0x65, 0x6d, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x20, 0x62, 0x75, 0x74, 0x20,
0x69, 0x74, 0x27, 0x73, 0x20, 0x6a, 0x75, 0x73, 0x74, 0x20, 0x73, 0x68, 0x6f, 0x72,
0x74, 0x20, 0x65, 0x6e, 0x6f, 0x75, 0x67, 0x68
]))
);
assert_eq!(
Memo::from_str(
"thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryyyyyyyyyyyyyyyyyyyyyyyyyy \
looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong \
meeeeeeeeeeeeeeeeeeemooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo \
but it's now a bit too long"
),
Err(Error::TooLong(513))
);
}
#[test]
fn future_memo() {
let bytes = [0xFE; 512];
assert_eq!(
MemoBytes::from_bytes(&bytes).unwrap().try_into(),
Ok(Memo::Future(MemoBytes(Box::new(bytes))))
);
}
#[test]
fn arbitrary_memo() {
let bytes = [42; 511];
let memo = Memo::Arbitrary(Box::new(bytes));
let raw = memo.encode();
let encoded = raw.as_array();
assert_eq!(encoded[0], 0xFF);
assert_eq!(encoded[1..], bytes[..]);
assert_eq!(MemoBytes::from_bytes(encoded).unwrap().try_into(), Ok(memo));
}
}

View File

@ -2,6 +2,7 @@
use crate::{ use crate::{
consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD}, consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD},
memo::MemoBytes,
primitives::{Diversifier, Note, PaymentAddress, Rseed, SaplingIvk}, primitives::{Diversifier, Note, PaymentAddress, Rseed, SaplingIvk},
}; };
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams}; use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
@ -11,8 +12,6 @@ use ff::PrimeField;
use group::{cofactor::CofactorGroup, GroupEncoding}; use group::{cofactor::CofactorGroup, GroupEncoding};
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use std::convert::TryInto; use std::convert::TryInto;
use std::fmt;
use std::str;
use crate::keys::OutgoingViewingKey; use crate::keys::OutgoingViewingKey;
@ -29,106 +28,6 @@ const OUT_PLAINTEXT_SIZE: usize = 32 + // pk_d
pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + 16; pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + 16;
pub const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + 16; pub const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + 16;
/// Format a byte array as a colon-delimited hex string.
///
/// Source: https://github.com/tendermint/signatory
/// License: MIT / Apache 2.0
fn fmt_colon_delimited_hex<B>(f: &mut fmt::Formatter<'_>, bytes: B) -> fmt::Result
where
B: AsRef<[u8]>,
{
let len = bytes.as_ref().len();
for (i, byte) in bytes.as_ref().iter().enumerate() {
write!(f, "{:02x}", byte)?;
if i != len - 1 {
write!(f, ":")?;
}
}
Ok(())
}
/// An unencrypted memo received alongside a shielded note in a Zcash transaction.
#[derive(Clone)]
pub struct Memo([u8; 512]);
impl fmt::Debug for Memo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Memo(")?;
match self.to_utf8() {
Some(Ok(memo)) => write!(f, "\"{}\"", memo)?,
_ => fmt_colon_delimited_hex(f, &self.0[..])?,
}
write!(f, ")")
}
}
impl Default for Memo {
fn default() -> Self {
// Empty memo field indication per ZIP 302
let mut memo = [0u8; 512];
memo[0] = 0xF6;
Memo(memo)
}
}
impl PartialEq for Memo {
fn eq(&self, rhs: &Memo) -> bool {
self.0[..] == rhs.0[..]
}
}
impl Memo {
/// Returns a `Memo` containing the given slice, appending with zero bytes if
/// necessary, or `None` if the slice is too long. If the slice is empty,
/// `Memo::default` is returned.
pub fn from_bytes(memo: &[u8]) -> Option<Memo> {
if memo.is_empty() {
Some(Memo::default())
} else if memo.len() <= 512 {
let mut data = [0; 512];
data[0..memo.len()].copy_from_slice(memo);
Some(Memo(data))
} else {
// memo is too long
None
}
}
/// Returns the underlying bytes of the `Memo`.
pub fn as_bytes(&self) -> &[u8] {
&self.0[..]
}
/// Returns:
/// - `None` if the memo is not text
/// - `Some(Ok(memo))` if the memo contains a valid UTF-8 string
/// - `Some(Err(e))` if the memo contains invalid UTF-8
pub fn to_utf8(&self) -> Option<Result<String, str::Utf8Error>> {
// Check if it is a text or binary memo
if self.0[0] < 0xF5 {
// Check if it is valid UTF8
Some(str::from_utf8(&self.0).map(|memo| {
// Drop trailing zeroes
memo.trim_end_matches(char::from(0)).to_owned()
}))
} else {
None
}
}
}
impl str::FromStr for Memo {
type Err = ();
/// Returns a `Memo` containing the given string, or an error if the string is too long.
fn from_str(memo: &str) -> Result<Self, Self::Err> {
Memo::from_bytes(memo.as_bytes()).ok_or(())
}
}
/// Sapling key agreement for note encryption. /// Sapling key agreement for note encryption.
/// ///
/// Implements section 5.4.4.3 of the Zcash Protocol Specification. /// Implements section 5.4.4.3 of the Zcash Protocol Specification.
@ -214,7 +113,8 @@ pub fn prf_ock(
/// use rand_core::OsRng; /// use rand_core::OsRng;
/// use zcash_primitives::{ /// use zcash_primitives::{
/// keys::{OutgoingViewingKey, prf_expand}, /// keys::{OutgoingViewingKey, prf_expand},
/// note_encryption::{Memo, SaplingNoteEncryption}, /// memo::MemoBytes,
/// note_encryption::SaplingNoteEncryption,
/// primitives::{Diversifier, PaymentAddress, Rseed, ValueCommitment}, /// primitives::{Diversifier, PaymentAddress, Rseed, ValueCommitment},
/// }; /// };
/// ///
@ -235,7 +135,7 @@ pub fn prf_ock(
/// let note = to.create_note(value, Rseed::BeforeZip212(rcm)).unwrap(); /// let note = to.create_note(value, Rseed::BeforeZip212(rcm)).unwrap();
/// let cmu = note.cmu(); /// let cmu = note.cmu();
/// ///
/// let mut enc = SaplingNoteEncryption::new(ovk, note, to, Memo::default(), &mut rng); /// let mut enc = SaplingNoteEncryption::new(ovk, note, to, MemoBytes::empty(), &mut rng);
/// let encCiphertext = enc.encrypt_note_plaintext(); /// let encCiphertext = enc.encrypt_note_plaintext();
/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu); /// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu);
/// ``` /// ```
@ -244,7 +144,7 @@ pub struct SaplingNoteEncryption<R: RngCore> {
esk: jubjub::Fr, esk: jubjub::Fr,
note: Note, note: Note,
to: PaymentAddress, to: PaymentAddress,
memo: Memo, memo: MemoBytes,
/// `None` represents the `ovk = ⊥` case. /// `None` represents the `ovk = ⊥` case.
ovk: Option<OutgoingViewingKey>, ovk: Option<OutgoingViewingKey>,
rng: R, rng: R,
@ -259,7 +159,7 @@ impl<R: RngCore + CryptoRng> SaplingNoteEncryption<R> {
ovk: Option<OutgoingViewingKey>, ovk: Option<OutgoingViewingKey>,
note: Note, note: Note,
to: PaymentAddress, to: PaymentAddress,
memo: Memo, memo: MemoBytes,
rng: R, rng: R,
) -> Self { ) -> Self {
Self::new_internal(ovk, note, to, memo, rng) Self::new_internal(ovk, note, to, memo, rng)
@ -271,7 +171,7 @@ impl<R: RngCore> SaplingNoteEncryption<R> {
ovk: Option<OutgoingViewingKey>, ovk: Option<OutgoingViewingKey>,
note: Note, note: Note,
to: PaymentAddress, to: PaymentAddress,
memo: Memo, memo: MemoBytes,
mut rng: R, mut rng: R,
) -> Self { ) -> Self {
let esk = note.generate_or_derive_esk_internal(&mut rng); let esk = note.generate_or_derive_esk_internal(&mut rng);
@ -322,7 +222,7 @@ impl<R: RngCore> SaplingNoteEncryption<R> {
input[20..COMPACT_NOTE_SIZE].copy_from_slice(&rseed); input[20..COMPACT_NOTE_SIZE].copy_from_slice(&rseed);
} }
} }
input[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE].copy_from_slice(&self.memo.0); input[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE].copy_from_slice(self.memo.as_array());
let mut output = [0u8; ENC_CIPHERTEXT_SIZE]; let mut output = [0u8; ENC_CIPHERTEXT_SIZE];
assert_eq!( assert_eq!(
@ -462,7 +362,7 @@ pub fn try_sapling_note_decryption<P: consensus::Parameters>(
epk: &jubjub::ExtendedPoint, epk: &jubjub::ExtendedPoint,
cmu: &bls12_381::Scalar, cmu: &bls12_381::Scalar,
enc_ciphertext: &[u8], enc_ciphertext: &[u8],
) -> Option<(Note, PaymentAddress, Memo)> { ) -> Option<(Note, PaymentAddress, MemoBytes)> {
assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE);
let shared_secret = sapling_ka_agree(&ivk.0, &epk); let shared_secret = sapling_ka_agree(&ivk.0, &epk);
@ -484,10 +384,10 @@ pub fn try_sapling_note_decryption<P: consensus::Parameters>(
let (note, to) = parse_note_plaintext_without_memo(params, height, ivk, epk, cmu, &plaintext)?; let (note, to) = parse_note_plaintext_without_memo(params, height, ivk, epk, cmu, &plaintext)?;
let mut memo = [0u8; 512]; // Memo is the correct length by definition.
memo.copy_from_slice(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]); let memo = MemoBytes::from_bytes(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]).unwrap();
Some((note, to, Memo(memo))) Some((note, to, memo))
} }
/// Trial decryption of the compact note plaintext by the recipient for light clients. /// Trial decryption of the compact note plaintext by the recipient for light clients.
@ -536,7 +436,7 @@ pub fn try_sapling_output_recovery_with_ock<P: consensus::Parameters>(
epk: &jubjub::ExtendedPoint, epk: &jubjub::ExtendedPoint,
enc_ciphertext: &[u8], enc_ciphertext: &[u8],
out_ciphertext: &[u8], out_ciphertext: &[u8],
) -> Option<(Note, PaymentAddress, Memo)> { ) -> Option<(Note, PaymentAddress, MemoBytes)> {
assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE);
assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE); assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE);
@ -602,8 +502,7 @@ pub fn try_sapling_output_recovery_with_ock<P: consensus::Parameters>(
Rseed::AfterZip212(r) Rseed::AfterZip212(r)
}; };
let mut memo = [0u8; 512]; let memo = MemoBytes::from_bytes(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]).unwrap();
memo.copy_from_slice(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]);
let diversifier = Diversifier(d); let diversifier = Diversifier(d);
if (diversifier.g_d()? * esk).to_bytes() != epk.to_bytes() { if (diversifier.g_d()? * esk).to_bytes() != epk.to_bytes() {
@ -625,7 +524,7 @@ pub fn try_sapling_output_recovery_with_ock<P: consensus::Parameters>(
} }
} }
Some((note, to, Memo(memo))) Some((note, to, memo))
} }
/// Recovery of the full note plaintext by the sender. /// Recovery of the full note plaintext by the sender.
@ -645,7 +544,7 @@ pub fn try_sapling_output_recovery<P: consensus::Parameters>(
epk: &jubjub::ExtendedPoint, epk: &jubjub::ExtendedPoint,
enc_ciphertext: &[u8], enc_ciphertext: &[u8],
out_ciphertext: &[u8], out_ciphertext: &[u8],
) -> Option<(Note, PaymentAddress, Memo)> { ) -> Option<(Note, PaymentAddress, MemoBytes)> {
try_sapling_output_recovery_with_ock::<P>( try_sapling_output_recovery_with_ock::<P>(
params, params,
height, height,
@ -666,12 +565,11 @@ mod tests {
use rand_core::OsRng; use rand_core::OsRng;
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use std::convert::TryInto; use std::convert::TryInto;
use std::str::FromStr;
use super::{ use super::{
kdf_sapling, prf_ock, sapling_ka_agree, try_sapling_compact_note_decryption, kdf_sapling, prf_ock, sapling_ka_agree, try_sapling_compact_note_decryption,
try_sapling_note_decryption, try_sapling_output_recovery, try_sapling_note_decryption, try_sapling_output_recovery,
try_sapling_output_recovery_with_ock, Memo, OutgoingCipherKey, SaplingNoteEncryption, try_sapling_output_recovery_with_ock, OutgoingCipherKey, SaplingNoteEncryption,
COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE,
OUT_PLAINTEXT_SIZE, OUT_PLAINTEXT_SIZE,
}; };
@ -683,126 +581,11 @@ mod tests {
Parameters, TEST_NETWORK, ZIP212_GRACE_PERIOD, Parameters, TEST_NETWORK, ZIP212_GRACE_PERIOD,
}, },
keys::OutgoingViewingKey, keys::OutgoingViewingKey,
memo::MemoBytes,
primitives::{Diversifier, PaymentAddress, Rseed, SaplingIvk, ValueCommitment}, primitives::{Diversifier, PaymentAddress, Rseed, SaplingIvk, ValueCommitment},
util::generate_random_rseed, util::generate_random_rseed,
}; };
#[test]
fn memo_from_str() {
assert_eq!(
Memo::from_str("").unwrap(),
Memo([
0xf6, 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, 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, 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, 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, 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, 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, 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, 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, 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,
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, 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, 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, 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, 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
])
);
assert_eq!(
Memo::from_str(
"thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryyyyyyyyyyyyyyyyyyyyyyyyyy \
looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong \
meeeeeeeeeeeeeeeeeeemooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo \
but it's just short enough"
)
.unwrap(),
Memo([
0x74, 0x68, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x73, 0x20, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x73, 0x20, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x61, 0x20, 0x76, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x72, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
0x79, 0x20, 0x6c, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6e, 0x67, 0x20, 0x6d,
0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
0x65, 0x65, 0x65, 0x65, 0x65, 0x6d, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x20, 0x62, 0x75, 0x74, 0x20,
0x69, 0x74, 0x27, 0x73, 0x20, 0x6a, 0x75, 0x73, 0x74, 0x20, 0x73, 0x68, 0x6f, 0x72,
0x74, 0x20, 0x65, 0x6e, 0x6f, 0x75, 0x67, 0x68
])
);
assert_eq!(
Memo::from_str(
"thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryyyyyyyyyyyyyyyyyyyyyyyyyy \
looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong \
meeeeeeeeeeeeeeeeeeemooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo \
but it's now a bit too long"
),
Err(())
);
}
#[test]
fn memo_to_utf8() {
let memo = Memo::from_str("Test memo").unwrap();
assert_eq!(memo.to_utf8(), Some(Ok("Test memo".to_owned())));
assert_eq!(Memo::default().to_utf8(), None);
}
fn random_enc_ciphertext<R: RngCore + CryptoRng>( fn random_enc_ciphertext<R: RngCore + CryptoRng>(
height: BlockHeight, height: BlockHeight,
mut rng: &mut R, mut rng: &mut R,
@ -898,7 +681,7 @@ mod tests {
let cmu = note.cmu(); let cmu = note.cmu();
let ovk = OutgoingViewingKey([0; 32]); let ovk = OutgoingViewingKey([0; 32]);
let mut ne = SaplingNoteEncryption::new(Some(ovk), note, pa, Memo([0; 512]), &mut rng); let mut ne = SaplingNoteEncryption::new(Some(ovk), note, pa, MemoBytes::empty(), &mut rng);
let epk = ne.epk().clone().into(); let epk = ne.epk().clone().into();
let enc_ciphertext = ne.encrypt_note_plaintext(); let enc_ciphertext = ne.encrypt_note_plaintext();
let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu); let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu);
@ -1887,7 +1670,7 @@ mod tests {
Some((decrypted_note, decrypted_to, decrypted_memo)) => { Some((decrypted_note, decrypted_to, decrypted_memo)) => {
assert_eq!(decrypted_note, note); assert_eq!(decrypted_note, note);
assert_eq!(decrypted_to, to); assert_eq!(decrypted_to, to);
assert_eq!(&decrypted_memo.0[..], &tv.memo[..]); assert_eq!(&decrypted_memo.as_array()[..], &tv.memo[..]);
} }
None => panic!("Note decryption failed"), None => panic!("Note decryption failed"),
} }
@ -1920,7 +1703,7 @@ mod tests {
Some((decrypted_note, decrypted_to, decrypted_memo)) => { Some((decrypted_note, decrypted_to, decrypted_memo)) => {
assert_eq!(decrypted_note, note); assert_eq!(decrypted_note, note);
assert_eq!(decrypted_to, to); assert_eq!(decrypted_to, to);
assert_eq!(&decrypted_memo.0[..], &tv.memo[..]); assert_eq!(&decrypted_memo.as_array()[..], &tv.memo[..]);
} }
None => panic!("Output recovery failed"), None => panic!("Output recovery failed"),
} }
@ -1929,7 +1712,13 @@ mod tests {
// Test encryption // Test encryption
// //
let mut ne = SaplingNoteEncryption::new(Some(ovk), note, to, Memo(tv.memo), OsRng); let mut ne = SaplingNoteEncryption::new(
Some(ovk),
note,
to,
MemoBytes::from_bytes(&tv.memo).unwrap(),
OsRng,
);
// Swap in the ephemeral keypair from the test vectors // Swap in the ephemeral keypair from the test vectors
ne.esk = esk; ne.esk = esk;
ne.epk = epk.into_subgroup().unwrap(); ne.epk = epk.into_subgroup().unwrap();

View File

@ -14,8 +14,9 @@ use crate::{
consensus::{self, BlockHeight}, consensus::{self, BlockHeight},
keys::OutgoingViewingKey, keys::OutgoingViewingKey,
legacy::TransparentAddress, legacy::TransparentAddress,
memo::MemoBytes,
merkle_tree::MerklePath, merkle_tree::MerklePath,
note_encryption::{Memo, SaplingNoteEncryption}, note_encryption::SaplingNoteEncryption,
primitives::{Diversifier, Note, PaymentAddress}, primitives::{Diversifier, Note, PaymentAddress},
prover::TxProver, prover::TxProver,
redjubjub::PrivateKey, redjubjub::PrivateKey,
@ -98,7 +99,7 @@ pub struct SaplingOutput {
ovk: Option<OutgoingViewingKey>, ovk: Option<OutgoingViewingKey>,
to: PaymentAddress, to: PaymentAddress,
note: Note, note: Note,
memo: Memo, memo: MemoBytes,
} }
impl SaplingOutput { impl SaplingOutput {
@ -109,7 +110,7 @@ impl SaplingOutput {
ovk: Option<OutgoingViewingKey>, ovk: Option<OutgoingViewingKey>,
to: PaymentAddress, to: PaymentAddress,
value: Amount, value: Amount,
memo: Option<Memo>, memo: Option<MemoBytes>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
Self::new_internal(params, height, rng, ovk, to, value, memo) Self::new_internal(params, height, rng, ovk, to, value, memo)
} }
@ -121,7 +122,7 @@ impl SaplingOutput {
ovk: Option<OutgoingViewingKey>, ovk: Option<OutgoingViewingKey>,
to: PaymentAddress, to: PaymentAddress,
value: Amount, value: Amount,
memo: Option<Memo>, memo: Option<MemoBytes>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let g_d = to.g_d().ok_or(Error::InvalidAddress)?; let g_d = to.g_d().ok_or(Error::InvalidAddress)?;
if value.is_negative() { if value.is_negative() {
@ -141,7 +142,7 @@ impl SaplingOutput {
ovk, ovk,
to, to,
note, note,
memo: memo.unwrap_or_default(), memo: memo.unwrap_or_else(MemoBytes::empty),
}) })
} }
@ -520,7 +521,7 @@ impl<'a, P: consensus::Parameters, R: RngCore> Builder<'a, P, R> {
ovk: Option<OutgoingViewingKey>, ovk: Option<OutgoingViewingKey>,
to: PaymentAddress, to: PaymentAddress,
value: Amount, value: Amount,
memo: Option<Memo>, memo: Option<MemoBytes>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let output = SaplingOutput::new_internal( let output = SaplingOutput::new_internal(
&self.params, &self.params,