From 906b8e8fdd75332607d1502b2ae0a3eb8a777688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Mon, 17 Jun 2024 15:04:44 +0200 Subject: [PATCH 1/6] test-wallet: add test wallet code --- Cargo.toml | 3 + test-wallet/Cargo.toml | 32 ++ test-wallet/src/imp.rs | 919 +++++++++++++++++++++++++++++++++++++++++ test-wallet/src/lib.rs | 289 +++++++++++++ 4 files changed, 1243 insertions(+) create mode 100644 test-wallet/Cargo.toml create mode 100644 test-wallet/src/imp.rs create mode 100644 test-wallet/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 399ae7a457..19ecc94cb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,9 @@ members = [ "node-data", "consensus", "node", + + # Test utils + "test-wallet", ] resolver = "2" diff --git a/test-wallet/Cargo.toml b/test-wallet/Cargo.toml new file mode 100644 index 0000000000..96b0f35e00 --- /dev/null +++ b/test-wallet/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "test-wallet" +version = "0.1.0" +edition = "2021" +description = "Test wallet used for Rusk" +license = "MPL-2.0" + +[dependencies] +rand_core = "^0.6" +rand_chacha = { version = "^0.3", default-features = false } +sha2 = { version = "^0.10", default-features = false } +phoenix-core = { version = "0.26", default-features = false, features = ["alloc", "rkyv-impl"] } +dusk-bytes = "^0.1" +jubjub-schnorr = { version = "0.2", default-features = false, features = ["double"] } +dusk-jubjub = { version = "0.14", default-features = false } +dusk-poseidon = { version = "0.33", default-features = false } +poseidon-merkle = { version = "0.5", features = ["rkyv-impl"] } +dusk-plonk = { version = "0.19", default-features = false } +bls12_381-bls = { version = "0.2", default-features = false } +rkyv = { version = "0.7", default-features = false } +rusk-prover = { version = "0.3.0", path = "../rusk-prover", default-features = false } +ff = { version = "0.13", default-features = false } + +# rusk dependencies +rusk-abi = { version = "0.13.0-rc", path = "../rusk-abi", default-features = false } +execution-core = { version = "0.1.0", path = "../execution-core" } + +[dev-dependencies] +rand = "^0.8" + +[lib] +crate-type = ["cdylib", "rlib"] diff --git a/test-wallet/src/imp.rs b/test-wallet/src/imp.rs new file mode 100644 index 0000000000..554668b41f --- /dev/null +++ b/test-wallet/src/imp.rs @@ -0,0 +1,919 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use crate::{ + BalanceInfo, ProverClient, StakeInfo, StateClient, Store, MAX_CALL_SIZE, +}; + +use core::convert::Infallible; + +use alloc::string::{FromUtf8Error, String}; +use alloc::vec::Vec; + +use bls12_381_bls::PublicKey as StakePublicKey; +use dusk_bytes::{Error as BytesError, Serializable}; +use dusk_jubjub::{BlsScalar, JubJubScalar}; +use ff::Field; +use phoenix_core::transaction::{stct_signature_message, Transaction}; +use phoenix_core::{ + Crossover, Error as PhoenixError, Fee, Note, NoteType, Ownable, PublicKey, + SecretKey, ViewKey, +}; +use rand_core::{CryptoRng, Error as RngError, RngCore}; +use rkyv::ser::serializers::{ + AllocScratchError, AllocSerializer, CompositeSerializerError, + SharedSerializeMapError, +}; +use rkyv::validation::validators::CheckDeserializeError; +use rkyv::Serialize; +use rusk_prover::{UnprovenTransaction, UnprovenTransactionInput}; + +use execution_core::stake::{Stake, Unstake, Withdraw}; +use rusk_abi::{hash::Hasher, ContractId}; + +const MAX_INPUT_NOTES: usize = 4; + +const TX_STAKE: &str = "stake"; +const TX_UNSTAKE: &str = "unstake"; +const TX_WITHDRAW: &str = "withdraw"; + +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), + /// Error originating from the state client. + State(SC::Error), + /// Error originating from the prover client. + Prover(PC::Error), + /// Rkyv serialization. + Rkyv, + /// 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. + NotEnoughBalance, + /// Note combination for the given value is impossible given the maximum + /// amount if inputs in a transaction. + NoteCombinationProblem, + /// The key is already staked. This happens when there already is an amount + /// staked for a key and the user tries to make a stake transaction. + AlreadyStaked { + /// The key that already has a stake. + key: StakePublicKey, + /// Information about the key's stake. + stake: StakeInfo, + }, + /// The key is not staked. This happens when a key doesn't have an amount + /// staked and the user tries to make an unstake transaction. + NotStaked { + /// The key that is not staked. + key: StakePublicKey, + /// Information about the key's stake. + stake: StakeInfo, + }, + /// The key has no reward. This happens when a key has no reward in the + /// stake contract and the user tries to make a withdraw transaction. + NoReward { + /// The key that has no reward. + key: StakePublicKey, + /// Information about the key's stake. + stake: StakeInfo, + }, +} + +impl Error { + /// Returns an error from the underlying store error. + pub fn from_store_err(se: S::Error) -> Self { + Self::Store(se) + } + /// Returns an error from the underlying state client. + pub fn from_state_err(se: SC::Error) -> Self { + Self::State(se) + } + /// Returns an error from the underlying prover client. + pub fn from_prover_err(pe: PC::Error) -> Self { + Self::Prover(pe) + } +} + +impl From + for Error +{ + fn from(_: SerializerError) -> Self { + Self::Rkyv + } +} + +impl + From> for Error +{ + fn from(_: CheckDeserializeError) -> Self { + Self::Rkyv + } +} + +impl From + for Error +{ + fn from(re: RngError) -> Self { + Self::Rng(re) + } +} + +impl From + for Error +{ + fn from(be: BytesError) -> Self { + Self::Bytes(be) + } +} + +impl From + for Error +{ + fn from(err: FromUtf8Error) -> Self { + Self::Utf8(err) + } +} + +impl From + for Error +{ + fn from(pe: PhoenixError) -> Self { + Self::Phoenix(pe) + } +} + +/// A wallet implementation. +/// +/// This is responsible for holding the keys, and performing operations like +/// creating transactions. +pub struct Wallet { + store: S, + state: SC, + prover: PC, +} + +impl Wallet { + /// Create a new wallet given the underlying store and node client. + pub const fn new(store: S, state: SC, prover: PC) -> Self { + Self { + store, + state, + prover, + } + } + + /// Return the inner Store reference + pub const fn store(&self) -> &S { + &self.store + } + + /// Return the inner State reference + pub const fn state(&self) -> &SC { + &self.state + } + + /// Return the inner Prover reference + pub const fn prover(&self) -> &PC { + &self.prover + } +} + +impl Wallet +where + S: Store, + SC: StateClient, + PC: ProverClient, +{ + /// Retrieve the public spend key with the given index. + pub fn public_key( + &self, + index: u64, + ) -> Result> { + self.store + .retrieve_sk(index) + .map(|sk| PublicKey::from(sk)) + .map_err(Error::from_store_err) + } + + /// Retrieve the public key with the given index. + pub fn stake_public_key( + &self, + index: u64, + ) -> Result> { + self.store + .retrieve_stake_sk(index) + .map(|stake_sk| From::from(&stake_sk)) + .map_err(Error::from_store_err) + } + + /// Fetches the notes and nullifiers in the state and returns the notes that + /// are still available for spending. + fn unspent_notes( + &self, + sk: &SecretKey, + ) -> Result, Error> { + let vk = ViewKey::from(sk); + + let notes = + self.state.fetch_notes(&vk).map_err(Error::from_state_err)?; + + let nullifiers: Vec<_> = + notes.iter().map(|(n, _)| n.gen_nullifier(sk)).collect(); + + let existing_nullifiers = self + .state + .fetch_existing_nullifiers(&nullifiers) + .map_err(Error::from_state_err)?; + + let unspent_notes = notes + .into_iter() + .zip(nullifiers.into_iter()) + .filter(|(_, nullifier)| !existing_nullifiers.contains(nullifier)) + .map(|((note, _), _)| note) + .collect(); + + Ok(unspent_notes) + } + + /// Here we fetch the notes and perform a "minimum number of notes + /// required" algorithm to select which ones to use for this TX. This is + /// done by picking notes largest to smallest until they combined have + /// enough accumulated value. + /// + /// We also return the outputs with a possible change note (if applicable). + #[allow(clippy::type_complexity)] + fn inputs_and_change_output( + &self, + rng: &mut Rng, + sender: &SecretKey, + refund: &PublicKey, + value: u64, + ) -> Result< + ( + Vec<(Note, u64, JubJubScalar)>, + Vec<(Note, u64, JubJubScalar)>, + ), + Error, + > { + let notes = self.unspent_notes(sender)?; + let mut notes_and_values = Vec::with_capacity(notes.len()); + + let sender_vk = ViewKey::from(sender); + + let mut accumulated_value = 0; + for note in notes.into_iter() { + let val = note.value(Some(&sender_vk))?; + let blinder = note.blinding_factor(Some(&sender_vk))?; + + accumulated_value += val; + notes_and_values.push((note, val, blinder)); + } + + if accumulated_value < value { + return Err(Error::NotEnoughBalance); + } + + let inputs = pick_notes(value, notes_and_values); + + if inputs.is_empty() { + return Err(Error::NoteCombinationProblem); + } + + let change = inputs.iter().map(|v| v.1).sum::() - value; + + let mut outputs = vec![]; + if change > 0 { + let nonce = BlsScalar::random(&mut *rng); + let (change_note, change_blinder) = + generate_obfuscated_note(rng, refund, change, nonce); + + outputs.push((change_note, change, change_blinder)) + } + + Ok((inputs, outputs)) + } + + /// Execute a generic contract call + #[allow(clippy::too_many_arguments)] + pub fn execute( + &self, + rng: &mut Rng, + contract_id: ContractId, + call_name: String, + call_data: C, + sender_index: u64, + refund: &PublicKey, + gas_limit: u64, + gas_price: u64, + ) -> Result> + where + Rng: RngCore + CryptoRng, + C: Serialize>, + { + let sender = self + .store + .retrieve_sk(sender_index) + .map_err(Error::from_store_err)?; + + let (inputs, outputs) = self.inputs_and_change_output( + rng, + &sender, + refund, + gas_limit * gas_price, + )?; + + let fee = Fee::new(rng, gas_limit, gas_price, refund); + + let call_data = rkyv::to_bytes(&call_data)?.to_vec(); + let call = (contract_id, call_name, call_data); + + let utx = new_unproven_tx( + rng, + &self.state, + &sender, + inputs, + outputs, + fee, + None, + Some(call), + ) + .map_err(Error::from_state_err)?; + + self.prover + .compute_proof_and_propagate(&utx) + .map_err(Error::from_prover_err) + } + + /// Transfer Dusk from one key to another. + #[allow(clippy::too_many_arguments)] + pub fn transfer( + &self, + rng: &mut Rng, + sender_index: u64, + refund: &PublicKey, + receiver: &PublicKey, + value: u64, + gas_limit: u64, + gas_price: u64, + ref_id: BlsScalar, + ) -> Result> { + let sender = self + .store + .retrieve_sk(sender_index) + .map_err(Error::from_store_err)?; + + let (inputs, mut outputs) = self.inputs_and_change_output( + rng, + &sender, + refund, + value + gas_limit * gas_price, + )?; + + let (output_note, output_blinder) = + generate_obfuscated_note(rng, receiver, value, ref_id); + + outputs.push((output_note, value, output_blinder)); + + let crossover = None; + let fee = Fee::new(rng, gas_limit, gas_price, refund); + + let utx = new_unproven_tx( + rng, + &self.state, + &sender, + inputs, + outputs, + fee, + crossover, + None, + ) + .map_err(Error::from_state_err)?; + + self.prover + .compute_proof_and_propagate(&utx) + .map_err(Error::from_prover_err) + } + + /// Stakes an amount of Dusk. + #[allow(clippy::too_many_arguments)] + pub fn stake( + &self, + rng: &mut Rng, + sender_index: u64, + staker_index: u64, + refund: &PublicKey, + value: u64, + gas_limit: u64, + gas_price: u64, + ) -> Result> { + let sender = self + .store + .retrieve_sk(sender_index) + .map_err(Error::from_store_err)?; + + let stake_sk = self + .store + .retrieve_stake_sk(staker_index) + .map_err(Error::from_store_err)?; + let stake_pk = StakePublicKey::from(&stake_sk); + + let (inputs, outputs) = self.inputs_and_change_output( + rng, + &sender, + refund, + value + gas_limit * gas_price, + )?; + + let stake = self + .state + .fetch_stake(&stake_pk) + .map_err(Error::from_state_err)?; + if stake.amount.is_some() { + return Err(Error::AlreadyStaked { + key: stake_pk, + stake, + }); + } + + let blinder = JubJubScalar::random(&mut *rng); + let note = Note::obfuscated(rng, refund, value, blinder); + let (mut fee, crossover) = note + .try_into() + .expect("Obfuscated notes should always yield crossovers"); + + 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 contract_id = rusk_abi::contract_to_scalar(&contract_id); + + let stct_message = + stct_signature_message(&crossover, value, contract_id); + let stct_message = dusk_poseidon::sponge::hash(&stct_message); + + let nsk = sender.sk_r(fee.stealth_address()); + + let stct_signature = nsk.sign(rng, stct_message); + + let spend_proof = self + .prover + .request_stct_proof( + &fee, + &crossover, + value, + blinder, + address, + stct_signature, + ) + .map_err(Error::from_prover_err)? + .to_bytes() + .to_vec(); + + let msg = Stake::signature_message(stake.counter, value); + let stake_sig = stake_sk.sign(&stake_pk, &msg); + + let stake = Stake { + public_key: stake_pk, + signature: stake_sig, + value, + proof: spend_proof, + }; + + let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&stake)?.to_vec(); + let call = + (rusk_abi::STAKE_CONTRACT, String::from(TX_STAKE), call_data); + + let utx = new_unproven_tx( + rng, + &self.state, + &sender, + inputs, + outputs, + fee, + Some((crossover, value, blinder)), + Some(call), + ) + .map_err(Error::from_state_err)?; + + self.prover + .compute_proof_and_propagate(&utx) + .map_err(Error::from_prover_err) + } + + /// Unstake a key from the stake contract. + pub fn unstake( + &self, + rng: &mut Rng, + sender_index: u64, + staker_index: u64, + refund: &PublicKey, + gas_limit: u64, + gas_price: u64, + ) -> Result> { + let sender = self + .store + .retrieve_sk(sender_index) + .map_err(Error::from_store_err)?; + + let stake_sk = self + .store + .retrieve_stake_sk(staker_index) + .map_err(Error::from_store_err)?; + let stake_pk = StakePublicKey::from(&stake_sk); + + let (inputs, outputs) = self.inputs_and_change_output( + rng, + &sender, + refund, + gas_limit * gas_price, + )?; + + let stake = self + .state + .fetch_stake(&stake_pk) + .map_err(Error::from_state_err)?; + let (value, _) = stake.amount.ok_or(Error::NotStaked { + key: stake_pk, + stake, + })?; + + let blinder = JubJubScalar::random(&mut *rng); + + // Since we're not transferring value *to* the contract the crossover + // shouldn't contain a value. As such the note used to create it should + // be valueless as well. + let note = Note::obfuscated(rng, refund, 0, blinder); + let (mut fee, crossover) = note + .try_into() + .expect("Obfuscated notes should always yield crossovers"); + + fee.gas_limit = gas_limit; + fee.gas_price = gas_price; + + let unstake_note = + Note::transparent(rng, &PublicKey::from(&sender), value); + let unstake_blinder = unstake_note + .blinding_factor(None) + .expect("Note is transparent so blinding factor is unencrypted"); + + let unstake_proof = self + .prover + .request_wfct_proof( + unstake_note.value_commitment().into(), + value, + unstake_blinder, + ) + .map_err(Error::from_prover_err)? + .to_bytes() + .to_vec(); + + let unstake_note = unstake_note.to_bytes(); + let signature_message = + Unstake::signature_message(stake.counter, unstake_note); + + let stake_sig = stake_sk.sign(&stake_pk, &signature_message); + + let unstake = Unstake { + public_key: stake_pk, + signature: stake_sig, + note: unstake_note.to_vec(), + proof: unstake_proof, + }; + + let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&unstake)?.to_vec(); + let call = ( + rusk_abi::STAKE_CONTRACT, + String::from(TX_UNSTAKE), + call_data, + ); + + let utx = new_unproven_tx( + rng, + &self.state, + &sender, + inputs, + outputs, + fee, + Some((crossover, 0, blinder)), + Some(call), + ) + .map_err(Error::from_state_err)?; + + self.prover + .compute_proof_and_propagate(&utx) + .map_err(Error::from_prover_err) + } + + /// Withdraw the reward a key has reward if accumulated by staking and + /// taking part in operating the network. + pub fn withdraw( + &self, + rng: &mut Rng, + sender_index: u64, + staker_index: u64, + refund: &PublicKey, + gas_limit: u64, + gas_price: u64, + ) -> Result> { + let sender = self + .store + .retrieve_sk(sender_index) + .map_err(Error::from_store_err)?; + let sender_pk = PublicKey::from(sender); + + let stake_sk = self + .store + .retrieve_stake_sk(staker_index) + .map_err(Error::from_store_err)?; + let stake_pk = StakePublicKey::from(&stake_sk); + + let (inputs, outputs) = self.inputs_and_change_output( + rng, + &sender, + refund, + gas_limit * gas_price, + )?; + + let stake = self + .state + .fetch_stake(&stake_pk) + .map_err(Error::from_state_err)?; + if stake.reward == 0 { + return Err(Error::NoReward { + key: stake_pk, + stake, + }); + } + + let withdraw_r = JubJubScalar::random(&mut *rng); + let address = sender_pk.gen_stealth_address(&withdraw_r); + let nonce = BlsScalar::random(&mut *rng); + + let msg = Withdraw::signature_message(stake.counter, address, nonce); + let stake_sig = stake_sk.sign(&stake_pk, &msg); + + // 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 + // be valueless as well. + let blinder = JubJubScalar::random(&mut *rng); + let note = Note::obfuscated(rng, refund, 0, blinder); + let (mut fee, crossover) = note + .try_into() + .expect("Obfuscated notes should always yield crossovers"); + + fee.gas_limit = gas_limit; + fee.gas_price = gas_price; + + let withdraw = Withdraw { + public_key: stake_pk, + signature: stake_sig, + 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, String::from(TX_WITHDRAW), call_data); + + let utx = new_unproven_tx( + rng, + &self.state, + &sender, + inputs, + outputs, + fee, + Some((crossover, 0, blinder)), + Some(call), + ) + .map_err(Error::from_state_err)?; + + self.prover + .compute_proof_and_propagate(&utx) + .map_err(Error::from_prover_err) + } + + /// Gets the balance of a key. + pub fn get_balance( + &self, + sk_index: u64, + ) -> Result> { + let sender = self + .store + .retrieve_sk(sk_index) + .map_err(Error::from_store_err)?; + let vk = ViewKey::from(sender); + + let notes = self.unspent_notes(&sender)?; + let mut values = Vec::with_capacity(notes.len()); + + for note in notes.into_iter() { + values.push(note.value(Some(&vk))?); + } + values.sort_by(|a, b| b.cmp(a)); + + let spendable = values.iter().take(MAX_INPUT_NOTES).sum(); + let value = + spendable + values.iter().skip(MAX_INPUT_NOTES).sum::(); + + Ok(BalanceInfo { value, spendable }) + } + + /// Gets the stake and the expiration of said stake for a key. + pub fn get_stake( + &self, + sk_index: u64, + ) -> Result> { + let stake_sk = self + .store + .retrieve_stake_sk(sk_index) + .map_err(Error::from_store_err)?; + + let stake_pk = StakePublicKey::from(&stake_sk); + + let s = self + .state + .fetch_stake(&stake_pk) + .map_err(Error::from_state_err)?; + + Ok(s) + } +} + +/// Creates an unproven transaction that conforms to the transfer contract. +#[allow(clippy::too_many_arguments)] +fn new_unproven_tx( + rng: &mut Rng, + state: &SC, + sender: &SecretKey, + inputs: Vec<(Note, u64, JubJubScalar)>, + outputs: Vec<(Note, u64, JubJubScalar)>, + fee: Fee, + crossover: Option<(Crossover, u64, JubJubScalar)>, + call: Option<(ContractId, String, Vec)>, +) -> Result { + let nullifiers: Vec = inputs + .iter() + .map(|(note, _, _)| note.gen_nullifier(sender)) + .collect(); + + let mut openings = Vec::with_capacity(inputs.len()); + for (note, _, _) in &inputs { + let opening = state.fetch_opening(note)?; + openings.push(opening); + } + + let anchor = state.fetch_anchor()?; + + 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 = Hasher::digest(hash_bytes); + + let inputs: Vec = inputs + .into_iter() + .zip(openings.into_iter()) + .map(|((note, value, blinder), opening)| { + UnprovenTransactionInput::new( + rng, sender, note, value, blinder, opening, hash, + ) + }) + .collect(); + + Ok(UnprovenTransaction { + inputs, + outputs, + anchor, + fee, + crossover, + call, + }) +} + +/// Pick the notes to be used in a transaction from a vector of notes. +/// +/// The notes are picked in a way to maximize the number of notes used, while +/// minimizing the value employed. To do this we sort the notes in ascending +/// value order, and go through each combination in a lexicographic order +/// until we find the first combination whose sum is larger or equal to +/// the given value. If such a slice is not found, an empty vector is returned. +/// +/// Note: it is presupposed that the input notes contain enough balance to cover +/// the given `value`. +fn pick_notes( + value: u64, + notes_and_values: Vec<(Note, u64, JubJubScalar)>, +) -> Vec<(Note, u64, JubJubScalar)> { + let mut notes_and_values = notes_and_values; + let len = notes_and_values.len(); + + if len <= MAX_INPUT_NOTES { + return notes_and_values; + } + + notes_and_values.sort_by(|(_, aval, _), (_, bval, _)| aval.cmp(bval)); + + pick_lexicographic(notes_and_values.len(), |indices| { + indices + .iter() + .map(|index| notes_and_values[*index].1) + .sum::() + >= value + }) + .map(|indices| { + indices + .into_iter() + .map(|index| notes_and_values[index]) + .collect() + }) + .unwrap_or_default() +} + +fn pick_lexicographic bool>( + max_len: usize, + is_valid: F, +) -> Option<[usize; MAX_INPUT_NOTES]> { + let mut indices = [0; MAX_INPUT_NOTES]; + indices + .iter_mut() + .enumerate() + .for_each(|(i, index)| *index = i); + + loop { + if is_valid(&indices) { + return Some(indices); + } + + let mut i = MAX_INPUT_NOTES - 1; + + while indices[i] == i + max_len - MAX_INPUT_NOTES { + if i > 0 { + i -= 1; + } else { + break; + } + } + + indices[i] += 1; + for j in i + 1..MAX_INPUT_NOTES { + indices[j] = indices[j - 1] + 1; + } + + if indices[MAX_INPUT_NOTES - 1] == max_len { + break; + } + } + + None +} + +/// Generates an obfuscated note for the given public spend key. +fn generate_obfuscated_note( + rng: &mut Rng, + pk: &PublicKey, + value: u64, + nonce: BlsScalar, +) -> (Note, JubJubScalar) { + let r = JubJubScalar::random(&mut *rng); + let blinder = JubJubScalar::random(&mut *rng); + + ( + Note::deterministic( + NoteType::Obfuscated, + &r, + nonce, + pk, + value, + blinder, + ), + blinder, + ) +} diff --git a/test-wallet/src/lib.rs b/test-wallet/src/lib.rs new file mode 100644 index 0000000000..46be8eb258 --- /dev/null +++ b/test-wallet/src/lib.rs @@ -0,0 +1,289 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +//! The wallet specification. + +#![deny(missing_docs)] +#![deny(clippy::all)] +#![allow(clippy::result_large_err)] + +#[macro_use] +extern crate alloc; + +mod imp; + +use alloc::vec::Vec; +use bls12_381_bls::{PublicKey as StakePublicKey, SecretKey as StakeSecretKey}; +use dusk_bytes::{DeserializableSlice, Serializable, Write}; +use dusk_jubjub::{BlsScalar, JubJubAffine, JubJubScalar}; +use dusk_plonk::prelude::Proof; +use jubjub_schnorr::Signature; +use phoenix_core::{Crossover, Fee, Note, SecretKey, ViewKey}; +use poseidon_merkle::Opening as PoseidonOpening; +use rand_chacha::ChaCha12Rng; +use rand_core::SeedableRng; +use sha2::{Digest, Sha256}; + +pub use imp::*; +pub use rusk_prover::UnprovenTransaction; + +pub 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. + type Error; + + /// Retrieves the seed used to derive keys. + fn get_seed(&self) -> Result<[u8; 64], Self::Error>; + + /// Retrieves a derived secret spend key from the store. + /// + /// The provided implementation simply gets the seed and regenerates the key + /// every time with [`generate_sk`]. It may be reimplemented to + /// provide a cache for keys, or implement a different key generation + /// algorithm. + fn retrieve_sk(&self, index: u64) -> Result { + let seed = self.get_seed()?; + Ok(derive_sk(&seed, index)) + } + + /// Retrieves a derived secret key from the store. + /// + /// The provided implementation simply gets the seed and regenerates the key + /// every time with [`generate_sk`]. It may be reimplemented to + /// provide a cache for keys, or implement a different key generation + /// algorithm. + fn retrieve_stake_sk( + &self, + index: u64, + ) -> Result { + let seed = self.get_seed()?; + Ok(derive_stake_sk(&seed, index)) + } +} + +/// Generates a secret spend key from its seed and index. +/// +/// First the `seed` and then the little-endian representation of the key's +/// `index` are passed through SHA-256. A constant is then mixed in and the +/// resulting hash is then used to seed a `ChaCha12` CSPRNG, which is +/// subsequently used to generate the key. +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(b"SSK"); + + let hash = hash.finalize().into(); + let mut rng = ChaCha12Rng::from_seed(hash); + + SecretKey::random(&mut rng) +} + +/// Generates a secret key from its seed and index. +/// +/// First the `seed` and then the little-endian representation of the key's +/// `index` are passed through SHA-256. A constant is then mixed in and the +/// resulting hash is then used to seed a `ChaCha12` CSPRNG, which is +/// subsequently used to generate the key. +pub fn derive_stake_sk(seed: &[u8; 64], index: u64) -> StakeSecretKey { + let mut hash = Sha256::new(); + + hash.update(seed); + hash.update(index.to_le_bytes()); + hash.update(b"SK"); + + let hash = hash.finalize().into(); + let mut rng = ChaCha12Rng::from_seed(hash); + + StakeSecretKey::random(&mut rng) +} + +/// Types that are client of the prover. +pub trait ProverClient { + /// Error returned by the node client. + type Error; + + /// Requests that a node prove the given transaction and later propagates it + fn compute_proof_and_propagate( + &self, + utx: &UnprovenTransaction, + ) -> Result; + + /// Requests an STCT proof. + fn request_stct_proof( + &self, + fee: &Fee, + crossover: &Crossover, + value: u64, + blinder: JubJubScalar, + address: BlsScalar, + signature: Signature, + ) -> Result; + + /// Request a WFCT proof. + fn request_wfct_proof( + &self, + commitment: JubJubAffine, + value: u64, + blinder: JubJubScalar, + ) -> Result; +} + +/// Block height representation +pub type BlockHeight = u64; + +/// Tuple containing Note and Block height +pub type EnrichedNote = (Note, BlockHeight); + +/// Types that are clients of the state API. +pub trait StateClient { + /// Error returned by the node client. + type Error; + + /// Find notes for a view key. + fn fetch_notes( + &self, + vk: &ViewKey, + ) -> Result, Self::Error>; + + /// Fetch the current anchor of the state. + fn fetch_anchor(&self) -> Result; + + /// Asks the node to return the nullifiers that already exist from the given + /// nullifiers. + fn fetch_existing_nullifiers( + &self, + nullifiers: &[BlsScalar], + ) -> Result, Self::Error>; + + /// Queries the node to find the opening for a specific note. + fn fetch_opening( + &self, + note: &Note, + ) -> 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. + fn fetch_stake( + &self, + pk: &StakePublicKey, + ) -> Result; +} + +/// Information about the balance of a particular key. +#[derive(Debug, Default, Hash, Clone, Copy, PartialEq, Eq)] +pub struct BalanceInfo { + /// The total value of the balance. + pub value: u64, + /// The maximum _spendable_ value in a single transaction. This is + /// different from `value` since there is a maximum number of notes one can + /// spend. + pub spendable: u64, +} + +impl Serializable<16> for BalanceInfo { + type Error = dusk_bytes::Error; + + fn from_bytes(buf: &[u8; Self::SIZE]) -> Result + where + Self: Sized, + { + let mut reader = &buf[..]; + + let value = u64::from_reader(&mut reader)?; + let spendable = u64::from_reader(&mut reader)?; + + Ok(Self { value, spendable }) + } + + #[allow(unused_must_use)] + fn to_bytes(&self) -> [u8; Self::SIZE] { + let mut buf = [0u8; Self::SIZE]; + let mut writer = &mut buf[..]; + + writer.write(&self.value.to_bytes()); + writer.write(&self.spendable.to_bytes()); + + buf + } +} + +/// The stake of a particular key. +#[derive(Debug, Default, Hash, Clone, Copy, PartialEq, Eq)] +pub struct StakeInfo { + /// The value and eligibility of the stake, in that order. + pub amount: Option<(u64, u64)>, + /// The reward available for withdrawal. + pub reward: u64, + /// Signature counter. + 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; + + /// Deserializes in the same order as defined in [`to_bytes`]. If the + /// deserialized value is 0, then `amount` will be `None`. This means that + /// the eligibility value is left loose, and could be any number when value + /// is 0. + fn from_bytes(buf: &[u8; Self::SIZE]) -> Result + where + Self: Sized, + { + let mut reader = &buf[..]; + + let value = u64::from_reader(&mut reader)?; + let eligibility = u64::from_reader(&mut reader)?; + let reward = u64::from_reader(&mut reader)?; + let counter = u64::from_reader(&mut reader)?; + + let amount = match value > 0 { + true => Some((value, eligibility)), + false => None, + }; + + Ok(Self { + amount, + reward, + counter, + }) + } + + /// Serializes the amount and the eligibility first, and then the reward and + /// the counter. If `amount` is `None`, and since a stake of no value should + /// not be possible, the first 16 bytes are filled with zeros. + #[allow(unused_must_use)] + fn to_bytes(&self) -> [u8; Self::SIZE] { + let mut buf = [0u8; Self::SIZE]; + let mut writer = &mut buf[..]; + + let (value, eligibility) = self.amount.unwrap_or_default(); + + writer.write(&value.to_bytes()); + writer.write(&eligibility.to_bytes()); + writer.write(&self.reward.to_bytes()); + writer.write(&self.counter.to_bytes()); + + buf + } +} From 46df762ee91c85edc2ac0057ed5c8425ea2b4a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Mon, 17 Jun 2024 15:05:37 +0200 Subject: [PATCH 2/6] node-data: adjust comment referencing wallet-core --- node-data/src/ledger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-data/src/ledger.rs b/node-data/src/ledger.rs index 0093672004..934d5c60dd 100644 --- a/node-data/src/ledger.rs +++ b/node-data/src/ledger.rs @@ -494,7 +494,7 @@ pub mod faker { /// gas price. pub fn gen_dummy_tx(gas_price: u64) -> Transaction { // TODO: Replace this blob with making a valid transaction once - // dusk_wallet_core::Transaction allows this + // execution_core::Transaction allows this let fixed = "31858df61df8a7208b64540f1dafffcce0ec7613f38d6147ca4393c101d45e600200000000000000f1afb77f1551f559f1226f88b2efbbc37c811218cc50531f0a8a704c98c35a32e1b905bb45a09cbca4e003a9f9d2eb6c16adf6fa49ca8afd8dfdfca449f354700100000000000000017918bbfc50c9a07b4abf98e605085f543cca07ca802b94afe1d992995543335a2d6307d9dc46929cb072d09bc790b6214671fc55df8e8d7af1bb3ef33529d31dce2982d5fef7fd0f7e36ba3e88d119fb807a546f3e273d09380dbdf48d0c57f0513c03a5c1e98e40493acde840d384c965bfccdb26af78aec81333fe24028fc2ffffffffffffffffe2f34d13ef74e08d966ced4a75ea36af891247031f848b003755dff9e8060b01104e6237b437d401ba7ad37a2435fbcb39ad17e3308a0a80c97acbe336e68851d034589b1859998aeced6f060874daf17299ae5cba212020f6bb80701afb1f410065cd1d00000000GASPRICEd870bd7510b7d51655ce1122829ec083b7d0a5a4db2ffe16589b3eabaec51d0f7a600e3dae06a4f740336cedd2b5bbeaf50428fbee075bd29cef630bab5f86a9001004000000000000a0f630636f64629f0a4422b00d5dbccb0405c4124603b4f00b29ef1ab49a617bacda7c6ea142a7ca80b1817f072a0c8491a6966f8b648c6e552b4afaa4fc5d1867448d1e3a1a360cccb68d54f100e47ab0a98464e8e8bbcca13c057ea89bb0f1b1aedd1ed10e18b5661f2dd029ecb7060110cd784837675c5588cbc5721b9075eb9b6c6a1dacff8fd9ae5e2587994f0e8b74ecc9cd9254916dcb7518d807d452ba551a5590d3045283739f8f954d3550ae08717f717bcff71a8c87d3725135abb0e1cf62b16d3e0185d589f2c84f04b577ec154ab770719eec1b3e935736021a49dbfdbd0fc76e12d33d516e3ab187b680b5a42b6f9d8104699a0e5d440db122698bbc0e7d7907d32d73664b88a4efe650bcd2cd8e17b6e1060fb99e0ba7cea790bae9e87f920bd37bb5d95946e679150560659f037cc148780745b21a8f8f726a82ec597f8195622fda20700cc38bdeb1d64eb47a240c3c72b4e1752677e39157764231ee2667ab36ccdbfba42457f8aa78190e9f349fdb0f39419a0118585b830e1f5384814e9656b8979aa59833641c241d35c05dc76d7e364aee2450106ab83406673fc18e931b92e5ccdb9c147b867649519adf577c562d87284c4674ee2b4300dc24d0f1a6dc1181b28b14322e7102cb0bf4959f2d49c55f76b438bb3b98b1ae81660da05395dd2780c2b7a57f91dfea8ab8e0ef39c6d3aa90b6a9c796dc7a5442582066d7eb35d61e41e26ad02c5bec835f0e56f87369a55eb8aa9267638b4cffbf8595a67158f55c8dc85e4c607c114f2d51f5bbd0424906bd3a49b3979ccc1fd3266c11b4f6fee01452a215c2537200e2f96527fbfb2a2525cf4a856a3d7ad224f90e77ebd1564d83a75c34e4bfbb3a2e9112af6d5d684c693079671817d04798e47a891313e74401893d3cbed23ffcba13c62a9815a2207d3b4791a77bcd52326a8acd57918a371552bb46e486f1a6217e10ae74f5f8e4c1984c28ef8ac960abeb001c26282439eae48249cce5272acaba1be1ce770cd90c68efbda84dd3ccf2f2ce51d9b7351703d7cb6d928f27d0a662c26e54a6342d509e877b8355a50c2fb23f8f680c063a49d6ab31a2b34a17038141b04c19221df2f229ce7ca70b837ebf0c3a030527fc7037172d0ddc595e1aaba4774c1746a83dff4867b5986c5afe399c93ead34e37aaa79e48def89950118f5ab0c7d66074dd680fbc7146c977135a6aef55617befeba25f269b606a21f8880ce2c30e0ce8b898643c08871773ac1e41f62d86c24060b1072418275ded45bb819939ca45d681878ec16257902ce4d4aebc709027d77f2c8c622ce7f1a7f79a2d5efd460266a059ad242ff7b64947f44d1b9bd8d29a633b9223fb75ce14deb158c81e9a1703395531d173bdd88b8df90bee088618e12b875e42f45ff5cc7159b136e05b2426628b0dafa076e5dcc223a37cf5f0df7e75607f040101000000000000000000000000000000000000000000000000000000000000000400000000000000726f6f747aaa96c657582d963de258f184da3d7c95042920db92a2b2591c181466ea5a5311b5ef84f135b3d567120eaae941f35f8d5d62a8f42c8c9dacd2c78cc6f02e558d0d18fc4e78997967851fef237755ee6ee490036925c7fb100eefdbec1c033b8332252314a8fd0c05a01f9a8b87372381ee179818e8ffff36dc94a411c0d75e9ded4fcdf533fd58fc80c8dae586d783ed32168744d0f952dee4f02ce4e25f552327ac75c4aba87513dc54e4a5c2e2e36835828113ef92fea59dbf2457575903bf3156016acac7a44a1d6b546f4199bba7b24966c739ddc433a521ee5a776148349a0a53a6cd16d1cd20e13120bd404fd23a752a8f3facd53b78d360d3f3a03e57bf726eed5f1d8d4c530cd187919796fbb4cdc4dceb809f3839bcb453ae7022ae10a0f7ee7f4355e9d74fbf55d3221bb0beb1bf9ae1f6927e9d3cce9f24581640d55c7bf2cd4b6f130ef439a20b990223bb9022446b7e02e502619b4dd3f80d31e6ca8a4c8785453c9a4886d5e9159bcd1974030e70d1cdd008f3408845871c4cbfb0554963f5f47b91a81a0d1f7370be7c072d77a9e03489921981f503ec2f29a07829b2b4f9f285e2ff0d50378d59b12ad2fb8386001e561c3c3c5e66df31256fac5c58b056a49ffe0c23e8e583944e0252529b1c8438fbd22be0d89d5838bc023097bb353188a830dc1a280a0d36ad045d0691354f4a3cdf06724101be6bc23a3f90ad62b7eb699b215644326e61c546068bb46871cc5007e81aa117c1538a9297e803ff05b1f631d15791d5f3e179718a3a752649ef7c13f4e03f0390117a7e8542930b0d81da721a28e246a451c995bcc2f6a1961b56c6e0c030563068b32b6a97b404c884d9430e60d070354accd59c9e4e01f816c149898241f7f42e8724c0af7e5e84b28c9d830add2ab94c78dc474e06ca84d444267d37c86697615396cd4c74e95815fafd25e0f96ea952a9d2de0fbb646e83bf25147ed597df42"; let utx_bytes = hex::decode(fixed.replace( From 8769768855d73b0a03eb9080cf81e988760b3965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Mon, 17 Jun 2024 15:06:43 +0200 Subject: [PATCH 3/6] rusk-prover: add `UnprovenTransaction` struct This required adding in an `std` feature to properly control when the type is exposed, and when certain implementations of traits and functions are available. --- rusk-prover/Cargo.toml | 18 +- rusk-prover/src/errors.rs | 12 +- rusk-prover/src/lib.rs | 10 + rusk-prover/src/prover/execute.rs | 6 +- rusk-prover/src/tx.rs | 466 ++++++++++++++++++++++++++++++ 5 files changed, 502 insertions(+), 10 deletions(-) create mode 100644 rusk-prover/src/tx.rs diff --git a/rusk-prover/Cargo.toml b/rusk-prover/Cargo.toml index 1c7b8ff6b1..31a6acf0eb 100644 --- a/rusk-prover/Cargo.toml +++ b/rusk-prover/Cargo.toml @@ -4,14 +4,22 @@ version = "0.3.0" edition = "2021" autobins = false - [dependencies] dusk-bytes = { version = "0.1" } -dusk-wallet-core = { version = "0.25.0-phoenix.0.26", default-features = false } +phoenix-core = { version = "0.26", default-features = false, features = ["alloc", "rkyv-impl"] } +dusk-jubjub = { version = "0.14", default-features = false, features = ["rkyv-impl"] } +dusk-plonk = { version = "0.19", default-features = false } +jubjub-schnorr = { version = "0.2", default-features = false, features = ["double","rkyv-impl"] } +poseidon-merkle = { version = "0.5", features = ["rkyv-impl"] } +rand_core = "0.6" + +rkyv = { version = "0.7", default-features = false, features = ["size_32"] } +bytecheck = { version = "0.6", default-features = false } + +rusk-abi = { version = "0.13.0-rc", path = "../rusk-abi", default-features = false } ## feature local_prover once_cell = { version = "1.9", optional = true } -dusk-plonk = { version = "0.19", optional = true } rand = { version = "0.8", optional = true } rusk-profile = { version = "0.6", path = "../rusk-profile", optional = true } transfer-circuits = { version = "0.5", path = "../circuits/transfer", optional = true } @@ -25,10 +33,12 @@ tokio = { version = "1.17.0", features = ["full"] } default = ["local_prover"] local_prover = [ "once_cell", - "dusk-plonk", + "dusk-plonk/std", "rand", "rusk-profile", "transfer-circuits", "execution-core", + "std", ] no_random = [] +std = [] diff --git a/rusk-prover/src/errors.rs b/rusk-prover/src/errors.rs index 1a4958c565..1acd025ea5 100644 --- a/rusk-prover/src/errors.rs +++ b/rusk-prover/src/errors.rs @@ -4,8 +4,8 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use std::error; -use std::fmt; +use alloc::string::String; +use core::fmt; #[derive(Debug)] pub enum ProverError { @@ -15,10 +15,13 @@ pub enum ProverError { }, Other(String), } + impl ProverError { pub fn invalid_data(field: &'static str, inner: dusk_bytes::Error) -> Self { Self::InvalidData { field, inner } } + + #[cfg(feature = "std")] pub fn with_context( context: &'static str, err: E, @@ -27,8 +30,9 @@ impl ProverError { } } -impl error::Error for ProverError { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { +#[cfg(feature = "std")] +impl std::error::Error for ProverError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } diff --git a/rusk-prover/src/lib.rs b/rusk-prover/src/lib.rs index 9559cbecb1..f62d5f0195 100644 --- a/rusk-prover/src/lib.rs +++ b/rusk-prover/src/lib.rs @@ -4,7 +4,16 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + +use alloc::vec::Vec; + mod errors; +mod tx; #[cfg(feature = "local_prover")] pub mod prover; @@ -12,6 +21,7 @@ pub mod prover; pub use crate::prover::LocalProver; pub use errors::ProverError; +pub use tx::{UnprovenTransaction, UnprovenTransactionInput}; pub type ProverResult = Result, ProverError>; diff --git a/rusk-prover/src/prover/execute.rs b/rusk-prover/src/prover/execute.rs index e42eacb8c4..6dd542c68c 100644 --- a/rusk-prover/src/prover/execute.rs +++ b/rusk-prover/src/prover/execute.rs @@ -5,8 +5,7 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use super::*; -use crate::prover::fetch_prover; -use dusk_wallet_core::UnprovenTransaction; + use execution_core::transfer::TRANSFER_TREE_DEPTH; use rand::{CryptoRng, RngCore}; use transfer_circuits::{ @@ -14,6 +13,9 @@ use transfer_circuits::{ ExecuteCircuitTwoTwo, }; +use crate::prover::fetch_prover; +use crate::UnprovenTransaction; + pub static EXEC_1_2_PROVER: Lazy = Lazy::new(|| fetch_prover("ExecuteCircuitOneTwo")); diff --git a/rusk-prover/src/tx.rs b/rusk-prover/src/tx.rs new file mode 100644 index 0000000000..ef05bd51e7 --- /dev/null +++ b/rusk-prover/src/tx.rs @@ -0,0 +1,466 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use alloc::string::String; +use alloc::vec; +use alloc::vec::Vec; + +use dusk_bytes::{ + DeserializableSlice, Error as BytesError, Serializable, Write, +}; +use dusk_jubjub::{BlsScalar, JubJubAffine, JubJubExtended, JubJubScalar}; +use dusk_plonk::prelude::Proof; +use jubjub_schnorr::SignatureDouble; +use phoenix_core::transaction::Transaction; +use phoenix_core::{Crossover, Fee, Note, Ownable, SecretKey}; +use poseidon_merkle::Opening as PoseidonOpening; +use rand_core::{CryptoRng, RngCore}; +use rusk_abi::hash::Hasher; +use rusk_abi::{ContractId, CONTRACT_ID_BYTES, POSEIDON_TREE_DEPTH}; + +/// An input to a transaction that is yet to be proven. +#[derive(Debug, Clone)] +pub struct UnprovenTransactionInput { + pub nullifier: BlsScalar, + pub opening: PoseidonOpening<(), POSEIDON_TREE_DEPTH, 4>, + pub note: Note, + pub value: u64, + pub blinder: JubJubScalar, + pub npk_prime: JubJubExtended, + pub sig: SignatureDouble, +} + +impl UnprovenTransactionInput { + pub fn new( + rng: &mut Rng, + sk: &SecretKey, + note: Note, + value: u64, + blinder: JubJubScalar, + opening: PoseidonOpening<(), POSEIDON_TREE_DEPTH, 4>, + tx_hash: BlsScalar, + ) -> Self { + let nullifier = note.gen_nullifier(sk); + let nsk = sk.sk_r(note.stealth_address()); + let sig = nsk.sign_double(rng, tx_hash); + + let npk_prime = dusk_jubjub::GENERATOR_NUMS_EXTENDED * nsk.as_ref(); + + Self { + note, + value, + blinder, + sig, + nullifier, + opening, + npk_prime, + } + } + + /// Serialize the input to a variable size byte buffer. + pub fn to_var_bytes(&self) -> Vec { + let affine_npk_p = JubJubAffine::from(&self.npk_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 + + JubJubAffine::SIZE + + SignatureDouble::SIZE + + u64::SIZE + + JubJubScalar::SIZE + + opening_bytes.len(), + ); + + bytes.extend_from_slice(&self.nullifier.to_bytes()); + bytes.extend_from_slice(&self.note.to_bytes()); + bytes.extend_from_slice(&self.value.to_bytes()); + bytes.extend_from_slice(&self.blinder.to_bytes()); + bytes.extend_from_slice(&affine_npk_p.to_bytes()); + bytes.extend_from_slice(&self.sig.to_bytes()); + bytes.extend(opening_bytes); + + bytes + } + + /// Deserializes the the input from bytes. + pub fn from_slice(buf: &[u8]) -> Result { + let mut bytes = buf; + + let nullifier = BlsScalar::from_reader(&mut bytes)?; + let note = Note::from_reader(&mut bytes)?; + let value = u64::from_reader(&mut bytes)?; + let blinder = JubJubScalar::from_reader(&mut bytes)?; + let npk_prime = + JubJubExtended::from(JubJubAffine::from_reader(&mut bytes)?); + let sig = SignatureDouble::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, + value, + blinder, + sig, + nullifier, + opening, + npk_prime, + }) + } + + /// Returns the nullifier of the input. + pub fn nullifier(&self) -> BlsScalar { + self.nullifier + } + + /// Returns the opening of the input. + pub fn opening(&self) -> &PoseidonOpening<(), POSEIDON_TREE_DEPTH, 4> { + &self.opening + } + + /// Returns the note of the input. + pub fn note(&self) -> &Note { + &self.note + } + + /// Returns the value of the input. + pub fn value(&self) -> u64 { + self.value + } + + /// Returns the blinding factor of the input. + pub fn blinding_factor(&self) -> JubJubScalar { + self.blinder + } + + /// Returns the input's note public key prime. + pub fn note_pk_prime(&self) -> JubJubExtended { + self.npk_prime + } + + /// Returns the input's signature. + pub fn signature(&self) -> &SignatureDouble { + &self.sig + } +} + +/// 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)] +pub struct UnprovenTransaction { + pub inputs: Vec, + pub outputs: Vec<(Note, u64, JubJubScalar)>, + pub anchor: BlsScalar, + pub fee: Fee, + pub crossover: Option<(Crossover, u64, JubJubScalar)>, + pub call: Option<(ContractId, String, Vec)>, +} + +impl UnprovenTransaction { + /// Consumes self and a proof to generate a transaction. + pub fn prove(self, proof: Proof) -> Transaction { + 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. + #[allow(unused_must_use)] + pub fn to_var_bytes(&self) -> Vec { + let serialized_inputs: Vec> = self + .inputs + .iter() + .map(UnprovenTransactionInput::to_var_bytes) + .collect(); + let num_inputs = self.inputs.len(); + let total_input_len = serialized_inputs + .iter() + .fold(0, |len, input| len + input.len()); + + let serialized_outputs: Vec< + [u8; Note::SIZE + u64::SIZE + JubJubScalar::SIZE], + > = self + .outputs + .iter() + .map(|(note, value, blinder)| { + let mut buf = [0; Note::SIZE + u64::SIZE + JubJubScalar::SIZE]; + + buf[..Note::SIZE].copy_from_slice(¬e.to_bytes()); + buf[Note::SIZE..Note::SIZE + u64::SIZE] + .copy_from_slice(&value.to_bytes()); + buf[Note::SIZE + u64::SIZE + ..Note::SIZE + u64::SIZE + JubJubScalar::SIZE] + .copy_from_slice(&blinder.to_bytes()); + + buf + }) + .collect(); + let num_outputs = self.outputs.len(); + let total_output_len = serialized_outputs + .iter() + .fold(0, |len, output| len + output.len()); + + let size = u64::SIZE + + num_inputs * u64::SIZE + + total_input_len + + u64::SIZE + + total_output_len + + BlsScalar::SIZE + + Fee::SIZE + + u64::SIZE + + self.crossover.map_or(0, |_| { + Crossover::SIZE + u64::SIZE + JubJubScalar::SIZE + }) + + u64::SIZE + + self + .call + .as_ref() + .map(|(_, cname, cdata)| { + CONTRACT_ID_BYTES + u64::SIZE + cname.len() + cdata.len() + }) + .unwrap_or(0); + + let mut buf = vec![0; size]; + let mut writer = &mut buf[..]; + + writer.write(&(num_inputs as u64).to_bytes()); + for sinput in serialized_inputs { + writer.write(&(sinput.len() as u64).to_bytes()); + writer.write(&sinput); + } + + writer.write(&(num_outputs as u64).to_bytes()); + for soutput in serialized_outputs { + writer.write(&soutput); + } + + writer.write(&self.anchor.to_bytes()); + writer.write(&self.fee.to_bytes()); + + write_crossover_value_blinder(&mut writer, self.crossover); + write_optional_call(&mut writer, &self.call); + + buf + } + + /// Deserialize 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)?; + let mut inputs = Vec::with_capacity(num_inputs as usize); + for _ in 0..num_inputs { + let size = u64::from_reader(&mut buffer)? as usize; + inputs.push(UnprovenTransactionInput::from_slice(&buffer[..size])?); + buffer = &buffer[size..]; + } + + let num_outputs = u64::from_reader(&mut buffer)?; + let mut outputs = Vec::with_capacity(num_outputs as usize); + for _ in 0..num_outputs { + let note = Note::from_reader(&mut buffer)?; + let value = u64::from_reader(&mut buffer)?; + let blinder = JubJubScalar::from_reader(&mut buffer)?; + + outputs.push((note, value, blinder)); + } + + let anchor = BlsScalar::from_reader(&mut buffer)?; + let fee = Fee::from_reader(&mut buffer)?; + + let crossover = read_crossover_value_blinder(&mut buffer)?; + + let call = read_optional_call(&mut buffer)?; + + Ok(Self { + inputs, + outputs, + anchor, + fee, + crossover, + call, + }) + } + + /// Returns the hash of the transaction. + pub fn hash(&self) -> BlsScalar { + 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. + pub fn inputs(&self) -> &[UnprovenTransactionInput] { + &self.inputs + } + + /// Returns the outputs of the transaction. + pub fn outputs(&self) -> &[(Note, u64, JubJubScalar)] { + &self.outputs + } + + /// Returns the anchor of the transaction. + pub fn anchor(&self) -> BlsScalar { + self.anchor + } + + /// Returns the fee of the transaction. + pub fn fee(&self) -> &Fee { + &self.fee + } + + /// Returns the crossover of the transaction. + pub fn crossover(&self) -> Option<&(Crossover, u64, JubJubScalar)> { + self.crossover.as_ref() + } + + /// Returns the call of the transaction. + pub fn call(&self) -> Option<&(ContractId, String, Vec)> { + self.call.as_ref() + } +} + +/// Writes an optional call into the writer, prepending it with a `u64` denoting +/// if it is present or not. This should be called at the end of writing other +/// fields since it doesn't write any information about the length of the call +/// data. +fn write_optional_call( + writer: &mut W, + call: &Option<(ContractId, String, Vec)>, +) -> Result<(), BytesError> { + match call { + 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 => { + writer.write(&0_u64.to_bytes())?; + } + }; + + Ok(()) +} + +/// 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_BYTES { + return Err(BytesError::BadLength { + found: buf_len, + expected: CONTRACT_ID_BYTES, + }); + } + let (mid_buffer, mut buffer_left) = { + let (buf, left) = buffer.split_at(CONTRACT_ID_BYTES); + + let mut mid_buf = [0u8; CONTRACT_ID_BYTES]; + mid_buf.copy_from_slice(buf); + + (mid_buf, left) + }; + + let contract_id = ContractId::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((contract_id, cname, call_data)); + } + + Ok(call) +} + +fn write_crossover_value_blinder( + writer: &mut W, + crossover: Option<(Crossover, u64, JubJubScalar)>, +) -> Result<(), BytesError> { + match crossover { + Some((crossover, value, blinder)) => { + writer.write(&1_u64.to_bytes())?; + writer.write(&crossover.to_bytes())?; + writer.write(&value.to_bytes())?; + writer.write(&blinder.to_bytes())?; + } + None => { + writer.write(&0_u64.to_bytes())?; + } + } + + Ok(()) +} + +/// Reads an optional crossover from the given buffer. +fn read_crossover_value_blinder( + buffer: &mut &[u8], +) -> Result, BytesError> { + let ser = match u64::from_reader(buffer)? { + 0 => None, + _ => { + let crossover = Crossover::from_reader(buffer)?; + let value = u64::from_reader(buffer)?; + let blinder = JubJubScalar::from_reader(buffer)?; + Some((crossover, value, blinder)) + } + }; + + Ok(ser) +} From 2edde2174e2b46ce3f5182e3ef4818a1d91dcac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Mon, 17 Jun 2024 15:08:16 +0200 Subject: [PATCH 4/6] rusk: remove `wallet-core` as dev-dep in favor of `test-wallet` This will allow us to make atomic changes in `rusk` where previously we had to modify `dusk-wallet-core` for any significant change in protocol. Resolves: #1853 --- rusk/Cargo.toml | 2 +- rusk/src/lib/verifier.rs | 2 +- rusk/tests/common/state.rs | 3 +-- rusk/tests/common/wallet.rs | 8 ++++---- rusk/tests/rusk-state.rs | 2 +- rusk/tests/services/contract_pays.rs | 2 +- rusk/tests/services/gas_behavior.rs | 2 +- rusk/tests/services/multi_transfer.rs | 2 +- rusk/tests/services/stake.rs | 2 +- rusk/tests/services/transfer.rs | 2 +- rusk/tests/services/unspendable.rs | 2 +- 11 files changed, 14 insertions(+), 15 deletions(-) diff --git a/rusk/Cargo.toml b/rusk/Cargo.toml index 068e02dba0..cd5afccca3 100644 --- a/rusk/Cargo.toml +++ b/rusk/Cargo.toml @@ -45,7 +45,6 @@ sha3 = "0.10" dusk-plonk = "0.19" dusk-bytes = "0.1" kadcast = "0.6.0-rc" -dusk-wallet-core = "0.25.0-phoenix.0.26" pin-project = "1" tungstenite = "0.21" hyper-tungstenite = "0.13" @@ -83,6 +82,7 @@ rusk-recovery = { version = "0.6", path = "../rusk-recovery", optional = true } futures = { version = "0.3", optional = true } [dev-dependencies] +test-wallet = { version = "0.1.0", path = "../test-wallet" } test-context = "0.1" reqwest = "0.12" rusk-recovery = { version = "0.6", path = "../rusk-recovery", features = ["state"] } diff --git a/rusk/src/lib/verifier.rs b/rusk/src/lib/verifier.rs index 4ff57bcc9d..324c24c086 100644 --- a/rusk/src/lib/verifier.rs +++ b/rusk/src/lib/verifier.rs @@ -9,7 +9,7 @@ use crate::error::Error; use crate::Result; -use dusk_wallet_core::Transaction; +use execution_core::Transaction; use rusk_profile::Circuit as CircuitProfile; use transfer_circuits::CircuitOutput; diff --git a/rusk/tests/common/state.rs b/rusk/tests/common/state.rs index 36a0c59940..b88d5dcbda 100644 --- a/rusk/tests/common/state.rs +++ b/rusk/tests/common/state.rs @@ -13,8 +13,7 @@ use rusk::{Result, Rusk}; use rusk_recovery_tools::state::{self, Snapshot}; use dusk_consensus::operations::CallParams; -use dusk_wallet_core::Transaction as PhoenixTransaction; -use execution_core::StakePublicKey; +use execution_core::{StakePublicKey, Transaction as PhoenixTransaction}; use node_data::{ bls::PublicKeyBytes, ledger::{Attestation, Block, Header, IterationsInfo, SpentTransaction}, diff --git a/rusk/tests/common/wallet.rs b/rusk/tests/common/wallet.rs index ba8cf4f2fd..9a2fb21bf2 100644 --- a/rusk/tests/common/wallet.rs +++ b/rusk/tests/common/wallet.rs @@ -13,10 +13,6 @@ use crate::common::block::Block as BlockAwait; use dusk_bytes::{DeserializableSlice, Serializable}; use dusk_plonk::prelude::Proof; -use dusk_wallet_core::{ - self as wallet, StakeInfo, Store, Transaction as PhoenixTransaction, - UnprovenTransaction, -}; use execution_core::transfer::TRANSFER_TREE_DEPTH; use execution_core::{ BlsPublicKey, BlsScalar, Crossover, Fee, JubJubAffine, JubJubScalar, Note, @@ -27,6 +23,10 @@ use poseidon_merkle::Opening as PoseidonOpening; use rusk::{Error, Result, Rusk}; use rusk_prover::prover::{A, STCT_INPUT_LEN, WFCT_INPUT_LEN}; use rusk_prover::{LocalProver, Prover}; +use test_wallet::{ + self as wallet, StakeInfo, Store, Transaction as PhoenixTransaction, + UnprovenTransaction, +}; use tracing::info; #[derive(Debug, Clone)] diff --git a/rusk/tests/rusk-state.rs b/rusk/tests/rusk-state.rs index b0372b0161..e903d1bc7f 100644 --- a/rusk/tests/rusk-state.rs +++ b/rusk/tests/rusk-state.rs @@ -13,7 +13,6 @@ use std::collections::HashMap; use std::path::Path; use std::sync::{mpsc, Arc, RwLock}; -use dusk_wallet_core::{self as wallet}; use execution_core::{ transfer::TreeLeaf, BlsScalar, Note, PublicKey, SecretKey, }; @@ -26,6 +25,7 @@ use rusk::Result; use rusk_abi::dusk::LUX; use rusk_abi::{TRANSFER_CONTRACT, VM}; use tempfile::tempdir; +use test_wallet::{self as wallet}; use tokio::task; use tracing::info; diff --git a/rusk/tests/services/contract_pays.rs b/rusk/tests/services/contract_pays.rs index 5991782898..4c70a09c51 100644 --- a/rusk/tests/services/contract_pays.rs +++ b/rusk/tests/services/contract_pays.rs @@ -9,13 +9,13 @@ use std::collections::HashMap; use std::path::Path; use std::sync::{Arc, RwLock}; -use dusk_wallet_core::{self as wallet}; use rand::prelude::*; use rand::rngs::StdRng; use rusk::{Result, Rusk}; use rusk_abi::{ContractData, ContractId, EconomicMode, TRANSFER_CONTRACT}; use rusk_recovery_tools::state; use tempfile::tempdir; +use test_wallet::{self as wallet}; use tokio::sync::broadcast; use tracing::info; diff --git a/rusk/tests/services/gas_behavior.rs b/rusk/tests/services/gas_behavior.rs index 21f870c781..ab3f2bea02 100644 --- a/rusk/tests/services/gas_behavior.rs +++ b/rusk/tests/services/gas_behavior.rs @@ -8,12 +8,12 @@ use std::collections::HashMap; use std::path::Path; use std::sync::{Arc, RwLock}; -use dusk_wallet_core::{self as wallet}; use rand::prelude::*; use rand::rngs::StdRng; use rusk::{Result, Rusk}; use rusk_abi::TRANSFER_CONTRACT; use tempfile::tempdir; +use test_wallet::{self as wallet}; use tracing::info; use crate::common::logger; diff --git a/rusk/tests/services/multi_transfer.rs b/rusk/tests/services/multi_transfer.rs index 1569597fc3..335429ccbf 100644 --- a/rusk/tests/services/multi_transfer.rs +++ b/rusk/tests/services/multi_transfer.rs @@ -8,13 +8,13 @@ use std::collections::HashMap; use std::path::Path; use std::sync::{Arc, LazyLock, RwLock}; -use dusk_wallet_core::{self as wallet, Store}; use execution_core::{BlsScalar, PublicKey, SecretKey}; use ff::Field; use rand::prelude::*; use rand::rngs::StdRng; use rusk::{Result, Rusk}; use tempfile::tempdir; +use test_wallet::{self as wallet, Store}; use tracing::info; use crate::common::logger; diff --git a/rusk/tests/services/stake.rs b/rusk/tests/services/stake.rs index 2573e98e74..3304db19e2 100644 --- a/rusk/tests/services/stake.rs +++ b/rusk/tests/services/stake.rs @@ -7,7 +7,6 @@ use std::path::Path; use std::sync::{Arc, LazyLock, RwLock}; -use dusk_wallet_core::{self as wallet, Store}; use execution_core::{PublicKey, SecretKey, StakePublicKey}; use rand::prelude::*; use rand::rngs::StdRng; @@ -17,6 +16,7 @@ use rusk_abi::dusk::dusk; use rusk_abi::STAKE_CONTRACT; use std::collections::HashMap; use tempfile::tempdir; +use test_wallet::{self as wallet, Store}; use tracing::info; use crate::common::state::{generator_procedure, new_state}; diff --git a/rusk/tests/services/transfer.rs b/rusk/tests/services/transfer.rs index f1a69b9207..1046c40b36 100644 --- a/rusk/tests/services/transfer.rs +++ b/rusk/tests/services/transfer.rs @@ -8,7 +8,6 @@ use std::collections::HashMap; use std::path::Path; use std::sync::{Arc, LazyLock, RwLock}; -use dusk_wallet_core::{self as wallet, Store}; use execution_core::{BlsScalar, PublicKey, SecretKey}; use ff::Field; use node_data::ledger::SpentTransaction; @@ -16,6 +15,7 @@ use rand::prelude::*; use rand::rngs::StdRng; use rusk::{Result, Rusk}; use tempfile::tempdir; +use test_wallet::{self as wallet, Store}; use tracing::info; use crate::common::logger; diff --git a/rusk/tests/services/unspendable.rs b/rusk/tests/services/unspendable.rs index 2af911bc9e..494ca9ec1e 100644 --- a/rusk/tests/services/unspendable.rs +++ b/rusk/tests/services/unspendable.rs @@ -8,12 +8,12 @@ use std::collections::HashMap; use std::path::Path; use std::sync::{Arc, RwLock}; -use dusk_wallet_core::{self as wallet}; use rand::prelude::*; use rand::rngs::StdRng; use rusk::{Result, Rusk}; use rusk_abi::TRANSFER_CONTRACT; use tempfile::tempdir; +use test_wallet::{self as wallet}; use tracing::info; use crate::common::logger; From fc5e9e7400e09f13bf5001d72f0b54c1bc4c953e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Mon, 17 Jun 2024 15:50:34 +0200 Subject: [PATCH 5/6] execution-core: re-export `SchnorrSignatureDouble` --- execution-core/Cargo.toml | 2 +- execution-core/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/execution-core/Cargo.toml b/execution-core/Cargo.toml index 5d0846c45f..7cd838f1aa 100644 --- a/execution-core/Cargo.toml +++ b/execution-core/Cargo.toml @@ -8,7 +8,7 @@ dusk-bls12_381 = { version = "0.13", default-features = false, features = ["rkyv dusk-jubjub = { version = "0.14", default-features = false, features = ["rkyv-impl"] } dusk-poseidon = { version = "0.33", default-features = false, features = ["rkyv-impl", "alloc"] } bls12_381-bls = { version = "0.2", default-features = false, features = ["rkyv-impl"] } -jubjub-schnorr = { version = "0.2", default-features = false, features = ["rkyv-impl"] } +jubjub-schnorr = { version = "0.2", default-features = false, features = ["rkyv-impl", "double"] } phoenix-core = { version = "0.26", default-features = false, features = ["rkyv-impl", "alloc"] } dusk-bytes = "0.1" rkyv = { version = "0.7", default-features = false, features = ["size_32"] } diff --git a/execution-core/src/lib.rs b/execution-core/src/lib.rs index b73b1ab4e7..0bd4821f4f 100644 --- a/execution-core/src/lib.rs +++ b/execution-core/src/lib.rs @@ -40,7 +40,7 @@ pub type StakeAggPublicKey = BlsAggPublicKey; pub use jubjub_schnorr::{ PublicKey as SchnorrPublicKey, SecretKey as SchnorrSecretKey, - Signature as SchnorrSignature, + Signature as SchnorrSignature, SignatureDouble as SchnorrSignatureDouble, }; /// Secret key associated with a note. pub type NoteSecretKey = SchnorrSecretKey; From fe897a353d56d24980f69d10225e1f733abb68cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Mon, 17 Jun 2024 15:51:17 +0200 Subject: [PATCH 6/6] rusk-prover: minimize dependency surface --- rusk-prover/Cargo.toml | 6 +----- rusk-prover/src/tx.rs | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/rusk-prover/Cargo.toml b/rusk-prover/Cargo.toml index 31a6acf0eb..b2499dcf5e 100644 --- a/rusk-prover/Cargo.toml +++ b/rusk-prover/Cargo.toml @@ -6,10 +6,7 @@ autobins = false [dependencies] dusk-bytes = { version = "0.1" } -phoenix-core = { version = "0.26", default-features = false, features = ["alloc", "rkyv-impl"] } -dusk-jubjub = { version = "0.14", default-features = false, features = ["rkyv-impl"] } dusk-plonk = { version = "0.19", default-features = false } -jubjub-schnorr = { version = "0.2", default-features = false, features = ["double","rkyv-impl"] } poseidon-merkle = { version = "0.5", features = ["rkyv-impl"] } rand_core = "0.6" @@ -17,13 +14,13 @@ rkyv = { version = "0.7", default-features = false, features = ["size_32"] } bytecheck = { version = "0.6", default-features = false } rusk-abi = { version = "0.13.0-rc", path = "../rusk-abi", default-features = false } +execution-core = { version = "0.1.0", path = "../execution-core" } ## feature local_prover once_cell = { version = "1.9", optional = true } rand = { version = "0.8", optional = true } rusk-profile = { version = "0.6", path = "../rusk-profile", optional = true } transfer-circuits = { version = "0.5", path = "../circuits/transfer", optional = true } -execution-core = { version = "0.1.0", path = "../execution-core", optional = true } [dev-dependencies] hex = "0.4" @@ -37,7 +34,6 @@ local_prover = [ "rand", "rusk-profile", "transfer-circuits", - "execution-core", "std", ] no_random = [] diff --git a/rusk-prover/src/tx.rs b/rusk-prover/src/tx.rs index ef05bd51e7..012575f27f 100644 --- a/rusk-prover/src/tx.rs +++ b/rusk-prover/src/tx.rs @@ -11,11 +11,13 @@ use alloc::vec::Vec; use dusk_bytes::{ DeserializableSlice, Error as BytesError, Serializable, Write, }; -use dusk_jubjub::{BlsScalar, JubJubAffine, JubJubExtended, JubJubScalar}; use dusk_plonk::prelude::Proof; -use jubjub_schnorr::SignatureDouble; -use phoenix_core::transaction::Transaction; -use phoenix_core::{Crossover, Fee, Note, Ownable, SecretKey}; +use execution_core::{ + BlsScalar, Crossover, Fee, JubJubAffine, JubJubExtended, JubJubScalar, + Note, Ownable, SchnorrSignatureDouble, SecretKey, Transaction, + GENERATOR_NUMS_EXTENDED, +}; + use poseidon_merkle::Opening as PoseidonOpening; use rand_core::{CryptoRng, RngCore}; use rusk_abi::hash::Hasher; @@ -30,7 +32,7 @@ pub struct UnprovenTransactionInput { pub value: u64, pub blinder: JubJubScalar, pub npk_prime: JubJubExtended, - pub sig: SignatureDouble, + pub sig: SchnorrSignatureDouble, } impl UnprovenTransactionInput { @@ -47,7 +49,7 @@ impl UnprovenTransactionInput { let nsk = sk.sk_r(note.stealth_address()); let sig = nsk.sign_double(rng, tx_hash); - let npk_prime = dusk_jubjub::GENERATOR_NUMS_EXTENDED * nsk.as_ref(); + let npk_prime = GENERATOR_NUMS_EXTENDED * nsk.as_ref(); Self { note, @@ -72,7 +74,7 @@ impl UnprovenTransactionInput { BlsScalar::SIZE + Note::SIZE + JubJubAffine::SIZE - + SignatureDouble::SIZE + + SchnorrSignatureDouble::SIZE + u64::SIZE + JubJubScalar::SIZE + opening_bytes.len(), @@ -99,7 +101,7 @@ impl UnprovenTransactionInput { let blinder = JubJubScalar::from_reader(&mut bytes)?; let npk_prime = JubJubExtended::from(JubJubAffine::from_reader(&mut bytes)?); - let sig = SignatureDouble::from_reader(&mut bytes)?; + let sig = SchnorrSignatureDouble::from_reader(&mut bytes)?; // `to_vec` is required here otherwise `rkyv` will throw an alignment // error @@ -149,7 +151,7 @@ impl UnprovenTransactionInput { } /// Returns the input's signature. - pub fn signature(&self) -> &SignatureDouble { + pub fn signature(&self) -> &SchnorrSignatureDouble { &self.sig } }