From cd6b11211b1b911300572892acb5331cff469ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 19 Jan 2023 13:33:11 +0100 Subject: [PATCH 1/2] Add types to interact with the genesis contracts This requires the addition of `dusk-bls12_381-sign` as a dependency, as well as helper functions producing messages intended for signing. Resolves #119 --- CHANGELOG.md | 16 +++- Cargo.toml | 2 + src/transaction.rs | 107 ++++++++++++---------- src/transaction/stake.rs | 178 ++++++++++++++++++++++++++++++++++++ src/transaction/transfer.rs | 174 +++++++++++++++++++++++++++++++++++ 5 files changed, 423 insertions(+), 54 deletions(-) create mode 100644 src/transaction/stake.rs create mode 100644 src/transaction/transfer.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 65073f8..4f17e62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.18.0] - 2022-11-02 - -### Added - +## Added + +- Add `allow_signature_message`, `stake_signature_message`, + `unstake_signature_message`, and `withdraw_signature_message` + to generate signature messages for stake contract interaction [#119] +- Add `stct_signature_message` and `stco_signature_message` to generate + signature messages for transfer contract interaction [#119] +- Add `Stake`, `Unstake`, `Withdraw`, `Allow`, and `StakeData` structs to allow + interaction with the stake contract [#119] +- Add `Stct`, `Wfct`, `Stco`, `Wfco`, `Wfctc`, `Mint`, and `TreeLeaf` structs to + allow interaction with the transfer contract [#119] - Add `Error::Decryption` variant [#114] ### Changed @@ -173,6 +180,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removal of anyhow error implementation. - Canonical implementation shielded by feature. +[#119]: https://github.com/dusk-network/phoenix-core/issues/119 [#114]: https://github.com/dusk-network/phoenix-core/issues/114 [#107]: https://github.com/dusk-network/phoenix-core/issues/107 [#96]: https://github.com/dusk-network/phoenix-core/issues/96 diff --git a/Cargo.toml b/Cargo.toml index 943207c..e42c4cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ exclude = [".github/workflows/ci.yml", ".gitignore"] rand_core = { version = "0.6", default-features = false } dusk-bytes = "0.1" dusk-bls12_381 = { version = "0.11", default-features = false } +dusk-bls12_381-sign = { version = "0.4", default-features = false } dusk-jubjub = { version = "0.12", default-features = false } dusk-poseidon = { version = "0.28", default-features = false } dusk-pki = { version = "0.11", default-features = false } @@ -31,6 +32,7 @@ rkyv-impl = [ "dusk-jubjub/rkyv-impl", "dusk-pki/rkyv-impl", "dusk-bls12_381/rkyv-impl", + "dusk-bls12_381-sign/rkyv-impl", "rkyv", "bytecheck" ] diff --git a/src/transaction.rs b/src/transaction.rs index df63fd1..5985b25 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -6,6 +6,12 @@ //! Verifier data for the transfer circuits +mod stake; +pub use stake::*; + +mod transfer; +pub use transfer::*; + use alloc::string::String; use alloc::vec::Vec; @@ -14,12 +20,11 @@ use rkyv::{Archive, Deserialize, Serialize}; use dusk_bls12_381::BlsScalar; use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; -use dusk_poseidon::cipher::PoseidonCipher; -use crate::{Crossover, Fee, Message, Note}; +use crate::{Crossover, Fee, Note}; -const STCO_MESSAGE_SIZE: usize = 7 + 2 * PoseidonCipher::cipher_size(); -const STCT_MESSAGE_SIZE: usize = 5 + PoseidonCipher::cipher_size(); +/// Type alias for the ID of a module. +pub type ModuleId = [u8; 32]; /// A phoenix transaction. #[derive(Debug, Clone, PartialEq, Eq)] @@ -46,31 +51,39 @@ pub struct Transaction { /// A call to a contract. The `Vec` must be an `rkyv`ed representation /// of the data the contract expects, and the `String` the name of the /// function to call. - pub call: Option<(BlsScalar, String, Vec)>, + pub call: Option<(ModuleId, String, Vec)>, } impl Transaction { - /// Return input bytes to a hash function for the transaction. + /// Return the input bytes to a hash function for the transaction from its + /// components. #[must_use] - pub fn to_hash_input_bytes(&self) -> Vec { + pub fn hash_input_bytes_from_components( + nullifiers: &[BlsScalar], + outputs: &[Note], + anchor: &BlsScalar, + fee: &Fee, + crossover: &Option, + call: &Option<(ModuleId, String, Vec)>, + ) -> Vec { let mut bytes = Vec::new(); - for nullifier in &self.nullifiers { + for nullifier in nullifiers { bytes.extend(nullifier.to_bytes()); } - for note in &self.outputs { + for note in outputs { bytes.extend(note.to_bytes()); } - bytes.extend(self.anchor.to_bytes()); - bytes.extend(self.fee.to_bytes()); + bytes.extend(anchor.to_bytes()); + bytes.extend(fee.to_bytes()); - if let Some(crossover) = &self.crossover { + if let Some(crossover) = crossover { bytes.extend(crossover.to_bytes()); } - if let Some((module, fn_name, call_data)) = &self.call { - bytes.extend(module.to_bytes()); + if let Some((module, fn_name, call_data)) = call { + bytes.extend(module); bytes.extend(fn_name.as_bytes()); bytes.extend(call_data); } @@ -78,6 +91,19 @@ impl Transaction { bytes } + /// Return input bytes to a hash function for the transaction. + #[must_use] + pub fn to_hash_input_bytes(&self) -> Vec { + Self::hash_input_bytes_from_components( + &self.nullifiers, + &self.outputs, + &self.anchor, + &self.fee, + &self.crossover, + &self.call, + ) + } + /// Serialize the transaction to a variable length byte buffer. #[allow(unused_must_use)] pub fn to_var_bytes(&self) -> Vec { @@ -113,7 +139,7 @@ impl Transaction { if let Some((module, fn_name, call_data)) = &self.call { bytes.push(1); - bytes.extend(module.to_bytes()); + bytes.extend(module); let size = fn_name.len() as u64; bytes.extend(size.to_bytes()); @@ -159,8 +185,22 @@ impl Transaction { let has_call = buffer[0] != 0; let mut buffer = &buffer[1..]; + let call = if has_call { - let module = BlsScalar::from_reader(&mut buffer)?; + let buffer_len = buffer.len(); + if buffer.len() < 32 { + return Err(BytesError::BadLength { + found: buffer_len, + expected: 32, + }); + } + + let (module_buf, buf) = buffer.split_at(32); + buffer = buf; + + let mut module = [0u8; 32]; + module.copy_from_slice(module_buf); + let fn_name_size = u64::from_reader(&mut buffer)? as usize; let fn_name = String::from_utf8(buffer[..fn_name_size].to_vec()) .map_err(|_err| BytesError::InvalidData)?; @@ -202,40 +242,7 @@ impl Transaction { } /// Returns the call of the transaction. - pub fn call(&self) -> Option<&(BlsScalar, String, Vec)> { + pub fn call(&self) -> Option<&(ModuleId, String, Vec)> { self.call.as_ref() } } - -/// Signature message used for [`Stct`]. -#[must_use] -pub fn process_message_stct( - crossover: &Crossover, - value: u64, - module_id: BlsScalar, -) -> [BlsScalar; STCT_MESSAGE_SIZE] { - let mut array = [BlsScalar::default(); STCT_MESSAGE_SIZE]; - let hash_inputs = crossover.to_hash_inputs(); - array[..hash_inputs.len()].copy_from_slice(&hash_inputs); - array[hash_inputs.len()..].copy_from_slice(&[value.into(), module_id]); - array -} - -/// Signature message used for [`Stco`]. -#[must_use] -pub fn process_message_stco( - crossover: &Crossover, - message: &Message, - module_id: BlsScalar, -) -> [BlsScalar; STCO_MESSAGE_SIZE] { - let mut array = [BlsScalar::default(); STCO_MESSAGE_SIZE]; - let crossover_inputs = crossover.to_hash_inputs(); - let message_inputs = message.to_hash_inputs(); - array[..crossover_inputs.len()].copy_from_slice(&crossover_inputs); - array - [crossover_inputs.len()..crossover_inputs.len() + message_inputs.len()] - .copy_from_slice(&message_inputs); - array[crossover_inputs.len() + message_inputs.len()..] - .copy_from_slice(&[module_id]); - array -} diff --git a/src/transaction/stake.rs b/src/transaction/stake.rs new file mode 100644 index 0000000..bbf87c7 --- /dev/null +++ b/src/transaction/stake.rs @@ -0,0 +1,178 @@ +// 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::vec::Vec; + +use dusk_bls12_381::BlsScalar; +use dusk_bls12_381_sign::{PublicKey, Signature}; +use dusk_bytes::Serializable; +use dusk_pki::StealthAddress; +#[cfg(feature = "rkyv-impl")] +use rkyv::{Archive, Deserialize, Serialize}; + +use crate::note::Note; + +const ALLOW_MESSAGE_SIZE: usize = u64::SIZE + PublicKey::SIZE; +const STAKE_MESSAGE_SIZE: usize = u64::SIZE + u64::SIZE; +const UNSTAKE_MESSAGE_SIZE: usize = u64::SIZE + Note::SIZE; +const WITHDRAW_MESSAGE_SIZE: usize = + u64::SIZE + StealthAddress::SIZE + BlsScalar::SIZE; + +/// The representation of a public key's stake. +/// +/// A user can stake for a particular `amount` larger in value than the +/// `MINIMUM_STAKE` value and is `reward`ed for participating in the consensus. +/// A stake is valid only after a particular block height - called the +/// eligibility. +/// +/// To keep track of the number of interactions a public key has had with the +/// contract a `counter` is used to prevent replay attacks - where the same +/// signature could be used to prove ownership of the secret key in two +/// different transactions. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +pub struct StakeData { + /// Amount staked and eligibility. + pub amount: Option<(u64, u64)>, + /// The reward for participating in consensus. + pub reward: u64, + /// The signature counter to prevent replay. + pub counter: u64, +} + +/// Stake a value on the stake contract. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +pub struct Stake { + /// Public key to which the stake will belong. + pub public_key: PublicKey, + /// Signature belonging to the given public key. + pub signature: Signature, + /// Value to stake. + pub value: u64, + /// Proof of the `STCT` circuit. + pub proof: Vec, +} + +/// Unstake a value from the stake contract. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +pub struct Unstake { + /// Public key to unstake. + pub public_key: PublicKey, + /// Signature belonging to the given public key. + pub signature: Signature, + /// Note to withdraw to. + pub note: Note, + /// A proof of the `WFCT` circuit. + pub proof: Vec, +} + +/// Withdraw the accumulated reward. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +pub struct Withdraw { + /// Public key to withdraw the rewards. + pub public_key: PublicKey, + /// Signature belonging to the given public key. + pub signature: Signature, + /// The address to mint to. + pub address: StealthAddress, + /// A nonce to prevent replay. + pub nonce: BlsScalar, +} + +/// Allow a public key to stake. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +pub struct Allow { + /// The public key to allow staking to. + pub public_key: PublicKey, + /// The "owner" of the smart contract. + pub owner: PublicKey, + /// Signature of the `owner` key. + pub signature: Signature, +} + +/// Signature message used for [`Allow`]. +#[must_use] +pub fn allow_signature_message( + counter: u64, + staker: PublicKey, +) -> [u8; ALLOW_MESSAGE_SIZE] { + let mut bytes = [0u8; ALLOW_MESSAGE_SIZE]; + + bytes[..u64::SIZE].copy_from_slice(&counter.to_bytes()); + bytes[u64::SIZE..].copy_from_slice(&staker.to_bytes()); + + bytes +} + +/// Signature message used for [`Stake`]. +#[must_use] +pub fn stake_signature_message( + counter: u64, + value: u64, +) -> [u8; STAKE_MESSAGE_SIZE] { + let mut bytes = [0u8; STAKE_MESSAGE_SIZE]; + + bytes[..u64::SIZE].copy_from_slice(&counter.to_bytes()); + bytes[u64::SIZE..].copy_from_slice(&value.to_bytes()); + + bytes +} + +/// Signature message used for [`Unstake`]. +#[must_use] +pub fn unstake_signature_message( + counter: u64, + note: Note, +) -> [u8; UNSTAKE_MESSAGE_SIZE] { + let mut bytes = [0u8; UNSTAKE_MESSAGE_SIZE]; + + bytes[..u64::SIZE].copy_from_slice(&counter.to_bytes()); + bytes[u64::SIZE..].copy_from_slice(¬e.to_bytes()); + + bytes +} + +/// Signature message used for [`Withdraw`]. +#[must_use] +pub fn withdraw_signature_message( + counter: u64, + address: StealthAddress, + nonce: BlsScalar, +) -> [u8; WITHDRAW_MESSAGE_SIZE] { + let mut bytes = [0u8; WITHDRAW_MESSAGE_SIZE]; + + bytes[..u64::SIZE].copy_from_slice(&counter.to_bytes()); + bytes[u64::SIZE..u64::SIZE + StealthAddress::SIZE] + .copy_from_slice(&address.to_bytes()); + bytes[u64::SIZE + StealthAddress::SIZE..] + .copy_from_slice(&nonce.to_bytes()); + + bytes +} diff --git a/src/transaction/transfer.rs b/src/transaction/transfer.rs new file mode 100644 index 0000000..8cf5be9 --- /dev/null +++ b/src/transaction/transfer.rs @@ -0,0 +1,174 @@ +// 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::vec::Vec; + +use dusk_bls12_381::BlsScalar; +use dusk_pki::StealthAddress; +use dusk_poseidon::cipher::PoseidonCipher; +#[cfg(feature = "rkyv-impl")] +use rkyv::{Archive, Deserialize, Serialize}; + +use super::ModuleId; + +use crate::crossover::Crossover; +use crate::message::Message; +use crate::note::Note; + +/// The depth of the transfer tree. +pub const TRANSFER_TREE_DEPTH: usize = 17; + +const STCO_MESSAGE_SIZE: usize = 7 + 2 * PoseidonCipher::cipher_size(); +const STCT_MESSAGE_SIZE: usize = 5 + PoseidonCipher::cipher_size(); + +/// A leaf of the transfer tree. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +pub struct TreeLeaf { + /// The height of the block when the note was inserted in the tree. + pub block_height: u64, + /// The note inserted in the tree. + pub note: Note, +} + +/// Send value to a contract transparently. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +pub struct Stct { + /// Module to send the value to. + pub module: ModuleId, + /// The value to send to the contract. + pub value: u64, + /// Proof of the `STCT` circuit. + pub proof: Vec, +} + +/// Withdraw value from a contract transparently. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +pub struct Wfct { + /// The value to withdraw + pub value: u64, + /// The note to withdraw transparently to + pub note: Note, + /// A proof of the `WFCT` circuit. + pub proof: Vec, +} + +/// Send value to a contract anonymously. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +pub struct Stco { + /// Module to send the value to. + pub module: ModuleId, + /// Message containing the value commitment. + pub message: Message, + /// The stealth address of the message. + pub message_address: StealthAddress, + /// Proof of the `STCO` circuit. + pub proof: Vec, +} + +/// Withdraw value from a contract anonymously. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +pub struct Wfco { + /// Message containing the value commitment. + pub message: Message, + /// The stealth address of the message. + pub message_address: StealthAddress, + /// Message containing commitment on the change value. + pub change: Message, + /// The stealth address of the change message. + pub change_address: StealthAddress, + /// The note to withdraw to. + pub output: Note, + /// Proof of the `WFCO` circuit. + pub proof: Vec, +} + +/// Withdraw value from the calling contract to another contract. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +pub struct Wfctc { + /// The contract to transfer value to. + pub module: ModuleId, + /// The value to transfer. + pub value: u64, +} + +/// Mint value to a stealth address. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] +pub struct Mint { + /// The address to mint to. + pub address: StealthAddress, + /// The value to mint to the address. + pub value: u64, + /// A nonce to prevent replay. + pub nonce: BlsScalar, +} + +/// Signature message used for [`Stct`]. +#[must_use] +pub fn stct_signature_message( + crossover: &Crossover, + value: u64, + module_id: BlsScalar, +) -> [BlsScalar; STCT_MESSAGE_SIZE] { + let mut array = [BlsScalar::default(); STCT_MESSAGE_SIZE]; + let hash_inputs = crossover.to_hash_inputs(); + array[..hash_inputs.len()].copy_from_slice(&hash_inputs); + array[hash_inputs.len()..].copy_from_slice(&[value.into(), module_id]); + array +} + +/// Signature message used for [`Stco`]. +#[must_use] +pub fn stco_signature_message( + crossover: &Crossover, + message: &Message, + module_id: BlsScalar, +) -> [BlsScalar; STCO_MESSAGE_SIZE] { + let mut array = [BlsScalar::default(); STCO_MESSAGE_SIZE]; + let crossover_inputs = crossover.to_hash_inputs(); + let message_inputs = message.to_hash_inputs(); + array[..crossover_inputs.len()].copy_from_slice(&crossover_inputs); + array + [crossover_inputs.len()..crossover_inputs.len() + message_inputs.len()] + .copy_from_slice(&message_inputs); + array[crossover_inputs.len() + message_inputs.len()..] + .copy_from_slice(&[module_id]); + array +} From 038db3da0192198881f8e5022c4ca27e305dbcc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 19 Jan 2023 14:18:12 +0100 Subject: [PATCH 2/2] Fix CHANGELOG from previous issue --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f17e62..cb3c57a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 interaction with the stake contract [#119] - Add `Stct`, `Wfct`, `Stco`, `Wfco`, `Wfctc`, `Mint`, and `TreeLeaf` structs to allow interaction with the transfer contract [#119] +- Add `Transaction` structure [#116] + +## [0.18.0] - 2022-11-02 + +### Added + - Add `Error::Decryption` variant [#114] ### Changed @@ -181,6 +187,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Canonical implementation shielded by feature. [#119]: https://github.com/dusk-network/phoenix-core/issues/119 +[#116]: https://github.com/dusk-network/phoenix-core/issues/116 [#114]: https://github.com/dusk-network/phoenix-core/issues/114 [#107]: https://github.com/dusk-network/phoenix-core/issues/107 [#96]: https://github.com/dusk-network/phoenix-core/issues/96