Skip to content

Commit

Permalink
Implement a WtxId struct, and use it in Zebra's external network prot…
Browse files Browse the repository at this point in the history
…ocol (#2618)

* Make the `AuthDigest` display order match transaction IDs

And derive `Hash`, just like transaction IDs.

Don't derive `serde` for now, because it's not needed.

* Move transaction::Hash test to tests module

* Add a simple AuthDigest display order test

* Add a WtxId type for wide transaction IDs

* Add conversions between transaction IDs and bytes

* Use the WtxId type in external network protocol messages
  • Loading branch information
teor2345 authored Aug 16, 2021
1 parent c3c3023 commit 6c86c8d
Show file tree
Hide file tree
Showing 9 changed files with 472 additions and 68 deletions.
16 changes: 15 additions & 1 deletion zebra-chain/src/serialization/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{io, num::TryFromIntError};
use std::{array::TryFromSliceError, io, num::TryFromIntError, str::Utf8Error};

use thiserror::Error;

Expand All @@ -9,20 +9,34 @@ pub enum SerializationError {
/// An io error that prevented deserialization
#[error("io error: {0}")]
Io(#[from] io::Error),

/// The data to be deserialized was malformed.
// XXX refine errors
#[error("parse error: {0}")]
Parse(&'static str),

/// A string was not UTF-8.
///
/// Note: Rust `String` and `str` are always UTF-8.
#[error("string was not UTF-8: {0}")]
Utf8Error(#[from] Utf8Error),

/// A slice was an unexpected length during deserialization.
#[error("slice was the wrong length: {0}")]
TryFromSliceError(#[from] TryFromSliceError),

/// The length of a vec is too large to convert to a usize (and thus, too large to allocate on this platform)
#[error("compactsize too large: {0}")]
TryFromIntError(#[from] TryFromIntError),

/// An error caused when validating a zatoshi `Amount`
#[error("input couldn't be parsed as a zatoshi `Amount`: {source}")]
Amount {
/// The source error indicating how the num failed to validate
#[from]
source: crate::amount::Error,
},

/// Invalid transaction with a non-zero balance and no Sapling shielded spends or outputs.
///
/// Transaction does not conform to the Sapling [consensus
Expand Down
2 changes: 1 addition & 1 deletion zebra-chain/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub mod arbitrary;
mod tests;

pub use auth_digest::AuthDigest;
pub use hash::Hash;
pub use hash::{Hash, WtxId};
pub use joinsplit::JoinSplitData;
pub use lock_time::LockTime;
pub use memo::Memo;
Expand Down
98 changes: 93 additions & 5 deletions zebra-chain/src/transaction/auth_digest.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,108 @@
use crate::primitives::zcash_primitives::auth_digest;
use std::fmt;

#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;

use crate::{
primitives::zcash_primitives::auth_digest,
serialization::{
ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize,
},
};

use super::Transaction;

/// An authorizing data commitment hash as specified in [ZIP-244].
///
/// [ZIP-244]: https://zips.z.cash/zip-0244..
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
/// Note: Zebra displays transaction and block hashes in big-endian byte-order,
/// following the u256 convention set by Bitcoin and zcashd.
///
/// [ZIP-244]: https://zips.z.cash/zip-0244
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub struct AuthDigest(pub(crate) [u8; 32]);

impl<'a> From<&'a Transaction> for AuthDigest {
impl From<Transaction> for AuthDigest {
/// Computes the authorizing data commitment for a transaction.
///
/// # Panics
///
/// If passed a pre-v5 transaction.
fn from(transaction: Transaction) -> Self {
// use the ref implementation, to avoid cloning the transaction
AuthDigest::from(&transaction)
}
}

impl From<&Transaction> for AuthDigest {
/// Computes the authorizing data commitment for a transaction.
///
/// # Panics
///
/// If passed a pre-v5 transaction.
fn from(transaction: &'a Transaction) -> Self {
fn from(transaction: &Transaction) -> Self {
auth_digest(transaction)
}
}

impl From<[u8; 32]> for AuthDigest {
fn from(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}

impl From<AuthDigest> for [u8; 32] {
fn from(auth_digest: AuthDigest) -> Self {
auth_digest.0
}
}

impl From<&AuthDigest> for [u8; 32] {
fn from(auth_digest: &AuthDigest) -> Self {
(*auth_digest).into()
}
}

impl fmt::Display for AuthDigest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut reversed_bytes = self.0;
reversed_bytes.reverse();
f.write_str(&hex::encode(&reversed_bytes))
}
}

impl fmt::Debug for AuthDigest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut reversed_bytes = self.0;
reversed_bytes.reverse();
f.debug_tuple("AuthDigest")
.field(&hex::encode(reversed_bytes))
.finish()
}
}

impl std::str::FromStr for AuthDigest {
type Err = SerializationError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut bytes = [0; 32];
if hex::decode_to_slice(s, &mut bytes[..]).is_err() {
Err(SerializationError::Parse("hex decoding error"))
} else {
bytes.reverse();
Ok(AuthDigest(bytes))
}
}
}

impl ZcashSerialize for AuthDigest {
fn zcash_serialize<W: std::io::Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
writer.write_32_bytes(&self.into())
}
}

impl ZcashDeserialize for AuthDigest {
fn zcash_deserialize<R: std::io::Read>(mut reader: R) -> Result<Self, SerializationError> {
Ok(reader.read_32_bytes()?.into())
}
}
Loading

0 comments on commit 6c86c8d

Please sign in to comment.