From e45e0a7fcd6e3bd7f69d1b0ef93183cd0001a6cb Mon Sep 17 00:00:00 2001 From: moana Date: Fri, 23 Aug 2024 12:52:21 +0200 Subject: [PATCH] latest changes --- rusk/tests/common/wallet.rs | 1 - rusk/tests/services/stake.rs | 4 +- test-wallet/src/imp.rs | 359 +++++++++++++++++---------------- test-wallet/src/lib.rs | 91 +++++---- wallet-core/src/transaction.rs | 277 ++++++++++++++++++------- 5 files changed, 452 insertions(+), 280 deletions(-) diff --git a/rusk/tests/common/wallet.rs b/rusk/tests/common/wallet.rs index 1036d17983..4712d047aa 100644 --- a/rusk/tests/common/wallet.rs +++ b/rusk/tests/common/wallet.rs @@ -141,7 +141,6 @@ impl wallet::ProverClient for TestProverClient { type Error = Error; /// Requests that a node prove the given transaction and later propagates it fn compute_proof_and_propagate( - &self, utx: &PhoenixTransaction, ) -> Result { let circuit_bytes = &utx.proof()[..]; diff --git a/rusk/tests/services/stake.rs b/rusk/tests/services/stake.rs index faf5e6df0b..28888db522 100644 --- a/rusk/tests/services/stake.rs +++ b/rusk/tests/services/stake.rs @@ -20,7 +20,7 @@ use rand::rngs::StdRng; use rusk::{Result, Rusk}; use std::collections::HashMap; use tempfile::tempdir; -use test_wallet::{self as wallet, Store}; +use test_wallet::{self as wallet}; use tracing::info; use crate::common::state::{generator_procedure, new_state}; @@ -194,7 +194,7 @@ fn wallet_reward( ) { let mut rng = StdRng::seed_from_u64(0xdead); - let stake_sk = wallet.store().fetch_account_secret_key(2).unwrap(); + let stake_sk = wallet.account_secret_key(2).unwrap(); let stake_pk = BlsPublicKey::from(&stake_sk); let reward_calldata = (stake_pk, 6u32); diff --git a/test-wallet/src/imp.rs b/test-wallet/src/imp.rs index f7f6b404a4..1ad2d7acd2 100644 --- a/test-wallet/src/imp.rs +++ b/test-wallet/src/imp.rs @@ -4,7 +4,7 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use crate::{ProverClient, StateClient, Store}; +use crate::{StateClient, Store}; use core::convert::Infallible; @@ -12,6 +12,7 @@ use alloc::string::FromUtf8Error; use alloc::vec::Vec; use dusk_bytes::Error as BytesError; +use poseidon_merkle::Opening; use rand_core::{CryptoRng, Error as RngError, RngCore}; use rkyv::ser::serializers::{ AllocScratchError, CompositeSerializerError, SharedSerializeMapError, @@ -20,7 +21,7 @@ use rkyv::validation::validators::CheckDeserializeError; use zeroize::Zeroize; use execution_core::{ - signatures::bls::PublicKey as BlsPublicKey, + signatures::bls::{PublicKey as BlsPublicKey, SecretKey as BlsSecretKey}, stake::StakeData, transfer::{ contract_exec::ContractExec, @@ -28,17 +29,18 @@ use execution_core::{ phoenix::{ Error as PhoenixError, Note, PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey, ViewKey as PhoenixViewKey, + NOTES_TREE_DEPTH, }, Transaction, }, BlsScalar, }; use wallet_core::{ - keys::{derive_bls_sk, derive_phoenix_pk, derive_phoenix_sk}, + keys::{derive_bls_sk, derive_phoenix_sk}, phoenix_balance, transaction::{ - new_unproven_phoenix_tx, stake_call, unstake_to_phoenix, - withdraw_stake_reward_to_phoenix, + phoenix_stake, phoenix_transaction, phoenix_unstake, + phoenix_withdraw_stake_reward, ProverClient, }, BalanceInfo, }; @@ -207,18 +209,34 @@ where SC: StateClient, PC: ProverClient, { + /// Retrieve the secret key with the given index. + pub fn phoenix_secret_key( + &self, + index: u8, + ) -> Result> { + self.store + .phoenix_secret_key(index) + .map_err(Error::from_store_err) + } + /// Retrieve the public key with the given index. pub fn phoenix_public_key( &self, index: u8, ) -> Result> { - let mut seed = self.store.get_seed().map_err(Error::from_store_err)?; - - let pk = derive_phoenix_pk(&seed, index); - - seed.zeroize(); + self.store + .phoenix_public_key(index) + .map_err(Error::from_store_err) + } - Ok(pk) + /// Retrieve the account secret key with the given index. + pub fn account_secret_key( + &self, + index: u8, + ) -> Result> { + self.store + .account_secret_key(index) + .map_err(Error::from_store_err) } /// Retrieve the account public key with the given index. @@ -226,15 +244,9 @@ where &self, index: u8, ) -> Result> { - let mut seed = self.store.get_seed().map_err(Error::from_store_err)?; - - let mut sk = derive_bls_sk(&seed, index); - let pk = BlsPublicKey::from(&sk); - - seed.zeroize(); - sk.zeroize(); - - Ok(pk) + self.store + .account_public_key(index) + .map_err(Error::from_store_err) } /// Fetches the notes and nullifiers in the state and returns the notes that @@ -272,19 +284,13 @@ where Ok(unspent_notes_and_nullifiers) } - /// 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). + /// Here we fetch the notes and their nullifiers to cover the + /// transaction-costs. #[allow(clippy::type_complexity)] fn input_notes_nullifiers( &self, sender_sk: &PhoenixSecretKey, - transfer_value: u64, - max_fee: u64, - deposit: u64, + transaction_cost: u64, ) -> Result, Error> { let sender_vk = PhoenixViewKey::from(sender_sk); @@ -301,15 +307,12 @@ where notes_values_nullifiers.push((note, val, nullifier)); } - if accumulated_value < transfer_value + max_fee + deposit { + if accumulated_value < transaction_cost { return Err(Error::NotEnoughBalance); } // pick the four smallest notes that cover the costs - let inputs = pick_notes( - transfer_value + max_fee + deposit, - notes_values_nullifiers, - ); + let inputs = pick_notes(transaction_cost, notes_values_nullifiers); if inputs.is_empty() { return Err(Error::NoteCombinationProblem); @@ -318,69 +321,55 @@ where Ok(inputs) } - fn phoenix_transaction( + /// Here we fetch the notes, their openings and nullifiers to cover the + /// transfer-costs. + #[allow(clippy::type_complexity)] + fn input_notes_openings_nullifiers( &self, - rng: &mut Rng, - sender_index: u8, - receiver_pk: Option, - transfer_value: u64, - obfuscated_transaction: bool, - gas_limit: u64, - gas_price: u64, - deposit: u64, - exec: Option>, - ) -> Result> { - let mut seed = self.store.get_seed().map_err(Error::from_store_err)?; - let mut sender_sk = derive_phoenix_sk(&seed, sender_index); - - let sender_pk = PhoenixPublicKey::from(&sender_sk); - let change_pk = sender_pk; - let receiver_pk = receiver_pk.unwrap_or(sender_pk); - - let input_notes: Vec = self - .input_notes_nullifiers( - &sender_sk, - transfer_value, - gas_limit * gas_price, - deposit, - )? - .into_iter() - .map(|(note, _nullifier)| note) - .collect(); - - let mut input_notes_openings = Vec::new(); - - for note in input_notes { + sender_sk: &PhoenixSecretKey, + transaction_cost: u64, + ) -> Result< + Vec<(Note, Opening<(), NOTES_TREE_DEPTH>, BlsScalar)>, + Error, + > { + let notes_and_nullifiers = + self.input_notes_nullifiers(sender_sk, transaction_cost)?; + + let mut notes_openings_nullifiers = + Vec::with_capacity(notes_and_nullifiers.len()); + for (note, nullifier) in notes_and_nullifiers.into_iter() { let opening = self .state .fetch_opening(¬e) .map_err(Error::from_state_err)?; - input_notes_openings.push((note, opening)); + notes_openings_nullifiers.push((note, opening, nullifier)); } - let root = self.state.fetch_root().map_err(Error::from_state_err)?; - - let utx = new_unproven_phoenix_tx( - rng, - &sender_sk, - &change_pk, - &receiver_pk, - input_notes_openings, - root, - transfer_value, - obfuscated_transaction, - deposit, - gas_limit, - gas_price, - exec, - ); + Ok(notes_openings_nullifiers) + } - seed.zeroize(); - sender_sk.zeroize(); + /// Here we fetch the notes and their openings to cover the + /// transfer-costs. + #[allow(clippy::type_complexity)] + fn input_notes_openings( + &self, + sender_sk: &PhoenixSecretKey, + transaction_cost: u64, + ) -> Result)>, Error> + { + let notes_and_nullifiers = + self.input_notes_nullifiers(sender_sk, transaction_cost)?; + + let mut notes_openings = Vec::with_capacity(notes_and_nullifiers.len()); + for (note, _nullifier) in notes_and_nullifiers.into_iter() { + let opening = self + .state + .fetch_opening(¬e) + .map_err(Error::from_state_err)?; + notes_openings.push((note, opening)); + } - self.prover - .compute_proof_and_propagate(&utx) - .map_err(Error::from_prover_err) + Ok(notes_openings) } /// Execute a generic contract call or deployment, using Phoenix notes to @@ -395,22 +384,38 @@ where deposit: u64, exec: impl Into, ) -> Result> { - let receiver_pk = None; + let mut sender_sk = self.phoenix_secret_key(sender_index)?; + let receiver_pk = self.phoenix_public_key(sender_index)?; + let change_pk = receiver_pk; + + let input_notes_openings = self.input_notes_openings( + &sender_sk, + gas_limit * gas_price + deposit, + )?; + + let root = self.state.fetch_root().map_err(Error::from_state_err)?; let transfer_value = 0; let obfuscated_transaction = false; - self.phoenix_transaction( + let tx = phoenix_transaction::( rng, - sender_index, - receiver_pk, + &sender_sk, + &change_pk, + &receiver_pk, + input_notes_openings, + root, transfer_value, obfuscated_transaction, + deposit, gas_limit, gas_price, - deposit, Some(exec), - ) + ); + + sender_sk.zeroize(); + + tx.map_err(|e| Error::from_prover_err(e)) } /// Transfer Dusk in the form of Phoenix notes from one key to another. @@ -424,21 +429,39 @@ where gas_limit: u64, gas_price: u64, ) -> Result> { + let mut sender_sk = self.phoenix_secret_key(sender_index)?; + let change_pk = self.phoenix_public_key(sender_index)?; + + let input_notes_openings = self.input_notes_openings( + &sender_sk, + transfer_value + gas_limit * gas_price, + )?; + + let root = self.state.fetch_root().map_err(Error::from_state_err)?; + let obfuscated_transaction = true; let deposit = 0; + let exec: Option = None; - self.phoenix_transaction( + let tx = phoenix_transaction::( rng, - sender_index, - Some(*receiver_pk), + &sender_sk, + &change_pk, + &receiver_pk, + input_notes_openings, + root, transfer_value, obfuscated_transaction, + deposit, gas_limit, gas_price, - deposit, exec, - ) + ); + + sender_sk.zeroize(); + + tx.map_err(|e| Error::from_prover_err(e)) } /// Stakes an amount of Dusk using Phoenix notes. @@ -448,37 +471,44 @@ where rng: &mut Rng, sender_index: u8, staker_index: u8, - value: u64, + stake_value: u64, gas_limit: u64, gas_price: u64, ) -> Result> { - let mut seed = self.store.get_seed().map_err(Error::from_store_err)?; - let mut stake_sk = derive_bls_sk(&seed, staker_index); + let mut phoenix_sender_sk = self.phoenix_secret_key(sender_index)?; + let mut stake_sk = self.account_secret_key(staker_index)?; + let stake_pk = BlsPublicKey::from(&stake_sk); - // the stake value also needs to be deposited - let deposit = value; + let inputs = self.input_notes_openings( + &phoenix_sender_sk, + gas_limit * gas_price + stake_value, + )?; - let stake = self + let root = self.state.fetch_root().map_err(Error::from_state_err)?; + + let current_nonce = self .state .fetch_stake(&stake_pk) - .map_err(Error::from_state_err)?; - - let contract_call = stake_call(&stake_sk, value, stake.nonce); + .map_err(Error::from_state_err)? + .nonce; - let tx = self.phoenix_execute( + let tx = phoenix_stake::( rng, - sender_index, + &phoenix_sender_sk, + &stake_sk, + inputs, + root, gas_limit, gas_price, - deposit, - contract_call, + stake_value, + current_nonce, ); - seed.zeroize(); stake_sk.zeroize(); + phoenix_sender_sk.zeroize(); - tx + tx.map_err(|e| Error::from_prover_err(e)) } /// Unstakes a key from the stake contract, using Phoenix notes. @@ -490,13 +520,17 @@ where gas_limit: u64, gas_price: u64, ) -> Result> { - let mut seed = self.store.get_seed().map_err(Error::from_store_err)?; - let mut phoenix_sender_sk = derive_phoenix_sk(&seed, sender_index); - let mut stake_sk = derive_bls_sk(&seed, staker_index); + let mut phoenix_sender_sk = self.phoenix_secret_key(sender_index)?; + let mut stake_sk = self.account_secret_key(staker_index)?; + let stake_pk = BlsPublicKey::from(&stake_sk); - let transfer_value = 0; - let deposit = 0; + let inputs = self.input_notes_openings_nullifiers( + &phoenix_sender_sk, + gas_limit * gas_price, + )?; + + let root = self.state.fetch_root().map_err(Error::from_state_err)?; let stake = self .state @@ -511,37 +545,24 @@ where })? .value; - let input_nullifiers = self - .input_notes_nullifiers( - &phoenix_sender_sk, - transfer_value, - gas_limit * gas_price, - deposit, - )? - .into_iter() - .map(|(_note, nullifier)| nullifier) - .collect(); - - let contract_call = unstake_to_phoenix( + let tx = phoenix_unstake::( rng, &phoenix_sender_sk, &stake_sk, - input_nullifiers, + inputs, + root, staked_amount, + gas_limit, + gas_price, ); - seed.zeroize(); - phoenix_sender_sk.zeroize(); stake_sk.zeroize(); + phoenix_sender_sk.zeroize(); - self.phoenix_execute( - rng, - sender_index, - gas_limit, - gas_price, - deposit, - contract_call, - ) + tx.map_err(|e| Error::from_prover_err(e)) + // self.prover + // .compute_proof_and_propagate(&utx) + // .map_err(Error::from_prover_err) } /// Withdraw the accumulated staking reward for a key, into Phoenix notes. @@ -554,50 +575,42 @@ where gas_limit: u64, gas_price: u64, ) -> Result> { - let mut seed = self.store.get_seed().map_err(Error::from_store_err)?; - let mut phoenix_sender_sk = derive_phoenix_sk(&seed, sender_index); - let mut stake_sk = derive_bls_sk(&seed, staker_index); + let mut phoenix_sender_sk = self.phoenix_secret_key(sender_index)?; + let mut stake_sk = self.account_secret_key(staker_index)?; + let stake_pk = BlsPublicKey::from(&stake_sk); - let transfer_value = 0; - let deposit = 0; + let inputs = self.input_notes_openings_nullifiers( + &phoenix_sender_sk, + gas_limit * gas_price, + )?; - let stake = self + let root = self.state.fetch_root().map_err(Error::from_state_err)?; + + let stake_reward = self .state .fetch_stake(&stake_pk) - .map_err(Error::from_state_err)?; - - let input_nullifiers = self - .input_notes_nullifiers( - &phoenix_sender_sk, - transfer_value, - gas_limit * gas_price, - deposit, - )? - .into_iter() - .map(|(_note, nullifier)| nullifier) - .collect(); + .map_err(Error::from_state_err)? + .reward; - let contract_call = withdraw_stake_reward_to_phoenix( + let tx = phoenix_withdraw_stake_reward::( rng, &phoenix_sender_sk, &stake_sk, - input_nullifiers, - stake.reward, + inputs, + root, + stake_reward, + gas_limit, + gas_price, ); - seed.zeroize(); - phoenix_sender_sk.zeroize(); stake_sk.zeroize(); + phoenix_sender_sk.zeroize(); - self.phoenix_execute( - rng, - sender_index, - gas_limit, - gas_price, - deposit, - contract_call, - ) + tx.map_err(|e| Error::from_prover_err(e)) + // self.prover + // .compute_proof_and_propagate(&utx) + // .map_err(Error::from_prover_err) } /// Transfer Dusk from one account to another using moonlight. diff --git a/test-wallet/src/lib.rs b/test-wallet/src/lib.rs index 6db7f12c74..b0bbffe7b2 100644 --- a/test-wallet/src/lib.rs +++ b/test-wallet/src/lib.rs @@ -16,6 +16,7 @@ mod imp; use alloc::vec::Vec; use poseidon_merkle::Opening as PoseidonOpening; +use zeroize::Zeroize; use execution_core::{ signatures::bls::{PublicKey as BlsPublicKey, SecretKey as BlsSecretKey}, @@ -23,19 +24,19 @@ use execution_core::{ transfer::{ moonlight::AccountData, phoenix::{ - Note, SecretKey as PhoenixSecretKey, - Transaction as PhoenixTransaction, ViewKey, NOTES_TREE_DEPTH, + Note, PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey, + ViewKey as PhoenixViewKey, NOTES_TREE_DEPTH, }, - Transaction, }, BlsScalar, }; -use wallet_core::keys::{derive_bls_sk, derive_phoenix_sk}; -pub use imp::*; +pub use wallet_core::{ + keys::{derive_bls_sk, derive_phoenix_pk, derive_phoenix_sk}, + transaction::ProverClient, +}; -/// The maximum size of call data. -pub const MAX_CALL_SIZE: usize = execution_core::ARGBUF_LEN; +pub use imp::*; /// Stores the cryptographic material necessary to derive cryptographic keys. pub trait Store { @@ -45,45 +46,63 @@ pub trait Store { /// Retrieves the seed used to derive keys. fn get_seed(&self) -> Result<[u8; 64], Self::Error>; - /// 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 fetch_phoenix_secret_key( + /// Retrieve the secret key with the given index. + fn phoenix_secret_key( &self, index: u8, ) -> Result { - let seed = self.get_seed()?; - Ok(derive_phoenix_sk(&seed, index)) + let mut seed = self.get_seed()?; + + let sk = derive_phoenix_sk(&seed, index); + + seed.zeroize(); + + Ok(sk) } - /// Retrieves a derived account 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 fetch_account_secret_key( + /// Retrieve the public key with the given index. + fn phoenix_public_key( &self, index: u8, - ) -> Result { - let seed = self.get_seed()?; - Ok(derive_bls_sk(&seed, index)) + ) -> Result { + let mut seed = self.get_seed()?; + + let pk = derive_phoenix_pk(&seed, index); + + seed.zeroize(); + + Ok(pk) } -} -/// Types that are client of the prover. -pub trait ProverClient { - /// Error returned by the node client. - type Error; + /// Retrieve the account secret key with the given index. + fn account_secret_key( + &self, + index: u8, + ) -> Result { + let mut seed = self.get_seed()?; + + let sk = derive_bls_sk(&seed, index); + + seed.zeroize(); + + Ok(sk) + } - /// Requests that a node prove the given transaction and later propagates it - fn compute_proof_and_propagate( + /// Retrieve the account public key with the given index. + fn account_public_key( &self, - utx: &PhoenixTransaction, - ) -> Result; + index: u8, + ) -> Result { + let mut seed = self.get_seed()?; + + let mut sk = derive_bls_sk(&seed, index); + let pk = BlsPublicKey::from(&sk); + + seed.zeroize(); + sk.zeroize(); + + Ok(pk) + } } /// Tuple containing Note and block height @@ -97,7 +116,7 @@ pub trait StateClient { /// Find notes for a view key. fn fetch_notes( &self, - vk: &ViewKey, + vk: &PhoenixViewKey, ) -> Result, Self::Error>; /// Fetch the current root of the state. diff --git a/wallet-core/src/transaction.rs b/wallet-core/src/transaction.rs index 7332e94adc..430a2315ba 100644 --- a/wallet-core/src/transaction.rs +++ b/wallet-core/src/transaction.rs @@ -6,7 +6,6 @@ //! Implementations of basic wallet functionalities to create transactions. -use alloc::string::String; use alloc::vec::Vec; use rand::{CryptoRng, RngCore}; @@ -26,22 +25,26 @@ use execution_core::{ NOTES_TREE_DEPTH, }, withdraw::{Withdraw, WithdrawReceiver, WithdrawReplayToken}, + Transaction, }, BlsScalar, ContractId, JubJubScalar, }; -/// An unproven-transaction is nearly identical to a [`PhoenixTransaction`] with -/// the only difference being that it carries a serialized [`TxCircuitVec`] -/// instead of the proof bytes. -/// This way it is possible to delegate the proof generation of the -/// [`TxCircuitVec`] after the [`UnprovenTransaction`] was created while at the -/// same time ensuring non-malleability of the transaction as the transaction's -/// payload-hash is part of the public inputs of the circuit. -/// Once the proof is generated from the [`TxCircuitVec`] bytes, it can -/// replace the serialized circuit in the transaction by calling -/// [`Transaction::replace_proof`]. +/// 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 unproven [`PhoenixTransaction`] and + /// later propagates it. + fn compute_proof_and_propagate( + utx: &PhoenixTransaction, + ) -> Result; +} + +/// Create a [`Transaction`] that is paid in phoenix-notes. #[allow(clippy::too_many_arguments)] -pub fn new_unproven_phoenix_tx( +pub fn phoenix_transaction( rng: &mut R, sender_sk: &PhoenixSecretKey, change_pk: &PhoenixPublicKey, @@ -54,8 +57,8 @@ pub fn new_unproven_phoenix_tx( gas_limit: u64, gas_price: u64, exec: Option>, -) -> PhoenixTransaction { - PhoenixTransaction::new::( +) -> Result { + let utx = new_unproven_phoenix_tx( rng, sender_sk, change_pk, @@ -68,32 +71,90 @@ pub fn new_unproven_phoenix_tx( gas_limit, gas_price, exec, - ) + ); + + PC::compute_proof_and_propagate(&utx) } -/// Implementation of the Prove trait that adds the serialized circuit instead -/// of a proof. This way the proof creation can be delegated to a 3rd party. -struct UnprovenProver(); +/// Create a [`Transaction`] to stake from phoenix-notes. +#[allow(clippy::too_many_arguments)] +#[allow(clippy::missing_panics_doc)] +pub fn phoenix_stake( + rng: &mut R, + phoenix_sender_sk: &PhoenixSecretKey, + stake_sk: &BlsSecretKey, + inputs: Vec<(Note, Opening<(), NOTES_TREE_DEPTH>)>, + root: BlsScalar, + gas_limit: u64, + gas_price: u64, + stake_value: u64, + current_nonce: u64, +) -> Result { + let receiver_pk = PhoenixPublicKey::from(phoenix_sender_sk); + let change_pk = receiver_pk; -impl Prove for UnprovenProver { - // this implementation of the trait will never error. - type Error = (); + let transfer_value = 0; + let obfuscated_transaction = false; + let deposit = stake_value; - fn prove(circuit: &[u8]) -> Result, Self::Error> { - Ok(circuit.to_vec()) - } + let stake = Stake::new(stake_sk, stake_value, current_nonce + 1); + + let contract_call = ContractCall::new(STAKE_CONTRACT, "stake", &stake) + .expect("rkyv serialization of the stake struct should work."); + + let utx = new_unproven_phoenix_tx( + rng, + phoenix_sender_sk, + &change_pk, + &receiver_pk, + inputs, + root, + transfer_value, + obfuscated_transaction, + deposit, + gas_limit, + gas_price, + Some(contract_call), + ); + + PC::compute_proof_and_propagate(&utx) } -/// Create a [`ContractCall`] to withdraw stake rewards into a phoenix-note. +/// Create an unproven [`Transaction`] to withdraw stake rewards into a +/// phoenix-note. +#[allow(clippy::too_many_arguments)] #[allow(clippy::missing_panics_doc)] -pub fn withdraw_stake_reward_to_phoenix( +pub fn phoenix_withdraw_stake_reward< + R: RngCore + CryptoRng, + PC: ProverClient, +>( rng: &mut R, phoenix_sender_sk: &PhoenixSecretKey, stake_sk: &BlsSecretKey, - input_nullifiers: Vec, + inputs: Vec<(Note, Opening<(), NOTES_TREE_DEPTH>, BlsScalar)>, + root: BlsScalar, reward_amount: u64, -) -> ContractCall { - let gas_payment_token = WithdrawReplayToken::Phoenix(input_nullifiers); + gas_limit: u64, + gas_price: u64, +) -> Result { + let receiver_pk = PhoenixPublicKey::from(phoenix_sender_sk); + let change_pk = receiver_pk; + + let transfer_value = 0; + let obfuscated_transaction = false; + let deposit = 0; + + // split the input notes and openings from the nullifiers + let mut nullifiers = Vec::with_capacity(inputs.len()); + let inputs = inputs + .into_iter() + .map(|(note, opening, nullifier)| { + nullifiers.push(nullifier); + (note, opening) + }) + .collect(); + + let gas_payment_token = WithdrawReplayToken::Phoenix(nullifiers); let withdraw = withdraw_to_phoenix( rng, @@ -105,63 +166,89 @@ pub fn withdraw_stake_reward_to_phoenix( let reward_withdraw = StakeWithdraw::new(stake_sk, withdraw); - ContractCall::new(STAKE_CONTRACT, "withdraw", &reward_withdraw) - .expect("rkyv should serialize the reward_withdraw correctly") + let contract_call = + ContractCall::new(STAKE_CONTRACT, "withdraw", &reward_withdraw) + .expect("rkyv should serialize the reward_withdraw correctly"); + + let utx = new_unproven_phoenix_tx( + rng, + phoenix_sender_sk, + &change_pk, + &receiver_pk, + inputs, + root, + transfer_value, + obfuscated_transaction, + deposit, + gas_limit, + gas_price, + Some(contract_call), + ); + + PC::compute_proof_and_propagate(&utx) } -/// Create a [`ContractCall`] to withdraw stake rewards into a -/// moonlight-account. +/// Create an unproven [`Transaction`] to unstake into a phoenix-note. +#[allow(clippy::too_many_arguments)] #[allow(clippy::missing_panics_doc)] -pub fn unstake_to_phoenix( +pub fn phoenix_unstake( rng: &mut R, phoenix_sender_sk: &PhoenixSecretKey, stake_sk: &BlsSecretKey, - input_nullifiers: Vec, - staked_amount: u64, -) -> ContractCall { - let gas_payment_token = WithdrawReplayToken::Phoenix(input_nullifiers); + inputs: Vec<(Note, Opening<(), NOTES_TREE_DEPTH>, BlsScalar)>, + root: BlsScalar, + unstake_value: u64, + gas_limit: u64, + gas_price: u64, +) -> Result { + let receiver_pk = PhoenixPublicKey::from(phoenix_sender_sk); + let change_pk = receiver_pk; + + let transfer_value = 0; + let obfuscated_transaction = false; + let deposit = 0; + + // split the input notes and openings from the nullifiers + let mut nullifiers = Vec::with_capacity(inputs.len()); + let inputs = inputs + .into_iter() + .map(|(note, opening, nullifier)| { + nullifiers.push(nullifier); + (note, opening) + }) + .collect(); + + let gas_payment_token = WithdrawReplayToken::Phoenix(nullifiers); let withdraw = withdraw_to_phoenix( rng, phoenix_sender_sk, STAKE_CONTRACT, gas_payment_token, - staked_amount, + unstake_value, ); let unstake = StakeWithdraw::new(stake_sk, withdraw); - ContractCall::new(STAKE_CONTRACT, "unstake", &unstake) - .expect("unstake should serialize correctly") -} - -/// Create a [`ContractCall`] to deposit funds into a contract. -/// -/// It is important that the contract method `fn_name` picks up the deposit from -/// the transfer-contract by calling it's `deposit` method with the same deposit -/// amount as stated in the transaction itself. -#[allow(clippy::missing_panics_doc)] -pub fn deposit_call( - contract: ContractId, - fn_name: impl Into, - deposit: u64, -) -> ContractCall { - ContractCall::new(contract, fn_name, &deposit) - .expect("deposit should serialize correctly") -} + let contract_call = ContractCall::new(STAKE_CONTRACT, "unstake", &unstake) + .expect("unstake should serialize correctly"); -/// Create a [`ContractCall`] to create a new stake. -#[allow(clippy::missing_panics_doc)] -#[must_use] -pub fn stake_call( - stake_sk: &BlsSecretKey, - value: u64, - current_nonce: u64, -) -> ContractCall { - let stake = Stake::new(stake_sk, value, current_nonce + 1); + let utx = new_unproven_phoenix_tx( + rng, + phoenix_sender_sk, + &change_pk, + &receiver_pk, + inputs, + root, + transfer_value, + obfuscated_transaction, + deposit, + gas_limit, + gas_price, + Some(contract_call), + ); - ContractCall::new(STAKE_CONTRACT, "stake", &stake) - .expect("rkyv serialization of the stake struct should work.") + PC::compute_proof_and_propagate(&utx) } /// Create a [`Withdraw`] struct to be used to withdraw funds from a contract @@ -193,3 +280,57 @@ fn withdraw_to_phoenix( withdraw } + +/// An unproven-transaction is nearly identical to a [`PhoenixTransaction`] with +/// the only difference being that it carries a serialized [`TxCircuitVec`] +/// instead of the proof bytes. +/// This way it is possible to delegate the proof generation of the +/// [`TxCircuitVec`] after the [`UnprovenTransaction`] was created while at the +/// same time ensuring non-malleability of the transaction as the transaction's +/// payload-hash is part of the public inputs of the circuit. +/// Once the proof is generated from the [`TxCircuitVec`] bytes, it can +/// replace the serialized circuit in the transaction by calling +/// [`Transaction::replace_proof`]. +#[allow(clippy::too_many_arguments)] +fn new_unproven_phoenix_tx( + rng: &mut R, + sender_sk: &PhoenixSecretKey, + change_pk: &PhoenixPublicKey, + receiver_pk: &PhoenixPublicKey, + inputs: Vec<(Note, Opening<(), NOTES_TREE_DEPTH>)>, + root: BlsScalar, + transfer_value: u64, + obfuscated_transaction: bool, + deposit: u64, + gas_limit: u64, + gas_price: u64, + exec: Option>, +) -> PhoenixTransaction { + PhoenixTransaction::new::( + rng, + sender_sk, + change_pk, + receiver_pk, + inputs, + root, + transfer_value, + obfuscated_transaction, + deposit, + gas_limit, + gas_price, + exec, + ) +} + +/// Implementation of the Prove trait that adds the serialized circuit instead +/// of a proof. This way the proof creation can be delegated to a 3rd party. +struct UnprovenProver(); + +impl Prove for UnprovenProver { + // this implementation of the trait will never error. + type Error = (); + + fn prove(circuit: &[u8]) -> Result, Self::Error> { + Ok(circuit.to_vec()) + } +}