diff --git a/CHANGELOG.md b/CHANGELOG.md index 96dafe5..53738b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.5.0] - 27-11-20 +### Added +- To/From bytes impl for `Fee` & `Crossover`. + ## [0.5.0-alpha] - 27-11-20 ### Changed - No-Std compatibility. diff --git a/Cargo.toml b/Cargo.toml index 0123742..f5bd9c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "phoenix-core" -version = "0.5.0-alpha" +version = "0.5.0" authors = ["zer0 ", "Victor Lopez usize { + 32 * 2 + PoseidonCipher::cipher_size_bytes() + } + /// Returns a hash represented by `H(value_commitment)` pub fn hash(&self) -> BlsScalar { let value_commitment = self.value_commitment().to_hash_inputs(); @@ -55,4 +62,60 @@ impl Crossover { pub fn encrypted_data(&self) -> &PoseidonCipher { &self.encrypted_data } + + /// Converts a Crossover into it's byte representation + pub fn to_bytes(&self) -> [u8; Crossover::serialized_size()] { + let mut buf = [0u8; Crossover::serialized_size()]; + let mut n = 0; + + buf[n..n + 32].copy_from_slice( + &JubJubAffine::from(&self.value_commitment).to_bytes()[..], + ); + n += 32; + + buf[n..n + 32].copy_from_slice(&self.nonce.to_bytes()[..]); + n += 32; + + buf[n..n + PoseidonCipher::cipher_size_bytes()] + .copy_from_slice(&self.encrypted_data.to_bytes()[..]); + n += PoseidonCipher::cipher_size_bytes(); + + debug_assert_eq!(n, Crossover::serialized_size()); + + buf + } + + /// Attempts to convert a byte representation of a note into a `Note`, + /// failing if the input is invalid + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < Crossover::serialized_size() { + return Err(Error::InvalidCrossoverConversion); + } + + let mut bytes_32 = [0u8; 32]; + let mut one_cipher = [0u8; PoseidonCipher::cipher_size_bytes()]; + + let mut n = 0; + + bytes_32.copy_from_slice(&bytes[n..n + 32]); + let value_commitment = + JubJubExtended::from(jubjub_decode::(&bytes_32)?); + n += 32; + + bytes_32.copy_from_slice(&bytes[n..n + 32]); + let nonce = jubjub_decode::(&bytes_32)?; + n += 32; + + one_cipher.copy_from_slice( + &bytes[n..n + PoseidonCipher::cipher_size_bytes()], + ); + let encrypted_data = PoseidonCipher::from_bytes(&one_cipher) + .ok_or(Error::InvalidCipher)?; + + Ok(Crossover { + value_commitment, + nonce, + encrypted_data, + }) + } } diff --git a/src/error.rs b/src/error.rs index bb669c0..075392c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,6 +23,10 @@ pub enum Error { MissingViewKey, /// Invalid Note Type for conversion InvalidNoteConversion, + /// Invalid Crossover for conversion + InvalidCrossoverConversion, + /// Invalid Fee for conversion + InvalidFeeConversion, /// Dusk-Pki Error PKIError(PkiError), /// Poseidon Error diff --git a/src/fee.rs b/src/fee.rs index 8dad933..63f8f81 100644 --- a/src/fee.rs +++ b/src/fee.rs @@ -17,7 +17,7 @@ use canonical_derive::Canon; use core::cmp; -use crate::{BlsScalar, JubJubScalar}; +use crate::{BlsScalar, Error, JubJubScalar}; mod remainder; pub use remainder::Remainder; @@ -42,6 +42,11 @@ impl PartialEq for Fee { impl Eq for Fee {} impl Fee { + /// Returns the serialized size of the Fee. + pub const fn serialized_size() -> usize { + 8 * 2 + 64 + } + /// Create a new Fee with inner randomness pub fn new( rng: &mut R, @@ -97,6 +102,58 @@ impl Fee { stealth_address: self.stealth_address, } } + + /// Converts a Fee into it's byte representation + pub fn to_bytes(&self) -> [u8; Fee::serialized_size()] { + let mut buf = [0u8; Fee::serialized_size()]; + let mut n = 0; + + buf[n..n + 8].copy_from_slice(&self.gas_limit.to_le_bytes()[..]); + n += 8; + + buf[n..n + 8].copy_from_slice(&self.gas_price.to_le_bytes()[..]); + n += 8; + + buf[n..n + 64].copy_from_slice(&self.stealth_address.to_bytes()[..]); + n += 64; + + debug_assert_eq!(n, Fee::serialized_size()); + + buf + } + + /// Attempts to convert a byte representation of a note into a `Note`, + /// failing if the input is invalid + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < Fee::serialized_size() { + return Err(Error::InvalidFeeConversion); + } + + let mut one_u64 = [0u8; 8]; + let mut one_stealth_addr = [0u8; 64]; + + let mut n = 0; + + one_u64.copy_from_slice(&bytes[n..n + 8]); + let gas_limit = u64::from_le_bytes(one_u64); + n += 8; + + one_u64.copy_from_slice(&bytes[n..n + 8]); + let gas_price = u64::from_le_bytes(one_u64); + n += 8; + + one_stealth_addr.copy_from_slice(&bytes[n..n + 64]); + let stealth_address = StealthAddress::from_bytes(&one_stealth_addr)?; + n += 64; + + debug_assert_eq!(n, Fee::serialized_size()); + + Ok(Fee { + gas_limit, + gas_price, + stealth_address, + }) + } } impl Ownable for Fee { diff --git a/src/note.rs b/src/note.rs index 6bd5639..7aed54a 100644 --- a/src/note.rs +++ b/src/note.rs @@ -308,7 +308,7 @@ impl Note { .copy_from_slice(&self.encrypted_data.to_bytes()[..]); n += PoseidonCipher::cipher_size_bytes(); - assert_eq!(n, Note::serialized_size()); + debug_assert_eq!(n, Note::serialized_size()); buf }