From 5fc574c533cdcf08c84d2fcc09bb440c29480ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Tue, 17 Jan 2023 21:48:04 +0100 Subject: [PATCH 01/10] Update rust-toolchain to `nightly-2023-01-05` --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index 61d71c8..67946b1 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2022-08-28 +nightly-2023-01-05 From a1bf456c8153e6d9c71890269631b6e29acf93e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 19 Jan 2023 16:30:59 +0100 Subject: [PATCH 02/10] Port the crate to the `piecrust`-enabled ABI - Add `Error::Utf8` variant - Change `UnprovenTransaction` serialization to no longer use `canonical` - Change `Error::Canon` variant to `Error::Rkyv` - Remove `Transaction` and `TransactionSkeleton` - Remove `canonical` dependency - Add `rkyv` dependency - Update dependencies to latest versions Resolves #69 Resolves #70 --- CHANGELOG.md | 7 + Cargo.toml | 23 +-- src/ffi.rs | 37 ++-- src/imp.rs | 202 +++++++++++---------- src/lib.rs | 26 ++- src/tx.rs | 481 +++++++++++--------------------------------------- tests/mock.rs | 46 ++--- 7 files changed, 283 insertions(+), 539 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01ddd52..5b39203 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Change transactions to have an optional crossover - Update STCT to use Schnorr signatures [#34] - Update dependencies +- Update `phoenix-core` to `0.17` +- Update `dusk-pki` to `0.11` +- Update `dusk-schnorr` to `0.11` +- Update `dusk-jubjub` to `0.12` +- Update `dusk-poseidon` to `0.26` +- Update `dusk-plonk` to `0.12` +- Update `dusk-bls12_381-sign` to `0.4` ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 1f7678a..56d7ae0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,24 +9,19 @@ license = "MPL-2.0" rand_core = "^0.6" rand_chacha = { version = "^0.3", default-features = false } sha2 = { version = "^0.10", default-features = false } -phoenix-core = { version = "0.16.0-rc", features = ["canon"] } -dusk-pki = { version = "0.10.0-rc", features = ["canon"] } +phoenix-core = { version = "0.18", default-features = false, features = ["alloc", "rkyv-impl"] } +dusk-pki = { version = "0.11", default-features = false } dusk-bytes = "^0.1" -dusk-schnorr = { version = "0.10.0-rc", default-features = false } -dusk-jubjub = { version = "0.11", default-features = false } -dusk-poseidon = { version = "0.25.0-rc", features = [ - "canon", -], default-features = false } -dusk-plonk = { version = "0.10", default-features = false } -rusk-abi = "0.7" -canonical = "0.7" -dusk-bls12_381-sign = { version = "0.3.0-rc", default-features = false, features = [ - "canon", -] } +dusk-schnorr = { version = "0.12", default-features = false } +dusk-jubjub = { version = "0.12", default-features = false } +dusk-poseidon = { version = "0.28", default-features = false, features = ["alloc"] } +dusk-plonk = { version = "0.13", default-features = false } +rusk-abi = { version = "0.8.0-alpha", features = ["host"] } +dusk-bls12_381-sign = { version = "0.4", default-features = false } +rkyv = { version = "0.7", default-features = false } [dev-dependencies] rand = "^0.8" -canonical_derive = "0.7" [lib] crate-type = ["cdylib", "rlib"] diff --git a/src/ffi.rs b/src/ffi.rs index 68a7589..ccf6a6f 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -6,13 +6,13 @@ //! The foreign function interface for the wallet. +use alloc::string::String; use alloc::vec::Vec; use core::mem; use core::num::NonZeroU32; use core::ptr; -use canonical::{Canon, Source}; use dusk_bls12_381_sign::PublicKey; use dusk_bytes::Write; use dusk_bytes::{DeserializableSlice, Serializable}; @@ -26,7 +26,7 @@ use rand_core::{ impls::{next_u32_via_fill, next_u64_via_fill}, CryptoRng, RngCore, }; -use rusk_abi::ContractId; +use rusk_abi::ModuleId; use crate::tx::UnprovenTransaction; use crate::{ @@ -59,8 +59,7 @@ extern "C" { /// Queries the node to find the opening for a specific note. fn fetch_opening( note: *const [u8; Note::SIZE], - opening: *mut u8, - opening_len: *mut u32, + opening: *mut [u8; PoseidonBranch::::SIZE], ) -> u8; /// Asks the node to find the nullifiers that are already in the state and @@ -155,6 +154,8 @@ pub unsafe extern "C" fn public_spend_key( #[no_mangle] pub unsafe extern "C" fn execute( contract_id: *const [u8; 32], + call_name_ptr: *mut u8, + call_name_len: *const u32, call_data_ptr: *mut u8, call_data_len: *const u32, sender_index: *const u64, @@ -162,11 +163,18 @@ pub unsafe extern "C" fn execute( gas_limit: *const u64, gas_price: *const u64, ) -> u8 { - let contract_id = ContractId::from(*contract_id); + let contract_id = ModuleId::from_bytes(*contract_id); - // SAFETY: the buffer is expected to have been allocated with the + // SAFETY: these buffers are expected to have been allocated with the // correct size. If this is not the case problems with the allocator // *may* happen. + let call_name = Vec::from_raw_parts( + call_name_ptr, + call_name_len as usize, + call_name_len as usize, + ); + let call_name = unwrap_or_bail!(String::from_utf8(call_name)); + let call_data = Vec::from_raw_parts( call_data_ptr, call_data_len as usize, @@ -178,6 +186,7 @@ pub unsafe extern "C" fn execute( unwrap_or_bail!(WALLET.execute( &mut FfiRng, contract_id, + call_name, call_data, *sender_index, &refund, @@ -332,9 +341,6 @@ impl Store for FfiStore { } } -// 512 KB for a buffer. -const OPENING_BUF_SIZE: usize = 0x10000; - const STCT_INPUT_SIZE: usize = Fee::SIZE + Crossover::SIZE + u64::SIZE @@ -460,20 +466,18 @@ impl StateClient for FfiStateClient { &self, note: &Note, ) -> Result, Self::Error> { - let mut opening_buf = [0u8; OPENING_BUF_SIZE]; - - let mut opening_len = 0; + let mut opening_buf = + [0u8; PoseidonBranch::::SIZE]; let note = note.to_bytes(); unsafe { - let r = fetch_opening(¬e, &mut opening_buf[0], &mut opening_len); + let r = fetch_opening(¬e, &mut opening_buf); if r != 0 { return Err(r); } } - let mut source = Source::new(&opening_buf[..opening_len as usize]); - let branch = PoseidonBranch::decode(&mut source).map_err( + let branch = PoseidonBranch::from_slice(&opening_buf).map_err( Error::::from, )?; @@ -666,11 +670,12 @@ impl From> Error::Prover(_) => 251, Error::NotEnoughBalance => 250, Error::NoteCombinationProblem => 249, - Error::Canon(_) => 248, + Error::Rkyv(_) => 248, Error::Phoenix(_) => 247, Error::AlreadyStaked { .. } => 246, Error::NotStaked { .. } => 245, Error::NoReward { .. } => 244, + Error::Utf8(_) => 243, } } } diff --git a/src/imp.rs b/src/imp.rs index 7634c63..292013c 100644 --- a/src/imp.rs +++ b/src/imp.rs @@ -6,13 +6,14 @@ use crate::tx::UnprovenTransaction; use crate::{ - BalanceInfo, ProverClient, StakeInfo, StateClient, Store, Transaction, + BalanceInfo, ProverClient, StakeInfo, StateClient, Store, MAX_CALL_SIZE, }; +use core::convert::Infallible; + +use alloc::string::{FromUtf8Error, String}; use alloc::vec::Vec; -use canonical::EncodeToVec; -use canonical::{Canon, CanonError}; use dusk_bls12_381_sign::{PublicKey, SecretKey, Signature}; use dusk_bytes::{Error as BytesError, Serializable}; use dusk_jubjub::{BlsScalar, JubJubScalar}; @@ -20,17 +21,33 @@ use dusk_pki::{ Ownable, PublicSpendKey, SecretKey as SchnorrKey, SecretSpendKey, StealthAddress, }; -use dusk_poseidon::cipher::PoseidonCipher; -use dusk_poseidon::sponge; use dusk_schnorr::Signature as SchnorrSignature; -use phoenix_core::{Crossover, Error as PhoenixError, Fee, Note, NoteType}; +use phoenix_core::transaction::*; +use phoenix_core::{Error as PhoenixError, Fee, Note, NoteType}; use rand_core::{CryptoRng, Error as RngError, RngCore}; -use rusk_abi::ContractId; +use rkyv::ser::serializers::{ + AllocScratchError, AllocSerializer, CompositeSerializerError, + SharedSerializeMapError, +}; +use rkyv::Serialize; +use rusk_abi::ModuleId; const MAX_INPUT_NOTES: usize = 4; +const TX_STAKE: &str = "stake"; +const TX_UNSTAKE: &str = "unstake"; +const TX_WITHDRAW: &str = "withdraw"; +const TX_ADD_ALLOWLIST: &str = "allow"; + +type SerializerError = CompositeSerializerError< + Infallible, + AllocScratchError, + SharedSerializeMapError, +>; + /// The error type returned by this crate. #[derive(Debug)] +#[allow(clippy::large_enum_variant)] pub enum Error { /// Underlying store error. Store(S::Error), @@ -38,12 +55,14 @@ pub enum Error { State(SC::Error), /// Error originating from the prover client. Prover(PC::Error), - /// Canonical stores. - Canon(CanonError), + /// Rkyv serialization. + Rkyv(SerializerError), /// Random number generator error. Rng(RngError), /// Serialization and deserialization of Dusk types. Bytes(BytesError), + /// Bytes were meant to be utf8 but aren't. + Utf8(FromUtf8Error), /// Originating from the transaction model. Phoenix(PhoenixError), /// Not enough balance to perform transaction. @@ -92,6 +111,14 @@ impl Error { } } +impl From + for Error +{ + fn from(err: SerializerError) -> Self { + Self::Rkyv(err) + } +} + impl From for Error { @@ -108,19 +135,19 @@ impl From } } -impl From +impl From for Error { - fn from(pe: PhoenixError) -> Self { - Self::Phoenix(pe) + fn from(err: FromUtf8Error) -> Self { + Self::Utf8(err) } } -impl From +impl From for Error { - fn from(ce: CanonError) -> Self { - Self::Canon(ce) + fn from(pe: PhoenixError) -> Self { + Self::Phoenix(pe) } } @@ -160,11 +187,6 @@ impl Wallet { } } -const TX_STAKE: u8 = 0x00; -const TX_UNSTAKE: u8 = 0x01; -const TX_WITHDRAW: u8 = 0x02; -const TX_ADD_ALLOWLIST: u8 = 0x03; - impl Wallet where S: Store, @@ -285,7 +307,8 @@ where pub fn execute( &self, rng: &mut Rng, - contract_id: ContractId, + module_id: ModuleId, + call_name: String, call_data: C, sender_index: u64, refund: &PublicSpendKey, @@ -294,7 +317,7 @@ where ) -> Result> where Rng: RngCore + CryptoRng, - C: Canon, + C: Serialize>, { let sender = self .store @@ -309,7 +332,9 @@ where )?; let fee = Fee::new(rng, gas_limit, gas_price, refund); - let call = (contract_id, call_data.encode_to_vec()); + + let call_data = rkyv::to_bytes(&call_data)?.to_vec(); + let call = (module_id, call_name, call_data); let utx = UnprovenTransaction::new( rng, @@ -423,11 +448,18 @@ where fee.gas_limit = gas_limit; fee.gas_price = gas_price; - let contract_id = rusk_abi::stake_contract(); - let address = rusk_abi::contract_to_scalar(&contract_id); + let module_id = rusk_abi::stake_module(); + let address = rusk_abi::module_to_scalar(&module_id); - let stct_signature = - sign_stct(rng, &sender, &fee, &crossover, value, &address); + let module_id = rusk_abi::module_to_scalar(&rusk_abi::stake_module()); + + let stct_message = stct_signature_message(&crossover, value, module_id); + let stct_message = rusk_abi::poseidon_hash(stct_message.to_vec()); + + let sk_r = *sender.sk_r(fee.stealth_address()).as_ref(); + let secret = SchnorrKey::from(sk_r); + + let stct_signature = SchnorrSignature::new(&secret, rng, stct_message); let spend_proof = self .prover @@ -445,10 +477,16 @@ where let signature = stake_sign(&sk, &pk, stake.counter, value); - let call_data = - (TX_STAKE, pk, signature, value, spend_proof).encode_to_vec(); + let stake = Stake { + public_key: pk, + signature, + value, + proof: spend_proof, + }; - let call = (contract_id, call_data); + let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&stake)?.to_vec(); + let call = + (rusk_abi::stake_module(), String::from(TX_STAKE), call_data); let utx = UnprovenTransaction::new( rng, @@ -532,12 +570,19 @@ where let signature = unstake_sign(&sk, &pk, stake.counter, unstake_note); - let call_data = - (TX_UNSTAKE, pk, signature, unstake_note, unstake_proof) - .encode_to_vec(); + let unstake = Unstake { + public_key: pk, + signature, + note: unstake_note, + proof: unstake_proof, + }; - let contract_id = rusk_abi::stake_contract(); - let call = (contract_id, call_data); + let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&unstake)?.to_vec(); + let call = ( + rusk_abi::stake_module(), + String::from(TX_UNSTAKE), + call_data, + ); let utx = UnprovenTransaction::new( rng, @@ -610,11 +655,16 @@ where fee.gas_limit = gas_limit; fee.gas_price = gas_price; - let call_data = - (TX_WITHDRAW, pk, signature, address, nonce).encode_to_vec(); + let withdraw = Withdraw { + public_key: pk, + signature, + address, + nonce, + }; + let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&withdraw)?.to_vec(); - let contract_id = rusk_abi::stake_contract(); - let call = (contract_id, call_data); + let module_id = rusk_abi::stake_module(); + let call = (module_id, String::from(TX_WITHDRAW), call_data); let utx = UnprovenTransaction::new( rng, @@ -650,11 +700,11 @@ where .retrieve_ssk(sender_index) .map_err(Error::from_store_err)?; - let sk = self + let owner_sk = self .store .retrieve_sk(owner_index) .map_err(Error::from_store_err)?; - let pk = PublicKey::from(&sk); + let owner_pk = PublicKey::from(&owner_sk); let (inputs, outputs) = self.inputs_and_change_output( rng, @@ -663,10 +713,12 @@ where gas_limit * gas_price, )?; - let stake = - self.state.fetch_stake(&pk).map_err(Error::from_state_err)?; + let stake = self + .state + .fetch_stake(&owner_pk) + .map_err(Error::from_state_err)?; - let signature = allow_sign(&sk, &pk, stake.counter, staker); + let signature = allow_sign(&owner_sk, &owner_pk, stake.counter, staker); // Since we're not transferring value *to* the contract the crossover // shouldn't contain a value. As such the note used to created it should @@ -680,11 +732,15 @@ where fee.gas_limit = gas_limit; fee.gas_price = gas_price; - let call_data = - (TX_ADD_ALLOWLIST, *staker, pk, signature).encode_to_vec(); + let allow = Allow { + public_key: *staker, + owner: owner_pk, + signature, + }; + let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&allow)?.to_vec(); - let contract_id = rusk_abi::stake_contract(); - let call = (contract_id, call_data); + let module_id = rusk_abi::stake_module(); + let call = (module_id, String::from(TX_ADD_ALLOWLIST), call_data); let utx = UnprovenTransaction::new( rng, @@ -824,56 +880,6 @@ fn pick_lexicographic bool>( None } -const STCT_MESSAGE_SIZE: usize = 5 + PoseidonCipher::cipher_size(); - -// TODO: this is copied from the circuits. We should find a way to reuse this -// instead of duplicating it. -fn sign_stct( - rng: &mut Rng, - ssk: &SecretSpendKey, - fee: &Fee, - crossover: &Crossover, - value: u64, - address: &BlsScalar, -) -> SchnorrSignature { - let sk_r = *ssk.sk_r(fee.stealth_address()).as_ref(); - let secret = SchnorrKey::from(sk_r); - - let message = { - let mut message = [BlsScalar::zero(); STCT_MESSAGE_SIZE]; - let mut m = message.iter_mut(); - - crossover - .value_commitment() - .to_hash_inputs() - .iter() - .zip(m.by_ref()) - .for_each(|(c, m)| *m = *c); - - if let Some(m) = m.next() { - *m = *crossover.nonce(); - } - - crossover - .encrypted_data() - .cipher() - .iter() - .zip(m.by_ref()) - .for_each(|(c, m)| *m = *c); - - if let Some(m) = m.next() { - *m = value.into(); - } - if let Some(m) = m.next() { - *m = *address; - } - - sponge::hash(&message) - }; - - SchnorrSignature::new(&secret, rng, message) -} - /// Creates a signature compatible with what the stake contract expects for a /// stake transaction. /// diff --git a/src/lib.rs b/src/lib.rs index 6ab91ba..2dd0b6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ #![deny(missing_docs)] #![deny(clippy::all)] +#![allow(clippy::result_large_err)] #![no_std] #[macro_use] @@ -33,10 +34,15 @@ use rand_core::SeedableRng; use sha2::{Digest, Sha256}; pub use imp::*; -pub use tx::{Transaction, UnprovenTransaction, UnprovenTransactionInput}; +pub use phoenix_core::Transaction; +pub use tx::{UnprovenTransaction, UnprovenTransactionInput}; +use phoenix_core::transaction::*; pub use rusk_abi::POSEIDON_TREE_DEPTH; +/// The maximum size of call data. +pub const MAX_CALL_SIZE: usize = rusk_abi::ARGBUF_LEN; + /// Stores the cryptographic material necessary to derive cryptographic keys. pub trait Store { /// The error type returned from the store. @@ -77,8 +83,8 @@ pub trait Store { pub fn derive_ssk(seed: &[u8; 64], index: u64) -> SecretSpendKey { let mut hash = Sha256::new(); - hash.update(&seed); - hash.update(&index.to_le_bytes()); + hash.update(seed); + hash.update(index.to_le_bytes()); hash.update(b"SSK"); let hash = hash.finalize().into(); @@ -96,8 +102,8 @@ pub fn derive_ssk(seed: &[u8; 64], index: u64) -> SecretSpendKey { pub fn derive_sk(seed: &[u8; 64], index: u64) -> SecretKey { let mut hash = Sha256::new(); - hash.update(&seed); - hash.update(&index.to_le_bytes()); + hash.update(seed); + hash.update(index.to_le_bytes()); hash.update(b"SK"); let hash = hash.finalize().into(); @@ -224,6 +230,16 @@ pub struct StakeInfo { pub counter: u64, } +impl From for StakeInfo { + fn from(data: StakeData) -> Self { + StakeInfo { + amount: data.amount, + reward: data.reward, + counter: data.counter, + } + } +} + impl Serializable<32> for StakeInfo { type Error = dusk_bytes::Error; diff --git a/src/tx.rs b/src/tx.rs index 57fd702..6ca1cf6 100644 --- a/src/tx.rs +++ b/src/tx.rs @@ -6,10 +6,9 @@ use crate::StateClient; +use alloc::string::String; use alloc::vec::Vec; -use core::mem; -use canonical::{Canon, CanonError, Sink, Source}; use dusk_bytes::{ DeserializableSlice, Error as BytesError, Serializable, Write, }; @@ -18,295 +17,11 @@ use dusk_pki::{Ownable, SecretSpendKey}; use dusk_plonk::prelude::{JubJubScalar, Proof}; use dusk_poseidon::tree::PoseidonBranch; use dusk_schnorr::Proof as SchnorrSig; +use phoenix_core::transaction::Transaction; use phoenix_core::{Crossover, Fee, Note}; use rand_core::{CryptoRng, RngCore}; use rusk_abi::hash::Hasher; -use rusk_abi::{ContractId, POSEIDON_TREE_DEPTH}; - -const CONTRACT_ID_SIZE: usize = mem::size_of::(); - -/// The structure sent over the network representing a transaction. -#[derive(Debug, Clone)] -pub struct Transaction { - nullifiers: Vec, - outputs: Vec, - anchor: BlsScalar, - proof: Vec, - fee: Fee, - crossover: Option, - call: Option<(ContractId, Vec)>, -} - -// This implements `Canon` to allow a transaction to pass directly though the -// transfer contract. -impl Canon for Transaction { - fn encode(&self, sink: &mut Sink) { - 0u8.encode(sink); // tag included to call `execute` in the contract. - self.anchor.encode(sink); - self.nullifiers.encode(sink); - self.fee.encode(sink); - self.crossover.encode(sink); - self.outputs.encode(sink); - self.proof.encode(sink); - self.call.encode(sink); - } - - fn decode(source: &mut Source) -> Result { - u8::decode(source)?; - - let anchor = Canon::decode(source)?; - let nullifiers = Canon::decode(source)?; - let fee = Canon::decode(source)?; - let crossover = Canon::decode(source)?; - let outputs = Canon::decode(source)?; - let proof = Canon::decode(source)?; - let call = Canon::decode(source)?; - - Ok(Transaction { - anchor, - nullifiers, - fee, - crossover, - outputs, - proof, - call, - }) - } - - fn encoded_len(&self) -> usize { - 0u8.encoded_len() - + self.anchor.encoded_len() - + self.nullifiers.encoded_len() - + self.fee.encoded_len() - + self.crossover.encoded_len() - + self.outputs.encoded_len() - + self.proof.encoded_len() - + self.call.encoded_len() - } -} - -impl Transaction { - /// Creates a transaction from the skeleton and the proof. - fn new(tx_skel: TransactionSkeleton, proof: Proof) -> Self { - Self { - proof: proof.to_bytes().to_vec(), - nullifiers: tx_skel.nullifiers, - outputs: tx_skel.outputs, - anchor: tx_skel.anchor, - fee: tx_skel.fee, - crossover: tx_skel.crossover, - call: tx_skel.call, - } - } - - /// Hashes the transaction excluding. - pub fn hash(&self) -> BlsScalar { - let skel = TransactionSkeleton::from(self.clone()); - skel.hash() - } - - /// Serializes the transaction into a variable length byte buffer. - #[allow(unused_must_use)] - pub fn to_var_bytes(&self) -> Vec { - // compute the serialized size to preallocate space - let size = u64::SIZE - + self.nullifiers.len() * BlsScalar::SIZE - + u64::SIZE - + self.outputs.len() * Note::SIZE - + BlsScalar::SIZE - + Fee::SIZE - + Proof::SIZE - + u64::SIZE - + self.crossover.map_or(0, |_| Crossover::SIZE) - + u64::SIZE - + self - .call - .as_ref() - .map(|(_, cdata)| CONTRACT_ID_SIZE + cdata.len()) - .unwrap_or(0); - - let mut bytes = vec![0u8; size]; - let mut writer = &mut bytes[..]; - - writer.write(&(self.nullifiers.len() as u64).to_bytes()); - for input in &self.nullifiers { - writer.write(&input.to_bytes()); - } - - writer.write(&(self.outputs.len() as u64).to_bytes()); - for output in &self.outputs { - writer.write(&output.to_bytes()); - } - - writer.write(&self.anchor.to_bytes()); - writer.write(&self.fee.to_bytes()); - writer.write(&self.proof); - - write_crossover(&mut writer, self.crossover); - - write_optional_call(&mut writer, self.call.as_ref()); - - bytes - } - - /// Deserializes the transaction from a bytes buffer. - pub fn from_slice(buf: &[u8]) -> Result { - let mut buffer = buf; - - let num_inputs = u64::from_reader(&mut buffer)? as usize; - let mut nullifiers = Vec::with_capacity(num_inputs); - - for _ in 0..num_inputs { - nullifiers.push(BlsScalar::from_reader(&mut buffer)?); - } - - let num_outputs = u64::from_reader(&mut buffer)? as usize; - let mut outputs = Vec::with_capacity(num_outputs); - - for _ in 0..num_outputs { - outputs.push(Note::from_reader(&mut buffer)?); - } - - let anchor = BlsScalar::from_reader(&mut buffer)?; - let fee = Fee::from_reader(&mut buffer)?; - let proof = Proof::from_reader(&mut buffer)?.to_bytes().to_vec(); - - let crossover = read_crossover(&mut buffer)?; - - let call = read_optional_call(&mut buffer)?; - - Ok(Self { - nullifiers, - outputs, - anchor, - fee, - crossover, - call, - proof, - }) - } - - /// The nullifiers in the transaction. - pub fn inputs(&self) -> &[BlsScalar] { - &self.nullifiers - } - - /// The output notes of the transaction. - pub fn outputs(&self) -> &[Note] { - &self.outputs - } - - /// The anchor of the transaction. - pub fn anchor(&self) -> BlsScalar { - self.anchor - } - - /// The proof of the transaction. - pub fn proof(&self) -> &[u8] { - &self.proof - } - - /// The fee of the transaction. - pub fn fee(&self) -> &Fee { - &self.fee - } - - /// The crossover of the transaction. - pub fn crossover(&self) -> Option<&Crossover> { - self.crossover.as_ref() - } - - /// The call data of the transaction. - pub fn call(&self) -> Option<&(ContractId, Vec)> { - self.call.as_ref() - } -} - -/// Transaction skeleton. -struct TransactionSkeleton { - nullifiers: Vec, - outputs: Vec, - anchor: BlsScalar, - fee: Fee, - crossover: Option, - call: Option<(ContractId, Vec)>, -} - -impl TransactionSkeleton { - fn new( - nullifiers: Vec, - outputs: Vec, - anchor: BlsScalar, - fee: Fee, - crossover: Option, - call: Option<(ContractId, Vec)>, - ) -> Self { - Self { - nullifiers, - outputs, - anchor, - fee, - crossover, - call, - } - } - - fn hash(&self) -> BlsScalar { - let mut hasher = Hasher::new(); - - for nullifier in &self.nullifiers { - hasher.update(nullifier.to_bytes()); - } - for note in &self.outputs { - hasher.update(note.to_bytes()); - } - - hasher = hasher - .chain_update(self.anchor.to_bytes()) - .chain_update(self.fee.to_bytes()); - - if let Some(crossover) = &self.crossover { - hasher.update(&crossover.to_bytes()); - } - - if let Some((cid, cdata)) = &self.call { - hasher.update(cid.as_bytes()); - hasher.update(cdata); - } - - hasher.finalize() - } -} - -impl From for TransactionSkeleton { - fn from(tx: Transaction) -> Self { - Self { - nullifiers: tx.nullifiers, - outputs: tx.outputs, - anchor: tx.anchor, - fee: tx.fee, - crossover: tx.crossover, - call: tx.call, - } - } -} - -impl From for TransactionSkeleton { - fn from(utx: UnprovenTransaction) -> Self { - Self { - nullifiers: utx - .inputs - .iter() - .map(UnprovenTransactionInput::nullifier) - .collect(), - outputs: utx.outputs.iter().map(|o| o.0).collect(), - anchor: utx.anchor, - fee: utx.fee, - crossover: utx.crossover.map(|c| c.0), - call: utx.call, - } - } -} +use rusk_abi::{ModuleId, MODULE_ID_BYTES, POSEIDON_TREE_DEPTH}; /// An input to a transaction that is yet to be proven. #[derive(Debug, Clone)] @@ -351,13 +66,6 @@ impl UnprovenTransactionInput { pub fn to_var_bytes(&self) -> Vec { let affine_pkr = JubJubAffine::from(&self.pk_r_prime); - // TODO Magic number for the buffer size here. - // Should be corrected once dusk-poseidon implements `Serializable` for - // `PoseidonBranch`. - let mut opening_bytes = [0; opening_buf_size(POSEIDON_TREE_DEPTH)]; - let mut sink = Sink::new(&mut opening_bytes[..]); - self.opening.encode(&mut sink); - let mut bytes = Vec::with_capacity( BlsScalar::SIZE + Note::SIZE @@ -365,7 +73,7 @@ impl UnprovenTransactionInput { + SchnorrSig::SIZE + u64::SIZE + JubJubScalar::SIZE - + opening_bytes.len(), + + PoseidonBranch::::SIZE, ); bytes.extend_from_slice(&self.nullifier.to_bytes()); @@ -374,7 +82,7 @@ impl UnprovenTransactionInput { bytes.extend_from_slice(&self.blinder.to_bytes()); bytes.extend_from_slice(&affine_pkr.to_bytes()); bytes.extend_from_slice(&self.sig.to_bytes()); - bytes.extend_from_slice(&opening_bytes); + bytes.extend_from_slice(&self.opening.to_bytes()); bytes } @@ -390,10 +98,7 @@ impl UnprovenTransactionInput { let pk_r_prime = JubJubExtended::from(JubJubAffine::from_reader(&mut bytes)?); let sig = SchnorrSig::from_reader(&mut bytes)?; - - let mut source = Source::new(bytes); - let opening = PoseidonBranch::decode(&mut source) - .map_err(|_| BytesError::InvalidData)?; + let opening = PoseidonBranch::from_reader(&mut bytes)?; Ok(Self { note, @@ -442,10 +147,6 @@ impl UnprovenTransactionInput { } } -const fn opening_buf_size(depth: usize) -> usize { - (depth + 2) * (BlsScalar::SIZE * 5 + 8) -} - /// A transaction that is yet to be proven. The purpose of this is solely to /// send to the node to perform a circuit proof. #[derive(Debug, Clone)] @@ -455,7 +156,7 @@ pub struct UnprovenTransaction { anchor: BlsScalar, fee: Fee, crossover: Option<(Crossover, u64, JubJubScalar)>, - call: Option<(ContractId, Vec)>, + call: Option<(ModuleId, String, Vec)>, } impl UnprovenTransaction { @@ -469,7 +170,7 @@ impl UnprovenTransaction { outputs: Vec<(Note, u64, JubJubScalar)>, fee: Fee, crossover: Option<(Crossover, u64, JubJubScalar)>, - call: Option<(ContractId, Vec)>, + call: Option<(ModuleId, String, Vec)>, ) -> Result { let nullifiers: Vec = inputs .iter() @@ -484,15 +185,19 @@ impl UnprovenTransaction { let anchor = state.fetch_anchor()?; - let skel = TransactionSkeleton::new( - nullifiers, - outputs.iter().map(|o| o.0).collect(), - anchor, - fee, - crossover.map(|c| c.0), - call, + let hash_outputs: Vec = outputs.iter().map(|o| o.0).collect(); + let hash_crossover = crossover.map(|c| c.0); + + let hash_call = call.clone().map(|c| (c.0.to_bytes(), c.1, c.2)); + let hash_bytes = Transaction::hash_input_bytes_from_components( + &nullifiers, + &hash_outputs, + &anchor, + &fee, + &hash_crossover, + &hash_call, ); - let hash = skel.hash(); + let hash = Hasher::digest(hash_bytes); let inputs: Vec = inputs .into_iter() @@ -510,14 +215,29 @@ impl UnprovenTransaction { anchor, fee, crossover, - call: skel.call, + call, }) } /// Consumes self and a proof to generate a transaction. pub fn prove(self, proof: Proof) -> Transaction { - let skel = TransactionSkeleton::from(self); - Transaction::new(skel, proof) + Transaction { + anchor: self.anchor, + nullifiers: self + .inputs + .into_iter() + .map(|input| input.nullifier) + .collect(), + outputs: self + .outputs + .into_iter() + .map(|(note, _, _)| note) + .collect(), + fee: self.fee, + crossover: self.crossover.map(|c| c.0), + proof: proof.to_bytes().to_vec(), + call: self.call.map(|c| (c.0.to_bytes(), c.1, c.2)), + } } /// Serialize the transaction to a variable length byte buffer. @@ -571,7 +291,9 @@ impl UnprovenTransaction { + self .call .as_ref() - .map(|(_, cdata)| CONTRACT_ID_SIZE + cdata.len()) + .map(|(_, cname, cdata)| { + MODULE_ID_BYTES + u64::SIZE + cname.len() + cdata.len() + }) .unwrap_or(0); let mut buf = vec![0; size]; @@ -592,7 +314,7 @@ impl UnprovenTransaction { writer.write(&self.fee.to_bytes()); write_crossover_value_blinder(&mut writer, self.crossover); - write_optional_call(&mut writer, self.call.as_ref()); + write_optional_call(&mut writer, &self.call); buf } @@ -638,7 +360,22 @@ impl UnprovenTransaction { /// Returns the hash of the transaction. pub fn hash(&self) -> BlsScalar { - TransactionSkeleton::from(self.clone()).hash() + let nullifiers: Vec = + self.inputs.iter().map(|input| input.nullifier).collect(); + + let hash_outputs: Vec = + self.outputs.iter().map(|(note, _, _)| *note).collect(); + let hash_crossover = self.crossover.map(|c| c.0); + let hash_bytes = self.call.clone().map(|c| (c.0.to_bytes(), c.1, c.2)); + + Hasher::digest(Transaction::hash_input_bytes_from_components( + &nullifiers, + &hash_outputs, + &self.anchor, + &self.fee, + &hash_crossover, + &hash_bytes, + )) } /// Returns the inputs to the transaction. @@ -667,7 +404,7 @@ impl UnprovenTransaction { } /// Returns the call of the transaction. - pub fn call(&self) -> Option<&(ContractId, Vec)> { + pub fn call(&self) -> Option<&(ModuleId, String, Vec)> { self.call.as_ref() } } @@ -678,12 +415,18 @@ impl UnprovenTransaction { /// data. fn write_optional_call( writer: &mut W, - call: Option<&(ContractId, Vec)>, + call: &Option<(ModuleId, String, Vec)>, ) -> Result<(), BytesError> { match call { - Some((cid, cdata)) => { + Some((cid, cname, cdata)) => { writer.write(&1_u64.to_bytes())?; + writer.write(cid.as_bytes())?; + + let cname_len = cname.len() as u64; + writer.write(&cname_len.to_bytes())?; + writer.write(cname.as_bytes())?; + writer.write(cdata)?; } None => { @@ -694,21 +437,48 @@ fn write_optional_call( Ok(()) } -fn write_crossover( - writer: &mut W, - crossover: Option, -) -> Result<(), BytesError> { - match crossover { - Some(c) => { - writer.write(&1_u64.to_bytes())?; - writer.write(&c.to_bytes())?; - } - None => { - writer.write(&0_u64.to_bytes())?; +/// Reads an optional call from the given buffer. This should be called at the +/// end of parsing other fields since it consumes the entirety of the buffer. +fn read_optional_call( + buffer: &mut &[u8], +) -> Result)>, BytesError> { + let mut call = None; + + if u64::from_reader(buffer)? != 0 { + let buf_len = buffer.len(); + + // needs to be at least the size of a contract ID and have some call + // data. + if buf_len < MODULE_ID_BYTES { + return Err(BytesError::BadLength { + found: buf_len, + expected: MODULE_ID_BYTES, + }); } + let (mid_buffer, mut buffer_left) = { + let (buf, left) = buffer.split_at(MODULE_ID_BYTES); + + let mut mid_buf = [0u8; MODULE_ID_BYTES]; + mid_buf.copy_from_slice(buf); + + (mid_buf, left) + }; + + let module_id = ModuleId::from(mid_buffer); + + let buffer = &mut buffer_left; + + let cname_len = u64::from_reader(buffer)?; + let (cname_bytes, buffer_left) = buffer.split_at(cname_len as usize); + + let cname = String::from_utf8(cname_bytes.to_vec()) + .map_err(|_| BytesError::InvalidData)?; + + let call_data = Vec::from(buffer_left); + call = Some((module_id, cname, call_data)); } - Ok(()) + Ok(call) } fn write_crossover_value_blinder( @@ -730,16 +500,6 @@ fn write_crossover_value_blinder( Ok(()) } -/// Reads an optional crossover from the given buffer. -fn read_crossover(buffer: &mut &[u8]) -> Result, BytesError> { - let ser = match u64::from_reader(buffer)? { - 0 => None, - _ => Some(Crossover::from_reader(buffer)?), - }; - - Ok(ser) -} - /// Reads an optional crossover from the given buffer. fn read_crossover_value_blinder( buffer: &mut &[u8], @@ -756,32 +516,3 @@ fn read_crossover_value_blinder( Ok(ser) } - -/// Reads an optional call from the given buffer. This should be called at the -/// end of parsing other fields since it consumes the entirety of the buffer. -fn read_optional_call( - buffer: &mut &[u8], -) -> Result)>, BytesError> { - let mut call = None; - - if u64::from_reader(buffer)? != 0 { - let buf_len = buffer.len(); - - // needs to be at least the size of a contract ID and have some call - // data. - if buf_len < CONTRACT_ID_SIZE { - return Err(BytesError::BadLength { - found: buf_len, - expected: CONTRACT_ID_SIZE, - }); - } - let (cid_buffer, cdata_buffer) = buffer.split_at(CONTRACT_ID_SIZE); - - let contract_id = ContractId::from(cid_buffer); - let call_data = Vec::from(cdata_buffer); - - call = Some((contract_id, call_data)); - } - - Ok(call) -} diff --git a/tests/mock.rs b/tests/mock.rs index 4c68b79..47f16e9 100644 --- a/tests/mock.rs +++ b/tests/mock.rs @@ -6,8 +6,6 @@ //! Mocks of the traits supplied by the user of the crate.. -use canonical::{Canon, Sink, Source}; -use canonical_derive::Canon; use dusk_bls12_381_sign::PublicKey; use dusk_jubjub::{BlsScalar, JubJubAffine, JubJubScalar}; use dusk_pki::{PublicSpendKey, ViewKey}; @@ -20,7 +18,6 @@ use dusk_wallet_core::{ }; use phoenix_core::{Crossover, Fee, Note, NoteType}; use rand_core::{CryptoRng, RngCore}; -use rusk_abi::ContractId; /// Create a new wallet meant for tests. It includes a client that will always /// return a random anchor (same every time), and the default opening. @@ -44,11 +41,11 @@ pub fn mock_wallet( } /// Create a new wallet equivalent in all ways to `mock_wallet`, but serializing -/// and deserializing a `Transaction` using `Canon`. +/// and deserializing a `Transaction` using `rkyv`. pub fn mock_canon_wallet( rng: &mut Rng, note_values: &[u64], -) -> Wallet { +) -> Wallet { let store = TestStore::new(rng); let psk = store.retrieve_ssk(0).unwrap().public_spend_key(); @@ -57,7 +54,7 @@ pub fn mock_canon_wallet( let opening = Default::default(); let state = TestStateClient::new(notes, anchor, opening); - let prover = CanonProverClient { + let prover = RkyvProverClient { prover: TestProverClient, }; @@ -218,28 +215,11 @@ impl ProverClient for TestProverClient { } #[derive(Debug)] -pub struct CanonProverClient { +pub struct RkyvProverClient { prover: TestProverClient, } -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Clone, Canon)] -pub enum Call { - Execute { - anchor: BlsScalar, - nullifiers: Vec, - fee: Fee, - crossover: Option, - notes: Vec, - spend_proof: Vec, - call: Option<(ContractId, Transaction)>, - }, - SomeOtherVariant { - anchor: BlsScalar, - }, -} - -impl ProverClient for CanonProverClient { +impl ProverClient for RkyvProverClient { type Error = (); fn compute_proof_and_propagate( @@ -250,13 +230,17 @@ impl ProverClient for CanonProverClient { let tx = utx_clone.prove(Proof::default()); - let mut buf = [0u8; 4096]; - let mut sink = Sink::new(&mut buf); - tx.encode(&mut sink); - drop(sink); + let bytes = rkyv::to_bytes::<_, 65536>(&tx) + .expect("Encoding a tx should succeed") + .to_vec(); + + let decoded_tx: Transaction = rkyv::from_bytes(&bytes) + .expect("Deserializing a transaction should succeed"); - let mut source = Source::new(&buf); - let _ = Call::decode(&mut source).expect("decoding to Call to go fine"); + assert_eq!( + tx, decoded_tx, + "Encoded and decoded transaction should be equal" + ); self.prover.compute_proof_and_propagate(utx) } From 91cb2e2f45bab85719ec92a446880ac4db070d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Tue, 13 Jun 2023 17:05:50 +0200 Subject: [PATCH 03/10] Upgrade to `rusk-abi@0.8.0-piecrust0.5` --- Cargo.toml | 2 +- src/ffi.rs | 4 ++-- src/imp.rs | 23 ++++++++++++----------- src/tx.rs | 16 ++++++++-------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 56d7ae0..10bfd0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ dusk-schnorr = { version = "0.12", default-features = false } dusk-jubjub = { version = "0.12", default-features = false } dusk-poseidon = { version = "0.28", default-features = false, features = ["alloc"] } dusk-plonk = { version = "0.13", default-features = false } -rusk-abi = { version = "0.8.0-alpha", features = ["host"] } +rusk-abi = { version = "0.8.0-piecrust.0.5", features = ["host"] } dusk-bls12_381-sign = { version = "0.4", default-features = false } rkyv = { version = "0.7", default-features = false } diff --git a/src/ffi.rs b/src/ffi.rs index ccf6a6f..f05b3e2 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -26,7 +26,7 @@ use rand_core::{ impls::{next_u32_via_fill, next_u64_via_fill}, CryptoRng, RngCore, }; -use rusk_abi::ModuleId; +use rusk_abi::ContractId; use crate::tx::UnprovenTransaction; use crate::{ @@ -163,7 +163,7 @@ pub unsafe extern "C" fn execute( gas_limit: *const u64, gas_price: *const u64, ) -> u8 { - let contract_id = ModuleId::from_bytes(*contract_id); + let contract_id = ContractId::from_bytes(*contract_id); // SAFETY: these buffers are expected to have been allocated with the // correct size. If this is not the case problems with the allocator diff --git a/src/imp.rs b/src/imp.rs index 292013c..48aae35 100644 --- a/src/imp.rs +++ b/src/imp.rs @@ -30,7 +30,7 @@ use rkyv::ser::serializers::{ SharedSerializeMapError, }; use rkyv::Serialize; -use rusk_abi::ModuleId; +use rusk_abi::ContractId; const MAX_INPUT_NOTES: usize = 4; @@ -307,7 +307,7 @@ where pub fn execute( &self, rng: &mut Rng, - module_id: ModuleId, + contract_id: ContractId, call_name: String, call_data: C, sender_index: u64, @@ -334,7 +334,7 @@ where let fee = Fee::new(rng, gas_limit, gas_price, refund); let call_data = rkyv::to_bytes(&call_data)?.to_vec(); - let call = (module_id, call_name, call_data); + let call = (contract_id, call_name, call_data); let utx = UnprovenTransaction::new( rng, @@ -448,12 +448,13 @@ where fee.gas_limit = gas_limit; fee.gas_price = gas_price; - let module_id = rusk_abi::stake_module(); - let address = rusk_abi::module_to_scalar(&module_id); + let contract_id = rusk_abi::stake_module(); + let address = rusk_abi::module_to_scalar(&contract_id); - let module_id = rusk_abi::module_to_scalar(&rusk_abi::stake_module()); + let contract_id = rusk_abi::module_to_scalar(&rusk_abi::stake_module()); - let stct_message = stct_signature_message(&crossover, value, module_id); + let stct_message = + stct_signature_message(&crossover, value, contract_id); let stct_message = rusk_abi::poseidon_hash(stct_message.to_vec()); let sk_r = *sender.sk_r(fee.stealth_address()).as_ref(); @@ -663,8 +664,8 @@ where }; let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&withdraw)?.to_vec(); - let module_id = rusk_abi::stake_module(); - let call = (module_id, String::from(TX_WITHDRAW), call_data); + let contract_id = rusk_abi::stake_module(); + let call = (contract_id, String::from(TX_WITHDRAW), call_data); let utx = UnprovenTransaction::new( rng, @@ -739,8 +740,8 @@ where }; let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&allow)?.to_vec(); - let module_id = rusk_abi::stake_module(); - let call = (module_id, String::from(TX_ADD_ALLOWLIST), call_data); + let contract_id = rusk_abi::stake_module(); + let call = (contract_id, String::from(TX_ADD_ALLOWLIST), call_data); let utx = UnprovenTransaction::new( rng, diff --git a/src/tx.rs b/src/tx.rs index 6ca1cf6..e78f7bf 100644 --- a/src/tx.rs +++ b/src/tx.rs @@ -21,7 +21,7 @@ use phoenix_core::transaction::Transaction; use phoenix_core::{Crossover, Fee, Note}; use rand_core::{CryptoRng, RngCore}; use rusk_abi::hash::Hasher; -use rusk_abi::{ModuleId, MODULE_ID_BYTES, POSEIDON_TREE_DEPTH}; +use rusk_abi::{ContractId, MODULE_ID_BYTES, POSEIDON_TREE_DEPTH}; /// An input to a transaction that is yet to be proven. #[derive(Debug, Clone)] @@ -156,7 +156,7 @@ pub struct UnprovenTransaction { anchor: BlsScalar, fee: Fee, crossover: Option<(Crossover, u64, JubJubScalar)>, - call: Option<(ModuleId, String, Vec)>, + call: Option<(ContractId, String, Vec)>, } impl UnprovenTransaction { @@ -170,7 +170,7 @@ impl UnprovenTransaction { outputs: Vec<(Note, u64, JubJubScalar)>, fee: Fee, crossover: Option<(Crossover, u64, JubJubScalar)>, - call: Option<(ModuleId, String, Vec)>, + call: Option<(ContractId, String, Vec)>, ) -> Result { let nullifiers: Vec = inputs .iter() @@ -404,7 +404,7 @@ impl UnprovenTransaction { } /// Returns the call of the transaction. - pub fn call(&self) -> Option<&(ModuleId, String, Vec)> { + pub fn call(&self) -> Option<&(ContractId, String, Vec)> { self.call.as_ref() } } @@ -415,7 +415,7 @@ impl UnprovenTransaction { /// data. fn write_optional_call( writer: &mut W, - call: &Option<(ModuleId, String, Vec)>, + call: &Option<(ContractId, String, Vec)>, ) -> Result<(), BytesError> { match call { Some((cid, cname, cdata)) => { @@ -441,7 +441,7 @@ fn write_optional_call( /// end of parsing other fields since it consumes the entirety of the buffer. fn read_optional_call( buffer: &mut &[u8], -) -> Result)>, BytesError> { +) -> Result)>, BytesError> { let mut call = None; if u64::from_reader(buffer)? != 0 { @@ -464,7 +464,7 @@ fn read_optional_call( (mid_buf, left) }; - let module_id = ModuleId::from(mid_buffer); + let contract_id = ContractId::from(mid_buffer); let buffer = &mut buffer_left; @@ -475,7 +475,7 @@ fn read_optional_call( .map_err(|_| BytesError::InvalidData)?; let call_data = Vec::from(buffer_left); - call = Some((module_id, cname, call_data)); + call = Some((contract_id, cname, call_data)); } Ok(call) From 69eee7c4353581a350d86eaa19da660ba6118a54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Tue, 13 Jun 2023 17:10:25 +0200 Subject: [PATCH 04/10] Bump version to `0.16.0-piecrust.0.5` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 10bfd0e..18cb9c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dusk-wallet-core" -version = "0.15.0-rc.0" +version = "0.16.0-piecrust.0.5" edition = "2021" description = "The core functionality of the Dusk wallet" license = "MPL-2.0" From af44aa254013fefa605de376a918cfb4a9ff3e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Tue, 27 Jun 2023 15:31:11 +0200 Subject: [PATCH 05/10] Upgrade to use `dusk-merkle` opening --- Cargo.toml | 3 ++- src/ffi.rs | 15 +++++++++------ src/lib.rs | 4 ++-- src/tx.rs | 23 ++++++++++++++++------- tests/mock.rs | 30 ++++++++++++++++++++++-------- 5 files changed, 51 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 18cb9c2..a671f4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ dusk-pki = { version = "0.11", default-features = false } dusk-bytes = "^0.1" dusk-schnorr = { version = "0.12", default-features = false } dusk-jubjub = { version = "0.12", default-features = false } -dusk-poseidon = { version = "0.28", default-features = false, features = ["alloc"] } +dusk-poseidon = { version = "0.29.1-rc.0", default-features = false } +dusk-merkle = { version = "0.4", features = ["poseidon", "rkyv-impl"] } dusk-plonk = { version = "0.13", default-features = false } rusk-abi = { version = "0.8.0-piecrust.0.5", features = ["host"] } dusk-bls12_381-sign = { version = "0.4", default-features = false } diff --git a/src/ffi.rs b/src/ffi.rs index f05b3e2..14cfbac 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -17,9 +17,9 @@ use dusk_bls12_381_sign::PublicKey; use dusk_bytes::Write; use dusk_bytes::{DeserializableSlice, Serializable}; use dusk_jubjub::{BlsScalar, JubJubAffine, JubJubScalar}; +use dusk_merkle::poseidon::Opening as PoseidonOpening; use dusk_pki::{PublicSpendKey, ViewKey}; use dusk_plonk::prelude::Proof; -use dusk_poseidon::tree::PoseidonBranch; use dusk_schnorr::Signature; use phoenix_core::{Crossover, Fee, Note}; use rand_core::{ @@ -59,7 +59,8 @@ extern "C" { /// Queries the node to find the opening for a specific note. fn fetch_opening( note: *const [u8; Note::SIZE], - opening: *mut [u8; PoseidonBranch::::SIZE], + opening: *mut u8, + opening_len: *mut u32, ) -> u8; /// Asks the node to find the nullifiers that are already in the state and @@ -465,13 +466,15 @@ impl StateClient for FfiStateClient { fn fetch_opening( &self, note: &Note, - ) -> Result, Self::Error> { - let mut opening_buf = - [0u8; PoseidonBranch::::SIZE]; + ) -> Result, Self::Error> { + const OPENING_BUF_SIZE: usize = 3000; + + let mut opening_buf = Vec::with_capacity(OPENING_BUF_SIZE); + let mut opening_len = 0; let note = note.to_bytes(); unsafe { - let r = fetch_opening(¬e, &mut opening_buf); + let r = fetch_opening(¬e, &mut opening_buf, &mut opening_len); if r != 0 { return Err(r); } diff --git a/src/lib.rs b/src/lib.rs index 2dd0b6e..ccb9f06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,9 +24,9 @@ use alloc::vec::Vec; use dusk_bls12_381_sign::{PublicKey, SecretKey}; use dusk_bytes::{DeserializableSlice, Serializable, Write}; use dusk_jubjub::{BlsScalar, JubJubAffine, JubJubScalar}; +use dusk_merkle::poseidon::Opening as PoseidonOpening; use dusk_pki::{SecretSpendKey, ViewKey}; use dusk_plonk::proof_system::Proof; -use dusk_poseidon::tree::PoseidonBranch; use dusk_schnorr::Signature; use phoenix_core::{Crossover, Fee, Note}; use rand_chacha::ChaCha12Rng; @@ -174,7 +174,7 @@ pub trait StateClient { fn fetch_opening( &self, note: &Note, - ) -> Result, Self::Error>; + ) -> Result, Self::Error>; /// Queries the node for the stake of a key. If the key has no stake, a /// `Default` stake info should be returned. diff --git a/src/tx.rs b/src/tx.rs index e78f7bf..6da36a9 100644 --- a/src/tx.rs +++ b/src/tx.rs @@ -13,9 +13,9 @@ use dusk_bytes::{ DeserializableSlice, Error as BytesError, Serializable, Write, }; use dusk_jubjub::{BlsScalar, JubJubAffine, JubJubExtended}; +use dusk_merkle::poseidon::Opening as PoseidonOpening; use dusk_pki::{Ownable, SecretSpendKey}; use dusk_plonk::prelude::{JubJubScalar, Proof}; -use dusk_poseidon::tree::PoseidonBranch; use dusk_schnorr::Proof as SchnorrSig; use phoenix_core::transaction::Transaction; use phoenix_core::{Crossover, Fee, Note}; @@ -27,7 +27,7 @@ use rusk_abi::{ContractId, MODULE_ID_BYTES, POSEIDON_TREE_DEPTH}; #[derive(Debug, Clone)] pub struct UnprovenTransactionInput { nullifier: BlsScalar, - opening: PoseidonBranch, + opening: PoseidonOpening<(), POSEIDON_TREE_DEPTH, 4>, note: Note, value: u64, blinder: JubJubScalar, @@ -42,7 +42,7 @@ impl UnprovenTransactionInput { note: Note, value: u64, blinder: JubJubScalar, - opening: PoseidonBranch, + opening: PoseidonOpening<(), POSEIDON_TREE_DEPTH, 4>, tx_hash: BlsScalar, ) -> Self { let nullifier = note.gen_nullifier(ssk); @@ -66,6 +66,10 @@ impl UnprovenTransactionInput { pub fn to_var_bytes(&self) -> Vec { let affine_pkr = JubJubAffine::from(&self.pk_r_prime); + let opening_bytes = rkyv::to_bytes::<_, 256>(&self.opening) + .expect("Rkyv serialization should always succeed for an opening") + .to_vec(); + let mut bytes = Vec::with_capacity( BlsScalar::SIZE + Note::SIZE @@ -73,7 +77,7 @@ impl UnprovenTransactionInput { + SchnorrSig::SIZE + u64::SIZE + JubJubScalar::SIZE - + PoseidonBranch::::SIZE, + + opening_bytes.len(), ); bytes.extend_from_slice(&self.nullifier.to_bytes()); @@ -82,7 +86,7 @@ impl UnprovenTransactionInput { bytes.extend_from_slice(&self.blinder.to_bytes()); bytes.extend_from_slice(&affine_pkr.to_bytes()); bytes.extend_from_slice(&self.sig.to_bytes()); - bytes.extend_from_slice(&self.opening.to_bytes()); + bytes.extend(opening_bytes); bytes } @@ -98,7 +102,12 @@ impl UnprovenTransactionInput { let pk_r_prime = JubJubExtended::from(JubJubAffine::from_reader(&mut bytes)?); let sig = SchnorrSig::from_reader(&mut bytes)?; - let opening = PoseidonBranch::from_reader(&mut bytes)?; + + // `to_vec` is required here otherwise `rkyv` will throw an alignment + // error + #[allow(clippy::unnecessary_to_owned)] + let opening = rkyv::from_bytes(&bytes.to_vec()) + .map_err(|_| BytesError::InvalidData)?; Ok(Self { note, @@ -117,7 +126,7 @@ impl UnprovenTransactionInput { } /// Returns the opening of the input. - pub fn opening(&self) -> &PoseidonBranch { + pub fn opening(&self) -> &PoseidonOpening<(), POSEIDON_TREE_DEPTH, 4> { &self.opening } diff --git a/tests/mock.rs b/tests/mock.rs index 47f16e9..2ed8d7b 100644 --- a/tests/mock.rs +++ b/tests/mock.rs @@ -8,9 +8,9 @@ use dusk_bls12_381_sign::PublicKey; use dusk_jubjub::{BlsScalar, JubJubAffine, JubJubScalar}; +use dusk_merkle::poseidon::{Item, Opening as PoseidonOpening, Tree}; use dusk_pki::{PublicSpendKey, ViewKey}; use dusk_plonk::prelude::Proof; -use dusk_poseidon::tree::PoseidonBranch; use dusk_schnorr::Signature; use dusk_wallet_core::{ EnrichedNote, ProverClient, StakeInfo, StateClient, Store, Transaction, @@ -19,6 +19,20 @@ use dusk_wallet_core::{ use phoenix_core::{Crossover, Fee, Note, NoteType}; use rand_core::{CryptoRng, RngCore}; +fn default_opening() -> PoseidonOpening<(), POSEIDON_TREE_DEPTH, 4> { + // Build a "default" opening + const POS: u64 = 42; + let mut tree = Tree::new(); + tree.insert( + POS, + Item { + hash: BlsScalar::zero(), + data: (), + }, + ); + tree.opening(POS).unwrap() +} + /// Create a new wallet meant for tests. It includes a client that will always /// return a random anchor (same every time), and the default opening. /// @@ -32,7 +46,7 @@ pub fn mock_wallet( let notes = new_notes(rng, &psk, note_values); let anchor = BlsScalar::random(rng); - let opening = Default::default(); + let opening = default_opening(); let state = TestStateClient::new(notes, anchor, opening); let prover = TestProverClient; @@ -51,7 +65,7 @@ pub fn mock_canon_wallet( let notes = new_notes(rng, &psk, note_values); let anchor = BlsScalar::random(rng); - let opening = Default::default(); + let opening = default_opening(); let state = TestStateClient::new(notes, anchor, opening); let prover = RkyvProverClient { @@ -72,7 +86,7 @@ pub fn mock_serde_wallet( let notes = new_notes(rng, &psk, note_values); let anchor = BlsScalar::random(rng); - let opening = Default::default(); + let opening = default_opening(); let state = TestStateClient::new(notes, anchor, opening); let prover = SerdeProverClient { @@ -125,7 +139,7 @@ impl Store for TestStore { pub struct TestStateClient { notes: Vec, anchor: BlsScalar, - opening: PoseidonBranch, + opening: PoseidonOpening<(), POSEIDON_TREE_DEPTH, 4>, } impl TestStateClient { @@ -133,7 +147,7 @@ impl TestStateClient { fn new( notes: Vec, anchor: BlsScalar, - opening: PoseidonBranch, + opening: PoseidonOpening<(), POSEIDON_TREE_DEPTH, 4>, ) -> Self { Self { notes, @@ -167,8 +181,8 @@ impl StateClient for TestStateClient { fn fetch_opening( &self, _: &Note, - ) -> Result, Self::Error> { - Ok(self.opening.clone()) + ) -> Result, Self::Error> { + Ok(self.opening) } fn fetch_stake(&self, _pk: &PublicKey) -> Result { From dbb90bc71a99f89d0b52fecb326da4f754d0a5e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Tue, 27 Jun 2023 15:31:35 +0200 Subject: [PATCH 06/10] Bump version to `0.17.0-piecrust.0.5` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a671f4f..f7afc9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dusk-wallet-core" -version = "0.16.0-piecrust.0.5" +version = "0.17.0-piecrust.0.5" edition = "2021" description = "The core functionality of the Dusk wallet" license = "MPL-2.0" From 3ca58279309703ba9fd840db2ae0fcb7e0d74215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Tue, 27 Jun 2023 15:37:46 +0200 Subject: [PATCH 07/10] Upgrade to `dusk-merkle@0.4.1-rc.0` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f7afc9b..0f5ca92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ dusk-bytes = "^0.1" dusk-schnorr = { version = "0.12", default-features = false } dusk-jubjub = { version = "0.12", default-features = false } dusk-poseidon = { version = "0.29.1-rc.0", default-features = false } -dusk-merkle = { version = "0.4", features = ["poseidon", "rkyv-impl"] } +dusk-merkle = { version = "0.4.1-rc.0", features = ["poseidon", "rkyv-impl"] } dusk-plonk = { version = "0.13", default-features = false } rusk-abi = { version = "0.8.0-piecrust.0.5", features = ["host"] } dusk-bls12_381-sign = { version = "0.4", default-features = false } From d652f0b5f4ae2cb19346448465c35de34b2cb039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Tue, 27 Jun 2023 15:38:13 +0200 Subject: [PATCH 08/10] Bump version to `0.17.1-piecrust.0.5` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0f5ca92..bb10cba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dusk-wallet-core" -version = "0.17.0-piecrust.0.5" +version = "0.17.1-piecrust.0.5" edition = "2021" description = "The core functionality of the Dusk wallet" license = "MPL-2.0" From 2f4c5c1e2343c3f92b1e6abda32c3af1e535e874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Tue, 4 Jul 2023 16:22:03 +0200 Subject: [PATCH 09/10] Upgrade to `piecrust@0.6` version of the ABI --- Cargo.toml | 2 +- src/ffi.rs | 15 ++++++++++----- src/imp.rs | 31 ++++++++++++++++++++----------- src/lib.rs | 2 +- src/tx.rs | 12 ++++++------ 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bb10cba..cc36d38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ dusk-jubjub = { version = "0.12", default-features = false } dusk-poseidon = { version = "0.29.1-rc.0", default-features = false } dusk-merkle = { version = "0.4.1-rc.0", features = ["poseidon", "rkyv-impl"] } dusk-plonk = { version = "0.13", default-features = false } -rusk-abi = { version = "0.8.0-piecrust.0.5", features = ["host"] } +rusk-abi = { version = "0.9.0-piecrust.0.6", default-features = false } dusk-bls12_381-sign = { version = "0.4", default-features = false } rkyv = { version = "0.7", default-features = false } diff --git a/src/ffi.rs b/src/ffi.rs index 14cfbac..ad0e449 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -474,15 +474,20 @@ impl StateClient for FfiStateClient { let note = note.to_bytes(); unsafe { - let r = fetch_opening(¬e, &mut opening_buf, &mut opening_len); + let r = fetch_opening( + ¬e, + opening_buf.as_mut_ptr(), + &mut opening_len, + ); if r != 0 { return Err(r); } } - let branch = PoseidonBranch::from_slice(&opening_buf).map_err( - Error::::from, - )?; + let branch = rkyv::from_bytes(&opening_buf[..opening_len as usize]) + .map_err( + Error::::from, + )?; Ok(branch) } @@ -673,7 +678,7 @@ impl From> Error::Prover(_) => 251, Error::NotEnoughBalance => 250, Error::NoteCombinationProblem => 249, - Error::Rkyv(_) => 248, + Error::Rkyv => 248, Error::Phoenix(_) => 247, Error::AlreadyStaked { .. } => 246, Error::NotStaked { .. } => 245, diff --git a/src/imp.rs b/src/imp.rs index 48aae35..916ae4e 100644 --- a/src/imp.rs +++ b/src/imp.rs @@ -29,6 +29,7 @@ use rkyv::ser::serializers::{ AllocScratchError, AllocSerializer, CompositeSerializerError, SharedSerializeMapError, }; +use rkyv::validation::validators::CheckDeserializeError; use rkyv::Serialize; use rusk_abi::ContractId; @@ -56,7 +57,7 @@ pub enum Error { /// Error originating from the prover client. Prover(PC::Error), /// Rkyv serialization. - Rkyv(SerializerError), + Rkyv, /// Random number generator error. Rng(RngError), /// Serialization and deserialization of Dusk types. @@ -114,8 +115,16 @@ impl Error { impl From for Error { - fn from(err: SerializerError) -> Self { - Self::Rkyv(err) + fn from(_: SerializerError) -> Self { + Self::Rkyv + } +} + +impl + From> for Error +{ + fn from(_: CheckDeserializeError) -> Self { + Self::Rkyv } } @@ -448,14 +457,14 @@ where fee.gas_limit = gas_limit; fee.gas_price = gas_price; - let contract_id = rusk_abi::stake_module(); - let address = rusk_abi::module_to_scalar(&contract_id); + let contract_id = rusk_abi::STAKE_CONTRACT; + let address = rusk_abi::contract_to_scalar(&contract_id); - let contract_id = rusk_abi::module_to_scalar(&rusk_abi::stake_module()); + let contract_id = rusk_abi::contract_to_scalar(&contract_id); let stct_message = stct_signature_message(&crossover, value, contract_id); - let stct_message = rusk_abi::poseidon_hash(stct_message.to_vec()); + let stct_message = dusk_poseidon::sponge::hash(&stct_message); let sk_r = *sender.sk_r(fee.stealth_address()).as_ref(); let secret = SchnorrKey::from(sk_r); @@ -487,7 +496,7 @@ where let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&stake)?.to_vec(); let call = - (rusk_abi::stake_module(), String::from(TX_STAKE), call_data); + (rusk_abi::STAKE_CONTRACT, String::from(TX_STAKE), call_data); let utx = UnprovenTransaction::new( rng, @@ -580,7 +589,7 @@ where let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&unstake)?.to_vec(); let call = ( - rusk_abi::stake_module(), + rusk_abi::STAKE_CONTRACT, String::from(TX_UNSTAKE), call_data, ); @@ -664,7 +673,7 @@ where }; let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&withdraw)?.to_vec(); - let contract_id = rusk_abi::stake_module(); + let contract_id = rusk_abi::STAKE_CONTRACT; let call = (contract_id, String::from(TX_WITHDRAW), call_data); let utx = UnprovenTransaction::new( @@ -740,7 +749,7 @@ where }; let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&allow)?.to_vec(); - let contract_id = rusk_abi::stake_module(); + let contract_id = rusk_abi::STAKE_CONTRACT; let call = (contract_id, String::from(TX_ADD_ALLOWLIST), call_data); let utx = UnprovenTransaction::new( diff --git a/src/lib.rs b/src/lib.rs index ccb9f06..c38cd46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ #![deny(missing_docs)] #![deny(clippy::all)] #![allow(clippy::result_large_err)] -#![no_std] +// #![no_std] #[macro_use] extern crate alloc; diff --git a/src/tx.rs b/src/tx.rs index 6da36a9..57b941b 100644 --- a/src/tx.rs +++ b/src/tx.rs @@ -21,7 +21,7 @@ use phoenix_core::transaction::Transaction; use phoenix_core::{Crossover, Fee, Note}; use rand_core::{CryptoRng, RngCore}; use rusk_abi::hash::Hasher; -use rusk_abi::{ContractId, MODULE_ID_BYTES, POSEIDON_TREE_DEPTH}; +use rusk_abi::{ContractId, CONTRACT_ID_BYTES, POSEIDON_TREE_DEPTH}; /// An input to a transaction that is yet to be proven. #[derive(Debug, Clone)] @@ -301,7 +301,7 @@ impl UnprovenTransaction { .call .as_ref() .map(|(_, cname, cdata)| { - MODULE_ID_BYTES + u64::SIZE + cname.len() + cdata.len() + CONTRACT_ID_BYTES + u64::SIZE + cname.len() + cdata.len() }) .unwrap_or(0); @@ -458,16 +458,16 @@ fn read_optional_call( // needs to be at least the size of a contract ID and have some call // data. - if buf_len < MODULE_ID_BYTES { + if buf_len < CONTRACT_ID_BYTES { return Err(BytesError::BadLength { found: buf_len, - expected: MODULE_ID_BYTES, + expected: CONTRACT_ID_BYTES, }); } let (mid_buffer, mut buffer_left) = { - let (buf, left) = buffer.split_at(MODULE_ID_BYTES); + let (buf, left) = buffer.split_at(CONTRACT_ID_BYTES); - let mut mid_buf = [0u8; MODULE_ID_BYTES]; + let mut mid_buf = [0u8; CONTRACT_ID_BYTES]; mid_buf.copy_from_slice(buf); (mid_buf, left) From a04d87731a8a26506860f38b67cdd1afc7ffb126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Tue, 4 Jul 2023 16:32:00 +0200 Subject: [PATCH 10/10] Bump version to `0.19.0-piecrust.0.6` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cc36d38..01de56a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dusk-wallet-core" -version = "0.17.1-piecrust.0.5" +version = "0.19.0-piecrust.0.6" edition = "2021" description = "The core functionality of the Dusk wallet" license = "MPL-2.0"