From c3e8c939b5a3c3b9a9176ab53f146f975f085bb7 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Thu, 8 Feb 2024 15:46:06 +0100 Subject: [PATCH 01/33] refactor: Rename `native_coin` -> `native_currency` Avoid confusion with `neptune_coins`, which is for an amount of the native currency. --- src/models/blockchain/transaction/mod.rs | 4 ++-- .../transaction/{native_coin.rs => native_currency.rs} | 0 src/models/blockchain/transaction/neptune_coins.rs | 2 +- src/models/blockchain/transaction/utxo.rs | 6 +++--- src/models/state/wallet/wallet_state.rs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename src/models/blockchain/transaction/{native_coin.rs => native_currency.rs} (100%) diff --git a/src/models/blockchain/transaction/mod.rs b/src/models/blockchain/transaction/mod.rs index c4a9186f..6e496a9c 100644 --- a/src/models/blockchain/transaction/mod.rs +++ b/src/models/blockchain/transaction/mod.rs @@ -1,7 +1,7 @@ use crate::models::consensus::mast_hash::MastHash; use crate::prelude::{triton_vm, twenty_first}; -pub mod native_coin; +pub mod native_currency; pub mod neptune_coins; pub mod transaction_kernel; pub mod utxo; @@ -24,7 +24,7 @@ use twenty_first::shared_math::bfield_codec::BFieldCodec; use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; use twenty_first::util_types::emojihash_trait::Emojihash; -use self::native_coin::native_coin_program; +use self::native_currency::native_coin_program; use self::neptune_coins::NeptuneCoins; use self::transaction_kernel::TransactionKernel; use self::utxo::{LockScript, TypeScript, Utxo}; diff --git a/src/models/blockchain/transaction/native_coin.rs b/src/models/blockchain/transaction/native_currency.rs similarity index 100% rename from src/models/blockchain/transaction/native_coin.rs rename to src/models/blockchain/transaction/native_currency.rs diff --git a/src/models/blockchain/transaction/neptune_coins.rs b/src/models/blockchain/transaction/neptune_coins.rs index 95c44f00..1fcc61dc 100644 --- a/src/models/blockchain/transaction/neptune_coins.rs +++ b/src/models/blockchain/transaction/neptune_coins.rs @@ -16,7 +16,7 @@ use std::{ }; use twenty_first::{amount::u32s::U32s, shared_math::bfield_codec::BFieldCodec}; -use super::{native_coin::NATIVE_COIN_TYPESCRIPT_DIGEST, utxo::Coin}; +use super::{native_currency::NATIVE_COIN_TYPESCRIPT_DIGEST, utxo::Coin}; const NUM_LIMBS: usize = 4; diff --git a/src/models/blockchain/transaction/utxo.rs b/src/models/blockchain/transaction/utxo.rs index 34a8e75d..ade75228 100644 --- a/src/models/blockchain/transaction/utxo.rs +++ b/src/models/blockchain/transaction/utxo.rs @@ -13,8 +13,8 @@ use triton_vm::triton_asm; use twenty_first::shared_math::bfield_codec::BFieldCodec; use twenty_first::shared_math::tip5::Digest; -use super::native_coin::{native_coin_program, NATIVE_COIN_TYPESCRIPT_DIGEST}; -use super::{native_coin, NeptuneCoins}; +use super::native_currency::{native_coin_program, NATIVE_COIN_TYPESCRIPT_DIGEST}; +use super::{native_currency, NeptuneCoins}; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; @@ -64,7 +64,7 @@ impl Utxo { Self::new( lock_script, vec![Coin { - type_script_hash: native_coin::NATIVE_COIN_TYPESCRIPT_DIGEST, + type_script_hash: native_currency::NATIVE_COIN_TYPESCRIPT_DIGEST, state: amount.encode(), }], ) diff --git a/src/models/state/wallet/wallet_state.rs b/src/models/state/wallet/wallet_state.rs index 294cb5a7..8776e5ca 100644 --- a/src/models/state/wallet/wallet_state.rs +++ b/src/models/state/wallet/wallet_state.rs @@ -27,7 +27,7 @@ use super::{WalletSecret, WALLET_INCOMING_SECRETS_FILE_NAME}; use crate::config_models::cli_args::Args; use crate::config_models::data_directory::DataDirectory; use crate::models::blockchain::block::Block; -use crate::models::blockchain::transaction::native_coin::NATIVE_COIN_TYPESCRIPT_DIGEST; +use crate::models::blockchain::transaction::native_currency::NATIVE_COIN_TYPESCRIPT_DIGEST; use crate::models::blockchain::transaction::utxo::{LockScript, Utxo}; use crate::models::blockchain::transaction::{neptune_coins::NeptuneCoins, Transaction}; use crate::models::state::wallet::monitored_utxo::MonitoredUtxo; From ac0637b9c269e8ad7dbf945ec4c92acd77c6f21c Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Thu, 8 Feb 2024 16:29:49 +0100 Subject: [PATCH 02/33] refactor: Add `type_scripts` directory Both `native_currency` and `neptune_coin` belong in the new directory. Additionally, this directory anticipates a the new typescript, `time_lock`. --- src/bin/dashboard_src/history_screen.rs | 6 +- src/bin/dashboard_src/overview_screen.rs | 2 +- src/bin/dashboard_src/send_screen.rs | 2 +- src/bin/neptune-cli.rs | 2 +- src/mine_loop.rs | 3 +- src/models/blockchain.rs | 1 + src/models/blockchain/block/mod.rs | 3 +- src/models/blockchain/transaction/mod.rs | 9 ++- .../transaction/transaction_kernel.rs | 7 ++- src/models/blockchain/transaction/utxo.rs | 51 ++-------------- .../validity/kernel_to_type_scripts.rs | 2 +- .../transaction/validity/typescripts_halt.rs | 6 +- src/models/blockchain/type_scripts/mod.rs | 58 +++++++++++++++++++ .../native_currency.rs | 15 +++-- .../neptune_coins.rs | 6 +- src/models/state/archival_state.rs | 3 +- src/models/state/mempool.rs | 10 ++-- src/models/state/mod.rs | 6 +- .../wallet/address/generation_address.rs | 5 +- src/models/state/wallet/mod.rs | 2 +- .../state/wallet/utxo_notification_pool.rs | 4 +- src/models/state/wallet/wallet_state.rs | 5 +- src/models/state/wallet/wallet_status.rs | 3 +- src/rpc_server.rs | 2 +- src/tests/shared.rs | 6 +- .../mutator_set/mutator_set_kernel.rs | 2 +- 26 files changed, 123 insertions(+), 98 deletions(-) create mode 100644 src/models/blockchain/type_scripts/mod.rs rename src/models/blockchain/{transaction => type_scripts}/native_currency.rs (95%) rename src/models/blockchain/{transaction => type_scripts}/neptune_coins.rs (98%) diff --git a/src/bin/dashboard_src/history_screen.rs b/src/bin/dashboard_src/history_screen.rs index d7324ed5..10aaab2d 100644 --- a/src/bin/dashboard_src/history_screen.rs +++ b/src/bin/dashboard_src/history_screen.rs @@ -8,8 +8,10 @@ use super::{dashboard_app::DashboardEvent, screen::Screen}; use crossterm::event::{Event, KeyCode, KeyEventKind}; use itertools::Itertools; use neptune_core::{ - models::blockchain::block::block_height::BlockHeight, - models::blockchain::transaction::neptune_coins::NeptuneCoins, rpc_server::RPCClient, + models::blockchain::{ + block::block_height::BlockHeight, type_scripts::neptune_coins::NeptuneCoins, + }, + rpc_server::RPCClient, }; use num_traits::{CheckedSub, Zero}; use ratatui::{ diff --git a/src/bin/dashboard_src/overview_screen.rs b/src/bin/dashboard_src/overview_screen.rs index fad4ec41..0697b3d0 100644 --- a/src/bin/dashboard_src/overview_screen.rs +++ b/src/bin/dashboard_src/overview_screen.rs @@ -1,3 +1,4 @@ +use neptune_core::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use neptune_core::prelude::twenty_first; use std::net::SocketAddr; @@ -15,7 +16,6 @@ use neptune_core::config_models::network::Network; use neptune_core::models::blockchain::block::block_header::BlockHeader; use neptune_core::models::blockchain::block::block_height::BlockHeight; use neptune_core::models::blockchain::shared::Hash; -use neptune_core::models::blockchain::transaction::neptune_coins::NeptuneCoins; use neptune_core::rpc_server::RPCClient; use num_traits::Zero; use ratatui::{ diff --git a/src/bin/dashboard_src/send_screen.rs b/src/bin/dashboard_src/send_screen.rs index f7b20d54..ce20ee89 100644 --- a/src/bin/dashboard_src/send_screen.rs +++ b/src/bin/dashboard_src/send_screen.rs @@ -14,7 +14,7 @@ use crossterm::event::{Event, KeyCode, KeyEventKind}; use neptune_core::{ config_models::network::Network, models::{ - blockchain::transaction::neptune_coins::NeptuneCoins, + blockchain::type_scripts::neptune_coins::NeptuneCoins, state::wallet::address::generation_address, }, rpc_server::RPCClient, diff --git a/src/bin/neptune-cli.rs b/src/bin/neptune-cli.rs index c99fe873..a3daf91f 100644 --- a/src/bin/neptune-cli.rs +++ b/src/bin/neptune-cli.rs @@ -1,3 +1,4 @@ +use neptune_core::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use neptune_core::prelude::twenty_first; use anyhow::{bail, Result}; @@ -6,7 +7,6 @@ use clap_complete::{generate, Shell}; use neptune_core::config_models::data_directory::DataDirectory; use neptune_core::config_models::network::Network; -use neptune_core::models::blockchain::transaction::neptune_coins::NeptuneCoins; use neptune_core::models::state::wallet::address::generation_address; use neptune_core::models::state::wallet::WalletSecret; use std::io; diff --git a/src/mine_loop.rs b/src/mine_loop.rs index 56a0eac3..52961c53 100644 --- a/src/mine_loop.rs +++ b/src/mine_loop.rs @@ -4,11 +4,12 @@ use crate::models::blockchain::block::block_height::BlockHeight; use crate::models::blockchain::block::mutator_set_update::*; use crate::models::blockchain::block::*; use crate::models::blockchain::shared::*; -use crate::models::blockchain::transaction::neptune_coins::NeptuneCoins; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; use crate::models::blockchain::transaction::utxo::*; use crate::models::blockchain::transaction::validity::TransactionValidationLogic; use crate::models::blockchain::transaction::*; +use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; +use crate::models::blockchain::type_scripts::TypeScript; use crate::models::channel::*; use crate::models::consensus::mast_hash::MastHash; use crate::models::shared::SIZE_20MB_IN_BYTES; diff --git a/src/models/blockchain.rs b/src/models/blockchain.rs index b3b96d08..bd946336 100644 --- a/src/models/blockchain.rs +++ b/src/models/blockchain.rs @@ -1,3 +1,4 @@ pub mod block; pub mod shared; pub mod transaction; +pub mod type_scripts; diff --git a/src/models/blockchain/block/mod.rs b/src/models/blockchain/block/mod.rs index 708646af..09bb4859 100644 --- a/src/models/blockchain/block/mod.rs +++ b/src/models/blockchain/block/mod.rs @@ -41,7 +41,8 @@ use self::mutator_set_update::MutatorSetUpdate; use self::transfer_block::TransferBlock; use super::transaction::transaction_kernel::TransactionKernel; use super::transaction::utxo::Utxo; -use super::transaction::{neptune_coins::NeptuneCoins, Transaction}; +use super::transaction::Transaction; +use super::type_scripts::neptune_coins::NeptuneCoins; use crate::models::blockchain::shared::Hash; use crate::models::consensus::Witness; use crate::models::state::wallet::address::generation_address::{self, ReceivingAddress}; diff --git a/src/models/blockchain/transaction/mod.rs b/src/models/blockchain/transaction/mod.rs index 6e496a9c..c674686f 100644 --- a/src/models/blockchain/transaction/mod.rs +++ b/src/models/blockchain/transaction/mod.rs @@ -1,8 +1,6 @@ use crate::models::consensus::mast_hash::MastHash; use crate::prelude::{triton_vm, twenty_first}; -pub mod native_currency; -pub mod neptune_coins; pub mod transaction_kernel; pub mod utxo; pub mod validity; @@ -24,13 +22,13 @@ use twenty_first::shared_math::bfield_codec::BFieldCodec; use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; use twenty_first::util_types::emojihash_trait::Emojihash; -use self::native_currency::native_coin_program; -use self::neptune_coins::NeptuneCoins; use self::transaction_kernel::TransactionKernel; -use self::utxo::{LockScript, TypeScript, Utxo}; +use self::utxo::{LockScript, Utxo}; use self::validity::TransactionValidationLogic; use super::block::Block; use super::shared::Hash; +use super::type_scripts::native_currency::native_coin_program; +use super::type_scripts::TypeScript; use crate::util_types::mutator_set::addition_record::AdditionRecord; use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; use crate::util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulator; @@ -518,6 +516,7 @@ mod transaction_tests { use super::*; use crate::{ + models::blockchain::type_scripts::neptune_coins::NeptuneCoins, tests::shared::make_mock_transaction, util_types::mutator_set::mutator_set_trait::commit, }; diff --git a/src/models/blockchain/transaction/transaction_kernel.rs b/src/models/blockchain/transaction/transaction_kernel.rs index 6228ca9f..e95d65e5 100644 --- a/src/models/blockchain/transaction/transaction_kernel.rs +++ b/src/models/blockchain/transaction/transaction_kernel.rs @@ -1,5 +1,8 @@ use crate::{ - models::consensus::mast_hash::{HasDiscriminant, MastHash}, + models::{ + blockchain::type_scripts::neptune_coins::{pseudorandom_amount, NeptuneCoins}, + consensus::mast_hash::{HasDiscriminant, MastHash}, + }, prelude::twenty_first, }; @@ -12,7 +15,7 @@ use twenty_first::shared_math::{ b_field_element::BFieldElement, bfield_codec::BFieldCodec, tip5::Digest, }; -use super::{neptune_coins::pseudorandom_amount, NeptuneCoins, PublicAnnouncement}; +use super::PublicAnnouncement; use crate::util_types::mutator_set::{ addition_record::{pseudorandom_addition_record, AdditionRecord}, removal_record::{pseudorandom_removal_record, RemovalRecord}, diff --git a/src/models/blockchain/transaction/utxo.rs b/src/models/blockchain/transaction/utxo.rs index ade75228..99590f23 100644 --- a/src/models/blockchain/transaction/utxo.rs +++ b/src/models/blockchain/transaction/utxo.rs @@ -1,6 +1,8 @@ +use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::prelude::{triton_vm, twenty_first}; use crate::models::blockchain::shared::Hash; +use crate::models::blockchain::type_scripts::native_currency; use get_size::GetSize; use num_traits::Zero; use rand::rngs::StdRng; @@ -13,8 +15,7 @@ use triton_vm::triton_asm; use twenty_first::shared_math::bfield_codec::BFieldCodec; use twenty_first::shared_math::tip5::Digest; -use super::native_currency::{native_coin_program, NATIVE_COIN_TYPESCRIPT_DIGEST}; -use super::{native_currency, NeptuneCoins}; +use crate::models::blockchain::type_scripts::native_currency::NATIVE_COIN_TYPESCRIPT_DIGEST; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; @@ -142,52 +143,10 @@ impl LockScript { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] -pub struct TypeScript { - pub program: Program, -} - -// Standard hash needed for filtering out duplicates. -impl std::hash::Hash for TypeScript { - fn hash(&self, state: &mut H) { - self.program.instructions.hash(state); - } -} - -impl From> for TypeScript { - fn from(instrs: Vec) -> Self { - Self { - program: Program::new(&instrs), - } - } -} - -impl From<&[LabelledInstruction]> for TypeScript { - fn from(instrs: &[LabelledInstruction]) -> Self { - Self { - program: Program::new(instrs), - } - } -} - -impl TypeScript { - pub fn new(program: Program) -> Self { - Self { program } - } - - pub fn hash(&self) -> Digest { - self.program.hash::() - } - - pub fn native_coin() -> Self { - Self { - program: native_coin_program(), - } - } -} - #[cfg(test)] mod utxo_tests { + use crate::models::blockchain::type_scripts::TypeScript; + use super::*; use rand::{thread_rng, Rng}; use tracing_test::traced_test; diff --git a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs index 0ccdfdb6..8f523b0a 100644 --- a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs +++ b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs @@ -1,8 +1,8 @@ use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; +use crate::models::blockchain::type_scripts::TypeScript; use crate::models::consensus::mast_hash::MastHash; use crate::prelude::{triton_vm, twenty_first}; -use crate::models::blockchain::transaction::utxo::TypeScript; use crate::models::blockchain::transaction::TransactionPrimitiveWitness; use crate::models::consensus::{ClaimSupport, SecretWitness, SupportedClaim, ValidationLogic}; diff --git a/src/models/blockchain/transaction/validity/typescripts_halt.rs b/src/models/blockchain/transaction/validity/typescripts_halt.rs index cfc332bd..02965cc8 100644 --- a/src/models/blockchain/transaction/validity/typescripts_halt.rs +++ b/src/models/blockchain/transaction/validity/typescripts_halt.rs @@ -1,5 +1,5 @@ use crate::{ - models::consensus::mast_hash::MastHash, + models::{blockchain::type_scripts::TypeScript, consensus::mast_hash::MastHash}, prelude::{triton_vm, twenty_first}, }; @@ -11,9 +11,7 @@ use twenty_first::shared_math::bfield_codec::BFieldCodec; use crate::models::{ blockchain::transaction::{ - transaction_kernel::TransactionKernel, - utxo::{TypeScript, Utxo}, - TransactionPrimitiveWitness, + transaction_kernel::TransactionKernel, utxo::Utxo, TransactionPrimitiveWitness, }, consensus::{ClaimSupport, SecretWitness, SupportedClaim, ValidationLogic}, }; diff --git a/src/models/blockchain/type_scripts/mod.rs b/src/models/blockchain/type_scripts/mod.rs new file mode 100644 index 00000000..ab727596 --- /dev/null +++ b/src/models/blockchain/type_scripts/mod.rs @@ -0,0 +1,58 @@ +use crate::Hash; +use get_size::GetSize; +use serde::{Deserialize, Serialize}; +use std::hash::{Hash as StdHash, Hasher as StdHasher}; +use tasm_lib::{ + triton_vm::{instruction::LabelledInstruction, program::Program}, + twenty_first::shared_math::bfield_codec::BFieldCodec, + Digest, +}; + +use native_currency::native_coin_program; + +pub mod native_currency; +pub mod neptune_coins; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] +pub struct TypeScript { + pub program: Program, +} + +// Standard hash needed for filtering out duplicates. +impl StdHash for TypeScript { + fn hash(&self, state: &mut H) { + self.program.instructions.hash(state); + } +} + +impl From> for TypeScript { + fn from(instrs: Vec) -> Self { + Self { + program: Program::new(&instrs), + } + } +} + +impl From<&[LabelledInstruction]> for TypeScript { + fn from(instrs: &[LabelledInstruction]) -> Self { + Self { + program: Program::new(instrs), + } + } +} + +impl TypeScript { + pub fn new(program: Program) -> Self { + Self { program } + } + + pub fn hash(&self) -> Digest { + self.program.hash::() + } + + pub fn native_coin() -> Self { + Self { + program: native_coin_program(), + } + } +} diff --git a/src/models/blockchain/transaction/native_currency.rs b/src/models/blockchain/type_scripts/native_currency.rs similarity index 95% rename from src/models/blockchain/transaction/native_currency.rs rename to src/models/blockchain/type_scripts/native_currency.rs index 0bf9fc0a..40539aa9 100644 --- a/src/models/blockchain/transaction/native_currency.rs +++ b/src/models/blockchain/type_scripts/native_currency.rs @@ -1,4 +1,7 @@ -use crate::prelude::{triton_vm, twenty_first}; +use crate::{ + models::blockchain::type_scripts::TypeScript, + prelude::{triton_vm, twenty_first}, +}; use anyhow::bail; use num_traits::Zero; @@ -12,13 +15,9 @@ use twenty_first::{ }, }; -use crate::models::blockchain::{ - shared::Hash, - transaction::{ - neptune_coins::NeptuneCoins, - utxo::{TypeScript, Utxo}, - }, -}; +use crate::models::blockchain::{shared::Hash, transaction::utxo::Utxo}; + +use super::neptune_coins::NeptuneCoins; pub const NATIVE_COIN_TYPESCRIPT_DIGEST: Digest = Digest::new([ BFieldElement::new(4843866011885844809), diff --git a/src/models/blockchain/transaction/neptune_coins.rs b/src/models/blockchain/type_scripts/neptune_coins.rs similarity index 98% rename from src/models/blockchain/transaction/neptune_coins.rs rename to src/models/blockchain/type_scripts/neptune_coins.rs index 1fcc61dc..55a36a25 100644 --- a/src/models/blockchain/transaction/neptune_coins.rs +++ b/src/models/blockchain/type_scripts/neptune_coins.rs @@ -1,4 +1,4 @@ -use crate::prelude::twenty_first; +use crate::{models::blockchain::transaction::utxo::Coin, prelude::twenty_first}; use anyhow::bail; use get_size::GetSize; @@ -16,7 +16,7 @@ use std::{ }; use twenty_first::{amount::u32s::U32s, shared_math::bfield_codec::BFieldCodec}; -use super::{native_currency::NATIVE_COIN_TYPESCRIPT_DIGEST, utxo::Coin}; +use super::native_currency::NATIVE_COIN_TYPESCRIPT_DIGEST; const NUM_LIMBS: usize = 4; @@ -307,8 +307,6 @@ mod amount_tests { use rand::{thread_rng, Rng, RngCore}; use std::{ops::ShlAssign, str::FromStr}; - use crate::models::blockchain::transaction::neptune_coins::NeptuneCoins; - use super::*; #[test] diff --git a/src/models/state/archival_state.rs b/src/models/state/archival_state.rs index 337d0dec..29315d52 100644 --- a/src/models/state/archival_state.rs +++ b/src/models/state/archival_state.rs @@ -791,8 +791,9 @@ mod archival_state_tests { use crate::config_models::network::Network; use crate::models::blockchain::transaction::utxo::LockScript; + use crate::models::blockchain::transaction::utxo::Utxo; use crate::models::blockchain::transaction::PublicAnnouncement; - use crate::models::blockchain::transaction::{neptune_coins::NeptuneCoins, utxo::Utxo}; + use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::models::consensus::mast_hash::MastHash; use crate::models::state::archival_state::ArchivalState; use crate::models::state::wallet::utxo_notification_pool::UtxoNotifier; diff --git a/src/models/state/mempool.rs b/src/models/state/mempool.rs index 9209d2b7..ff560a3c 100644 --- a/src/models/state/mempool.rs +++ b/src/models/state/mempool.rs @@ -9,7 +9,8 @@ //! density'. use crate::{ - prelude::twenty_first, util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulator, + models::blockchain::type_scripts::neptune_coins::NeptuneCoins, prelude::twenty_first, + util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulator, }; use bytesize::ByteSize; @@ -26,7 +27,7 @@ use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; use crate::models::blockchain::block::Block; use crate::models::blockchain::shared::Hash; -use crate::models::blockchain::transaction::{neptune_coins::NeptuneCoins, Transaction}; +use crate::models::blockchain::transaction::Transaction; /// `FeeDensity` is a measure of 'Fee/Bytes' or 'reward per storage unit' for a /// transactions. Different strategies are possible for selecting transactions @@ -393,9 +394,8 @@ mod tests { models::{ blockchain::{ block::block_height::BlockHeight, - transaction::{ - neptune_coins::NeptuneCoins, utxo::Utxo, PublicAnnouncement, Transaction, - }, + transaction::{utxo::Utxo, PublicAnnouncement, Transaction}, + type_scripts::neptune_coins::NeptuneCoins, }, shared::SIZE_20MB_IN_BYTES, state::{ diff --git a/src/models/state/mod.rs b/src/models/state/mod.rs index afd8b08f..96d89304 100644 --- a/src/models/state/mod.rs +++ b/src/models/state/mod.rs @@ -27,10 +27,12 @@ use self::wallet::wallet_status::WalletStatus; use super::blockchain::block::block_height::BlockHeight; use super::blockchain::block::Block; use super::blockchain::transaction::transaction_kernel::TransactionKernel; -use super::blockchain::transaction::utxo::{LockScript, TypeScript, Utxo}; +use super::blockchain::transaction::utxo::{LockScript, Utxo}; use super::blockchain::transaction::validity::TransactionValidationLogic; -use super::blockchain::transaction::{neptune_coins::NeptuneCoins, Transaction}; +use super::blockchain::transaction::Transaction; use super::blockchain::transaction::{PublicAnnouncement, TransactionPrimitiveWitness}; +use super::blockchain::type_scripts::neptune_coins::NeptuneCoins; +use super::blockchain::type_scripts::TypeScript; use super::consensus::ValidationLogic; use crate::config_models::cli_args; use crate::models::consensus::Witness; diff --git a/src/models/state/wallet/address/generation_address.rs b/src/models/state/wallet/address/generation_address.rs index f38999a0..0da98b3c 100644 --- a/src/models/state/wallet/address/generation_address.rs +++ b/src/models/state/wallet/address/generation_address.rs @@ -443,8 +443,7 @@ mod test_generation_addresses { use crate::{ config_models::network::Network, models::blockchain::{ - shared::Hash, - transaction::{neptune_coins::NeptuneCoins, utxo::Utxo}, + shared::Hash, transaction::utxo::Utxo, type_scripts::neptune_coins::NeptuneCoins, }, tests::shared::make_mock_transaction, }; @@ -539,7 +538,7 @@ mod test_generation_addresses { let spending_key = SpendingKey::derive_from_seed(seed); let receiving_address = ReceivingAddress::from_spending_key(&spending_key); - let amount: NeptuneCoins = NeptuneCoins::new(rng.gen_range(0..42000000)); + let amount = NeptuneCoins::new(rng.gen_range(0..42000000)); let coins = amount.to_native_coins(); let lock_script = receiving_address.lock_script(); let utxo = Utxo::new(lock_script, coins); diff --git a/src/models/state/wallet/mod.rs b/src/models/state/wallet/mod.rs index 858ed52f..923624c7 100644 --- a/src/models/state/wallet/mod.rs +++ b/src/models/state/wallet/mod.rs @@ -358,9 +358,9 @@ mod wallet_tests { use crate::models::blockchain::block::block_height::BlockHeight; use crate::models::blockchain::block::Block; use crate::models::blockchain::shared::Hash; - use crate::models::blockchain::transaction::neptune_coins::NeptuneCoins; use crate::models::blockchain::transaction::utxo::{LockScript, Utxo}; use crate::models::blockchain::transaction::PublicAnnouncement; + use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::models::state::wallet::utxo_notification_pool::UtxoNotifier; use crate::models::state::UtxoReceiverData; use crate::tests::shared::{ diff --git a/src/models/state/wallet/utxo_notification_pool.rs b/src/models/state/wallet/utxo_notification_pool.rs index 1e157339..f98d3b47 100644 --- a/src/models/state/wallet/utxo_notification_pool.rs +++ b/src/models/state/wallet/utxo_notification_pool.rs @@ -360,7 +360,9 @@ mod wallet_state_tests { use super::*; use crate::{ - models::blockchain::transaction::{neptune_coins::NeptuneCoins, utxo::LockScript}, + models::blockchain::{ + transaction::utxo::LockScript, type_scripts::neptune_coins::NeptuneCoins, + }, tests::shared::make_mock_transaction, }; diff --git a/src/models/state/wallet/wallet_state.rs b/src/models/state/wallet/wallet_state.rs index 8776e5ca..242a3c71 100644 --- a/src/models/state/wallet/wallet_state.rs +++ b/src/models/state/wallet/wallet_state.rs @@ -1,3 +1,4 @@ +use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::prelude::twenty_first; use anyhow::{bail, Result}; @@ -27,9 +28,9 @@ use super::{WalletSecret, WALLET_INCOMING_SECRETS_FILE_NAME}; use crate::config_models::cli_args::Args; use crate::config_models::data_directory::DataDirectory; use crate::models::blockchain::block::Block; -use crate::models::blockchain::transaction::native_currency::NATIVE_COIN_TYPESCRIPT_DIGEST; use crate::models::blockchain::transaction::utxo::{LockScript, Utxo}; -use crate::models::blockchain::transaction::{neptune_coins::NeptuneCoins, Transaction}; +use crate::models::blockchain::transaction::Transaction; +use crate::models::blockchain::type_scripts::native_currency::NATIVE_COIN_TYPESCRIPT_DIGEST; use crate::models::state::wallet::monitored_utxo::MonitoredUtxo; use crate::util_types::mutator_set::addition_record::AdditionRecord; use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; diff --git a/src/models/state/wallet/wallet_status.rs b/src/models/state/wallet/wallet_status.rs index ebf491db..7cf315a0 100644 --- a/src/models/state/wallet/wallet_status.rs +++ b/src/models/state/wallet/wallet_status.rs @@ -3,7 +3,8 @@ use std::fmt::Display; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use crate::models::blockchain::transaction::{neptune_coins::NeptuneCoins, utxo::Utxo}; +use crate::models::blockchain::transaction::utxo::Utxo; +use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/src/rpc_server.rs b/src/rpc_server.rs index 8d584db7..335300d4 100644 --- a/src/rpc_server.rs +++ b/src/rpc_server.rs @@ -1,3 +1,4 @@ +use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::prelude::twenty_first; use anyhow::Result; @@ -18,7 +19,6 @@ use crate::config_models::network::Network; use crate::models::blockchain::block::block_header::BlockHeader; use crate::models::blockchain::block::block_height::BlockHeight; use crate::models::blockchain::shared::Hash; -use crate::models::blockchain::transaction::neptune_coins::NeptuneCoins; use crate::models::blockchain::transaction::utxo::Utxo; use crate::models::channel::RPCServerToMain; use crate::models::peer::InstanceId; diff --git a/src/tests/shared.rs b/src/tests/shared.rs index 4a0f6c29..8fcc504d 100644 --- a/src/tests/shared.rs +++ b/src/tests/shared.rs @@ -1,3 +1,5 @@ +use crate::models::blockchain::type_scripts::neptune_coins::pseudorandom_amount; +use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::prelude::twenty_first; use anyhow::Result; @@ -49,19 +51,17 @@ use crate::models::blockchain::block::block_body::BlockBody; use crate::models::blockchain::block::block_header::BlockHeader; use crate::models::blockchain::block::block_header::TARGET_BLOCK_INTERVAL; use crate::models::blockchain::block::{block_height::BlockHeight, Block}; -use crate::models::blockchain::transaction::neptune_coins::pseudorandom_amount; -use crate::models::blockchain::transaction::neptune_coins::NeptuneCoins; use crate::models::blockchain::transaction::transaction_kernel::pseudorandom_option; use crate::models::blockchain::transaction::transaction_kernel::pseudorandom_public_announcement; use crate::models::blockchain::transaction::transaction_kernel::pseudorandom_transaction_kernel; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; -use crate::models::blockchain::transaction::utxo::TypeScript; use crate::models::blockchain::transaction::validity::removal_records_integrity::RemovalRecordsIntegrityWitness; use crate::models::blockchain::transaction::validity::TransactionValidationLogic; use crate::models::blockchain::transaction::PublicAnnouncement; use crate::models::blockchain::transaction::TransactionPrimitiveWitness; use crate::models::blockchain::transaction::TransactionWitness; use crate::models::blockchain::transaction::{utxo::Utxo, Transaction}; +use crate::models::blockchain::type_scripts::TypeScript; use crate::models::channel::{MainToPeerThread, PeerThreadToMain}; use crate::models::database::BlockIndexKey; use crate::models::database::BlockIndexValue; diff --git a/src/util_types/mutator_set/mutator_set_kernel.rs b/src/util_types/mutator_set/mutator_set_kernel.rs index 135b53ab..7c3ae0bd 100644 --- a/src/util_types/mutator_set/mutator_set_kernel.rs +++ b/src/util_types/mutator_set/mutator_set_kernel.rs @@ -567,9 +567,9 @@ mod accumulation_scheme_tests { use crate::config_models::network::Network; use crate::models::blockchain::block::Block; - use crate::models::blockchain::transaction::neptune_coins::NeptuneCoins; use crate::models::blockchain::transaction::utxo::Utxo; use crate::models::blockchain::transaction::PublicAnnouncement; + use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::models::state::wallet::utxo_notification_pool::UtxoNotifier; use crate::models::state::wallet::WalletSecret; use crate::models::state::UtxoReceiverData; From e1bc045508d903d22c78a094b6bc0db8865dc6d4 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Thu, 8 Feb 2024 16:43:05 +0100 Subject: [PATCH 03/33] refactor: Finish renaming "Native coin" is now "native currency". --- src/models/blockchain/transaction/mod.rs | 6 +++--- src/models/blockchain/transaction/utxo.rs | 6 +++--- src/models/blockchain/type_scripts/mod.rs | 4 ++-- .../blockchain/type_scripts/native_currency.rs | 12 ++++++------ src/models/blockchain/type_scripts/neptune_coins.rs | 4 ++-- src/models/state/wallet/wallet_state.rs | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/models/blockchain/transaction/mod.rs b/src/models/blockchain/transaction/mod.rs index c674686f..675e924c 100644 --- a/src/models/blockchain/transaction/mod.rs +++ b/src/models/blockchain/transaction/mod.rs @@ -27,7 +27,7 @@ use self::utxo::{LockScript, Utxo}; use self::validity::TransactionValidationLogic; use super::block::Block; use super::shared::Hash; -use super::type_scripts::native_currency::native_coin_program; +use super::type_scripts::native_currency::native_currency_program; use super::type_scripts::TypeScript; use crate::util_types::mutator_set::addition_record::AdditionRecord; use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; @@ -389,11 +389,11 @@ impl Transaction { // verify type scripts for type_script_hash in type_scripts { - let type_script = if type_script_hash != native_coin_program().hash::() { + let type_script = if type_script_hash != native_currency_program().hash::() { warn!("Observed non-native type script: {} Non-native type scripts are not supported yet.", type_script_hash.emojihash()); continue; } else { - native_coin_program() + native_currency_program() }; let public_input = self.kernel.mast_hash().encode(); diff --git a/src/models/blockchain/transaction/utxo.rs b/src/models/blockchain/transaction/utxo.rs index 99590f23..ded285e6 100644 --- a/src/models/blockchain/transaction/utxo.rs +++ b/src/models/blockchain/transaction/utxo.rs @@ -15,7 +15,7 @@ use triton_vm::triton_asm; use twenty_first::shared_math::bfield_codec::BFieldCodec; use twenty_first::shared_math::tip5::Digest; -use crate::models::blockchain::type_scripts::native_currency::NATIVE_COIN_TYPESCRIPT_DIGEST; +use crate::models::blockchain::type_scripts::native_currency::NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; @@ -65,7 +65,7 @@ impl Utxo { Self::new( lock_script, vec![Coin { - type_script_hash: native_currency::NATIVE_COIN_TYPESCRIPT_DIGEST, + type_script_hash: native_currency::NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST, state: amount.encode(), }], ) @@ -74,7 +74,7 @@ impl Utxo { pub fn get_native_coin_amount(&self) -> NeptuneCoins { self.coins .iter() - .filter(|coin| coin.type_script_hash == NATIVE_COIN_TYPESCRIPT_DIGEST) + .filter(|coin| coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST) .map(|coin| match NeptuneCoins::decode(&coin.state) { Ok(boxed_amount) => *boxed_amount, Err(_) => NeptuneCoins::zero(), diff --git a/src/models/blockchain/type_scripts/mod.rs b/src/models/blockchain/type_scripts/mod.rs index ab727596..5e34ad69 100644 --- a/src/models/blockchain/type_scripts/mod.rs +++ b/src/models/blockchain/type_scripts/mod.rs @@ -8,7 +8,7 @@ use tasm_lib::{ Digest, }; -use native_currency::native_coin_program; +use native_currency::native_currency_program; pub mod native_currency; pub mod neptune_coins; @@ -52,7 +52,7 @@ impl TypeScript { pub fn native_coin() -> Self { Self { - program: native_coin_program(), + program: native_currency_program(), } } } diff --git a/src/models/blockchain/type_scripts/native_currency.rs b/src/models/blockchain/type_scripts/native_currency.rs index 40539aa9..6aba2bce 100644 --- a/src/models/blockchain/type_scripts/native_currency.rs +++ b/src/models/blockchain/type_scripts/native_currency.rs @@ -19,7 +19,7 @@ use crate::models::blockchain::{shared::Hash, transaction::utxo::Utxo}; use super::neptune_coins::NeptuneCoins; -pub const NATIVE_COIN_TYPESCRIPT_DIGEST: Digest = Digest::new([ +pub const NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST: Digest = Digest::new([ BFieldElement::new(4843866011885844809), BFieldElement::new(16618866032559590857), BFieldElement::new(18247689143239181392), @@ -27,12 +27,12 @@ pub const NATIVE_COIN_TYPESCRIPT_DIGEST: Digest = Digest::new([ BFieldElement::new(9104890367162237026), ]); -pub fn native_coin_program() -> Program { +pub fn native_currency_program() -> Program { // todo: insert inflation check logic here Program::new(&triton_asm!(halt)) } -pub fn native_coin_reference( +pub fn native_currency_rust_shadow( public_input: &mut VecDeque, secret_input: &mut VecDeque, _output: &mut VecDeque, @@ -143,10 +143,10 @@ mod tests_native_coin { #[test] fn hash_is_really_hash() { - let calculated_digest = native_coin_program().hash::(); + let calculated_digest = native_currency_program().hash::(); assert_eq!( - calculated_digest, NATIVE_COIN_TYPESCRIPT_DIGEST, - "\ncalculated: ({calculated_digest})\nhardcoded: ({NATIVE_COIN_TYPESCRIPT_DIGEST})" + calculated_digest, NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST, + "\ncalculated: ({calculated_digest})\nhardcoded: ({NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST})" ); } } diff --git a/src/models/blockchain/type_scripts/neptune_coins.rs b/src/models/blockchain/type_scripts/neptune_coins.rs index 55a36a25..479fea5e 100644 --- a/src/models/blockchain/type_scripts/neptune_coins.rs +++ b/src/models/blockchain/type_scripts/neptune_coins.rs @@ -16,7 +16,7 @@ use std::{ }; use twenty_first::{amount::u32s::U32s, shared_math::bfield_codec::BFieldCodec}; -use super::native_currency::NATIVE_COIN_TYPESCRIPT_DIGEST; +use super::native_currency::NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST; const NUM_LIMBS: usize = 4; @@ -68,7 +68,7 @@ impl NeptuneCoins { /// Create a `coins` object for use in a UTXO pub fn to_native_coins(&self) -> Vec { let dictionary = vec![Coin { - type_script_hash: NATIVE_COIN_TYPESCRIPT_DIGEST, + type_script_hash: NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST, state: self.encode(), }]; dictionary diff --git a/src/models/state/wallet/wallet_state.rs b/src/models/state/wallet/wallet_state.rs index 242a3c71..7c789957 100644 --- a/src/models/state/wallet/wallet_state.rs +++ b/src/models/state/wallet/wallet_state.rs @@ -30,7 +30,7 @@ use crate::config_models::data_directory::DataDirectory; use crate::models::blockchain::block::Block; use crate::models::blockchain::transaction::utxo::{LockScript, Utxo}; use crate::models::blockchain::transaction::Transaction; -use crate::models::blockchain::type_scripts::native_currency::NATIVE_COIN_TYPESCRIPT_DIGEST; +use crate::models::blockchain::type_scripts::native_currency::NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST; use crate::models::state::wallet::monitored_utxo::MonitoredUtxo; use crate::util_types::mutator_set::addition_record::AdditionRecord; use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; @@ -429,7 +429,7 @@ impl WalletState { new_block.kernel.header.height, utxo.coins .iter() - .filter(|coin| coin.type_script_hash == NATIVE_COIN_TYPESCRIPT_DIGEST) + .filter(|coin| coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST) .map(|coin| *NeptuneCoins::decode(&coin.state) .expect("Failed to decode coin state as amount")) .sum::(), From c4ce58bbbd3127f9e688988dc1a5486e752169d8 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Thu, 8 Feb 2024 17:29:54 +0100 Subject: [PATCH 04/33] Add stub `time_lock.rs` --- src/models/blockchain/type_scripts/mod.rs | 16 ++++- .../blockchain/type_scripts/time_lock.rs | 67 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/models/blockchain/type_scripts/time_lock.rs diff --git a/src/models/blockchain/type_scripts/mod.rs b/src/models/blockchain/type_scripts/mod.rs index 5e34ad69..5351ff19 100644 --- a/src/models/blockchain/type_scripts/mod.rs +++ b/src/models/blockchain/type_scripts/mod.rs @@ -1,4 +1,7 @@ -use crate::Hash; +use crate::{ + models::consensus::{SecretWitness, ValidationLogic}, + Hash, +}; use get_size::GetSize; use serde::{Deserialize, Serialize}; use std::hash::{Hash as StdHash, Hasher as StdHasher}; @@ -10,8 +13,19 @@ use tasm_lib::{ use native_currency::native_currency_program; +use super::transaction::TransactionPrimitiveWitness; + pub mod native_currency; pub mod neptune_coins; +pub mod time_lock; + +trait TypeScriptValidationLogic: + ValidationLogic<(TransactionPrimitiveWitness, ExternalWitness)> +where + ExternalWitness: BFieldCodec, + (TransactionPrimitiveWitness, ExternalWitness): SecretWitness, +{ +} #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] pub struct TypeScript { diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs new file mode 100644 index 00000000..adeea34d --- /dev/null +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -0,0 +1,67 @@ +use chrono::Duration; +use get_size::GetSize; +use serde::{Deserialize, Serialize}; +use tasm_lib::twenty_first::shared_math::bfield_codec::BFieldCodec; + +use crate::models::{ + blockchain::transaction::{transaction_kernel::TransactionKernel, TransactionPrimitiveWitness}, + consensus::{SecretWitness, ValidationLogic}, +}; + +pub struct TimeLock { + /// Duration since the unix epoch (00:00 am on Jan 1 1970). + release_date: Duration, +} + +impl TimeLock { + pub fn until(date: Duration) -> TimeLock { + Self { release_date: date } + } +} + +#[derive(BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize, Clone)] +struct NoExternalWitness; + +impl SecretWitness for (TransactionPrimitiveWitness, NoExternalWitness) { + fn nondeterminism( + &self, + ) -> tasm_lib::prelude::triton_vm::program::NonDeterminism< + tasm_lib::prelude::twenty_first::prelude::BFieldElement, + > { + todo!() + } + + fn subprogram(&self) -> tasm_lib::prelude::triton_vm::program::Program { + todo!() + } +} + +// impl TypeScriptValidationLogic for TimeLock +// Not yet; type aliases are still experimental ;-) +impl ValidationLogic<(TransactionPrimitiveWitness, NoExternalWitness)> for TimeLock { + type PrimitiveWitness = (TransactionPrimitiveWitness, NoExternalWitness); + + type Kernel = TransactionKernel; + + fn subprogram(&self) -> tasm_lib::prelude::triton_vm::program::Program { + todo!() + } + + fn support( + &self, + ) -> crate::models::consensus::ClaimSupport<(TransactionPrimitiveWitness, NoExternalWitness)> + { + todo!() + } + + fn claim(&self) -> tasm_lib::prelude::triton_vm::proof::Claim { + todo!() + } + + fn new_from_primitive_witness( + primitive_witness: &Self::PrimitiveWitness, + tx_kernel: &Self::Kernel, + ) -> Self { + todo!() + } +} From dc1dfcf7105f0a3417510036eb344de3933fe7af Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Fri, 9 Feb 2024 15:37:09 +0100 Subject: [PATCH 05/33] refactor(consensus): Simplify `PrimitiveTransactionWitness` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also: add stub code for time lock type script. Hat-tip-to: Thorkil Værge --- src/mine_loop.rs | 26 +- src/models/blockchain/transaction/mod.rs | 26 +- src/models/blockchain/transaction/validity.rs | 18 +- .../validity/kernel_to_lock_scripts.rs | 16 +- .../validity/kernel_to_type_scripts.rs | 10 +- .../transaction/validity/lockscripts_halt.rs | 9 +- .../validity/removal_records_integrity.rs | 19 +- .../transaction/validity/typescripts_halt.rs | 13 +- .../blockchain/type_scripts/time_lock.rs | 307 ++++++++++++++++-- src/models/consensus/mod.rs | 6 +- src/models/state/mod.rs | 4 +- src/tests/shared.rs | 27 +- 12 files changed, 360 insertions(+), 121 deletions(-) diff --git a/src/mine_loop.rs b/src/mine_loop.rs index 52961c53..78c268a3 100644 --- a/src/mine_loop.rs +++ b/src/mine_loop.rs @@ -197,21 +197,19 @@ fn make_coinbase_transaction( receiver_digest, ); - let timestamp: BFieldElement = BFieldElement::new( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Got bad time timestamp in mining process") - .as_millis() - .try_into() - .expect("Must call this function before 584 million years from genesis."), - ); + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Got bad time timestamp in mining process") + .as_millis() + .try_into() + .expect("Must call this function before 584 million years from genesis."); let kernel = TransactionKernel { inputs: vec![], outputs: vec![coinbase_addition_record], public_announcements: vec![], fee: NeptuneCoins::zero(), - timestamp, + timestamp: BFieldElement::new(timestamp), coinbase: Some(coinbase_amount), mutator_set_hash: mutator_set_accumulator.hash(), }; @@ -223,15 +221,15 @@ fn make_coinbase_transaction( lock_script_witnesses: vec![], input_membership_proofs: vec![], output_utxos: vec![coinbase_utxo.clone()], - public_announcements: vec![], mutator_set_accumulator, + kernel, }; - let validity_logic = - TransactionValidationLogic::new_from_primitive_witness(&primitive_witness, &kernel); + let transaction_validation_logic = + TransactionValidationLogic::new_from_primitive_witness(&primitive_witness); ( Transaction { - kernel, - witness: TransactionWitness::ValidationLogic(validity_logic), + kernel: primitive_witness.kernel, + witness: TransactionWitness::ValidationLogic(transaction_validation_logic), }, sender_randomness, ) diff --git a/src/models/blockchain/transaction/mod.rs b/src/models/blockchain/transaction/mod.rs index 675e924c..6b23cb86 100644 --- a/src/models/blockchain/transaction/mod.rs +++ b/src/models/blockchain/transaction/mod.rs @@ -58,8 +58,8 @@ pub struct TransactionPrimitiveWitness { pub lock_script_witnesses: Vec>, pub input_membership_proofs: Vec, pub output_utxos: Vec, - pub public_announcements: Vec, pub mutator_set_accumulator: MutatorSetAccumulator, + pub kernel: TransactionKernel, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] @@ -240,6 +240,9 @@ impl Transaction { let merged_witness = match (&self.witness, &other.witness) { (Witness::Primitive(self_witness), Witness::Primitive(other_witness)) => { + if self_witness.kernel.mutator_set_hash != other_witness.kernel.mutator_set_hash { + error!("Cannot merge two transactions with distinct mutator set hashes."); + } Witness::Primitive(TransactionPrimitiveWitness { input_utxos: [ self_witness.input_utxos.clone(), @@ -273,12 +276,8 @@ impl Transaction { other_witness.output_utxos.clone(), ] .concat(), - public_announcements: [ - self_witness.public_announcements.clone(), - other_witness.public_announcements.clone(), - ] - .concat(), mutator_set_accumulator: self_witness.mutator_set_accumulator.clone(), + kernel: merged_kernel.clone(), }) } @@ -487,10 +486,23 @@ impl Transaction { #[cfg(test)] mod witness_tests { + use tasm_lib::Digest; + + use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; + use super::*; #[test] fn decode_encode_test_empty() { + let empty_kernel = TransactionKernel { + inputs: vec![], + outputs: vec![], + public_announcements: vec![], + fee: NeptuneCoins::new(0), + coinbase: None, + timestamp: BFieldElement::new(0), + mutator_set_hash: Digest::default(), + }; let primitive_witness = TransactionPrimitiveWitness { input_utxos: vec![], type_scripts: vec![], @@ -498,8 +510,8 @@ mod witness_tests { lock_script_witnesses: vec![], input_membership_proofs: vec![], output_utxos: vec![], - public_announcements: vec![], mutator_set_accumulator: MutatorSetAccumulator::new(), + kernel: empty_kernel, }; let encoded = primitive_witness.encode(); diff --git a/src/models/blockchain/transaction/validity.rs b/src/models/blockchain/transaction/validity.rs index 32cf405a..2a49a672 100644 --- a/src/models/blockchain/transaction/validity.rs +++ b/src/models/blockchain/transaction/validity.rs @@ -21,7 +21,6 @@ use self::{ kernel_to_lock_scripts::KernelToLockScripts, kernel_to_type_scripts::KernelToTypeScripts, typescripts_halt::TypeScriptsHalt, }; -use super::transaction_kernel::TransactionKernel; use super::TransactionPrimitiveWitness; /// The validity of a transaction, in the base case, decomposes into @@ -45,20 +44,15 @@ pub struct TransactionValidationLogic { } impl TransactionValidationLogic { - pub fn new_from_primitive_witness( - primitive_witness: &TransactionPrimitiveWitness, - tx_kernel: &TransactionKernel, - ) -> Self { - let lock_scripts_halt = - LockScriptsHalt::new_from_primitive_witness(primitive_witness, tx_kernel); + pub fn new_from_primitive_witness(primitive_witness: &TransactionPrimitiveWitness) -> Self { + let lock_scripts_halt = LockScriptsHalt::new_from_primitive_witness(primitive_witness); let kernel_to_lock_scripts = - KernelToLockScripts::new_from_primitive_witness(primitive_witness, tx_kernel); + KernelToLockScripts::new_from_primitive_witness(primitive_witness); let removal_records_integrity = - RemovalRecordsIntegrity::new_from_primitive_witness(primitive_witness, tx_kernel); + RemovalRecordsIntegrity::new_from_primitive_witness(primitive_witness); let kernel_to_typescripts = - KernelToTypeScripts::new_from_primitive_witness(primitive_witness, tx_kernel); - let type_scripts_halt = - TypeScriptsHalt::new_from_primitive_witness(primitive_witness, tx_kernel); + KernelToTypeScripts::new_from_primitive_witness(primitive_witness); + let type_scripts_halt = TypeScriptsHalt::new_from_primitive_witness(primitive_witness); Self { lock_scripts_halt, kernel_to_lock_scripts, diff --git a/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs b/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs index ef21015f..4795bdb2 100644 --- a/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs +++ b/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs @@ -3,8 +3,7 @@ use crate::prelude::{triton_vm, twenty_first}; use crate::models::blockchain::transaction::TransactionPrimitiveWitness; use crate::models::blockchain::transaction::{ - transaction_kernel::{TransactionKernel, TransactionKernelField}, - utxo::Utxo, + transaction_kernel::TransactionKernelField, utxo::Utxo, }; use crate::models::consensus::{ClaimSupport, SupportedClaim}; @@ -50,14 +49,9 @@ impl KernelToLockScripts { impl ValidationLogic for KernelToLockScripts { type PrimitiveWitness = TransactionPrimitiveWitness; - type Kernel = TransactionKernel; - - fn new_from_primitive_witness( - primitive_witness: &TransactionPrimitiveWitness, - tx_kernel: &TransactionKernel, - ) -> Self { + fn new_from_primitive_witness(primitive_witness: &TransactionPrimitiveWitness) -> Self { let claim = Claim { - input: tx_kernel.mast_hash().into(), + input: primitive_witness.kernel.mast_hash().into(), output: primitive_witness .input_lock_scripts .iter() @@ -68,7 +62,9 @@ impl ValidationLogic for KernelToLockScripts { }; let _kernel_to_lock_scripts_witness = KernelToLockScriptsWitness { input_utxos: primitive_witness.input_utxos.clone(), - mast_path: tx_kernel.mast_path(TransactionKernelField::InputUtxos), + mast_path: primitive_witness + .kernel + .mast_path(TransactionKernelField::InputUtxos), }; let supported_claim = SupportedClaim { claim, diff --git a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs index 8f523b0a..2c72c341 100644 --- a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs +++ b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs @@ -1,4 +1,3 @@ -use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; use crate::models::blockchain::type_scripts::TypeScript; use crate::models::consensus::mast_hash::MastHash; use crate::prelude::{triton_vm, twenty_first}; @@ -47,12 +46,7 @@ impl KernelToTypeScripts { impl ValidationLogic for KernelToTypeScripts { type PrimitiveWitness = TransactionPrimitiveWitness; - type Kernel = TransactionKernel; - - fn new_from_primitive_witness( - primitive_witness: &crate::models::blockchain::transaction::TransactionPrimitiveWitness, - tx_kernel: &crate::models::blockchain::transaction::transaction_kernel::TransactionKernel, - ) -> Self { + fn new_from_primitive_witness(primitive_witness: &TransactionPrimitiveWitness) -> Self { let mut type_script_digests = primitive_witness .input_utxos .iter() @@ -67,7 +61,7 @@ impl ValidationLogic for KernelToTypeScripts { type_script_digests.sort(); type_script_digests.dedup(); let claim = Claim { - input: tx_kernel.mast_hash().values().to_vec(), + input: primitive_witness.kernel.mast_hash().values().to_vec(), output: type_script_digests .into_iter() .flat_map(|d| d.values().to_vec()) diff --git a/src/models/blockchain/transaction/validity/lockscripts_halt.rs b/src/models/blockchain/transaction/validity/lockscripts_halt.rs index 5f075b2b..0e60bbdd 100644 --- a/src/models/blockchain/transaction/validity/lockscripts_halt.rs +++ b/src/models/blockchain/transaction/validity/lockscripts_halt.rs @@ -10,9 +10,7 @@ use triton_vm::prelude::{BFieldElement, Claim, NonDeterminism, Program}; use twenty_first::shared_math::bfield_codec::BFieldCodec; use crate::models::{ - blockchain::transaction::{ - transaction_kernel::TransactionKernel, utxo::LockScript, TransactionPrimitiveWitness, - }, + blockchain::transaction::{utxo::LockScript, TransactionPrimitiveWitness}, consensus::{ClaimSupport, SecretWitness, SupportedClaim, ValidationLogic}, }; @@ -40,18 +38,15 @@ pub struct LockScriptsHalt { impl ValidationLogic for LockScriptsHalt { type PrimitiveWitness = TransactionPrimitiveWitness; - type Kernel = TransactionKernel; - fn new_from_primitive_witness( primitive_witness: &TransactionPrimitiveWitness, - tx_kernel: &TransactionKernel, ) -> LockScriptsHalt { let program_and_program_digests_and_spending_keys = primitive_witness .input_lock_scripts .iter() .zip_eq(primitive_witness.lock_script_witnesses.iter()) .map(|(lockscr, spendkey)| (lockscr, lockscr.hash(), spendkey)); - let tx_kernel_mast_hash = tx_kernel.mast_hash(); + let tx_kernel_mast_hash = primitive_witness.kernel.mast_hash(); let empty_string = vec![]; Self { diff --git a/src/models/blockchain/transaction/validity/removal_records_integrity.rs b/src/models/blockchain/transaction/validity/removal_records_integrity.rs index 6ab08eca..fa7d5de6 100644 --- a/src/models/blockchain/transaction/validity/removal_records_integrity.rs +++ b/src/models/blockchain/transaction/validity/removal_records_integrity.rs @@ -48,14 +48,11 @@ pub struct RemovalRecordsIntegrityWitness { } impl RemovalRecordsIntegrityWitness { - pub fn new( - primitive_witness: &TransactionPrimitiveWitness, - tx_kernel: &TransactionKernel, - ) -> Self { + pub fn new(primitive_witness: &TransactionPrimitiveWitness) -> Self { Self { input_utxos: primitive_witness.input_utxos.clone(), membership_proofs: primitive_witness.input_membership_proofs.clone(), - kernel: tx_kernel.to_owned(), + kernel: primitive_witness.kernel.clone(), aocl: primitive_witness .mutator_set_accumulator .kernel @@ -95,20 +92,16 @@ pub struct RemovalRecordsIntegrity { impl ValidationLogic for RemovalRecordsIntegrity { type PrimitiveWitness = TransactionPrimitiveWitness; - type Kernel = TransactionKernel; - - fn new_from_primitive_witness( - primitive_witness: &crate::models::blockchain::transaction::TransactionPrimitiveWitness, - tx_kernel: &crate::models::blockchain::transaction::transaction_kernel::TransactionKernel, - ) -> Self { + fn new_from_primitive_witness(primitive_witness: &TransactionPrimitiveWitness) -> Self { let removal_records_integrity_witness = - RemovalRecordsIntegrityWitness::new(primitive_witness, tx_kernel); + RemovalRecordsIntegrityWitness::new(primitive_witness); Self { supported_claim: SupportedClaim { claim: Claim { program_digest: Hash::hash_varlen(&Self::program().encode()), - input: tx_kernel + input: primitive_witness + .kernel .mast_hash() .values() .into_iter() diff --git a/src/models/blockchain/transaction/validity/typescripts_halt.rs b/src/models/blockchain/transaction/validity/typescripts_halt.rs index 02965cc8..90aee333 100644 --- a/src/models/blockchain/transaction/validity/typescripts_halt.rs +++ b/src/models/blockchain/transaction/validity/typescripts_halt.rs @@ -10,9 +10,7 @@ use triton_vm::prelude::{BFieldElement, Claim, NonDeterminism, Program}; use twenty_first::shared_math::bfield_codec::BFieldCodec; use crate::models::{ - blockchain::transaction::{ - transaction_kernel::TransactionKernel, utxo::Utxo, TransactionPrimitiveWitness, - }, + blockchain::transaction::{utxo::Utxo, TransactionPrimitiveWitness}, consensus::{ClaimSupport, SecretWitness, SupportedClaim, ValidationLogic}, }; @@ -50,14 +48,9 @@ impl TypeScriptsHalt { impl ValidationLogic for TypeScriptsHalt { type PrimitiveWitness = TransactionPrimitiveWitness; - type Kernel = TransactionKernel; - - fn new_from_primitive_witness( - primitive_witness: &TransactionPrimitiveWitness, - tx_kernel: &TransactionKernel, - ) -> Self { + fn new_from_primitive_witness(primitive_witness: &TransactionPrimitiveWitness) -> Self { let claim = Claim { - input: tx_kernel.mast_hash().values().to_vec(), + input: primitive_witness.kernel.mast_hash().values().to_vec(), output: vec![], program_digest: TypeScript::native_coin().hash(), }; diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index adeea34d..8da52912 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -1,37 +1,311 @@ -use chrono::Duration; use get_size::GetSize; use serde::{Deserialize, Serialize}; -use tasm_lib::twenty_first::shared_math::bfield_codec::BFieldCodec; +use tasm_lib::{ + triton_vm::{ + instruction::LabelledInstruction, + program::{NonDeterminism, Program}, + triton_asm, + }, + twenty_first::shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, +}; +use crate::models::consensus::mast_hash::MastHash; use crate::models::{ - blockchain::transaction::{transaction_kernel::TransactionKernel, TransactionPrimitiveWitness}, + blockchain::transaction::{ + transaction_kernel::TransactionKernelField, TransactionPrimitiveWitness, + }, consensus::{SecretWitness, ValidationLogic}, }; pub struct TimeLock { - /// Duration since the unix epoch (00:00 am on Jan 1 1970). - release_date: Duration, + /// milliseconds since the unix epoch (00:00 UTC am on Jan 1 1970). + release_date: u64, } impl TimeLock { - pub fn until(date: Duration) -> TimeLock { + /// Create a `TimeLock` type script that releases the coins at the given release date, + /// which corresponds to the number of milliseconds that passed since the unix epoch + /// started (00:00 am UTC on Jan 1 1970). + pub fn until(date: u64) -> TimeLock { Self { release_date: date } } + + pub fn code(&self) -> Vec { + let unlock_hi = self.release_date >> 32; + let unlock_lo = self.release_date & 0xffffffff; + + // Generated by tasm-lang compiler + // `cargo test -- --nocapture typescript_timelock_test` + // 2024-02-09 + // Adapted for dynamic unlock date + triton_asm! { + call main + halt + main: + push {unlock_hi} + push {unlock_lo} + hint unlock_date = stack[0..2] + call tasm_io_read_stdin___digest + hint tx_kernel_digest = stack[0..5] + call tasm_io_read_secin___bfe + hint timestamp = stack[0] + push 5 + hint leaf_index = stack[0] + dup 1 + call encode_BField + call tasm_langs_hash_varlen + hint leaf = stack[0..5] + push 3 + hint tree_height = stack[0] + dup 12 + dup 12 + dup 12 + dup 12 + dup 12 + dup 11 + dup 11 + dup 11 + dup 11 + dup 11 + dup 11 + dup 11 + call tasm_hashing_merkle_verify + dup 14 + dup 14 + dup 9 + split + swap 3 + swap 1 + swap 3 + swap 2 + call tasm_arithmetic_u64_lt_standard + assert + pop 5 + pop 5 + pop 5 + return + encode_BField: + push 2 + call tasm_memory_dyn_malloc + push 1 + swap 1 + write_mem 1 + write_mem 1 + push -2 + add + return + tasm_langs_hash_varlen: + read_mem 1 + push 2 + add + swap 1 + call tasm_hashing_algebraic_hasher_hash_varlen + return + tasm_arithmetic_u64_lt_standard: + call tasm_arithmetic_u64_lt_standard_aux + swap 4 + pop 4 + return + tasm_arithmetic_u64_lt_standard_aux: + dup 3 + dup 2 + lt + dup 0 + skiz + return + dup 4 + dup 3 + eq + skiz + call tasm_arithmetic_u64_lt_standard_lo + return + tasm_arithmetic_u64_lt_standard_lo: + pop 1 + dup 2 + dup 1 + lt + return + tasm_hashing_absorb_multiple: + dup 0 + push 10 + swap 1 + div_mod + swap 1 + pop 1 + swap 1 + dup 1 + push -1 + mul + dup 3 + add + add + push -1 + add + swap 1 + swap 2 + push -1 + add + call tasm_hashing_absorb_multiple_hash_all_full_chunks + pop 1 + push 9 + dup 2 + push -1 + mul + add + call tasm_hashing_absorb_multiple_pad_varnum_zeros + pop 1 + push 1 + swap 2 + dup 1 + add + call tasm_hashing_absorb_multiple_read_remainder + pop 2 + sponge_absorb + return + tasm_hashing_absorb_multiple_hash_all_full_chunks: + dup 1 + dup 1 + eq + skiz + return + push 10 + add + dup 0 + read_mem 5 + read_mem 5 + pop 1 + sponge_absorb + recurse + tasm_hashing_absorb_multiple_pad_varnum_zeros: + dup 0 + push 0 + eq + skiz + return + push 0 + swap 3 + swap 2 + swap 1 + push -1 + add + recurse + tasm_hashing_absorb_multiple_read_remainder: + dup 1 + dup 1 + eq + skiz + return + read_mem 1 + swap 1 + swap 2 + swap 1 + recurse + tasm_hashing_algebraic_hasher_hash_varlen: + sponge_init + call tasm_hashing_absorb_multiple + sponge_squeeze + swap 5 + pop 1 + swap 5 + pop 1 + swap 5 + pop 1 + swap 5 + pop 1 + swap 5 + pop 1 + return + tasm_hashing_merkle_verify: + hint tree_height: u32 = stack[0] + hint leaf: Digest = stack[1..6] + hint leaf_index: u32 = stack[6] + hint root: Digest = stack[7..12] + push 2 + pow + hint num_leaves: u32 = stack[0] + dup 0 + dup 7 + lt + assert + dup 6 + add + hint node_index: u32 = stack[0] + swap 6 + pop 1 + call tasm_hashing_merkle_verify_traverse_tree + swap 1 + swap 2 + swap 3 + swap 4 + swap 5 + pop 1 + assert_vector + pop 5 + return + tasm_hashing_merkle_verify_traverse_tree: + dup 5 + push 1 + eq + skiz + return + divine_sibling + hash + recurse + tasm_io_read_secin___bfe: + divine 1 + return + tasm_io_read_stdin___digest: + read_io 5 + return + tasm_memory_dyn_malloc: + push 00000000004294967296 + read_mem 1 + pop 1 + dup 0 + push 0 + eq + push 00000000004294967297 + mul + add + dup 0 + swap 2 + split + swap 1 + push 0 + eq + assert + add + dup 0 + split + pop 1 + push 0 + eq + push 0 + eq + assert + push 00000000004294967296 + write_mem 1 + pop 1 + return + } + } } #[derive(BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize, Clone)] struct NoExternalWitness; impl SecretWitness for (TransactionPrimitiveWitness, NoExternalWitness) { - fn nondeterminism( - &self, - ) -> tasm_lib::prelude::triton_vm::program::NonDeterminism< - tasm_lib::prelude::twenty_first::prelude::BFieldElement, - > { - todo!() + fn nondeterminism(&self) -> NonDeterminism { + // - individual elements: timestamp (1) + // - digests: authentication path + + let timestamp = self.0.kernel.timestamp; + let individual_tokens = vec![timestamp]; + let digests = self.0.kernel.mast_path(TransactionKernelField::Timestamp); + NonDeterminism::new(individual_tokens).with_digests(digests) } - fn subprogram(&self) -> tasm_lib::prelude::triton_vm::program::Program { + fn subprogram(&self) -> Program { todo!() } } @@ -41,8 +315,6 @@ impl SecretWitness for (TransactionPrimitiveWitness, NoExternalWitness) { impl ValidationLogic<(TransactionPrimitiveWitness, NoExternalWitness)> for TimeLock { type PrimitiveWitness = (TransactionPrimitiveWitness, NoExternalWitness); - type Kernel = TransactionKernel; - fn subprogram(&self) -> tasm_lib::prelude::triton_vm::program::Program { todo!() } @@ -58,10 +330,7 @@ impl ValidationLogic<(TransactionPrimitiveWitness, NoExternalWitness)> for TimeL todo!() } - fn new_from_primitive_witness( - primitive_witness: &Self::PrimitiveWitness, - tx_kernel: &Self::Kernel, - ) -> Self { + fn new_from_primitive_witness(_primitive_witness: &Self::PrimitiveWitness) -> Self { todo!() } } diff --git a/src/models/consensus/mod.rs b/src/models/consensus/mod.rs index 79686909..4ffeeb02 100644 --- a/src/models/consensus/mod.rs +++ b/src/models/consensus/mod.rs @@ -110,7 +110,6 @@ impl SupportedClaim { /// sometimes with and sometimes without witness data. pub trait ValidationLogic { type PrimitiveWitness; - type Kernel; fn subprogram(&self) -> Program; fn support(&self) -> ClaimSupport; @@ -121,10 +120,7 @@ pub trait ValidationLogic { todo!() } - fn new_from_primitive_witness( - primitive_witness: &Self::PrimitiveWitness, - tx_kernel: &Self::Kernel, - ) -> Self; + fn new_from_primitive_witness(primitive_witness: &Self::PrimitiveWitness) -> Self; /// Prove the claim. fn prove(&mut self) -> Result<()> { diff --git a/src/models/state/mod.rs b/src/models/state/mod.rs index 96d89304..af4bcca7 100644 --- a/src/models/state/mod.rs +++ b/src/models/state/mod.rs @@ -557,14 +557,14 @@ impl GlobalState { lock_script_witnesses: vec![secret_input; spendable_utxos_and_mps.len()], input_membership_proofs, output_utxos: output_utxos.clone(), - public_announcements, mutator_set_accumulator, + kernel: kernel.clone(), }; // Convert the secret-supported claim to a proof, several proofs, or // at the very least hide sensitive data. let mut transaction_validity_logic = - TransactionValidationLogic::new_from_primitive_witness(&primitive_witness, &kernel); + TransactionValidationLogic::new_from_primitive_witness(&primitive_witness); if self.cli().privacy { transaction_validity_logic diff --git a/src/tests/shared.rs b/src/tests/shared.rs index 8fcc504d..fd1698e2 100644 --- a/src/tests/shared.rs +++ b/src/tests/shared.rs @@ -440,7 +440,7 @@ pub fn pseudorandom_removal_record_integrity_witness( let mut rng: StdRng = SeedableRng::from_seed(seed); let num_inputs = 2; let num_outputs = 2; - let num_pubscripts = 1; + let num_public_announcements = 1; let input_utxos = (0..num_inputs) .map(|_| pseudorandom_utxo(rng.gen::<[u8; 32]>())) @@ -479,8 +479,12 @@ pub fn pseudorandom_removal_record_integrity_witness( } let swbfi = pseudorandom_mmra(rng.gen::<[u8; 32]>()); let swbfa_hash: Digest = rng.gen(); - let mut kernel = - pseudorandom_transaction_kernel(rng.gen(), num_inputs, num_outputs, num_pubscripts); + let mut kernel = pseudorandom_transaction_kernel( + rng.gen(), + num_inputs, + num_outputs, + num_public_announcements, + ); kernel.mutator_set_hash = Hash::hash_pair( Hash::hash_pair(aocl.bag_peaks(), swbfi.bag_peaks()), Hash::hash_pair(swbfa_hash, Digest::default()), @@ -525,8 +529,8 @@ pub fn random_transaction_kernel() -> TransactionKernel { let mut rng = thread_rng(); let num_inputs = 1 + (rng.next_u32() % 5) as usize; let num_outputs = 1 + (rng.next_u32() % 6) as usize; - let num_pubscripts = (rng.next_u32() % 5) as usize; - pseudorandom_transaction_kernel(rng.gen(), num_inputs, num_outputs, num_pubscripts) + let num_public_announcements = (rng.next_u32() % 5) as usize; + pseudorandom_transaction_kernel(rng.gen(), num_inputs, num_outputs, num_public_announcements) } pub fn random_addition_record() -> AdditionRecord { @@ -794,10 +798,6 @@ pub fn make_mock_transaction_with_generation_key( .iter() .map(|(_utxo, _mp, sk)| sk.to_address().lock_script()) .collect_vec(); - let pubscripts = receiver_data - .iter() - .map(|rd| rd.public_announcement.clone()) - .collect(); let output_utxos = receiver_data.into_iter().map(|rd| rd.utxo).collect(); let primitive_witness = TransactionPrimitiveWitness { input_utxos, @@ -806,11 +806,10 @@ pub fn make_mock_transaction_with_generation_key( lock_script_witnesses: spending_key_unlock_keys, input_membership_proofs, output_utxos, - public_announcements: pubscripts, mutator_set_accumulator: tip_msa, + kernel: kernel.clone(), }; - let validity_logic = - TransactionValidationLogic::new_from_primitive_witness(&primitive_witness, &kernel); + let validity_logic = TransactionValidationLogic::new_from_primitive_witness(&primitive_witness); Transaction { kernel, @@ -932,12 +931,12 @@ pub fn make_mock_block( lock_script_witnesses: vec![], input_membership_proofs: vec![], output_utxos: vec![coinbase_utxo.clone()], - public_announcements: vec![], mutator_set_accumulator: previous_mutator_set.clone(), input_lock_scripts: vec![], + kernel: tx_kernel.clone(), }; let validation_logic = - TransactionValidationLogic::new_from_primitive_witness(&primitive_witness, &tx_kernel); + TransactionValidationLogic::new_from_primitive_witness(&primitive_witness); let transaction = Transaction { witness: TransactionWitness::ValidationLogic(validation_logic), From c9e427cdcbe46fde1e56785fc1da2344a9ad4940 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Fri, 9 Feb 2024 17:50:15 +0100 Subject: [PATCH 06/33] generalize type script checking --- src/models/blockchain/transaction/mod.rs | 37 ++++++++---- .../transaction/transaction_kernel.rs | 3 +- .../validity/kernel_to_lock_scripts.rs | 2 +- .../validity/kernel_to_type_scripts.rs | 2 +- .../transaction/validity/lockscripts_halt.rs | 11 +--- .../validity/removal_records_integrity.rs | 2 +- .../transaction/validity/typescripts_halt.rs | 4 +- .../blockchain/type_scripts/time_lock.rs | 56 +++++-------------- src/models/consensus/mod.rs | 10 ++-- 9 files changed, 53 insertions(+), 74 deletions(-) diff --git a/src/models/blockchain/transaction/mod.rs b/src/models/blockchain/transaction/mod.rs index 6b23cb86..01789dde 100644 --- a/src/models/blockchain/transaction/mod.rs +++ b/src/models/blockchain/transaction/mod.rs @@ -13,8 +13,10 @@ use num_bigint::BigInt; use num_rational::BigRational; use serde::{Deserialize, Serialize}; use std::cmp::max; +use std::collections::HashMap; use std::hash::{Hash as StdHash, Hasher as StdHasher}; use std::time::SystemTime; +use tasm_lib::Digest; use tracing::{debug, error, warn}; use triton_vm::prelude::NonDeterminism; use twenty_first::shared_math::b_field_element::BFieldElement; @@ -27,7 +29,6 @@ use self::utxo::{LockScript, Utxo}; use self::validity::TransactionValidationLogic; use super::block::Block; use super::shared::Hash; -use super::type_scripts::native_currency::native_currency_program; use super::type_scripts::TypeScript; use crate::util_types::mutator_set::addition_record::AdditionRecord; use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; @@ -323,6 +324,8 @@ impl Transaction { .all(|rr| rr.validate(&mutator_set_accumulator.kernel)) } + /// Verify the transaction directly from the primitive witness, without proofs or + /// decomposing into subclaims. fn validate_primitive_witness(&self, primitive_witness: &TransactionPrimitiveWitness) -> bool { // verify lock scripts for (lock_script, secret_input) in primitive_witness @@ -377,8 +380,8 @@ impl Transaction { witnessed_removal_records.push(removal_record); } - // collect type scripts - let type_scripts = primitive_witness + // collect type script hashes + let type_script_hashes = primitive_witness .output_utxos .iter() .flat_map(|utxo| utxo.coins.iter().map(|coin| coin.type_script_hash)) @@ -386,13 +389,25 @@ impl Transaction { .dedup() .collect_vec(); + // verify that all type script hashes are represented by the witness's type script list + let mut type_script_dictionary = HashMap::::new(); + for ts in primitive_witness.type_scripts.iter() { + type_script_dictionary.insert(ts.hash(), ts.clone()); + } + if !type_script_hashes + .clone() + .into_iter() + .all(|tsh| type_script_dictionary.contains_key(&tsh)) + { + warn!("Transaction contains input(s) or output(s) with unknown typescript."); + return false; + } + // verify type scripts - for type_script_hash in type_scripts { - let type_script = if type_script_hash != native_currency_program().hash::() { - warn!("Observed non-native type script: {} Non-native type scripts are not supported yet.", type_script_hash.emojihash()); - continue; - } else { - native_currency_program() + for type_script_hash in type_script_hashes { + let Some(type_script) = type_script_dictionary.get(&type_script_hash) else { + warn!("Type script hash not found; should not get here."); + return false; }; let public_input = self.kernel.mast_hash().encode(); @@ -405,7 +420,9 @@ impl Transaction { // The type script is satisfied if it halts gracefully, i.e., // without panicking. So we don't care about the output - if let Err(e) = type_script.run(public_input.into(), NonDeterminism::new(secret_input)) + if let Err(e) = type_script + .program + .run(public_input.into(), NonDeterminism::new(secret_input)) { warn!( "Type script {} not satisfied for transaction: {}", diff --git a/src/models/blockchain/transaction/transaction_kernel.rs b/src/models/blockchain/transaction/transaction_kernel.rs index e95d65e5..1e252a0d 100644 --- a/src/models/blockchain/transaction/transaction_kernel.rs +++ b/src/models/blockchain/transaction/transaction_kernel.rs @@ -10,6 +10,7 @@ use get_size::GetSize; use itertools::Itertools; use rand::{rngs::StdRng, Rng, RngCore, SeedableRng}; use serde::{Deserialize, Serialize}; +use strum::EnumCount; use tasm_lib::structure::tasm_object::TasmObject; use twenty_first::shared_math::{ b_field_element::BFieldElement, bfield_codec::BFieldCodec, tip5::Digest, @@ -45,7 +46,7 @@ pub struct TransactionKernel { pub mutator_set_hash: Digest, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, EnumCount)] pub enum TransactionKernelField { InputUtxos, OutputUtxos, diff --git a/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs b/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs index 4795bdb2..e369a584 100644 --- a/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs +++ b/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs @@ -74,7 +74,7 @@ impl ValidationLogic for KernelToLockScripts { Self { supported_claim } } - fn subprogram(&self) -> Program { + fn validation_program(&self) -> Program { todo!() } diff --git a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs index 2c72c341..fa9b970d 100644 --- a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs +++ b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs @@ -84,7 +84,7 @@ impl ValidationLogic for KernelToTypeScripts { todo!() } - fn subprogram(&self) -> Program { + fn validation_program(&self) -> Program { todo!() } diff --git a/src/models/blockchain/transaction/validity/lockscripts_halt.rs b/src/models/blockchain/transaction/validity/lockscripts_halt.rs index 0e60bbdd..88e1c64a 100644 --- a/src/models/blockchain/transaction/validity/lockscripts_halt.rs +++ b/src/models/blockchain/transaction/validity/lockscripts_halt.rs @@ -67,7 +67,7 @@ impl ValidationLogic for LockScriptsHalt { } } - fn subprogram(&self) -> Program { + fn validation_program(&self) -> Program { todo!() } @@ -86,15 +86,6 @@ impl ValidationLogic for LockScriptsHalt { ) } - // fn support(&self) -> ClaimSupport { - // let supports = self - // .supported_claims - // .iter() - // .map(|sc| sc.support.clone()) - // .collect_vec(); - // ClaimSupport::MultipleSupports(supports) - // } - fn claim(&self) -> Claim { let input = self .supported_claims diff --git a/src/models/blockchain/transaction/validity/removal_records_integrity.rs b/src/models/blockchain/transaction/validity/removal_records_integrity.rs index fa7d5de6..73605424 100644 --- a/src/models/blockchain/transaction/validity/removal_records_integrity.rs +++ b/src/models/blockchain/transaction/validity/removal_records_integrity.rs @@ -114,7 +114,7 @@ impl ValidationLogic for RemovalRecordsIntegrity } } - fn subprogram(&self) -> Program { + fn validation_program(&self) -> Program { Self::program() } diff --git a/src/models/blockchain/transaction/validity/typescripts_halt.rs b/src/models/blockchain/transaction/validity/typescripts_halt.rs index 90aee333..4606c2e9 100644 --- a/src/models/blockchain/transaction/validity/typescripts_halt.rs +++ b/src/models/blockchain/transaction/validity/typescripts_halt.rs @@ -68,7 +68,7 @@ impl ValidationLogic for TypeScriptsHalt { } } - fn subprogram(&self) -> Program { + fn validation_program(&self) -> Program { todo!() } @@ -94,7 +94,7 @@ impl ValidationLogic for TypeScriptsHalt { .flat_map(|sc| sc.claim.program_digest.values().to_vec()) .collect_vec(); let output = vec![]; - // let program_hash = AllLockScriptsHalt::program().hash(); + // let program_hash = AllTypeScriptsHalt::program().hash(); let program_digest = Default::default(); Claim { program_digest, diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index 8da52912..a0561d9b 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -7,16 +7,12 @@ use tasm_lib::{ triton_asm, }, twenty_first::shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, + Digest, }; -use crate::models::consensus::mast_hash::MastHash; -use crate::models::{ - blockchain::transaction::{ - transaction_kernel::TransactionKernelField, TransactionPrimitiveWitness, - }, - consensus::{SecretWitness, ValidationLogic}, -}; +use crate::models::consensus::SecretWitness; +#[derive(Debug, Clone, Deserialize, Serialize, BFieldCodec, GetSize, PartialEq, Eq)] pub struct TimeLock { /// milliseconds since the unix epoch (00:00 UTC am on Jan 1 1970). release_date: u64, @@ -291,46 +287,20 @@ impl TimeLock { } } -#[derive(BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize, Clone)] -struct NoExternalWitness; +#[derive(Debug, Clone, Deserialize, Serialize, BFieldCodec, GetSize, PartialEq, Eq)] +pub struct TimeLockWitness { + time_lock: TimeLock, + transaction_timestamp: u64, + mast_path: Vec, +} -impl SecretWitness for (TransactionPrimitiveWitness, NoExternalWitness) { +impl SecretWitness for TimeLockWitness { fn nondeterminism(&self) -> NonDeterminism { - // - individual elements: timestamp (1) - // - digests: authentication path - - let timestamp = self.0.kernel.timestamp; - let individual_tokens = vec![timestamp]; - let digests = self.0.kernel.mast_path(TransactionKernelField::Timestamp); - NonDeterminism::new(individual_tokens).with_digests(digests) + NonDeterminism::new(vec![BFieldElement::new(self.transaction_timestamp)]) + .with_digests(self.mast_path.clone()) } fn subprogram(&self) -> Program { - todo!() - } -} - -// impl TypeScriptValidationLogic for TimeLock -// Not yet; type aliases are still experimental ;-) -impl ValidationLogic<(TransactionPrimitiveWitness, NoExternalWitness)> for TimeLock { - type PrimitiveWitness = (TransactionPrimitiveWitness, NoExternalWitness); - - fn subprogram(&self) -> tasm_lib::prelude::triton_vm::program::Program { - todo!() - } - - fn support( - &self, - ) -> crate::models::consensus::ClaimSupport<(TransactionPrimitiveWitness, NoExternalWitness)> - { - todo!() - } - - fn claim(&self) -> tasm_lib::prelude::triton_vm::proof::Claim { - todo!() - } - - fn new_from_primitive_witness(_primitive_witness: &Self::PrimitiveWitness) -> Self { - todo!() + Program::new(&self.time_lock.code()) } } diff --git a/src/models/consensus/mod.rs b/src/models/consensus/mod.rs index 4ffeeb02..f38c1ecc 100644 --- a/src/models/consensus/mod.rs +++ b/src/models/consensus/mod.rs @@ -63,7 +63,7 @@ pub trait SecretWitness: /// The non-determinism for the VM that this witness corresponds to fn nondeterminism(&self) -> NonDeterminism; - /// Returns the subprogram + /// Returns the subprogram that this secret witness relates to fn subprogram(&self) -> Program; } @@ -75,7 +75,7 @@ pub struct SupportedClaim { pub support: ClaimSupport, } -/// When a claimto validity decomposes into multiple subclaims via variant +/// When a claim to validity decomposes into multiple subclaims via variant /// `ValidationLogic` of `Witness`, those subclaims pertain to the graceful halting of /// programs ("subprograms"), which is itself supported by either a proof or some witness /// that can help the prover produce one. @@ -111,7 +111,7 @@ impl SupportedClaim { pub trait ValidationLogic { type PrimitiveWitness; - fn subprogram(&self) -> Program; + fn validation_program(&self) -> Program; fn support(&self) -> ClaimSupport; fn claim(&self) -> Claim; @@ -131,7 +131,7 @@ pub trait ValidationLogic { } ClaimSupport::SecretWitness(witness) => { // Run program before proving - self.subprogram() + self.validation_program() .run( self.claim().public_input().into(), witness.nondeterminism().clone(), @@ -141,7 +141,7 @@ pub trait ValidationLogic { let proof = triton_vm::prove( StarkParameters::default(), &self.claim(), - &self.subprogram(), + &self.validation_program(), witness.nondeterminism().clone(), ) .expect("Proving integrity of removal records must succeed."); From 535d1b92550433f8a059f9f16464618958687e30 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Wed, 14 Feb 2024 17:55:10 +0100 Subject: [PATCH 07/33] Support for Triton VM environment emulation With this support, we can write programs in rust such that they can be run natively or compiled first and then run in Triton VM. --- .../blockchain/type_scripts/time_lock.rs | 105 +++++++++- src/models/consensus/mod.rs | 1 + src/models/consensus/tasm/builtins.rs | 189 ++++++++++++++++++ src/models/consensus/tasm/environment.rs | 51 +++++ src/models/consensus/tasm/mod.rs | 3 + src/models/consensus/tasm/program.rs | 30 +++ 6 files changed, 377 insertions(+), 2 deletions(-) create mode 100644 src/models/consensus/tasm/builtins.rs create mode 100644 src/models/consensus/tasm/environment.rs create mode 100644 src/models/consensus/tasm/mod.rs create mode 100644 src/models/consensus/tasm/program.rs diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index a0561d9b..4e431881 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -1,5 +1,14 @@ +use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; +use crate::models::blockchain::transaction::utxo::Coin; +use crate::models::blockchain::transaction::utxo::Utxo; +use crate::models::consensus::SecretWitness; +use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; +use crate::util_types::mutator_set::mutator_set_kernel::get_swbf_indices; +use crate::util_types::mutator_set::shared::NUM_TRIALS; +use crate::Hash; use get_size::GetSize; use serde::{Deserialize, Serialize}; +use tasm_lib::twenty_first::prelude::AlgebraicHasher; use tasm_lib::{ triton_vm::{ instruction::LabelledInstruction, @@ -10,7 +19,8 @@ use tasm_lib::{ Digest, }; -use crate::models::consensus::SecretWitness; +use crate::models::consensus::tasm::builtins as tasm; +use crate::models::consensus::tasm::program::ConsensusProgram; #[derive(Debug, Clone, Deserialize, Serialize, BFieldCodec, GetSize, PartialEq, Eq)] pub struct TimeLock { @@ -25,8 +35,99 @@ impl TimeLock { pub fn until(date: u64) -> TimeLock { Self { release_date: date } } +} + +impl ConsensusProgram for TimeLock { + #[allow(clippy::needless_return)] + fn source(&self) { + // get in the current program's hash digest + let self_digest: Digest = tasm::own_program_digest(); + + // read standard input: the transaction kernel mast hash + let tx_kernel_digest: Digest = tasm::tasm_io_read_stdin___digest(); + + // divine the timestamp and authenticate it against the kernel mast hash + let leaf_index: u32 = 5; + let timestamp: BFieldElement = tasm::tasm_io_read_secin___bfe(); + let leaf: Digest = Hash::hash_varlen(×tamp.encode()); + let tree_height: u32 = 3; + tasm::tasm_hashing_merkle_verify(tx_kernel_digest, leaf_index, leaf, tree_height); + + // get pointers to objects living in nondeterministic memory: + // - list of input UTXOs + // - list of input UTXOs' membership proofs in the mutator set + // - transaction kernel + let input_utxos_pointer: u64 = tasm::tasm_io_read_secin___bfe().value(); + let input_utxos: Vec = + tasm::decode_from_memory(BFieldElement::new(input_utxos_pointer)); + let input_mps_pointer: BFieldElement = tasm::tasm_io_read_secin___bfe(); + let input_mps: Vec = tasm::decode_from_memory(input_mps_pointer); + let transaction_kernel_pointer: BFieldElement = tasm::tasm_io_read_secin___bfe(); + let transaction_kernel: TransactionKernel = + tasm::decode_from_memory(transaction_kernel_pointer); + + // authenticate kernel + let transaction_kernel_hash = Hash::hash(&transaction_kernel); + assert_eq!(transaction_kernel_hash, tx_kernel_digest); + + // compute the inputs (removal records' absolute index sets) + let mut inputs_derived: Vec = Vec::with_capacity(input_utxos.len()); + let mut i: usize = 0; + while i < input_utxos.len() { + let aocl_leaf_index: u64 = input_mps[i].auth_path_aocl.leaf_index; + let receiver_preimage: Digest = input_mps[i].receiver_preimage; + let sender_randomness: Digest = input_mps[i].sender_randomness; + let item: Digest = Hash::hash(&input_utxos[i]); + let index_set: [u128; NUM_TRIALS as usize] = + get_swbf_indices(item, sender_randomness, receiver_preimage, aocl_leaf_index); + inputs_derived.push(Hash::hash(&index_set)); + i += 1; + } + + // read inputs (absolute index sets) from kernel + let mut inputs_kernel: Vec = Vec::with_capacity(transaction_kernel.inputs.len()); + i = 0; + while i < transaction_kernel.inputs.len() { + let index_set = transaction_kernel.inputs[i].absolute_indices.to_vec(); + inputs_kernel.push(Hash::hash(&index_set)); + i += 1; + } + + // authenticate inputs + tasm::tasm_list_unsafeimplu32_multiset_equality(inputs_derived, inputs_kernel); + + // iterate over inputs + i = 0; + while i < input_utxos.len() { + // get coins + let coins: &Vec = &input_utxos[i].coins; + + // if this typescript is present + let mut j: usize = 0; + while j < coins.len() { + let coin: &Coin = &coins[j]; + if coin.type_script_hash == self_digest { + // extract state + let state: &Vec = &coin.state; + + // assert format + assert!(state.len() == 1); + + // extract timestamp + let release_date: BFieldElement = state[0]; + + // test time lock + assert!(release_date.value() < timestamp.value()); + } + j += 1; + } + i += 1; + } + + return; + } - pub fn code(&self) -> Vec { + fn code(&self) -> Vec { let unlock_hi = self.release_date >> 32; let unlock_lo = self.release_date & 0xffffffff; diff --git a/src/models/consensus/mod.rs b/src/models/consensus/mod.rs index f38c1ecc..a206c145 100644 --- a/src/models/consensus/mod.rs +++ b/src/models/consensus/mod.rs @@ -14,6 +14,7 @@ use triton_vm::prelude::PublicInput; use triton_vm::prelude::StarkParameters; pub mod mast_hash; +pub mod tasm; /// This file contains abstractions for verifying consensus logic using TritonVM STARK /// proofs. The concrete logic is specified in the directories `transaction` and `block`. diff --git a/src/models/consensus/tasm/builtins.rs b/src/models/consensus/tasm/builtins.rs new file mode 100644 index 00000000..7bd9b642 --- /dev/null +++ b/src/models/consensus/tasm/builtins.rs @@ -0,0 +1,189 @@ +use tasm_lib::{ + structure::tasm_object::TasmObject, + twenty_first::{ + shared_math::{ + b_field_element::BFieldElement, bfield_codec::BFieldCodec, + x_field_element::XFieldElement, + }, + util_types::merkle_tree::MerkleTreeInclusionProof, + }, + Digest, +}; + +use crate::models::{blockchain::shared::Hash, consensus::tasm::environment::ND_DIGESTS}; + +use super::environment::{ND_INDIVIDUAL_TOKEN, ND_MEMORY, PROGRAM_DIGEST, PUB_INPUT, PUB_OUTPUT}; + +/// Get the hash digest of the program that's currently running. +pub fn own_program_digest() -> Digest { + PROGRAM_DIGEST.with(|v| *v.borrow()) +} + +#[allow(non_snake_case)] +pub fn tasm_io_read_stdin___bfe() -> BFieldElement { + #[allow(clippy::unwrap_used)] + PUB_INPUT.with(|v| v.borrow_mut().pop().unwrap()) +} + +#[allow(non_snake_case)] +pub fn tasm_io_read_stdin___xfe() -> XFieldElement { + let x2 = PUB_INPUT.with(|v| v.borrow_mut().pop().unwrap()); + let x1 = PUB_INPUT.with(|v| v.borrow_mut().pop().unwrap()); + let x0 = PUB_INPUT.with(|v| v.borrow_mut().pop().unwrap()); + XFieldElement::new([x0, x1, x2]) +} + +#[allow(non_snake_case)] +pub fn tasm_io_read_stdin___u32() -> u32 { + #[allow(clippy::unwrap_used)] + let val: u32 = PUB_INPUT + .with(|v| v.borrow_mut().pop().unwrap()) + .try_into() + .unwrap(); + val +} + +#[allow(non_snake_case)] +pub fn tasm_io_read_stdin___u64() -> u64 { + #[allow(clippy::unwrap_used)] + let hi: u32 = PUB_INPUT + .with(|v| v.borrow_mut().pop().unwrap()) + .try_into() + .unwrap(); + let lo: u32 = PUB_INPUT + .with(|v| v.borrow_mut().pop().unwrap()) + .try_into() + .unwrap(); + ((hi as u64) << 32) + lo as u64 +} + +#[allow(non_snake_case)] +pub fn tasm_io_read_stdin___u128() -> u128 { + #[allow(clippy::unwrap_used)] + let e3: u32 = PUB_INPUT + .with(|v| v.borrow_mut().pop().unwrap()) + .try_into() + .unwrap(); + let e2: u32 = PUB_INPUT + .with(|v| v.borrow_mut().pop().unwrap()) + .try_into() + .unwrap(); + let e1: u32 = PUB_INPUT + .with(|v| v.borrow_mut().pop().unwrap()) + .try_into() + .unwrap(); + let e0: u32 = PUB_INPUT + .with(|v| v.borrow_mut().pop().unwrap()) + .try_into() + .unwrap(); + ((e3 as u128) << 96) + ((e2 as u128) << 64) + ((e1 as u128) << 32) + e0 as u128 +} + +#[allow(non_snake_case)] +pub fn tasm_io_read_stdin___digest() -> Digest { + let e4 = PUB_INPUT.with(|v| v.borrow_mut().pop().unwrap()); + let e3 = PUB_INPUT.with(|v| v.borrow_mut().pop().unwrap()); + let e2 = PUB_INPUT.with(|v| v.borrow_mut().pop().unwrap()); + let e1 = PUB_INPUT.with(|v| v.borrow_mut().pop().unwrap()); + let e0 = PUB_INPUT.with(|v| v.borrow_mut().pop().unwrap()); + Digest::new([e0, e1, e2, e3, e4]) +} + +#[allow(non_snake_case)] +pub fn tasm_io_write_to_stdout___bfe(x: BFieldElement) { + PUB_OUTPUT.with(|v| v.borrow_mut().push(x)); +} + +#[allow(non_snake_case)] +pub fn tasm_io_write_to_stdout___xfe(x: XFieldElement) { + PUB_OUTPUT.with(|v| v.borrow_mut().extend(x.coefficients.to_vec())); +} + +#[allow(non_snake_case)] +pub fn tasm_io_write_to_stdout___digest(x: Digest) { + PUB_OUTPUT.with(|v| v.borrow_mut().extend(x.values().to_vec())); +} + +#[allow(non_snake_case)] +pub fn tasm_io_write_to_stdout___bool(x: bool) { + PUB_OUTPUT.with(|v| v.borrow_mut().push(BFieldElement::new(x as u64))); +} + +#[allow(non_snake_case)] +pub fn tasm_io_write_to_stdout___u32(x: u32) { + PUB_OUTPUT.with(|v| v.borrow_mut().push(BFieldElement::new(x as u64))); +} + +#[allow(non_snake_case)] +pub fn tasm_io_write_to_stdout___u64(x: u64) { + PUB_OUTPUT.with(|v| v.borrow_mut().extend(x.encode())); +} + +#[allow(non_snake_case)] +pub fn tasm_io_write_to_stdout___u128(x: u128) { + PUB_OUTPUT.with(|v| v.borrow_mut().extend(x.encode())); +} + +#[allow(non_snake_case)] +pub fn tasm_io_read_secin___bfe() -> BFieldElement { + #[allow(clippy::unwrap_used)] + ND_INDIVIDUAL_TOKEN.with(|v| v.borrow_mut().pop().unwrap()) +} + +/// Verify a Merkle tree membership claim using the nondeterministically supplied digests +/// as authentication path. +pub fn tasm_hashing_merkle_verify(root: Digest, leaf_index: u32, leaf: Digest, tree_height: u32) { + let mut path: Vec = vec![]; + + ND_DIGESTS.with_borrow_mut(|nd_digests| { + for _ in 0..tree_height { + path.push(nd_digests.pop().unwrap()); + } + }); + + let mt_inclusion_proof = MerkleTreeInclusionProof:: { + tree_height: tree_height as usize, + indexed_leaves: vec![(leaf_index as usize, leaf)], + authentication_structure: path, + _hasher: std::marker::PhantomData, + }; + + assert!(mt_inclusion_proof.verify(root)); +} + +/// Test whether two lists of digests are equal, up to order. +pub fn tasm_list_unsafeimplu32_multiset_equality(left: Vec, right: Vec) { + assert_eq!(left.len(), right.len()); + let mut left_sorted = left.clone(); + left_sorted.sort(); + + let mut right_sorted = right.clone(); + right_sorted.sort(); + + assert_eq!(left_sorted, right_sorted); +} + +struct EnvironmentMemoryIter(pub BFieldElement); + +impl Iterator for EnvironmentMemoryIter { + type Item = BFieldElement; + + fn next(&mut self) -> Option { + let value = ND_MEMORY.with(|v| { + v.borrow() + .get(&self.0) + .cloned() + .unwrap_or_else(|| BFieldElement::new(0)) + }); + self.0.increment(); + Some(value) + } +} + +/// In nondeterministically-initialized memory, there lives an object of type T. Given a +/// pointer to it, get that object. In TritonVM, this operation has no effect. the rust +/// shadow, we decode the object that lives there. +pub fn decode_from_memory(start_address: BFieldElement) -> T { + let mut iterator = EnvironmentMemoryIter(start_address); + *T::decode_iter(&mut iterator).unwrap() +} diff --git a/src/models/consensus/tasm/environment.rs b/src/models/consensus/tasm/environment.rs new file mode 100644 index 00000000..cc26d380 --- /dev/null +++ b/src/models/consensus/tasm/environment.rs @@ -0,0 +1,51 @@ +// This module contains functions for interacting with the input/output monad +// implicit in a VM execution. It contains functions for mutating and verifying +// the correct content of the input/output while executing a Rust function +// on the host machine's native architecture (i.e. your machine). +// It has been shamelessly copied from greenhat's omnizk compiler project: +// https://github.com/greenhat/omnizk + +use std::{cell::RefCell, collections::HashMap}; + +use tasm_lib::{ + triton_vm::program::NonDeterminism, twenty_first::shared_math::b_field_element::BFieldElement, + Digest, +}; + +thread_local! { + pub(super) static PUB_INPUT: RefCell> = RefCell::new(vec![]); + pub(super) static PUB_OUTPUT: RefCell> = RefCell::new(vec![]); + + pub(super) static ND_INDIVIDUAL_TOKEN: RefCell> = RefCell::new(vec![]); + pub(super) static ND_DIGESTS: RefCell> = RefCell::new(vec![]); + pub(super) static ND_MEMORY: RefCell> = + RefCell::new(HashMap::default()); + + pub(super) static PROGRAM_DIGEST: RefCell = RefCell::new(Digest::default()); +} + +pub(crate) fn init(input: &[BFieldElement], nondeterminism: NonDeterminism) { + let mut pub_input_reversed = input.to_vec(); + pub_input_reversed.reverse(); + let mut inidividual_tokens_reversed = nondeterminism.individual_tokens; + inidividual_tokens_reversed.reverse(); + let mut digests_reversed = nondeterminism.digests; + digests_reversed.reverse(); + + // TODO: Do we need to handle ND-memory as well? + PUB_INPUT.with(|v| { + *v.borrow_mut() = pub_input_reversed; + }); + ND_INDIVIDUAL_TOKEN.with(|v| { + *v.borrow_mut() = inidividual_tokens_reversed; + }); + ND_DIGESTS.with(|v| { + *v.borrow_mut() = digests_reversed; + }); + ND_MEMORY.with(|v| { + *v.borrow_mut() = nondeterminism.ram; + }); + PUB_OUTPUT.with(|v| { + *v.borrow_mut() = vec![]; + }); +} diff --git a/src/models/consensus/tasm/mod.rs b/src/models/consensus/tasm/mod.rs new file mode 100644 index 00000000..ddde72d5 --- /dev/null +++ b/src/models/consensus/tasm/mod.rs @@ -0,0 +1,3 @@ +pub mod builtins; +mod environment; +pub mod program; diff --git a/src/models/consensus/tasm/program.rs b/src/models/consensus/tasm/program.rs new file mode 100644 index 00000000..ea51b193 --- /dev/null +++ b/src/models/consensus/tasm/program.rs @@ -0,0 +1,30 @@ +use tasm_lib::{ + triton_vm::{instruction::LabelledInstruction, program::NonDeterminism}, + twenty_first::shared_math::b_field_element::BFieldElement, +}; + +use super::environment; + +pub trait ConsensusProgram { + /// The canonical reference source code for the consensus program, written in the + /// subset of rust that the tasm-lang compiler understands. To run this program, call + /// [`run`][`run`], which spawns a new thread, boots the environment, and executes + /// the program. + fn source(&self); + + /// A derivative of source, in Triton-assembler (tasm) rather than rust. Either + /// produced automatically or hand-optimized. + fn code(&self) -> Vec; + + /// Run the source program natively in rust, but with the emulated TritonVM + /// environment for input, output, nondeterminism, and program digest. + fn run( + &self, + input: &[BFieldElement], + nondeterminism: NonDeterminism, + ) -> Vec { + environment::init(input, nondeterminism); + self.source(); + environment::PUB_OUTPUT.take() + } +} From ca3cd31dc4bf9378f2903918722b1758c822dc80 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Wed, 14 Feb 2024 19:58:47 +0100 Subject: [PATCH 08/33] drop argument self from ConsensusProgram methods --- .../blockchain/type_scripts/time_lock.rs | 38 +++++++++---------- src/models/consensus/tasm/program.rs | 20 +++++++--- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index 4e431881..ab065248 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -23,23 +23,23 @@ use crate::models::consensus::tasm::builtins as tasm; use crate::models::consensus::tasm::program::ConsensusProgram; #[derive(Debug, Clone, Deserialize, Serialize, BFieldCodec, GetSize, PartialEq, Eq)] -pub struct TimeLock { - /// milliseconds since the unix epoch (00:00 UTC am on Jan 1 1970). - release_date: u64, -} +pub struct TimeLock {} impl TimeLock { - /// Create a `TimeLock` type script that releases the coins at the given release date, - /// which corresponds to the number of milliseconds that passed since the unix epoch - /// started (00:00 am UTC on Jan 1 1970). - pub fn until(date: u64) -> TimeLock { - Self { release_date: date } + /// Create a `TimeLock` type-script-and-state-pair that releases the coins at the + /// given release date, which corresponds to the number of milliseconds that passed + /// since the unix epoch started (00:00 am UTC on Jan 1 1970). + pub fn until(date: u64) -> Coin { + Coin { + type_script_hash: Self::hash(), + state: vec![BFieldElement::new(date)], + } } } impl ConsensusProgram for TimeLock { #[allow(clippy::needless_return)] - fn source(&self) { + fn source() { // get in the current program's hash digest let self_digest: Digest = tasm::own_program_digest(); @@ -127,10 +127,7 @@ impl ConsensusProgram for TimeLock { return; } - fn code(&self) -> Vec { - let unlock_hi = self.release_date >> 32; - let unlock_lo = self.release_date & 0xffffffff; - + fn code() -> Vec { // Generated by tasm-lang compiler // `cargo test -- --nocapture typescript_timelock_test` // 2024-02-09 @@ -139,8 +136,8 @@ impl ConsensusProgram for TimeLock { call main halt main: - push {unlock_hi} - push {unlock_lo} + push 0 // hi + push 1 // lo hint unlock_date = stack[0..2] call tasm_io_read_stdin___digest hint tx_kernel_digest = stack[0..5] @@ -391,17 +388,20 @@ impl ConsensusProgram for TimeLock { #[derive(Debug, Clone, Deserialize, Serialize, BFieldCodec, GetSize, PartialEq, Eq)] pub struct TimeLockWitness { time_lock: TimeLock, - transaction_timestamp: u64, + release_date: u64, mast_path: Vec, + input_utxos: Vec, + input_mps: Vec, + transaction_kernel: TransactionKernel, } impl SecretWitness for TimeLockWitness { fn nondeterminism(&self) -> NonDeterminism { - NonDeterminism::new(vec![BFieldElement::new(self.transaction_timestamp)]) + NonDeterminism::new(vec![BFieldElement::new(self.release_date)]) .with_digests(self.mast_path.clone()) } fn subprogram(&self) -> Program { - Program::new(&self.time_lock.code()) + Program::new(&TimeLock::code()) } } diff --git a/src/models/consensus/tasm/program.rs b/src/models/consensus/tasm/program.rs index ea51b193..ca50afba 100644 --- a/src/models/consensus/tasm/program.rs +++ b/src/models/consensus/tasm/program.rs @@ -1,8 +1,14 @@ use tasm_lib::{ - triton_vm::{instruction::LabelledInstruction, program::NonDeterminism}, + triton_vm::{ + instruction::LabelledInstruction, + program::{NonDeterminism, Program}, + }, twenty_first::shared_math::b_field_element::BFieldElement, + Digest, }; +use crate::models::blockchain::shared::Hash; + use super::environment; pub trait ConsensusProgram { @@ -10,21 +16,25 @@ pub trait ConsensusProgram { /// subset of rust that the tasm-lang compiler understands. To run this program, call /// [`run`][`run`], which spawns a new thread, boots the environment, and executes /// the program. - fn source(&self); + fn source(); /// A derivative of source, in Triton-assembler (tasm) rather than rust. Either /// produced automatically or hand-optimized. - fn code(&self) -> Vec; + fn code() -> Vec; + + /// Get the program hash digest. + fn hash() -> Digest { + Program::new(&Self::code()).hash::() + } /// Run the source program natively in rust, but with the emulated TritonVM /// environment for input, output, nondeterminism, and program digest. fn run( - &self, input: &[BFieldElement], nondeterminism: NonDeterminism, ) -> Vec { environment::init(input, nondeterminism); - self.source(); + Self::source(); environment::PUB_OUTPUT.take() } } From df00fe0f9824f0f06b0635c20a0b0b8c2c00f76d Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Thu, 15 Feb 2024 20:06:23 +0100 Subject: [PATCH 09/33] chore: Implement arbitrary Specifically: - Add dependencies for proptest - Move transaction primitive witness to a separate file - Rename the above to primitive witness - Implement arbitrary for a bunch of data structures, most importantly the primitive transaction witness --- Cargo.lock | 116 +++- Cargo.toml | 5 +- src/mine_loop.rs | 4 +- src/models/blockchain/transaction/mod.rs | 33 +- .../transaction/primitive_witness.rs | 527 ++++++++++++++++++ .../transaction/transaction_kernel.rs | 54 +- src/models/blockchain/transaction/utxo.rs | 5 +- src/models/blockchain/transaction/validity.rs | 4 +- .../validity/kernel_to_lock_scripts.rs | 6 +- .../validity/kernel_to_type_scripts.rs | 6 +- .../transaction/validity/lockscripts_halt.rs | 8 +- .../validity/removal_records_integrity.rs | 291 +++++++++- .../transaction/validity/typescripts_halt.rs | 6 +- src/models/blockchain/type_scripts/mod.rs | 6 +- .../blockchain/type_scripts/neptune_coins.rs | 29 + .../blockchain/type_scripts/time_lock.rs | 64 ++- src/models/state/mod.rs | 5 +- src/tests/shared.rs | 6 +- src/util_types/mutator_set/active_window.rs | 3 +- src/util_types/mutator_set/addition_record.rs | 5 +- src/util_types/mutator_set/chunk.rs | 3 +- .../mutator_set/chunk_dictionary.rs | 3 +- .../mutator_set/ms_membership_proof.rs | 5 +- src/util_types/mutator_set/removal_record.rs | 7 +- 24 files changed, 1126 insertions(+), 75 deletions(-) create mode 100644 src/models/blockchain/transaction/primitive_witness.rs diff --git a/Cargo.lock b/Cargo.lock index 42474f24..71715e95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -334,6 +334,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -1007,6 +1022,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + [[package]] name = "ffi-opaque" version = "2.0.1" @@ -1269,9 +1290,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" +checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" [[package]] name = "hex" @@ -1688,6 +1709,7 @@ dependencies = [ "aead", "aes-gcm", "anyhow", + "arbitrary", "bech32", "bincode", "bytes", @@ -1709,6 +1731,8 @@ dependencies = [ "num-traits", "pin-project-lite", "priority-queue", + "proptest", + "proptest-arbitrary-interop", "rand", "ratatui", "regex", @@ -2189,6 +2213,36 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.4.2", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.8.2", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "proptest-arbitrary-interop" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1981e49bd2432249da8b0e11e5557099a8e74690d6b94e721f7dc0bb7f3555f" +dependencies = [ + "arbitrary", + "proptest", +] + [[package]] name = "prost" version = "0.12.3" @@ -2221,6 +2275,12 @@ dependencies = [ "prost", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.35" @@ -2293,6 +2353,15 @@ dependencies = [ "rand", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + [[package]] name = "ratatui" version = "0.23.0" @@ -2447,6 +2516,18 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.16" @@ -2728,6 +2809,18 @@ dependencies = [ "triton-vm", ] +[[package]] +name = "tempfile" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "terminal_size" version = "0.3.0" @@ -3185,6 +3278,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -3240,6 +3339,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.4.0" diff --git a/Cargo.toml b/Cargo.toml index f9f27874..1a54a982 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,9 @@ publish = false aead = "0" aes-gcm = "0" anyhow = "1" +arbitrary = { version = "1", features = ["derive"] } bech32 = "0" bincode = "1" -tiny-bip39 = "1.0.0" bytes = "1" bytesize = "1" chrono = "^0.4.31" @@ -40,6 +40,7 @@ serde_json = "1" strum = { version = "0.25", features = ["derive"] } tarpc = { version = "^0.34", features = ["tokio1", "serde-transport", "serde-transport-json", "tcp"] } tasm-lib = "0.2.1" +tiny-bip39 = "1.0.0" tokio = { version = "1", features = ["full", "tracing"] } tokio-serde = { version = "0", features = ["bincode", "json"] } tokio-util = { version = "0", features = ["codec"] } @@ -57,6 +58,8 @@ zeroize = "1.7.0" [dev-dependencies] pin-project-lite = "0.2.13" tokio-test = "0" +proptest = "1.4" +proptest-arbitrary-interop = "0.1" [dev-dependencies.cargo-husky] default-features = false diff --git a/src/mine_loop.rs b/src/mine_loop.rs index 78c268a3..407193ce 100644 --- a/src/mine_loop.rs +++ b/src/mine_loop.rs @@ -42,6 +42,8 @@ use twenty_first::shared_math::digest::Digest; use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; use twenty_first::util_types::emojihash_trait::Emojihash; +use self::primitive_witness::PrimitiveWitness; + const MOCK_MAX_BLOCK_SIZE: u32 = 1_000_000; /// Prepare a Block for mining @@ -214,7 +216,7 @@ fn make_coinbase_transaction( mutator_set_hash: mutator_set_accumulator.hash(), }; - let primitive_witness = TransactionPrimitiveWitness { + let primitive_witness = PrimitiveWitness { input_utxos: vec![], type_scripts: vec![TypeScript::native_coin()], input_lock_scripts: vec![], diff --git a/src/models/blockchain/transaction/mod.rs b/src/models/blockchain/transaction/mod.rs index 01789dde..aa0f5391 100644 --- a/src/models/blockchain/transaction/mod.rs +++ b/src/models/blockchain/transaction/mod.rs @@ -1,12 +1,14 @@ use crate::models::consensus::mast_hash::MastHash; use crate::prelude::{triton_vm, twenty_first}; +pub mod primitive_witness; pub mod transaction_kernel; pub mod utxo; pub mod validity; use crate::models::consensus::Witness; use anyhow::Result; +use arbitrary::Arbitrary; use get_size::GetSize; use itertools::Itertools; use num_bigint::BigInt; @@ -24,8 +26,8 @@ use twenty_first::shared_math::bfield_codec::BFieldCodec; use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; use twenty_first::util_types::emojihash_trait::Emojihash; +use self::primitive_witness::PrimitiveWitness; use self::transaction_kernel::TransactionKernel; -use self::utxo::{LockScript, Utxo}; use self::validity::TransactionValidationLogic; use super::block::Block; use super::shared::Hash; @@ -36,9 +38,11 @@ use crate::util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulat use crate::util_types::mutator_set::mutator_set_trait::MutatorSet; use crate::util_types::mutator_set::removal_record::RemovalRecord; -pub type TransactionWitness = Witness; +pub type TransactionWitness = Witness; -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec, Default)] +#[derive( + Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec, Default, Arbitrary, +)] pub struct PublicAnnouncement { pub message: Vec, } @@ -49,20 +53,6 @@ impl PublicAnnouncement { } } -/// The raw witness is the most primitive type of transaction witness. -/// It exposes secret data and is therefore not for broadcasting. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] -pub struct TransactionPrimitiveWitness { - pub input_utxos: Vec, - pub input_lock_scripts: Vec, - pub type_scripts: Vec, - pub lock_script_witnesses: Vec>, - pub input_membership_proofs: Vec, - pub output_utxos: Vec, - pub mutator_set_accumulator: MutatorSetAccumulator, - pub kernel: TransactionKernel, -} - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] pub struct Transaction { pub kernel: TransactionKernel, @@ -244,7 +234,7 @@ impl Transaction { if self_witness.kernel.mutator_set_hash != other_witness.kernel.mutator_set_hash { error!("Cannot merge two transactions with distinct mutator set hashes."); } - Witness::Primitive(TransactionPrimitiveWitness { + Witness::Primitive(PrimitiveWitness { input_utxos: [ self_witness.input_utxos.clone(), other_witness.input_utxos.clone(), @@ -326,7 +316,7 @@ impl Transaction { /// Verify the transaction directly from the primitive witness, without proofs or /// decomposing into subclaims. - fn validate_primitive_witness(&self, primitive_witness: &TransactionPrimitiveWitness) -> bool { + fn validate_primitive_witness(&self, primitive_witness: &PrimitiveWitness) -> bool { // verify lock scripts for (lock_script, secret_input) in primitive_witness .input_lock_scripts @@ -520,7 +510,7 @@ mod witness_tests { timestamp: BFieldElement::new(0), mutator_set_hash: Digest::default(), }; - let primitive_witness = TransactionPrimitiveWitness { + let primitive_witness = PrimitiveWitness { input_utxos: vec![], type_scripts: vec![], input_lock_scripts: vec![], @@ -532,7 +522,7 @@ mod witness_tests { }; let encoded = primitive_witness.encode(); - let decoded = *TransactionPrimitiveWitness::decode(&encoded).unwrap(); + let decoded = *PrimitiveWitness::decode(&encoded).unwrap(); assert_eq!(primitive_witness, decoded); } } @@ -542,6 +532,7 @@ mod transaction_tests { use rand::random; use std::time::Duration; use tracing_test::traced_test; + use transaction_tests::utxo::{LockScript, Utxo}; use super::*; use crate::{ diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs new file mode 100644 index 00000000..e55ad250 --- /dev/null +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -0,0 +1,527 @@ +use std::{ + collections::HashMap, + time::{SystemTime, UNIX_EPOCH}, +}; + +use arbitrary::Arbitrary; +use get_size::GetSize; +use itertools::Itertools; +use rand::{rngs::StdRng, Rng, RngCore, SeedableRng}; +use serde::{Deserialize, Serialize}; +use tasm_lib::{ + twenty_first::{ + shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, + util_types::{ + algebraic_hasher::AlgebraicHasher, + mmr::{ + mmr_accumulator::MmrAccumulator, mmr_membership_proof::MmrMembershipProof, + mmr_trait::Mmr, + }, + }, + }, + Digest, +}; + +use crate::{ + models::blockchain::type_scripts::native_currency::{ + native_currency_program, NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST, + }, + util_types::mutator_set::{ + chunk::Chunk, + chunk_dictionary::ChunkDictionary, + mutator_set_kernel::{get_swbf_indices, MutatorSetKernel}, + mutator_set_trait::{commit, MutatorSet}, + removal_record::{AbsoluteIndexSet, RemovalRecord}, + shared::{BATCH_SIZE, CHUNK_SIZE}, + }, + Hash, +}; +use crate::{ + models::blockchain::type_scripts::neptune_coins::NeptuneCoins, + twenty_first::util_types::mmr::shared_basic::leaf_index_to_mt_index_and_peak_index, +}; +use crate::{ + models::{blockchain::type_scripts::TypeScript, state::wallet::address::generation_address}, + util_types::mutator_set::{ + ms_membership_proof::MsMembershipProof, mutator_set_accumulator::MutatorSetAccumulator, + }, +}; + +use super::{ + transaction_kernel::TransactionKernel, + utxo::{Coin, LockScript, Utxo}, +}; + +/// The raw witness is the most primitive type of transaction witness. +/// It exposes secret data and is therefore not for broadcasting. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] +pub struct PrimitiveWitness { + pub input_utxos: Vec, + pub input_lock_scripts: Vec, + pub type_scripts: Vec, + pub lock_script_witnesses: Vec>, + pub input_membership_proofs: Vec, + pub output_utxos: Vec, + pub mutator_set_accumulator: MutatorSetAccumulator, + pub kernel: TransactionKernel, +} + +impl PrimitiveWitness { + pub fn pseudorandom_mmra_with_mps( + seed: [u8; 32], + leafs: &[Digest], + ) -> (MmrAccumulator, Vec>) { + let mut rng: StdRng = SeedableRng::from_seed(seed); + + // sample size of MMR + let mut leaf_count = rng.next_u64(); + while leaf_count < leafs.len() as u64 { + leaf_count = rng.next_u64(); + } + let num_peaks = leaf_count.count_ones(); + + // sample mmr leaf indices and calculate matching derived indices + let leaf_indices = leafs + .iter() + .enumerate() + .map(|(original_index, _leaf)| (original_index, rng.next_u64() % leaf_count)) + .map(|(original_index, mmr_index)| { + let (mt_index, peak_index) = + leaf_index_to_mt_index_and_peak_index(mmr_index, leaf_count); + (original_index, mmr_index, mt_index, peak_index) + }) + .collect_vec(); + let leafs_and_indices = leafs.iter().copied().zip(leaf_indices).collect_vec(); + + // iterate over all trees + let mut peaks = vec![]; + let dummy_mp = MmrMembershipProof::new(0u64, vec![]); + let mut mps: Vec> = + (0..leafs.len()).map(|_| dummy_mp.clone()).collect_vec(); + for tree in 0..num_peaks { + // select all leafs and merkle tree indices for this tree + let leafs_and_mt_indices = leafs_and_indices + .iter() + .copied() + .filter( + |(_leaf, (_original_index, _mmr_index, _mt_index, peak_index))| { + *peak_index == tree + }, + ) + .map( + |(leaf, (original_index, _mmr_index, mt_index, _peak_index))| { + (leaf, mt_index, original_index) + }, + ) + .collect_vec(); + if leafs_and_mt_indices.is_empty() { + peaks.push(rng.gen()); + continue; + } + + // generate root and authentication paths + let tree_height = (*leafs_and_mt_indices.first().map(|(_l, i, _o)| i).unwrap() as u128) + .ilog2() as usize; + let (root, authentication_paths) = + Self::pseudorandom_merkle_root_with_authentication_paths( + rng.gen(), + tree_height, + &leafs_and_mt_indices + .iter() + .map(|(l, i, _o)| (*l, *i)) + .collect_vec(), + ); + + // sanity check + // for ((leaf, mt_index, _original_index), auth_path) in + // leafs_and_mt_indices.iter().zip(authentication_paths.iter()) + // { + // assert!(merkle_verify_tester_helper::( + // root, *mt_index, auth_path, *leaf + // )); + // } + + // update peaks list + peaks.push(root); + + // generate membership proof objects + let membership_proofs = leafs_and_indices + .iter() + .copied() + .filter( + |(_leaf, (_original_index, _mmr_index, _mt_index, peak_index))| { + *peak_index == tree + }, + ) + .zip(authentication_paths.into_iter()) + .map( + |( + (_leaf, (_original_index, mmr_index, _mt_index, _peak_index)), + authentication_path, + )| { + MmrMembershipProof::::new(mmr_index, authentication_path) + }, + ) + .collect_vec(); + + // sanity check: test if membership proofs agree with peaks list (up until now) + let dummy_remainder: Vec = (peaks.len()..num_peaks as usize) + .map(|_| rng.gen()) + .collect_vec(); + let dummy_peaks = [peaks.clone(), dummy_remainder].concat(); + for (&(leaf, _mt_index, _original_index), mp) in + leafs_and_mt_indices.iter().zip(membership_proofs.iter()) + { + assert!(mp.verify(&dummy_peaks, leaf, leaf_count).0); + } + + // collect membership proofs in vector, with indices matching those of the supplied leafs + for ((_leaf, _mt_index, original_index), mp) in + leafs_and_mt_indices.iter().zip(membership_proofs.iter()) + { + mps[*original_index] = mp.clone(); + } + } + + let mmra = MmrAccumulator::::init(peaks, leaf_count); + + // sanity check + for (&leaf, mp) in leafs.iter().zip(mps.iter()) { + assert!(mp.verify(&mmra.get_peaks(), leaf, mmra.count_leaves()).0); + } + + (mmra, mps) + } + + pub fn pseudorandom_merkle_root_with_authentication_paths( + seed: [u8; 32], + tree_height: usize, + leafs_and_indices: &[(Digest, u64)], + ) -> (Digest, Vec>) { + let mut rng: StdRng = SeedableRng::from_seed(seed); + let mut nodes: HashMap = HashMap::new(); + + // populate nodes dictionary with leafs + for (leaf, index) in leafs_and_indices.iter() { + nodes.insert(*index, *leaf); + } + + // walk up tree layer by layer + // when we need nodes not already present, sample at random + let mut depth = tree_height + 1; + while depth > 0 { + let mut working_indices = nodes + .keys() + .copied() + .filter(|i| { + (*i as u128) < (1u128 << (depth)) && (*i as u128) >= (1u128 << (depth - 1)) + }) + .collect_vec(); + working_indices.sort(); + working_indices.dedup(); + for wi in working_indices { + let wi_odd = wi | 1; + if nodes.get(&wi_odd).is_none() { + nodes.insert(wi_odd, rng.gen::()); + } + let wi_even = wi_odd ^ 1; + if nodes.get(&wi_even).is_none() { + nodes.insert(wi_even, rng.gen::()); + } + let hash = Hash::hash_pair(nodes[&wi_even], nodes[&wi_odd]); + nodes.insert(wi >> 1, hash); + } + depth -= 1; + } + + // read out root + let root = *nodes.get(&1).unwrap_or(&rng.gen()); + + // read out paths + let paths = leafs_and_indices + .iter() + .map(|(_d, i)| { + (0..tree_height) + .map(|j| *nodes.get(&((*i >> j) ^ 1)).unwrap()) + .collect_vec() + }) + .collect_vec(); + + (root, paths) + } + + fn pseudorandom_mmra_of_given_size_with_mps_at_indices( + seed: [u8; 32], + size: u64, + leafs: &[Digest], + indices: &[u64], + ) -> (MmrAccumulator, Vec>) { + let mut rng: StdRng = SeedableRng::from_seed(seed); + let mut tree_heights = vec![]; + for h in (0..64).rev() { + if size & (1 << h) != 0 { + tree_heights.push(h); + } + } + + let mut peaks = vec![]; + let mut membership_proofs = vec![]; + let leaf_and_various_indices = leafs + .iter() + .zip(indices.iter()) + .map(|(&l, &idx)| (l, idx, leaf_index_to_mt_index_and_peak_index(idx, size))); + for (i, tree_height) in tree_heights.into_iter().enumerate() { + let leafs_and_indices = leaf_and_various_indices + .clone() + .filter(|(_l, _idx, (_mti, pki))| *pki == i as u32) + .map(|(l, _idx, (mti, _pki))| (l, mti)) + .collect_vec(); + let (root, paths) = Self::pseudorandom_merkle_root_with_authentication_paths( + rng.gen(), + tree_height, + &leafs_and_indices, + ); + peaks.push(root); + membership_proofs.append( + &mut leafs_and_indices + .into_iter() + .zip(paths.into_iter()) + .map(|((_l, idx), path)| MmrMembershipProof::new(idx, path)) + .collect_vec(), + ); + } + (MmrAccumulator::init(peaks, size), membership_proofs) + } +} + +impl<'a> Arbitrary<'a> for PrimitiveWitness { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let num_inputs = u.int_in_range(0..=4)?; + let num_outputs = u.int_in_range(0..=4)?; + let num_public_announcements = u.int_in_range(0..=2)?; + let sender_spending_keys = (0..num_inputs) + .map(|_| { + generation_address::SpendingKey::derive_from_seed(u.arbitrary::().unwrap()) + }) + .collect_vec(); + let sender_receiving_addresses = sender_spending_keys + .iter() + .map(|ssk| ssk.to_address()) + .collect_vec(); + let input_lock_scripts = sender_receiving_addresses + .iter() + .map(|sra| sra.lock_script()) + .collect_vec(); + let lock_script_witnesses = sender_spending_keys + .iter() + .map(|ssk| ssk.unlock_key.values().to_vec()) + .collect_vec(); + let input_amounts = (0..num_inputs) + .map(|_| u.arbitrary::().unwrap()) + .collect_vec(); + let total_inputs = input_amounts.iter().cloned().sum::(); + let mut output_fractions = (0..=num_outputs) + .map(|_| u.int_in_range(0..=99).unwrap() as f64) + .collect_vec(); + let output_sum = output_fractions.iter().cloned().sum::(); + output_fractions.iter_mut().for_each(|f| *f /= output_sum); + let input_utxos = input_lock_scripts + .iter() + .map(|ils| { + Utxo::new( + ils.clone(), + vec![Coin { + type_script_hash: NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST, + state: u.arbitrary::().unwrap().encode(), + }], + ) + }) + .collect_vec(); + let input_triples = input_utxos + .iter() + .map(|utxo| { + ( + Hash::hash(utxo), + u.arbitrary::().unwrap(), + u.arbitrary::().unwrap(), + ) + }) + .collect_vec(); + let input_commitments = input_triples + .iter() + .map(|(item, sender_randomness, receiver_preimage)| { + commit(*item, *sender_randomness, Hash::hash(receiver_preimage)) + }) + .map(|ar| ar.canonical_commitment) + .collect_vec(); + let (aocl_mmr, aocl_authentication_paths) = + Self::pseudorandom_mmra_with_mps(u.arbitrary().unwrap(), &input_commitments); + let all_index_sets = input_triples + .iter() + .zip(aocl_authentication_paths.iter()) + .map( + |((item, sender_randomness, receiver_preimage), authentication_path)| { + get_swbf_indices( + *item, + *sender_randomness, + *receiver_preimage, + authentication_path.leaf_index, + ) + }, + ) + .collect_vec(); + let mut all_indices = all_index_sets.iter().flatten().cloned().collect_vec(); + all_indices.sort(); + let mut all_chunk_indices = all_indices + .iter() + .map(|index| index / (CHUNK_SIZE as u128)) + .map(|index| index as u64) + .collect_vec(); + all_chunk_indices.dedup(); + let mmr_chunk_indices = all_chunk_indices + .iter() + .cloned() + .filter(|ci| *ci < aocl_mmr.count_leaves() / (BATCH_SIZE as u64)) + .collect_vec(); + let mmr_chunks = (0..mmr_chunk_indices.len()) + .map(|_| u.arbitrary::().unwrap()) + .collect_vec(); + let (swbf_mmr, swbf_authentication_paths) = + Self::pseudorandom_mmra_of_given_size_with_mps_at_indices( + u.arbitrary().unwrap(), + aocl_mmr.count_leaves() / (BATCH_SIZE as u64), + &mmr_chunks.iter().map(Hash::hash).collect_vec(), + &mmr_chunk_indices, + ); + let chunk_dictionary: HashMap, Chunk)> = mmr_chunk_indices + .iter() + .cloned() + .zip(swbf_authentication_paths.into_iter().zip(mmr_chunks)) + .collect(); + let personalized_chunk_dictionaries = all_index_sets + .iter() + .map(|index_set| { + let mut is = index_set + .iter() + .map(|index| index / (BATCH_SIZE as u128)) + .map(|index| index as u64) + .collect_vec(); + is.sort(); + is.dedup(); + is + }) + .map(|chunk_indices| { + ChunkDictionary::new( + chunk_indices + .iter() + .map(|chunk_index| { + ( + *chunk_index, + chunk_dictionary.get(chunk_index).cloned().unwrap(), + ) + }) + .collect::, Chunk)>>(), + ) + }) + .collect_vec(); + let input_membership_proofs = input_triples + .iter() + .zip(aocl_authentication_paths.iter()) + .zip(personalized_chunk_dictionaries.iter()) + .map( + |(((_item, sender_randomness, receiver_preimage), auth_path), target_chunks)| { + MsMembershipProof { + sender_randomness: *sender_randomness, + receiver_preimage: *receiver_preimage, + auth_path_aocl: auth_path.clone(), + target_chunks: target_chunks.clone(), + } + }, + ) + .collect_vec(); + let input_removal_records = all_index_sets + .iter() + .zip(personalized_chunk_dictionaries.iter()) + .map(|(index_set, target_chunks)| RemovalRecord { + absolute_indices: AbsoluteIndexSet::new(index_set), + target_chunks: target_chunks.clone(), + }) + .collect_vec(); + let type_scripts = vec![TypeScript::new(native_currency_program())]; + let output_utxos = output_fractions + .iter() + .take(num_outputs) + .map(|f| Utxo { + lock_script_hash: u.arbitrary().unwrap(), + coins: vec![Coin { + type_script_hash: NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST, + state: NeptuneCoins::new((f * total_inputs.to_nau_f64()) as u32).encode(), + }], + }) + .collect_vec(); + let output_commitments = output_utxos + .iter() + .map(|utxo| { + commit( + Hash::hash(utxo), + u.arbitrary().unwrap(), + u.arbitrary().unwrap(), + ) + }) + .collect_vec(); + let mutator_set_accumulator = MutatorSetAccumulator { + kernel: MutatorSetKernel { + aocl: aocl_mmr, + swbf_inactive: swbf_mmr, + swbf_active: u.arbitrary()?, + }, + }; + let has_coinbase: bool = u.arbitrary()?; + let coinbase = if !has_coinbase { + None + } else { + let fraction_to_coinbase: f64 = u.int_in_range(0..=99)? as f64 / 100.0; + let coinbase_amount = + total_inputs.to_nau_f64() * output_fractions.last().unwrap() * fraction_to_coinbase; + Some(NeptuneCoins::new(coinbase_amount as u32)) + }; + let fee = match coinbase { + Some(cb) => { + NeptuneCoins::new( + (total_inputs.to_nau_f64() * output_fractions.last().unwrap()) as u32, + ) - cb + } + None => NeptuneCoins::new( + (total_inputs.to_nau_f64() * output_fractions.last().unwrap()) as u32, + ), + }; + let public_announcements = (0..num_public_announcements) + .map(|_| u.arbitrary().unwrap()) + .collect_vec(); + let kernel = TransactionKernel { + inputs: input_removal_records, + outputs: output_commitments, + public_announcements, + fee, + coinbase, + timestamp: BFieldElement::new( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64, + ), + mutator_set_hash: mutator_set_accumulator.hash(), + }; + let primitive_witness = PrimitiveWitness { + input_lock_scripts, + input_utxos, + input_membership_proofs, + type_scripts, + lock_script_witnesses, + output_utxos, + mutator_set_accumulator, + kernel, + }; + Ok(primitive_witness) + } +} diff --git a/src/models/blockchain/transaction/transaction_kernel.rs b/src/models/blockchain/transaction/transaction_kernel.rs index 1e252a0d..95466f9b 100644 --- a/src/models/blockchain/transaction/transaction_kernel.rs +++ b/src/models/blockchain/transaction/transaction_kernel.rs @@ -1,3 +1,5 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + use crate::{ models::{ blockchain::type_scripts::neptune_coins::{pseudorandom_amount, NeptuneCoins}, @@ -6,6 +8,7 @@ use crate::{ prelude::twenty_first, }; +use arbitrary::Arbitrary; use get_size::GetSize; use itertools::Itertools; use rand::{rngs::StdRng, Rng, RngCore, SeedableRng}; @@ -16,7 +19,7 @@ use twenty_first::shared_math::{ b_field_element::BFieldElement, bfield_codec::BFieldCodec, tip5::Digest, }; -use super::PublicAnnouncement; +use super::{primitive_witness::PrimitiveWitness, PublicAnnouncement}; use crate::util_types::mutator_set::{ addition_record::{pseudorandom_addition_record, AdditionRecord}, removal_record::{pseudorandom_removal_record, RemovalRecord}, @@ -45,6 +48,13 @@ pub struct TransactionKernel { pub mutator_set_hash: Digest, } +impl TransactionKernel { + pub(crate) fn from_primitive_witness( + transaction_primitive_witness: &PrimitiveWitness, + ) -> TransactionKernel { + transaction_primitive_witness.kernel.clone() + } +} #[derive(Debug, Clone, EnumCount)] pub enum TransactionKernelField { @@ -116,7 +126,7 @@ pub fn pseudorandom_transaction_kernel( let outputs = (0..num_outputs) .map(|_| pseudorandom_addition_record(rng.gen::<[u8; 32]>())) .collect_vec(); - let pubscripts = (0..num_public_announcements) + let public_announcements = (0..num_public_announcements) .map(|_| pseudorandom_public_announcement(rng.gen::<[u8; 32]>())) .collect_vec(); let fee = pseudorandom_amount(rng.gen::<[u8; 32]>()); @@ -127,7 +137,7 @@ pub fn pseudorandom_transaction_kernel( TransactionKernel { inputs, outputs, - public_announcements: pubscripts, + public_announcements, fee, coinbase, timestamp, @@ -135,6 +145,44 @@ pub fn pseudorandom_transaction_kernel( } } +impl<'a> Arbitrary<'a> for TransactionKernel { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let num_inputs = u.int_in_range(0..=4)?; + let num_outputs = u.int_in_range(0..=4)?; + let num_public_announcements = u.int_in_range(0..=2)?; + let inputs: Vec = (0..num_inputs) + .map(|_| u.arbitrary().unwrap()) + .collect_vec(); + let outputs: Vec = (0..num_outputs) + .map(|_| u.arbitrary().unwrap()) + .collect_vec(); + let public_announcements: Vec = (0..num_public_announcements) + .map(|_| u.arbitrary().unwrap()) + .collect_vec(); + let fee: NeptuneCoins = u.arbitrary()?; + let coinbase: Option = u.arbitrary()?; + let timestamp: BFieldElement = BFieldElement::new( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64, + ); + let mutator_set_hash: Digest = u.arbitrary()?; + + let transaction_kernel = TransactionKernel { + inputs, + outputs, + public_announcements, + fee, + coinbase, + timestamp, + mutator_set_hash, + }; + + Ok(transaction_kernel) + } +} + #[cfg(test)] pub mod transaction_kernel_tests { diff --git a/src/models/blockchain/transaction/utxo.rs b/src/models/blockchain/transaction/utxo.rs index ded285e6..a18be0b5 100644 --- a/src/models/blockchain/transaction/utxo.rs +++ b/src/models/blockchain/transaction/utxo.rs @@ -3,6 +3,7 @@ use crate::prelude::{triton_vm, twenty_first}; use crate::models::blockchain::shared::Hash; use crate::models::blockchain::type_scripts::native_currency; +use arbitrary::Arbitrary; use get_size::GetSize; use num_traits::Zero; use rand::rngs::StdRng; @@ -19,14 +20,14 @@ use crate::models::blockchain::type_scripts::native_currency::NATIVE_CURRENCY_TY use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, BFieldCodec)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, BFieldCodec, Arbitrary)] pub struct Coin { pub type_script_hash: Digest, pub state: Vec, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, BFieldCodec)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, BFieldCodec, Arbitrary)] pub struct Utxo { pub lock_script_hash: Digest, pub coins: Vec, diff --git a/src/models/blockchain/transaction/validity.rs b/src/models/blockchain/transaction/validity.rs index 2a49a672..edbcd3b7 100644 --- a/src/models/blockchain/transaction/validity.rs +++ b/src/models/blockchain/transaction/validity.rs @@ -21,7 +21,7 @@ use self::{ kernel_to_lock_scripts::KernelToLockScripts, kernel_to_type_scripts::KernelToTypeScripts, typescripts_halt::TypeScriptsHalt, }; -use super::TransactionPrimitiveWitness; +use super::PrimitiveWitness; /// The validity of a transaction, in the base case, decomposes into /// these subclaims. @@ -44,7 +44,7 @@ pub struct TransactionValidationLogic { } impl TransactionValidationLogic { - pub fn new_from_primitive_witness(primitive_witness: &TransactionPrimitiveWitness) -> Self { + pub fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> Self { let lock_scripts_halt = LockScriptsHalt::new_from_primitive_witness(primitive_witness); let kernel_to_lock_scripts = KernelToLockScripts::new_from_primitive_witness(primitive_witness); diff --git a/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs b/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs index e369a584..0a8e51a1 100644 --- a/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs +++ b/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs @@ -1,7 +1,7 @@ use crate::models::consensus::mast_hash::MastHash; use crate::prelude::{triton_vm, twenty_first}; -use crate::models::blockchain::transaction::TransactionPrimitiveWitness; +use crate::models::blockchain::transaction::PrimitiveWitness; use crate::models::blockchain::transaction::{ transaction_kernel::TransactionKernelField, utxo::Utxo, }; @@ -47,9 +47,9 @@ impl KernelToLockScripts { } impl ValidationLogic for KernelToLockScripts { - type PrimitiveWitness = TransactionPrimitiveWitness; + type PrimitiveWitness = PrimitiveWitness; - fn new_from_primitive_witness(primitive_witness: &TransactionPrimitiveWitness) -> Self { + fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> Self { let claim = Claim { input: primitive_witness.kernel.mast_hash().into(), output: primitive_witness diff --git a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs index fa9b970d..2785e391 100644 --- a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs +++ b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs @@ -2,7 +2,7 @@ use crate::models::blockchain::type_scripts::TypeScript; use crate::models::consensus::mast_hash::MastHash; use crate::prelude::{triton_vm, twenty_first}; -use crate::models::blockchain::transaction::TransactionPrimitiveWitness; +use crate::models::blockchain::transaction::PrimitiveWitness; use crate::models::consensus::{ClaimSupport, SecretWitness, SupportedClaim, ValidationLogic}; use get_size::GetSize; @@ -44,9 +44,9 @@ impl KernelToTypeScripts { } impl ValidationLogic for KernelToTypeScripts { - type PrimitiveWitness = TransactionPrimitiveWitness; + type PrimitiveWitness = PrimitiveWitness; - fn new_from_primitive_witness(primitive_witness: &TransactionPrimitiveWitness) -> Self { + fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> Self { let mut type_script_digests = primitive_witness .input_utxos .iter() diff --git a/src/models/blockchain/transaction/validity/lockscripts_halt.rs b/src/models/blockchain/transaction/validity/lockscripts_halt.rs index 88e1c64a..eafecc67 100644 --- a/src/models/blockchain/transaction/validity/lockscripts_halt.rs +++ b/src/models/blockchain/transaction/validity/lockscripts_halt.rs @@ -10,7 +10,7 @@ use triton_vm::prelude::{BFieldElement, Claim, NonDeterminism, Program}; use twenty_first::shared_math::bfield_codec::BFieldCodec; use crate::models::{ - blockchain::transaction::{utxo::LockScript, TransactionPrimitiveWitness}, + blockchain::transaction::{utxo::LockScript, PrimitiveWitness}, consensus::{ClaimSupport, SecretWitness, SupportedClaim, ValidationLogic}, }; @@ -36,11 +36,9 @@ pub struct LockScriptsHalt { } impl ValidationLogic for LockScriptsHalt { - type PrimitiveWitness = TransactionPrimitiveWitness; + type PrimitiveWitness = PrimitiveWitness; - fn new_from_primitive_witness( - primitive_witness: &TransactionPrimitiveWitness, - ) -> LockScriptsHalt { + fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> LockScriptsHalt { let program_and_program_digests_and_spending_keys = primitive_witness .input_lock_scripts .iter() diff --git a/src/models/blockchain/transaction/validity/removal_records_integrity.rs b/src/models/blockchain/transaction/validity/removal_records_integrity.rs index 73605424..188c3a19 100644 --- a/src/models/blockchain/transaction/validity/removal_records_integrity.rs +++ b/src/models/blockchain/transaction/validity/removal_records_integrity.rs @@ -1,14 +1,24 @@ use crate::models::consensus::mast_hash::MastHash; use crate::prelude::{triton_vm, twenty_first}; +use crate::util_types::mutator_set::addition_record::AdditionRecord; +use crate::util_types::mutator_set::mutator_set_kernel::get_swbf_indices; +use crate::util_types::mutator_set::mutator_set_trait::commit; +use crate::util_types::mutator_set::removal_record::{AbsoluteIndexSet, RemovalRecord}; +use crate::twenty_first::util_types::mmr::shared_basic::leaf_index_to_mt_index_and_peak_index; +use arbitrary::Arbitrary; use field_count::FieldCount; use get_size::GetSize; use itertools::Itertools; +use rand::rngs::StdRng; +use rand::{Rng, RngCore, SeedableRng}; use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; use tasm_lib::memory::{encode_to_memory, FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS}; use tasm_lib::structure::tasm_object::TasmObject; use tasm_lib::traits::compiled_program::CompiledProgram; +use tasm_lib::twenty_first::util_types::mmr::mmr_membership_proof::MmrMembershipProof; +use tasm_lib::twenty_first::util_types::mmr::mmr_trait::Mmr; use triton_vm::prelude::{BFieldElement, Claim, NonDeterminism, Program}; use twenty_first::{ shared_math::{bfield_codec::BFieldCodec, tip5::Digest}, @@ -19,9 +29,7 @@ use crate::models::consensus::{ClaimSupport, SecretWitness, SupportedClaim, Vali use crate::{ models::blockchain::{ shared::Hash, - transaction::{ - transaction_kernel::TransactionKernel, utxo::Utxo, TransactionPrimitiveWitness, - }, + transaction::{transaction_kernel::TransactionKernel, utxo::Utxo, PrimitiveWitness}, }, util_types::mutator_set::ms_membership_proof::MsMembershipProof, }; @@ -48,7 +56,7 @@ pub struct RemovalRecordsIntegrityWitness { } impl RemovalRecordsIntegrityWitness { - pub fn new(primitive_witness: &TransactionPrimitiveWitness) -> Self { + pub fn new(primitive_witness: &PrimitiveWitness) -> Self { Self { input_utxos: primitive_witness.input_utxos.clone(), membership_proofs: primitive_witness.input_membership_proofs.clone(), @@ -90,9 +98,9 @@ pub struct RemovalRecordsIntegrity { } impl ValidationLogic for RemovalRecordsIntegrity { - type PrimitiveWitness = TransactionPrimitiveWitness; + type PrimitiveWitness = PrimitiveWitness; - fn new_from_primitive_witness(primitive_witness: &TransactionPrimitiveWitness) -> Self { + fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> Self { let removal_records_integrity_witness = RemovalRecordsIntegrityWitness::new(primitive_witness); @@ -126,3 +134,274 @@ impl ValidationLogic for RemovalRecordsIntegrity self.supported_claim.claim.clone() } } + +impl RemovalRecordsIntegrityWitness { + pub fn pseudorandom_merkle_root_with_authentication_paths( + seed: [u8; 32], + tree_height: usize, + leafs_and_indices: &[(Digest, u64)], + ) -> (Digest, Vec>) { + let mut rng: StdRng = SeedableRng::from_seed(seed); + let mut nodes: HashMap = HashMap::new(); + + // populate nodes dictionary with leafs + for (leaf, index) in leafs_and_indices.iter() { + nodes.insert(*index, *leaf); + } + + // walk up tree layer by layer + // when we need nodes not already present, sample at random + let mut depth = tree_height + 1; + while depth > 0 { + let mut working_indices = nodes + .keys() + .copied() + .filter(|i| { + (*i as u128) < (1u128 << (depth)) && (*i as u128) >= (1u128 << (depth - 1)) + }) + .collect_vec(); + working_indices.sort(); + working_indices.dedup(); + for wi in working_indices { + let wi_odd = wi | 1; + if nodes.get(&wi_odd).is_none() { + nodes.insert(wi_odd, rng.gen::()); + } + let wi_even = wi_odd ^ 1; + if nodes.get(&wi_even).is_none() { + nodes.insert(wi_even, rng.gen::()); + } + let hash = Hash::hash_pair(nodes[&wi_even], nodes[&wi_odd]); + nodes.insert(wi >> 1, hash); + } + depth -= 1; + } + + // read out root + let root = *nodes.get(&1).unwrap_or(&rng.gen()); + + // read out paths + let paths = leafs_and_indices + .iter() + .map(|(_d, i)| { + (0..tree_height) + .map(|j| *nodes.get(&((*i >> j) ^ 1)).unwrap()) + .collect_vec() + }) + .collect_vec(); + + (root, paths) + } + + pub fn pseudorandom_mmra_with_mps( + seed: [u8; 32], + leafs: &[Digest], + ) -> (MmrAccumulator, Vec>) { + let mut rng: StdRng = SeedableRng::from_seed(seed); + + // sample size of MMR + let mut leaf_count = rng.next_u64(); + while leaf_count < leafs.len() as u64 { + leaf_count = rng.next_u64(); + } + let num_peaks = leaf_count.count_ones(); + + // sample mmr leaf indices and calculate matching derived indices + let leaf_indices = leafs + .iter() + .enumerate() + .map(|(original_index, _leaf)| (original_index, rng.next_u64() % leaf_count)) + .map(|(original_index, mmr_index)| { + let (mt_index, peak_index) = + leaf_index_to_mt_index_and_peak_index(mmr_index, leaf_count); + (original_index, mmr_index, mt_index, peak_index) + }) + .collect_vec(); + let leafs_and_indices = leafs.iter().copied().zip(leaf_indices).collect_vec(); + + // iterate over all trees + let mut peaks = vec![]; + let dummy_mp = MmrMembershipProof::new(0u64, vec![]); + let mut mps: Vec> = + (0..leafs.len()).map(|_| dummy_mp.clone()).collect_vec(); + for tree in 0..num_peaks { + // select all leafs and merkle tree indices for this tree + let leafs_and_mt_indices = leafs_and_indices + .iter() + .copied() + .filter( + |(_leaf, (_original_index, _mmr_index, _mt_index, peak_index))| { + *peak_index == tree + }, + ) + .map( + |(leaf, (original_index, _mmr_index, mt_index, _peak_index))| { + (leaf, mt_index, original_index) + }, + ) + .collect_vec(); + if leafs_and_mt_indices.is_empty() { + peaks.push(rng.gen()); + continue; + } + + // generate root and authentication paths + let tree_height = (*leafs_and_mt_indices.first().map(|(_l, i, _o)| i).unwrap() as u128) + .ilog2() as usize; + let (root, authentication_paths) = + Self::pseudorandom_merkle_root_with_authentication_paths( + rng.gen(), + tree_height, + &leafs_and_mt_indices + .iter() + .map(|(l, i, _o)| (*l, *i)) + .collect_vec(), + ); + + // sanity check + // for ((leaf, mt_index, _original_index), auth_path) in + // leafs_and_mt_indices.iter().zip(authentication_paths.iter()) + // { + // assert!(merkle_verify_tester_helper::( + // root, *mt_index, auth_path, *leaf + // )); + // } + + // update peaks list + peaks.push(root); + + // generate membership proof objects + let membership_proofs = leafs_and_indices + .iter() + .copied() + .filter( + |(_leaf, (_original_index, _mmr_index, _mt_index, peak_index))| { + *peak_index == tree + }, + ) + .zip(authentication_paths.into_iter()) + .map( + |( + (_leaf, (_original_index, mmr_index, _mt_index, _peak_index)), + authentication_path, + )| { + MmrMembershipProof::::new(mmr_index, authentication_path) + }, + ) + .collect_vec(); + + // sanity check: test if membership proofs agree with peaks list (up until now) + let dummy_remainder: Vec = (peaks.len()..num_peaks as usize) + .map(|_| rng.gen()) + .collect_vec(); + let dummy_peaks = [peaks.clone(), dummy_remainder].concat(); + for (&(leaf, _mt_index, _original_index), mp) in + leafs_and_mt_indices.iter().zip(membership_proofs.iter()) + { + assert!(mp.verify(&dummy_peaks, leaf, leaf_count).0); + } + + // collect membership proofs in vector, with indices matching those of the supplied leafs + for ((_leaf, _mt_index, original_index), mp) in + leafs_and_mt_indices.iter().zip(membership_proofs.iter()) + { + mps[*original_index] = mp.clone(); + } + } + + let mmra = MmrAccumulator::::init(peaks, leaf_count); + + // sanity check + for (&leaf, mp) in leafs.iter().zip(mps.iter()) { + assert!(mp.verify(&mmra.get_peaks(), leaf, mmra.count_leaves()).0); + } + + (mmra, mps) + } +} + +impl<'a> Arbitrary<'a> for RemovalRecordsIntegrityWitness { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let num_inputs = u.int_in_range(1..=3usize)?; + let _num_outputs = u.int_in_range(1..=3usize)?; + let _num_public_announcements = u.int_in_range(0..=2usize)?; + + let input_utxos: Vec = (0..num_inputs) + .map(|_| u.arbitrary().unwrap()) + .collect_vec(); + let mut membership_proofs: Vec = (0..num_inputs) + .map(|_| u.arbitrary().unwrap()) + .collect_vec(); + let addition_records: Vec = input_utxos + .iter() + .zip(membership_proofs.iter()) + .map(|(utxo, msmp)| { + commit( + Hash::hash(utxo), + msmp.sender_randomness, + msmp.receiver_preimage.hash::(), + ) + }) + .collect_vec(); + let canonical_commitments = addition_records + .iter() + .map(|ar| ar.canonical_commitment) + .collect_vec(); + let (aocl, mmr_mps) = + Self::pseudorandom_mmra_with_mps(u.arbitrary()?, &canonical_commitments); + assert_eq!(num_inputs, mmr_mps.len()); + assert_eq!(num_inputs, canonical_commitments.len()); + + for (mp, &cc) in mmr_mps.iter().zip_eq(canonical_commitments.iter()) { + assert!( + mp.verify(&aocl.get_peaks(), cc, aocl.count_leaves()).0, + "Returned MPs must be valid for returned AOCL" + ); + } + + for (ms_mp, mmr_mp) in membership_proofs.iter_mut().zip(mmr_mps.iter()) { + ms_mp.auth_path_aocl = mmr_mp.clone(); + } + let swbfi: MmrAccumulator = u.arbitrary()?; + let swbfa_hash: Digest = u.arbitrary()?; + let mut kernel: TransactionKernel = u.arbitrary()?; + kernel.mutator_set_hash = Hash::hash_pair( + Hash::hash_pair(aocl.bag_peaks(), swbfi.bag_peaks()), + Hash::hash_pair(swbfa_hash, Digest::default()), + ); + kernel.inputs = input_utxos + .iter() + .zip(membership_proofs.iter()) + .map(|(utxo, msmp)| { + ( + Hash::hash(utxo), + msmp.sender_randomness, + msmp.receiver_preimage, + msmp.auth_path_aocl.leaf_index, + ) + }) + .map(|(item, sr, rp, li)| get_swbf_indices(item, sr, rp, li)) + .map(|ais| RemovalRecord { + absolute_indices: AbsoluteIndexSet::new(&ais), + target_chunks: u.arbitrary().unwrap(), + }) + .rev() + .collect_vec(); + + let mut kernel_index_set_hashes = kernel + .inputs + .iter() + .map(|rr| Hash::hash(&rr.absolute_indices)) + .collect_vec(); + kernel_index_set_hashes.sort(); + + Ok(RemovalRecordsIntegrityWitness { + input_utxos, + membership_proofs, + aocl, + swbfi, + swbfa_hash, + kernel, + }) + } +} diff --git a/src/models/blockchain/transaction/validity/typescripts_halt.rs b/src/models/blockchain/transaction/validity/typescripts_halt.rs index 4606c2e9..8179d5ec 100644 --- a/src/models/blockchain/transaction/validity/typescripts_halt.rs +++ b/src/models/blockchain/transaction/validity/typescripts_halt.rs @@ -10,7 +10,7 @@ use triton_vm::prelude::{BFieldElement, Claim, NonDeterminism, Program}; use twenty_first::shared_math::bfield_codec::BFieldCodec; use crate::models::{ - blockchain::transaction::{utxo::Utxo, TransactionPrimitiveWitness}, + blockchain::transaction::{utxo::Utxo, PrimitiveWitness}, consensus::{ClaimSupport, SecretWitness, SupportedClaim, ValidationLogic}, }; @@ -46,9 +46,9 @@ impl TypeScriptsHalt { } impl ValidationLogic for TypeScriptsHalt { - type PrimitiveWitness = TransactionPrimitiveWitness; + type PrimitiveWitness = PrimitiveWitness; - fn new_from_primitive_witness(primitive_witness: &TransactionPrimitiveWitness) -> Self { + fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> Self { let claim = Claim { input: primitive_witness.kernel.mast_hash().values().to_vec(), output: vec![], diff --git a/src/models/blockchain/type_scripts/mod.rs b/src/models/blockchain/type_scripts/mod.rs index 5351ff19..2c5020b8 100644 --- a/src/models/blockchain/type_scripts/mod.rs +++ b/src/models/blockchain/type_scripts/mod.rs @@ -13,17 +13,17 @@ use tasm_lib::{ use native_currency::native_currency_program; -use super::transaction::TransactionPrimitiveWitness; +use super::transaction::primitive_witness::PrimitiveWitness; pub mod native_currency; pub mod neptune_coins; pub mod time_lock; trait TypeScriptValidationLogic: - ValidationLogic<(TransactionPrimitiveWitness, ExternalWitness)> + ValidationLogic<(PrimitiveWitness, ExternalWitness)> where ExternalWitness: BFieldCodec, - (TransactionPrimitiveWitness, ExternalWitness): SecretWitness, + (PrimitiveWitness, ExternalWitness): SecretWitness, { } diff --git a/src/models/blockchain/type_scripts/neptune_coins.rs b/src/models/blockchain/type_scripts/neptune_coins.rs index 479fea5e..c90384a9 100644 --- a/src/models/blockchain/type_scripts/neptune_coins.rs +++ b/src/models/blockchain/type_scripts/neptune_coins.rs @@ -1,6 +1,7 @@ use crate::{models::blockchain::transaction::utxo::Coin, prelude::twenty_first}; use anyhow::bail; +use arbitrary::Arbitrary; use get_size::GetSize; use num_bigint::BigInt; use num_rational::BigRational; @@ -74,6 +75,24 @@ impl NeptuneCoins { dictionary } + /// Convert the amount to Neptune atomic units (nau) as a 64-bit floating point. + /// Note that this function loses precision! + pub fn to_nau_f64(&self) -> f64 { + if self.is_zero() { + return 0.0; + } + let nau = self.to_nau(); + let bit_size = nau.bits(); + let shift = if bit_size > 52 { bit_size - 52 } else { 0 }; + let (_sign, digits) = (nau >> shift).to_u64_digits(); + let top_digit = digits[0]; + let mut float = top_digit as f64; + for _ in 0..shift { + float *= 2.0; + } + float + } + /// Convert the amount to Neptune atomic units (nau) pub fn to_nau(&self) -> BigInt { BigInt::from_slice(num_bigint::Sign::Plus, self.0.as_ref()) @@ -298,6 +317,16 @@ pub fn pseudorandom_amount(seed: [u8; 32]) -> NeptuneCoins { NeptuneCoins(U32s::new(number)) } +impl<'a> Arbitrary<'a> for NeptuneCoins { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let mut nau: U32s = U32s::new(u.arbitrary()?); + while nau > NeptuneCoins::conversion_factor() * 42000000.into() { + nau = U32s::new(u.arbitrary()?); + } + Ok(NeptuneCoins(nau)) + } +} + #[cfg(test)] mod amount_tests { use get_size::GetSize; diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index ab065248..e533f079 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -1,12 +1,17 @@ +use crate::models::blockchain::transaction::primitive_witness::PrimitiveWitness; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; +use crate::models::blockchain::transaction::transaction_kernel::TransactionKernelField; use crate::models::blockchain::transaction::utxo::Coin; use crate::models::blockchain::transaction::utxo::Utxo; +use crate::models::consensus::mast_hash::MastHash; use crate::models::consensus::SecretWitness; use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; use crate::util_types::mutator_set::mutator_set_kernel::get_swbf_indices; use crate::util_types::mutator_set::shared::NUM_TRIALS; use crate::Hash; +use arbitrary::Arbitrary; use get_size::GetSize; +use itertools::Itertools; use serde::{Deserialize, Serialize}; use tasm_lib::twenty_first::prelude::AlgebraicHasher; use tasm_lib::{ @@ -387,21 +392,68 @@ impl ConsensusProgram for TimeLock { #[derive(Debug, Clone, Deserialize, Serialize, BFieldCodec, GetSize, PartialEq, Eq)] pub struct TimeLockWitness { - time_lock: TimeLock, - release_date: u64, - mast_path: Vec, + /// One timestamp for every input UTXO. Inputs that do not have a time lock are + /// assigned timestamp 0, which is automatically satisfied. + release_dates: Vec, input_utxos: Vec, - input_mps: Vec, + input_membership_proofs: Vec, transaction_kernel: TransactionKernel, } +impl TimeLockWitness { + pub fn from_primitive_witness(transaction_primitive_witness: &PrimitiveWitness) -> Self { + let release_dates = transaction_primitive_witness + .input_utxos + .iter() + .map(|utxo| { + utxo.coins + .iter() + .find(|coin| coin.type_script_hash == TimeLock::hash()) + .cloned() + .map(|coin| { + coin.state + .first() + .copied() + .unwrap_or_else(|| BFieldElement::new(0)) + }) + .unwrap_or_else(|| BFieldElement::new(0)) + }) + .map(|b| b.value()) + .collect_vec(); + let transaction_kernel = + TransactionKernel::from_primitive_witness(transaction_primitive_witness); + let input_utxos = transaction_primitive_witness.input_utxos.clone(); + let input_mps = transaction_primitive_witness + .input_membership_proofs + .clone(); + Self { + release_dates, + input_utxos, + input_membership_proofs: input_mps, + transaction_kernel, + } + } +} + impl SecretWitness for TimeLockWitness { fn nondeterminism(&self) -> NonDeterminism { - NonDeterminism::new(vec![BFieldElement::new(self.release_date)]) - .with_digests(self.mast_path.clone()) + NonDeterminism::new(self.release_dates.encode()).with_digests( + self.transaction_kernel + .mast_path(TransactionKernelField::Timestamp) + .clone(), + ) } fn subprogram(&self) -> Program { Program::new(&TimeLock::code()) } } + +impl<'a> Arbitrary<'a> for TimeLockWitness { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let transaction_primitive_witness: PrimitiveWitness = u.arbitrary()?; + Ok(TimeLockWitness::from_primitive_witness( + &transaction_primitive_witness, + )) + } +} diff --git a/src/models/state/mod.rs b/src/models/state/mod.rs index af4bcca7..d10cbe56 100644 --- a/src/models/state/mod.rs +++ b/src/models/state/mod.rs @@ -26,11 +26,12 @@ use self::wallet::wallet_state::WalletState; use self::wallet::wallet_status::WalletStatus; use super::blockchain::block::block_height::BlockHeight; use super::blockchain::block::Block; +use super::blockchain::transaction::primitive_witness::PrimitiveWitness; use super::blockchain::transaction::transaction_kernel::TransactionKernel; use super::blockchain::transaction::utxo::{LockScript, Utxo}; use super::blockchain::transaction::validity::TransactionValidationLogic; +use super::blockchain::transaction::PublicAnnouncement; use super::blockchain::transaction::Transaction; -use super::blockchain::transaction::{PublicAnnouncement, TransactionPrimitiveWitness}; use super::blockchain::type_scripts::neptune_coins::NeptuneCoins; use super::blockchain::type_scripts::TypeScript; use super::consensus::ValidationLogic; @@ -550,7 +551,7 @@ impl GlobalState { // is here the spending key reversed. let mut secret_input = spending_key.unlock_key.encode(); secret_input.reverse(); - let mut primitive_witness = TransactionPrimitiveWitness { + let mut primitive_witness = PrimitiveWitness { input_utxos, input_lock_scripts, type_scripts, diff --git a/src/tests/shared.rs b/src/tests/shared.rs index fd1698e2..0031f0d3 100644 --- a/src/tests/shared.rs +++ b/src/tests/shared.rs @@ -51,6 +51,7 @@ use crate::models::blockchain::block::block_body::BlockBody; use crate::models::blockchain::block::block_header::BlockHeader; use crate::models::blockchain::block::block_header::TARGET_BLOCK_INTERVAL; use crate::models::blockchain::block::{block_height::BlockHeight, Block}; +use crate::models::blockchain::transaction::primitive_witness::PrimitiveWitness; use crate::models::blockchain::transaction::transaction_kernel::pseudorandom_option; use crate::models::blockchain::transaction::transaction_kernel::pseudorandom_public_announcement; use crate::models::blockchain::transaction::transaction_kernel::pseudorandom_transaction_kernel; @@ -58,7 +59,6 @@ use crate::models::blockchain::transaction::transaction_kernel::TransactionKerne use crate::models::blockchain::transaction::validity::removal_records_integrity::RemovalRecordsIntegrityWitness; use crate::models::blockchain::transaction::validity::TransactionValidationLogic; use crate::models::blockchain::transaction::PublicAnnouncement; -use crate::models::blockchain::transaction::TransactionPrimitiveWitness; use crate::models::blockchain::transaction::TransactionWitness; use crate::models::blockchain::transaction::{utxo::Utxo, Transaction}; use crate::models::blockchain::type_scripts::TypeScript; @@ -799,7 +799,7 @@ pub fn make_mock_transaction_with_generation_key( .map(|(_utxo, _mp, sk)| sk.to_address().lock_script()) .collect_vec(); let output_utxos = receiver_data.into_iter().map(|rd| rd.utxo).collect(); - let primitive_witness = TransactionPrimitiveWitness { + let primitive_witness = PrimitiveWitness { input_utxos, type_scripts, input_lock_scripts, @@ -925,7 +925,7 @@ pub fn make_mock_block( mutator_set_hash: previous_mutator_set.hash(), }; - let primitive_witness = TransactionPrimitiveWitness { + let primitive_witness = PrimitiveWitness { input_utxos: vec![], type_scripts: vec![TypeScript::native_coin()], lock_script_witnesses: vec![], diff --git a/src/util_types/mutator_set/active_window.rs b/src/util_types/mutator_set/active_window.rs index 8ed8496a..60ae68f1 100644 --- a/src/util_types/mutator_set/active_window.rs +++ b/src/util_types/mutator_set/active_window.rs @@ -1,5 +1,6 @@ use crate::prelude::twenty_first; +use arbitrary::Arbitrary; use get_size::GetSize; use itertools::Itertools; use serde::{Deserialize, Serialize}; @@ -9,7 +10,7 @@ use twenty_first::shared_math::bfield_codec::BFieldCodec; use super::chunk::Chunk; use super::shared::{CHUNK_SIZE, WINDOW_SIZE}; -#[derive(Clone, Debug, Eq, Serialize, Deserialize, GetSize, BFieldCodec)] +#[derive(Clone, Debug, Eq, Serialize, Deserialize, GetSize, BFieldCodec, Arbitrary)] pub struct ActiveWindow { // It's OK to store this in memory, since it's on the size of kilobytes, not gigabytes. pub sbf: Vec, diff --git a/src/util_types/mutator_set/addition_record.rs b/src/util_types/mutator_set/addition_record.rs index 6d0e3bc8..65d02933 100644 --- a/src/util_types/mutator_set/addition_record.rs +++ b/src/util_types/mutator_set/addition_record.rs @@ -1,5 +1,6 @@ use crate::prelude::twenty_first; +use arbitrary::Arbitrary; use get_size::GetSize; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; @@ -7,7 +8,9 @@ use serde::{Deserialize, Serialize}; use twenty_first::shared_math::bfield_codec::BFieldCodec; use twenty_first::shared_math::tip5::Digest; -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, GetSize, BFieldCodec)] +#[derive( + Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, GetSize, BFieldCodec, Arbitrary, +)] pub struct AdditionRecord { pub canonical_commitment: Digest, } diff --git a/src/util_types/mutator_set/chunk.rs b/src/util_types/mutator_set/chunk.rs index 3bc788b0..aa869bf3 100644 --- a/src/util_types/mutator_set/chunk.rs +++ b/src/util_types/mutator_set/chunk.rs @@ -1,5 +1,6 @@ use crate::prelude::twenty_first; +use arbitrary::Arbitrary; use get_size::GetSize; use itertools::Itertools; use serde_derive::{Deserialize, Serialize}; @@ -7,7 +8,7 @@ use twenty_first::shared_math::bfield_codec::BFieldCodec; use super::shared::CHUNK_SIZE; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, GetSize, BFieldCodec)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, GetSize, BFieldCodec, Arbitrary)] pub struct Chunk { pub relative_indices: Vec, } diff --git a/src/util_types/mutator_set/chunk_dictionary.rs b/src/util_types/mutator_set/chunk_dictionary.rs index 6161d744..ac6705aa 100644 --- a/src/util_types/mutator_set/chunk_dictionary.rs +++ b/src/util_types/mutator_set/chunk_dictionary.rs @@ -2,6 +2,7 @@ use crate::models::blockchain::shared::Hash; use crate::prelude::{triton_vm, twenty_first}; use anyhow::bail; +use arbitrary::Arbitrary; use get_size::GetSize; use itertools::Itertools; use rand::rngs::StdRng; @@ -15,7 +16,7 @@ use super::chunk::Chunk; use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::util_types::mmr::mmr_membership_proof::MmrMembershipProof; -#[derive(Clone, Debug, Serialize, Deserialize, GetSize, PartialEq, Eq, Default)] +#[derive(Clone, Debug, Serialize, Deserialize, GetSize, PartialEq, Eq, Default, Arbitrary)] pub struct ChunkDictionary { // {chunk index => (MMR membership proof for the whole chunk to which index belongs, chunk value)} pub dictionary: HashMap, Chunk)>, diff --git a/src/util_types/mutator_set/ms_membership_proof.rs b/src/util_types/mutator_set/ms_membership_proof.rs index 05bfe20d..fd441a5e 100644 --- a/src/util_types/mutator_set/ms_membership_proof.rs +++ b/src/util_types/mutator_set/ms_membership_proof.rs @@ -1,6 +1,7 @@ use crate::models::blockchain::shared::Hash; use crate::prelude::twenty_first; +use arbitrary::Arbitrary; use get_size::GetSize; use itertools::Itertools; use rand::rngs::StdRng; @@ -47,7 +48,9 @@ pub enum MembershipProofError { } // In order to store this structure in the database, it needs to be serializable. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, GetSize, BFieldCodec, TasmObject)] +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, GetSize, BFieldCodec, TasmObject, Arbitrary, +)] pub struct MsMembershipProof { pub sender_randomness: Digest, pub receiver_preimage: Digest, diff --git a/src/util_types/mutator_set/removal_record.rs b/src/util_types/mutator_set/removal_record.rs index 443c7be1..8a276319 100644 --- a/src/util_types/mutator_set/removal_record.rs +++ b/src/util_types/mutator_set/removal_record.rs @@ -1,6 +1,7 @@ use crate::models::blockchain::shared::Hash; use crate::prelude::twenty_first; +use arbitrary::Arbitrary; use get_size::GetSize; use itertools::Itertools; use rand::rngs::StdRng; @@ -27,7 +28,7 @@ use twenty_first::util_types::mmr; use twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator; use twenty_first::util_types::mmr::mmr_trait::Mmr; -#[derive(Debug, Clone, PartialEq, Eq, BFieldCodec)] +#[derive(Debug, Clone, PartialEq, Eq, BFieldCodec, Arbitrary)] pub struct AbsoluteIndexSet([u128; NUM_TRIALS as usize]); impl GetSize for AbsoluteIndexSet { @@ -126,7 +127,9 @@ impl<'de> Deserialize<'de> for AbsoluteIndexSet { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, GetSize, BFieldCodec, TasmObject)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, GetSize, BFieldCodec, TasmObject, Arbitrary, +)] pub struct RemovalRecord { pub absolute_indices: AbsoluteIndexSet, pub target_chunks: ChunkDictionary, From 76a3aab85bbc20b89961ab0aa186d0e7c0dccd88 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Fri, 16 Feb 2024 13:20:44 +0100 Subject: [PATCH 10/33] test: Implement Arbitrary for `RootAndPaths` This commit: - defines a new data structure, `RootAndPaths` which holds a single Merkle root and a bunch of authentication paths - implements `proptest::Arbitrary` for `RootAndPaths`, enabling the user to supply the tree height and the leafs and leaf indices - defines a property test using `test_strategy` for `RootAndPaths` Co-authored-by: Ferdinand Sauer --- Cargo.lock | 36 ++++++ Cargo.toml | 5 +- src/util_types/mutator_set.rs | 1 + src/util_types/mutator_set/root_and_paths.rs | 128 +++++++++++++++++++ 4 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 src/util_types/mutator_set/root_and_paths.rs diff --git a/Cargo.lock b/Cargo.lock index 71715e95..3e0f4943 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1743,6 +1743,7 @@ dependencies = [ "strum", "tarpc", "tasm-lib", + "test-strategy", "tiny-bip39", "tokio", "tokio-serde 0.9.0", @@ -2700,6 +2701,29 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +[[package]] +name = "structmeta" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ad9e09554f0456d67a69c1584c9798ba733a5b50349a6c0d0948710523922d" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn 2.0.48", +] + +[[package]] +name = "structmeta-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "strum" version = "0.25.0" @@ -2831,6 +2855,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "test-strategy" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8361c808554228ad09bfed70f5c823caf8a3450b6881cc3a38eb57e8c08c1d9" +dependencies = [ + "proc-macro2", + "quote", + "structmeta", + "syn 2.0.48", +] + [[package]] name = "thiserror" version = "1.0.57" diff --git a/Cargo.toml b/Cargo.toml index 1a54a982..703a6963 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,8 @@ num-bigint = { version = "0", features = ["serde"] } num-rational = "0" num-traits = "0" priority-queue = "1" +proptest = "1.4" +proptest-arbitrary-interop = "0.1" rand = "0.8" ratatui = "0.23" regex = "1.10.3" @@ -56,10 +58,9 @@ unicode-width = "0" zeroize = "1.7.0" [dev-dependencies] +test-strategy = "0.3" pin-project-lite = "0.2.13" tokio-test = "0" -proptest = "1.4" -proptest-arbitrary-interop = "0.1" [dev-dependencies.cargo-husky] default-features = false diff --git a/src/util_types/mutator_set.rs b/src/util_types/mutator_set.rs index 7f28a302..0bf5cd5b 100644 --- a/src/util_types/mutator_set.rs +++ b/src/util_types/mutator_set.rs @@ -9,5 +9,6 @@ pub mod mutator_set_accumulator; pub mod mutator_set_kernel; pub mod mutator_set_trait; pub mod removal_record; +pub mod root_and_paths; pub mod rusty_archival_mutator_set; pub mod shared; diff --git a/src/util_types/mutator_set/root_and_paths.rs b/src/util_types/mutator_set/root_and_paths.rs new file mode 100644 index 00000000..7be82e93 --- /dev/null +++ b/src/util_types/mutator_set/root_and_paths.rs @@ -0,0 +1,128 @@ +use std::collections::hash_map::Entry; +use std::collections::HashMap; + +use itertools::Itertools; +use proptest::collection::vec; +use proptest::{ + arbitrary::Arbitrary, + strategy::{BoxedStrategy, Just, Strategy}, +}; +use proptest_arbitrary_interop::arb; +use tasm_lib::{twenty_first::util_types::algebraic_hasher::AlgebraicHasher, Digest}; + +use crate::models::blockchain::shared::Hash; + +#[derive(Debug, Clone, Default)] +pub struct RootAndPaths { + pub root: Digest, + pub paths: Vec>, +} + +impl Arbitrary for RootAndPaths { + type Parameters = (usize, Vec<(u64, Digest)>); + type Strategy = BoxedStrategy; + + fn arbitrary_with(parameters: Self::Parameters) -> Self::Strategy { + let (tree_height_proper, indices_and_leafs_proper) = parameters; + let upper_bound_num_digests = tree_height_proper * indices_and_leafs_proper.len() + 1; + + let tree_height_strategy = Just(tree_height_proper); + let indices_and_leafs_strategy = Just(indices_and_leafs_proper); + let vec_digest_strategy = vec(arb::(), upper_bound_num_digests); + ( + tree_height_strategy, + indices_and_leafs_strategy, + vec_digest_strategy, + ) + .prop_map(|(tree_height, indices_and_leafs, mut digests)| { + let mut nodes = HashMap::new(); + + // populate nodes dictionary with leafs + for &(index, leaf) in &indices_and_leafs { + let node_index = index + (1 << tree_height); + nodes.insert(node_index, leaf); + } + + // walk up tree layer by layer + // when we need nodes not already present, sample at random + // note: depth 1 is the layer containing only the root + let by_layer = |index, layer| { + let sub_tree_height = tree_height - layer; + let layer_start = 1u64 << sub_tree_height; + let layer_stop = 1u64 << (sub_tree_height + 1); + index >= layer_start && index < layer_stop + }; + for layer in 0..tree_height { + let mut working_indices = nodes + .keys() + .copied() + .filter(|&i| by_layer(i, layer)) + .collect_vec(); + working_indices.sort(); + working_indices.dedup(); + for wi in working_indices { + let wi_odd = wi | 1; + if let Entry::Vacant(entry) = nodes.entry(wi_odd) { + entry.insert(digests.pop().unwrap()); + } + let wi_even = wi_odd ^ 1; + if let Entry::Vacant(entry) = nodes.entry(wi_even) { + entry.insert(digests.pop().unwrap()); + } + let hash = Hash::hash_pair(nodes[&wi_even], nodes[&wi_odd]); + nodes.insert(wi >> 1, hash); + } + } + + // read out root, even if no paths were traversed + let root = *nodes.get(&1).unwrap_or(&digests.pop().unwrap()); + + // read out paths + let paths = indices_and_leafs + .iter() + .map(|(leaf_idx, _)| leaf_idx + (1 << tree_height)) + .map(|node_idx| { + (0..tree_height) + .map(|layer_idx| nodes[&((node_idx >> layer_idx) ^ 1)]) + .collect_vec() + }) + .collect_vec(); + + RootAndPaths { root, paths } + }) + .boxed() + } +} + +#[cfg(test)] +mod test { + use proptest::prelude::*; + use tasm_lib::twenty_first::util_types::merkle_tree::MerkleTreeInclusionProof; + use test_strategy::proptest; + + use super::*; + + #[proptest(cases = 20)] + fn integrity( + // max tree height is 32 as per twenty-first (to be changed) + #[strategy(1usize..32)] tree_height: usize, + #[strategy(vec((0..(1u64 << #tree_height), arb()), 0..100))] + #[filter(#indexed_leafs.iter().map(|(idx, _)| idx).all_unique())] + indexed_leafs: Vec<(u64, Digest)>, + #[strategy(RootAndPaths::arbitrary_with((#tree_height, #indexed_leafs)))] + root_and_paths: RootAndPaths, + ) { + let indexed_leafs = indexed_leafs + .into_iter() + .map(|(idx, digest)| (idx as usize, digest)); + for (path, indexed_leaf) in root_and_paths.paths.into_iter().zip(indexed_leafs) { + let inclusion_proof = MerkleTreeInclusionProof:: { + tree_height, + indexed_leaves: vec![indexed_leaf], + authentication_structure: path, + ..Default::default() + }; + prop_assert!(inclusion_proof.verify(root_and_paths.root)); + } + } +} From 37cfe4f136dcd5c593298d028555be577153f7e8 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Sat, 17 Feb 2024 13:21:54 +0100 Subject: [PATCH 11/33] test: add struct `MmraAndMembershipProofs` WIP --- src/util_types/mutator_set.rs | 1 + .../mutator_set/mmra_and_membership_proofs.rs | 159 ++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 src/util_types/mutator_set/mmra_and_membership_proofs.rs diff --git a/src/util_types/mutator_set.rs b/src/util_types/mutator_set.rs index 0bf5cd5b..fec1cf28 100644 --- a/src/util_types/mutator_set.rs +++ b/src/util_types/mutator_set.rs @@ -4,6 +4,7 @@ pub mod archival_mutator_set; pub mod boxed_big_array; pub mod chunk; pub mod chunk_dictionary; +pub mod mmra_and_membership_proofs; pub mod ms_membership_proof; pub mod mutator_set_accumulator; pub mod mutator_set_kernel; diff --git a/src/util_types/mutator_set/mmra_and_membership_proofs.rs b/src/util_types/mutator_set/mmra_and_membership_proofs.rs new file mode 100644 index 00000000..b5d70e90 --- /dev/null +++ b/src/util_types/mutator_set/mmra_and_membership_proofs.rs @@ -0,0 +1,159 @@ +use itertools::Itertools; +use proptest::{ + arbitrary::{arbitrary_with, Arbitrary}, + collection::vec, + strategy::{BoxedStrategy, Just, Strategy}, +}; +use proptest_arbitrary_interop::arb; +use tasm_lib::twenty_first::util_types::mmr::{ + mmr_trait::Mmr, shared_basic::leaf_index_to_mt_index_and_peak_index, +}; +use tasm_lib::{ + twenty_first::util_types::mmr::{ + mmr_accumulator::MmrAccumulator, mmr_membership_proof::MmrMembershipProof, + }, + Digest, +}; + +use crate::models::blockchain::shared::Hash; + +use super::root_and_paths::RootAndPaths; + +#[derive(Debug, Clone)] +pub struct MmraAndMembershipProofs { + mmra: MmrAccumulator, + membership_proofs: Vec>, +} + +impl Arbitrary for MmraAndMembershipProofs { + type Parameters = Vec; + + fn arbitrary_with(leafs_proper: Self::Parameters) -> Self::Strategy { + let total_leaf_count_strategy = (leafs_proper.len() as u64)..u64::MAX; + let index_strategy = + (arb::(), total_leaf_count_strategy.clone()).prop_map(|(i, m)| i % m); + let indices_strategy = vec(index_strategy, leafs_proper.len()); + let leafs_strategy = Just(leafs_proper); + let digest_strategy = vec(arb::(), 3 * 64); + ( + total_leaf_count_strategy, + leafs_strategy, + indices_strategy, + digest_strategy, + ) + .prop_flat_map(|(total_leaf_count, leafs, mut indices, mut digests)| { + let num_peaks = total_leaf_count.count_ones(); + + // sample mmr leaf indices and calculate matching derived indices + let leaf_indices = leafs + .iter() + .enumerate() + .map(|(original_index, _leaf)| (original_index, indices.pop().unwrap())) + .map(|(original_index, mmr_index)| { + let (mt_index, peak_index) = + leaf_index_to_mt_index_and_peak_index(mmr_index, total_leaf_count); + (original_index, mmr_index, mt_index, peak_index) + }) + .collect_vec(); + let leafs_and_indices = leafs.iter().copied().zip(leaf_indices).collect_vec(); + + // iterate over all trees + let mut peaks: Vec> = vec![]; + let dummy_mp = MmrMembershipProof::new(0u64, vec![]); + let mut mps: Vec> = + (0..leafs.len()).map(|_| dummy_mp.clone()).collect_vec(); + for tree in 0..num_peaks { + // select all leafs and merkle tree indices for this tree + let leafs_and_mt_indices = leafs_and_indices + .iter() + .copied() + .filter( + |(_leaf, (_original_index, _mmr_index, _mt_index, peak_index))| { + *peak_index == tree + }, + ) + .map( + |(leaf, (original_index, _mmr_index, mt_index, _peak_index))| { + (leaf, mt_index, original_index) + }, + ) + .collect_vec(); + if leafs_and_mt_indices.is_empty() { + peaks.push(Just(digests.pop().unwrap()).boxed()); + continue; + } + + // generate root and authentication paths + let tree_height = (*leafs_and_mt_indices.first().map(|(_l, i, _o)| i).unwrap() + as u128) + .ilog2() as usize; + + let root_and_paths_strategy = RootAndPaths::arbitrary_with(( + tree_height, + leafs_and_mt_indices + .iter() + .map(|&(l, i, _o)| (i, l)) + .collect_vec(), + )); + let root_strategy = root_and_paths_strategy.prop_map(|rp| rp.root); + let paths_strategy = root_and_paths_strategy.prop_map(|rp| rp.paths); + + // update peaks list + peaks.push(root_strategy.boxed()); + + // generate membership proof objects + let membership_proofs_strategy = paths_strategy + .prop_map(|authentication_paths| { + leafs_and_indices + .iter() + .copied() + .filter( + |( + _leaf, + (_original_index, _mmr_index, _mt_index, peak_index), + )| { *peak_index == tree }, + ) + .zip(authentication_paths.into_iter()) + .map( + |( + ( + _leaf, + (_original_index, mmr_index, _mt_index, _peak_index), + ), + authentication_path, + )| { + MmrMembershipProof::::new( + mmr_index, + authentication_path, + ) + }, + ) + .collect_vec() + }) + .boxed(); + + // collect membership proofs in vector, with indices matching those of the supplied leafs + for ((_leaf, _mt_index, original_index), mp) in + leafs_and_mt_indices.iter().zip(membership_proofs.iter()) + { + mps[*original_index] = mp.clone(); + } + } + + let mmra = MmrAccumulator::::init(peaks, total_leaf_count); + + // sanity check + for (&leaf, mp) in leafs.iter().zip(mps.iter()) { + assert!(mp.verify(&mmra.get_peaks(), leaf, mmra.count_leaves()).0); + } + + Just(MmraAndMembershipProofs { + membership_proofs: mps, + mmra, + }) + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} From 70c136a8ad25e80888e3411adc164b6fbbe0f597 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Sat, 17 Feb 2024 15:08:20 +0100 Subject: [PATCH 12/33] make compile: `MmraAndMembershipProofs` --- .../mutator_set/mmra_and_membership_proofs.rs | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/util_types/mutator_set/mmra_and_membership_proofs.rs b/src/util_types/mutator_set/mmra_and_membership_proofs.rs index b5d70e90..fb479d71 100644 --- a/src/util_types/mutator_set/mmra_and_membership_proofs.rs +++ b/src/util_types/mutator_set/mmra_and_membership_proofs.rs @@ -1,13 +1,11 @@ use itertools::Itertools; use proptest::{ - arbitrary::{arbitrary_with, Arbitrary}, + arbitrary::Arbitrary, collection::vec, strategy::{BoxedStrategy, Just, Strategy}, }; use proptest_arbitrary_interop::arb; -use tasm_lib::twenty_first::util_types::mmr::{ - mmr_trait::Mmr, shared_basic::leaf_index_to_mt_index_and_peak_index, -}; +use tasm_lib::twenty_first::util_types::mmr::shared_basic::leaf_index_to_mt_index_and_peak_index; use tasm_lib::{ twenty_first::util_types::mmr::{ mmr_accumulator::MmrAccumulator, mmr_membership_proof::MmrMembershipProof, @@ -21,8 +19,8 @@ use super::root_and_paths::RootAndPaths; #[derive(Debug, Clone)] pub struct MmraAndMembershipProofs { - mmra: MmrAccumulator, - membership_proofs: Vec>, + pub mmra: MmrAccumulator, + pub membership_proofs: Vec>, } impl Arbitrary for MmraAndMembershipProofs { @@ -58,13 +56,14 @@ impl Arbitrary for MmraAndMembershipProofs { let leafs_and_indices = leafs.iter().copied().zip(leaf_indices).collect_vec(); // iterate over all trees - let mut peaks: Vec> = vec![]; + let mut peaks_strategy: Vec> = vec![]; let dummy_mp = MmrMembershipProof::new(0u64, vec![]); - let mut mps: Vec> = - (0..leafs.len()).map(|_| dummy_mp.clone()).collect_vec(); + let mut mps_strategy = + Just((0..leafs.len()).map(|_| dummy_mp.clone()).collect_vec()).boxed(); for tree in 0..num_peaks { // select all leafs and merkle tree indices for this tree let leafs_and_mt_indices = leafs_and_indices + .clone() .iter() .copied() .filter( @@ -79,7 +78,7 @@ impl Arbitrary for MmraAndMembershipProofs { ) .collect_vec(); if leafs_and_mt_indices.is_empty() { - peaks.push(Just(digests.pop().unwrap()).boxed()); + peaks_strategy.push(Just(digests.pop().unwrap()).boxed()); continue; } @@ -95,16 +94,17 @@ impl Arbitrary for MmraAndMembershipProofs { .map(|&(l, i, _o)| (i, l)) .collect_vec(), )); - let root_strategy = root_and_paths_strategy.prop_map(|rp| rp.root); + let root_strategy = root_and_paths_strategy.clone().prop_map(|rp| rp.root); let paths_strategy = root_and_paths_strategy.prop_map(|rp| rp.paths); // update peaks list - peaks.push(root_strategy.boxed()); + peaks_strategy.push(root_strategy.boxed()); // generate membership proof objects + let other_leafs_and_indices = leafs_and_indices.clone(); let membership_proofs_strategy = paths_strategy - .prop_map(|authentication_paths| { - leafs_and_indices + .prop_map(move |authentication_paths| { + other_leafs_and_indices .iter() .copied() .filter( @@ -133,23 +133,27 @@ impl Arbitrary for MmraAndMembershipProofs { .boxed(); // collect membership proofs in vector, with indices matching those of the supplied leafs - for ((_leaf, _mt_index, original_index), mp) in - leafs_and_mt_indices.iter().zip(membership_proofs.iter()) - { - mps[*original_index] = mp.clone(); - } + mps_strategy = (mps_strategy.clone(), membership_proofs_strategy) + .prop_map(move |(mps, membership_proofs)| { + let mut new_mps = mps.clone(); + for ((_leaf, _mt_index, original_index), membership_proof) in + leafs_and_mt_indices.iter().zip(membership_proofs.iter()) + { + new_mps[*original_index] = membership_proof.clone(); + } + new_mps + }) + .boxed(); } - let mmra = MmrAccumulator::::init(peaks, total_leaf_count); + let mmra_strategy = peaks_strategy + .prop_map(move |peaks| MmrAccumulator::::init(peaks, total_leaf_count)); - // sanity check - for (&leaf, mp) in leafs.iter().zip(mps.iter()) { - assert!(mp.verify(&mmra.get_peaks(), leaf, mmra.count_leaves()).0); - } - - Just(MmraAndMembershipProofs { - membership_proofs: mps, - mmra, + (mps_strategy, mmra_strategy).prop_map(|(membership_proofs, mmra)| { + MmraAndMembershipProofs { + membership_proofs, + mmra, + } }) }) .boxed() From 86df380d08e62d6d5222ae5fe223f5773e08008f Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Sat, 17 Feb 2024 16:39:03 +0100 Subject: [PATCH 13/33] fix: Potential overflow in `RootAndPaths` --- src/util_types/mutator_set/root_and_paths.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util_types/mutator_set/root_and_paths.rs b/src/util_types/mutator_set/root_and_paths.rs index 7be82e93..1cde9891 100644 --- a/src/util_types/mutator_set/root_and_paths.rs +++ b/src/util_types/mutator_set/root_and_paths.rs @@ -39,7 +39,7 @@ impl Arbitrary for RootAndPaths { // populate nodes dictionary with leafs for &(index, leaf) in &indices_and_leafs { - let node_index = index + (1 << tree_height); + let node_index = (index as u128) + (1u128 << tree_height); nodes.insert(node_index, leaf); } @@ -48,8 +48,8 @@ impl Arbitrary for RootAndPaths { // note: depth 1 is the layer containing only the root let by_layer = |index, layer| { let sub_tree_height = tree_height - layer; - let layer_start = 1u64 << sub_tree_height; - let layer_stop = 1u64 << (sub_tree_height + 1); + let layer_start = 1u128 << sub_tree_height; + let layer_stop = 1u128 << (sub_tree_height + 1); index >= layer_start && index < layer_stop }; for layer in 0..tree_height { @@ -80,7 +80,7 @@ impl Arbitrary for RootAndPaths { // read out paths let paths = indices_and_leafs .iter() - .map(|(leaf_idx, _)| leaf_idx + (1 << tree_height)) + .map(|(leaf_idx, _)| (*leaf_idx as u128) + (1u128 << tree_height)) .map(|node_idx| { (0..tree_height) .map(|layer_idx| nodes[&((node_idx >> layer_idx) ^ 1)]) From a7e7df653fd3eaa79e5d610ebbbbc877b91ac2f0 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Sat, 17 Feb 2024 18:01:30 +0100 Subject: [PATCH 14/33] test: Add test for `RootAndPaths` reaching max tree height --- src/util_types/mutator_set/root_and_paths.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/util_types/mutator_set/root_and_paths.rs b/src/util_types/mutator_set/root_and_paths.rs index 1cde9891..1de234d9 100644 --- a/src/util_types/mutator_set/root_and_paths.rs +++ b/src/util_types/mutator_set/root_and_paths.rs @@ -103,7 +103,7 @@ mod test { use super::*; #[proptest(cases = 20)] - fn integrity( + fn correct( // max tree height is 32 as per twenty-first (to be changed) #[strategy(1usize..32)] tree_height: usize, #[strategy(vec((0..(1u64 << #tree_height), arb()), 0..100))] @@ -125,4 +125,17 @@ mod test { prop_assert!(inclusion_proof.verify(root_and_paths.root)); } } + + #[proptest(cases = 20)] + fn no_fail( + // try max tree height 64 here + #[strategy(1usize..64)] _tree_height: usize, + #[strategy(vec((0..(1u64 << #_tree_height), arb()), 0..100))] + #[filter(#_indexed_leafs.iter().map(|(idx, _)| idx).all_unique())] + _indexed_leafs: Vec<(u64, Digest)>, + #[strategy(RootAndPaths::arbitrary_with((#_tree_height, #_indexed_leafs)))] + _root_and_paths: RootAndPaths, + ) { + prop_assert!(true); + } } From 1d7ff91b1f13116cbf56e4e0c61a84edff25d0ba Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Sun, 18 Feb 2024 12:00:09 +0100 Subject: [PATCH 15/33] test: ensure `RootAndPaths` panics for too-large indices --- src/util_types/mutator_set/root_and_paths.rs | 52 +++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/src/util_types/mutator_set/root_and_paths.rs b/src/util_types/mutator_set/root_and_paths.rs index 1de234d9..fb821633 100644 --- a/src/util_types/mutator_set/root_and_paths.rs +++ b/src/util_types/mutator_set/root_and_paths.rs @@ -24,6 +24,19 @@ impl Arbitrary for RootAndPaths { fn arbitrary_with(parameters: Self::Parameters) -> Self::Strategy { let (tree_height_proper, indices_and_leafs_proper) = parameters; + assert!( + indices_and_leafs_proper + .iter() + .map(|(idx, _)| idx) + .all_unique(), + "indices are not all unique" + ); + assert!( + indices_and_leafs_proper + .iter() + .all(|(i, _l)| (*i as u128) < 1u128 << tree_height_proper), + "some or all indices are too large; don't fit in a tree of height {tree_height_proper}" + ); let upper_bound_num_digests = tree_height_proper * indices_and_leafs_proper.len() + 1; let tree_height_strategy = Just(tree_height_proper); @@ -35,23 +48,23 @@ impl Arbitrary for RootAndPaths { vec_digest_strategy, ) .prop_map(|(tree_height, indices_and_leafs, mut digests)| { - let mut nodes = HashMap::new(); - + assert!(indices_and_leafs.iter().all(|(i,_l)| (*i as u128) < (1u128 << tree_height)), "indices too large for tree of height: {}", tree_height); // populate nodes dictionary with leafs + let mut nodes = HashMap::new(); for &(index, leaf) in &indices_and_leafs { let node_index = (index as u128) + (1u128 << tree_height); nodes.insert(node_index, leaf); } - // walk up tree layer by layer - // when we need nodes not already present, sample at random - // note: depth 1 is the layer containing only the root - let by_layer = |index, layer| { + let by_layer = |index : u128, layer : usize| { let sub_tree_height = tree_height - layer; let layer_start = 1u128 << sub_tree_height; let layer_stop = 1u128 << (sub_tree_height + 1); index >= layer_start && index < layer_stop }; + + // walk up tree layer by layer + // when we need nodes not already present, sample at random for layer in 0..tree_height { let mut working_indices = nodes .keys() @@ -83,7 +96,18 @@ impl Arbitrary for RootAndPaths { .map(|(leaf_idx, _)| (*leaf_idx as u128) + (1u128 << tree_height)) .map(|node_idx| { (0..tree_height) - .map(|layer_idx| nodes[&((node_idx >> layer_idx) ^ 1)]) + .map(|layer_idx| { + nodes + .get(&((node_idx >> layer_idx) ^ 1u128)) + .unwrap_or_else(|| { + panic!( + "node index \n{} \nnot present in node dictionary!\ndo have indices:\n{}", + (node_idx >> layer_idx) ^ 1, + nodes.keys().join("\n") + ) + }) + }) + .copied() .collect_vec() }) .collect_vec(); @@ -138,4 +162,18 @@ mod test { ) { prop_assert!(true); } + + #[proptest(cases = 20)] + #[should_panic] + fn indices_too_large( + // try max tree height 64 here + #[strategy(1usize..64)] _tree_height: usize, + #[strategy(vec(((1u64 << #_tree_height)..u64::MAX, arb()), 0..100))] + #[filter(#_indexed_leafs.iter().map(|(idx, _)| idx).all_unique())] + _indexed_leafs: Vec<(u64, Digest)>, + #[strategy(RootAndPaths::arbitrary_with((#_tree_height, #_indexed_leafs)))] + _root_and_paths: RootAndPaths, + ) { + prop_assert!(true); + } } From b51333f251ba3c734908a17c5510a21f16fd8cac Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Sun, 18 Feb 2024 12:01:12 +0100 Subject: [PATCH 16/33] test: Fix `arbitrary_with` for `MmraAndMembershipProofs` --- .../mutator_set/mmra_and_membership_proofs.rs | 321 +++++++++++------- 1 file changed, 206 insertions(+), 115 deletions(-) diff --git a/src/util_types/mutator_set/mmra_and_membership_proofs.rs b/src/util_types/mutator_set/mmra_and_membership_proofs.rs index fb479d71..bd95fbb3 100644 --- a/src/util_types/mutator_set/mmra_and_membership_proofs.rs +++ b/src/util_types/mutator_set/mmra_and_membership_proofs.rs @@ -1,11 +1,14 @@ +use std::marker::PhantomData; + use itertools::Itertools; use proptest::{ arbitrary::Arbitrary, collection::vec, strategy::{BoxedStrategy, Just, Strategy}, }; -use proptest_arbitrary_interop::arb; -use tasm_lib::twenty_first::util_types::mmr::shared_basic::leaf_index_to_mt_index_and_peak_index; +use tasm_lib::twenty_first::util_types::{ + merkle_tree::MerkleTreeInclusionProof, mmr::shared_basic::leaf_index_to_mt_index_and_peak_index, +}; use tasm_lib::{ twenty_first::util_types::mmr::{ mmr_accumulator::MmrAccumulator, mmr_membership_proof::MmrMembershipProof, @@ -27,137 +30,225 @@ impl Arbitrary for MmraAndMembershipProofs { type Parameters = Vec; fn arbitrary_with(leafs_proper: Self::Parameters) -> Self::Strategy { - let total_leaf_count_strategy = (leafs_proper.len() as u64)..u64::MAX; - let index_strategy = - (arb::(), total_leaf_count_strategy.clone()).prop_map(|(i, m)| i % m); - let indices_strategy = vec(index_strategy, leafs_proper.len()); - let leafs_strategy = Just(leafs_proper); - let digest_strategy = vec(arb::(), 3 * 64); - ( - total_leaf_count_strategy, - leafs_strategy, - indices_strategy, - digest_strategy, - ) - .prop_flat_map(|(total_leaf_count, leafs, mut indices, mut digests)| { - let num_peaks = total_leaf_count.count_ones(); - - // sample mmr leaf indices and calculate matching derived indices - let leaf_indices = leafs - .iter() - .enumerate() - .map(|(original_index, _leaf)| (original_index, indices.pop().unwrap())) - .map(|(original_index, mmr_index)| { - let (mt_index, peak_index) = - leaf_index_to_mt_index_and_peak_index(mmr_index, total_leaf_count); - (original_index, mmr_index, mt_index, peak_index) + let num_paths = leafs_proper.len() as u64; + // let total_leaf_count_strategy = num_paths..u64::MAX; + let total_leaf_count_strategy = num_paths..num_paths + 100; + + // unwrap total leaf count + total_leaf_count_strategy + .prop_flat_map(move |total_leaf_count| { + let indices_strategy = vec(0u64..total_leaf_count, num_paths as usize) + .prop_filter("indices must be unique", |indices| { + indices.iter().all_unique() }) - .collect_vec(); - let leafs_and_indices = leafs.iter().copied().zip(leaf_indices).collect_vec(); - - // iterate over all trees - let mut peaks_strategy: Vec> = vec![]; - let dummy_mp = MmrMembershipProof::new(0u64, vec![]); - let mut mps_strategy = - Just((0..leafs.len()).map(|_| dummy_mp.clone()).collect_vec()).boxed(); - for tree in 0..num_peaks { - // select all leafs and merkle tree indices for this tree - let leafs_and_mt_indices = leafs_and_indices - .clone() - .iter() - .copied() - .filter( - |(_leaf, (_original_index, _mmr_index, _mt_index, peak_index))| { - *peak_index == tree - }, - ) - .map( - |(leaf, (original_index, _mmr_index, mt_index, _peak_index))| { - (leaf, mt_index, original_index) - }, - ) - .collect_vec(); - if leafs_and_mt_indices.is_empty() { - peaks_strategy.push(Just(digests.pop().unwrap()).boxed()); - continue; - } - - // generate root and authentication paths - let tree_height = (*leafs_and_mt_indices.first().map(|(_l, i, _o)| i).unwrap() - as u128) - .ilog2() as usize; - - let root_and_paths_strategy = RootAndPaths::arbitrary_with(( - tree_height, - leafs_and_mt_indices + .boxed(); + + // unwrap leafs, indices, digests + (Just(leafs_proper.clone()), indices_strategy) + .prop_flat_map(move |(leafs, mut indices)| { + let num_peaks = total_leaf_count.count_ones(); + let mut tree_heights = vec![]; + for shift in (0..63).rev() { + if total_leaf_count & (1u64 << shift) != 0 { + tree_heights.push(shift); + } + } + + // sample mmr leaf indices and calculate matching derived indices + let index_sets = leafs + .iter() + .enumerate() + .map(|(enumeration_index, _leaf)| { + (enumeration_index, indices.pop().unwrap()) + }) + .map(|(enumeration_index, mmr_leaf_index)| { + let (mt_node_index, peak_index) = + leaf_index_to_mt_index_and_peak_index( + mmr_leaf_index, + total_leaf_count, + ); + (enumeration_index, mt_node_index, mmr_leaf_index, peak_index) + }) + .collect_vec(); + let leafs_and_indices = leafs .iter() - .map(|&(l, i, _o)| (i, l)) - .collect_vec(), - )); - let root_strategy = root_and_paths_strategy.clone().prop_map(|rp| rp.root); - let paths_strategy = root_and_paths_strategy.prop_map(|rp| rp.paths); - - // update peaks list - peaks_strategy.push(root_strategy.boxed()); - - // generate membership proof objects - let other_leafs_and_indices = leafs_and_indices.clone(); - let membership_proofs_strategy = paths_strategy - .prop_map(move |authentication_paths| { - other_leafs_and_indices + .copied() + .zip(index_sets.iter().cloned()) + .collect_vec(); + + // segregate by tree + let mut indices_and_leafs_by_tree = vec![]; + for tree in 0..num_peaks { + let local_indices_and_leafs = leafs_and_indices .iter() - .copied() .filter( |( _leaf, - (_original_index, _mmr_index, _mt_index, peak_index), + ( + _enumeration_index, + _mt_node_index, + _mmr_leaf_index, + peak_index, + ), )| { *peak_index == tree }, ) - .zip(authentication_paths.into_iter()) + .cloned() .map( |( + leaf, ( - _leaf, - (_original_index, mmr_index, _mt_index, _peak_index), + enumeration_index, + mt_node_index, + mmr_leaf_index, + _peak_index, ), - authentication_path, )| { - MmrMembershipProof::::new( - mmr_index, - authentication_path, - ) + (enumeration_index, mt_node_index, mmr_leaf_index, leaf) }, ) - .collect_vec() - }) - .boxed(); - - // collect membership proofs in vector, with indices matching those of the supplied leafs - mps_strategy = (mps_strategy.clone(), membership_proofs_strategy) - .prop_map(move |(mps, membership_proofs)| { - let mut new_mps = mps.clone(); - for ((_leaf, _mt_index, original_index), membership_proof) in - leafs_and_mt_indices.iter().zip(membership_proofs.iter()) - { - new_mps[*original_index] = membership_proof.clone(); - } - new_mps - }) - .boxed(); - } - - let mmra_strategy = peaks_strategy - .prop_map(move |peaks| MmrAccumulator::::init(peaks, total_leaf_count)); - - (mps_strategy, mmra_strategy).prop_map(|(membership_proofs, mmra)| { - MmraAndMembershipProofs { - membership_proofs, - mmra, - } - }) + .collect_vec(); + + indices_and_leafs_by_tree.push(local_indices_and_leafs); + } + + // for each tree generate root and paths + let mut root_and_paths_strategies = vec![]; + for (tree, local_indices_and_leafs) in + indices_and_leafs_by_tree.iter().enumerate() + { + let tree_height = tree_heights[tree]; + let root_and_paths_strategy = RootAndPaths::arbitrary_with(( + tree_height, + local_indices_and_leafs + .iter() + .copied() + .map(|(_ei, mtni, _mmri, leaf)| { + (mtni ^ (1 << tree_height), leaf) + }) + .collect_vec(), + )); + root_and_paths_strategies.push(root_and_paths_strategy); + } + + // unwrap vector of roots and pathses + (root_and_paths_strategies, Just(indices_and_leafs_by_tree)).prop_map( + move |(roots_and_pathses, indices_and_leafs_by_tree_)| { + // sanity check for roots and pathses + for (root_and_paths, indices_and_leafs) in roots_and_pathses + .iter() + .zip(indices_and_leafs_by_tree_.iter()) + { + let root = root_and_paths.root; + for ( + path, + ( + _enumeration_index, + merkle_tree_node_index, + _mmr_index, + leaf, + ), + ) in + root_and_paths.paths.iter().zip(indices_and_leafs.iter()) + { + let mip = MerkleTreeInclusionProof { + tree_height: path.len(), + indexed_leaves: vec![( + *merkle_tree_node_index as usize + ^ (1 << path.len()), + *leaf, + )], + authentication_structure: path.clone(), + _hasher: PhantomData::, + }; + assert!(mip.verify(root)); + } + } + + // extract peaks + let peaks = roots_and_pathses + .iter() + .map(|root_and_paths| root_and_paths.root) + .collect_vec(); + + // prepare to extract membership proofs + let mut membership_proofs = vec![ + MmrMembershipProof { + leaf_index: 0, + authentication_path: vec![], + _hasher: std::marker::PhantomData:: + }; + num_paths as usize + ]; + + // loop over all leaf indices and look up membership proof + for (root_and_paths, indices_and_leafs) in roots_and_pathses + .into_iter() + .zip(indices_and_leafs_by_tree_.iter()) + { + let paths = root_and_paths.paths; + for ( + path, + &(enumeration_index, _merkle_tree_index, mmr_index, _leaf), + ) in paths.into_iter().zip(indices_and_leafs.iter()) + { + membership_proofs[enumeration_index].authentication_path = + path; + membership_proofs[enumeration_index].leaf_index = mmr_index; + } + } + + // sanity check + for (mmr_mp, leaf) in membership_proofs.iter().zip(leafs.iter()) { + let (_mti, _pi) = leaf_index_to_mt_index_and_peak_index( + mmr_mp.leaf_index, + total_leaf_count, + ); + assert!(mmr_mp.verify(&peaks, *leaf, total_leaf_count).0); + } + + MmraAndMembershipProofs { + mmra: MmrAccumulator::init(peaks, total_leaf_count), + membership_proofs, + } + }, + ) + }) + .boxed() }) .boxed() } type Strategy = BoxedStrategy; } + +#[cfg(test)] +mod test { + + use super::*; + use crate::twenty_first::shared_math::tip5::Digest; + use proptest::prelude::*; + use proptest_arbitrary_interop::arb; + use tasm_lib::twenty_first::util_types::mmr::mmr_trait::Mmr; + use test_strategy::proptest; + + #[proptest(cases = 20)] + fn integrity( + #[strategy(vec(arb(), 0..10))] leafs: Vec, + #[strategy(MmraAndMembershipProofs::arbitrary_with(#leafs))] + mmra_and_membership_proofs: MmraAndMembershipProofs, + ) { + for (leaf, mp) in leafs + .into_iter() + .zip(mmra_and_membership_proofs.membership_proofs) + { + prop_assert!( + mp.verify( + &mmra_and_membership_proofs.mmra.get_peaks(), + leaf, + mmra_and_membership_proofs.mmra.count_leaves(), + ) + .0 + ); + } + } +} From ddfa13cc40c41ed47fec572b9f90bac8312e2abd Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Tue, 20 Feb 2024 13:47:01 +0100 Subject: [PATCH 17/33] test: Implement `arbitrary_with` for `PrimitiveWitness` --- .../transaction/primitive_witness.rs | 691 +++++++++++------- .../blockchain/type_scripts/time_lock.rs | 23 +- 2 files changed, 437 insertions(+), 277 deletions(-) diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index e55ad250..a616e9c9 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -3,9 +3,15 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; -use arbitrary::Arbitrary; use get_size::GetSize; use itertools::Itertools; +use num_traits::Zero; +use proptest::{ + arbitrary::Arbitrary, + collection::vec, + strategy::{BoxedStrategy, Strategy}, +}; +use proptest_arbitrary_interop::arb; use rand::{rngs::StdRng, Rng, RngCore, SeedableRng}; use serde::{Deserialize, Serialize}; use tasm_lib::{ @@ -23,12 +29,14 @@ use tasm_lib::{ }; use crate::{ - models::blockchain::type_scripts::native_currency::{ + models::blockchain::{type_scripts::native_currency::{ native_currency_program, NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST, - }, + }}, util_types::mutator_set::{ + active_window::ActiveWindow, chunk::Chunk, chunk_dictionary::ChunkDictionary, + mmra_and_membership_proofs::MmraAndMembershipProofs, mutator_set_kernel::{get_swbf_indices, MutatorSetKernel}, mutator_set_trait::{commit, MutatorSet}, removal_record::{AbsoluteIndexSet, RemovalRecord}, @@ -49,7 +57,8 @@ use crate::{ use super::{ transaction_kernel::TransactionKernel, - utxo::{Coin, LockScript, Utxo}, + utxo::{ LockScript, Utxo}, + PublicAnnouncement, }; /// The raw witness is the most primitive type of transaction witness. @@ -249,279 +258,421 @@ impl PrimitiveWitness { (root, paths) } - - fn pseudorandom_mmra_of_given_size_with_mps_at_indices( - seed: [u8; 32], - size: u64, - leafs: &[Digest], - indices: &[u64], - ) -> (MmrAccumulator, Vec>) { - let mut rng: StdRng = SeedableRng::from_seed(seed); - let mut tree_heights = vec![]; - for h in (0..64).rev() { - if size & (1 << h) != 0 { - tree_heights.push(h); - } - } - - let mut peaks = vec![]; - let mut membership_proofs = vec![]; - let leaf_and_various_indices = leafs - .iter() - .zip(indices.iter()) - .map(|(&l, &idx)| (l, idx, leaf_index_to_mt_index_and_peak_index(idx, size))); - for (i, tree_height) in tree_heights.into_iter().enumerate() { - let leafs_and_indices = leaf_and_various_indices - .clone() - .filter(|(_l, _idx, (_mti, pki))| *pki == i as u32) - .map(|(l, _idx, (mti, _pki))| (l, mti)) - .collect_vec(); - let (root, paths) = Self::pseudorandom_merkle_root_with_authentication_paths( - rng.gen(), - tree_height, - &leafs_and_indices, - ); - peaks.push(root); - membership_proofs.append( - &mut leafs_and_indices - .into_iter() - .zip(paths.into_iter()) - .map(|((_l, idx), path)| MmrMembershipProof::new(idx, path)) - .collect_vec(), - ); - } - (MmrAccumulator::init(peaks, size), membership_proofs) - } } -impl<'a> Arbitrary<'a> for PrimitiveWitness { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let num_inputs = u.int_in_range(0..=4)?; - let num_outputs = u.int_in_range(0..=4)?; - let num_public_announcements = u.int_in_range(0..=2)?; - let sender_spending_keys = (0..num_inputs) - .map(|_| { - generation_address::SpendingKey::derive_from_seed(u.arbitrary::().unwrap()) - }) - .collect_vec(); - let sender_receiving_addresses = sender_spending_keys - .iter() - .map(|ssk| ssk.to_address()) - .collect_vec(); - let input_lock_scripts = sender_receiving_addresses - .iter() - .map(|sra| sra.lock_script()) - .collect_vec(); - let lock_script_witnesses = sender_spending_keys - .iter() - .map(|ssk| ssk.unlock_key.values().to_vec()) - .collect_vec(); - let input_amounts = (0..num_inputs) - .map(|_| u.arbitrary::().unwrap()) - .collect_vec(); - let total_inputs = input_amounts.iter().cloned().sum::(); - let mut output_fractions = (0..=num_outputs) - .map(|_| u.int_in_range(0..=99).unwrap() as f64) - .collect_vec(); - let output_sum = output_fractions.iter().cloned().sum::(); - output_fractions.iter_mut().for_each(|f| *f /= output_sum); - let input_utxos = input_lock_scripts - .iter() - .map(|ils| { - Utxo::new( - ils.clone(), - vec![Coin { - type_script_hash: NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST, - state: u.arbitrary::().unwrap().encode(), - }], - ) - }) - .collect_vec(); - let input_triples = input_utxos - .iter() - .map(|utxo| { - ( - Hash::hash(utxo), - u.arbitrary::().unwrap(), - u.arbitrary::().unwrap(), - ) - }) - .collect_vec(); - let input_commitments = input_triples - .iter() - .map(|(item, sender_randomness, receiver_preimage)| { - commit(*item, *sender_randomness, Hash::hash(receiver_preimage)) - }) - .map(|ar| ar.canonical_commitment) - .collect_vec(); - let (aocl_mmr, aocl_authentication_paths) = - Self::pseudorandom_mmra_with_mps(u.arbitrary().unwrap(), &input_commitments); - let all_index_sets = input_triples - .iter() - .zip(aocl_authentication_paths.iter()) - .map( - |((item, sender_randomness, receiver_preimage), authentication_path)| { - get_swbf_indices( - *item, - *sender_randomness, - *receiver_preimage, - authentication_path.leaf_index, +impl Arbitrary for PrimitiveWitness { + type Parameters = (usize, usize, usize); + type Strategy = BoxedStrategy; + + fn arbitrary_with(parameters: Self::Parameters) -> Self::Strategy { + let (num_inputs, num_outputs, num_public_announcements) = parameters; + ( + vec(arb::(), num_inputs), + vec(arb::(), num_outputs), + vec(arb::(), num_public_announcements), + arb::(), + arb::>(), + ) + .prop_flat_map( + |(input_utxos, mut output_utxos, public_announcements, mut fee, maybe_coinbase)| { + let total_inputs = input_utxos.iter().flat_map(|utxo| utxo.coins.clone()).filter(|coin|coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST).map(|coin|coin.state).map(|state| NeptuneCoins::decode(&state)).filter(|r|r.is_ok()).map(|r|*r.unwrap()).sum::(); + let mut total_outputs = output_utxos.iter().flat_map(|utxo| utxo.coins.clone()).filter(|coin|coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST).map(|coin|coin.state).map(|state| NeptuneCoins::decode(&state)).filter(|r|r.is_ok()).map(|r|*r.unwrap()).sum::(); + let mut some_coinbase = match maybe_coinbase { + Some(coinbase) => coinbase, + None => NeptuneCoins::zero(), + }; + while total_inputs < total_outputs + fee + some_coinbase { + for output_utxo in output_utxos.iter_mut() { + for coin in output_utxo.coins.iter_mut() { + if coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST { + let mut amount = *NeptuneCoins::decode(&coin.state).unwrap(); + amount.div_two(); + coin.state = amount.encode(); + } + } + } + if let Some(mut coinbase) = maybe_coinbase { + coinbase.div_two(); + some_coinbase.div_two(); + } + fee.div_two(); + total_outputs = output_utxos.iter().flat_map(|utxo| utxo.coins.clone()).filter(|coin|coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST).map(|coin|coin.state).map(|state| NeptuneCoins::decode(&state)).filter(|r|r.is_ok()).map(|r|*r.unwrap()).sum::(); + } + arbitrary_primitive_witness_with( + &input_utxos, + &output_utxos, + &public_announcements, + fee, + maybe_coinbase, ) }, ) - .collect_vec(); - let mut all_indices = all_index_sets.iter().flatten().cloned().collect_vec(); - all_indices.sort(); - let mut all_chunk_indices = all_indices - .iter() - .map(|index| index / (CHUNK_SIZE as u128)) - .map(|index| index as u64) - .collect_vec(); - all_chunk_indices.dedup(); - let mmr_chunk_indices = all_chunk_indices - .iter() - .cloned() - .filter(|ci| *ci < aocl_mmr.count_leaves() / (BATCH_SIZE as u64)) - .collect_vec(); - let mmr_chunks = (0..mmr_chunk_indices.len()) - .map(|_| u.arbitrary::().unwrap()) - .collect_vec(); - let (swbf_mmr, swbf_authentication_paths) = - Self::pseudorandom_mmra_of_given_size_with_mps_at_indices( - u.arbitrary().unwrap(), - aocl_mmr.count_leaves() / (BATCH_SIZE as u64), - &mmr_chunks.iter().map(Hash::hash).collect_vec(), - &mmr_chunk_indices, - ); - let chunk_dictionary: HashMap, Chunk)> = mmr_chunk_indices - .iter() - .cloned() - .zip(swbf_authentication_paths.into_iter().zip(mmr_chunks)) - .collect(); - let personalized_chunk_dictionaries = all_index_sets - .iter() - .map(|index_set| { - let mut is = index_set + .boxed() + } +} + +pub(crate) fn arbitrary_primitive_witness_with( + input_utxos: &[Utxo], + output_utxos: &[Utxo], + public_announcements: &[PublicAnnouncement], + fee: NeptuneCoins, + coinbase: Option, +) -> BoxedStrategy { + let num_inputs = input_utxos.len(); + let num_outputs = output_utxos.len(); + let input_utxos = input_utxos.to_vec(); + let output_utxos = output_utxos.to_vec(); + let public_announcements = public_announcements.to_vec(); + + // unwrap spending key seeds + vec(arb::(), num_inputs) + .prop_flat_map(move |mut spending_key_seeds| { + let sender_spending_keys = (0..num_inputs) + .map(|_| { + generation_address::SpendingKey::derive_from_seed( + spending_key_seeds.pop().unwrap(), + ) + }) + .collect_vec(); + let sender_receiving_addresses = sender_spending_keys .iter() - .map(|index| index / (BATCH_SIZE as u128)) - .map(|index| index as u64) + .map(|ssk| ssk.to_address()) .collect_vec(); - is.sort(); - is.dedup(); - is - }) - .map(|chunk_indices| { - ChunkDictionary::new( - chunk_indices - .iter() - .map(|chunk_index| { - ( - *chunk_index, - chunk_dictionary.get(chunk_index).cloned().unwrap(), - ) + let input_lock_scripts = sender_receiving_addresses + .iter() + .map(|sra| sra.lock_script()) + .collect_vec(); + let lock_script_witnesses = sender_spending_keys + .iter() + .map(|ssk| ssk.unlock_key.values().to_vec()) + .collect_vec(); + + // prepare to unwrap + let lock_script_witnesses = lock_script_witnesses.clone(); + let input_lock_scripts = input_lock_scripts.clone(); + + // prepare to unwrap + let lock_script_witnesses = lock_script_witnesses.clone(); + let input_lock_scripts = input_lock_scripts.clone(); + let input_utxos = input_utxos.clone(); + let output_utxos = output_utxos.clone(); + let public_announcements = public_announcements.clone(); + + // unwrap random sender randomness and receiver preimages + let sender_randomnesses_strategy = vec(arb::(), num_inputs); + let receiver_preimages_strategy = vec(arb::(), num_inputs); + (sender_randomnesses_strategy, receiver_preimages_strategy) + .prop_flat_map(move |(mut sender_randomnesses, mut receiver_preimages)| { + let input_triples = input_utxos + .iter() + .map(|utxo| { + ( + Hash::hash(utxo), + sender_randomnesses.pop().unwrap(), + receiver_preimages.pop().unwrap(), + ) + }) + .collect_vec(); + let input_commitments = input_triples + .iter() + .map(|(item, sender_randomness, receiver_preimage)| { + commit( + *item, + *sender_randomness, + Hash::hash(receiver_preimage), + ) + }) + .map(|ar| ar.canonical_commitment) + .collect_vec(); + + // prepare to unwrap + let input_lock_scripts = input_lock_scripts.clone(); + let lock_script_witnesses = lock_script_witnesses.clone(); + let input_utxos = input_utxos.clone(); + let output_utxos = output_utxos.clone(); + let public_announcements = public_announcements.clone(); + + // unwrap random aocl mmr with membership proofs + MmraAndMembershipProofs::arbitrary_with(input_commitments) + .prop_flat_map(move |aocl_mmra_and_membership_proofs| { + let aocl_mmra = aocl_mmra_and_membership_proofs.mmra; + let aocl_membership_proofs = + aocl_mmra_and_membership_proofs.membership_proofs; + let all_index_sets = input_triples + .iter() + .zip(aocl_membership_proofs.iter()) + .map( + |( + (item, sender_randomness, receiver_preimage), + authentication_path, + )| { + get_swbf_indices( + *item, + *sender_randomness, + *receiver_preimage, + authentication_path.leaf_index, + ) + }, + ) + .collect_vec(); + let mut all_indices = + all_index_sets.iter().flatten().cloned().collect_vec(); + all_indices.sort(); + let mut all_chunk_indices = all_indices + .iter() + .map(|index| index / (CHUNK_SIZE as u128)) + .map(|index| index as u64) + .collect_vec(); + all_chunk_indices.dedup(); + let mmr_chunk_indices = all_chunk_indices + .iter() + .cloned() + .filter(|ci| { + *ci < aocl_mmra.count_leaves() / (BATCH_SIZE as u64) + }) + .collect_vec(); + + // prepare to unwrap + let input_lock_scripts = input_lock_scripts.clone(); + let lock_script_witnesses = lock_script_witnesses.clone(); + let aocl_mmra = aocl_mmra.clone(); + let mmr_chunk_indices = mmr_chunk_indices.clone(); + let all_index_sets = all_index_sets.clone(); + let input_triples = input_triples.clone(); + let aocl_membership_proofs = aocl_membership_proofs.clone(); + let input_utxos = input_utxos.clone(); + let output_utxos = output_utxos.clone(); + let public_announcements = public_announcements.clone(); + + // unwrap random mmr_chunks + (0..mmr_chunk_indices.len()) + .map(|_| arb::()) + .collect_vec() + .prop_flat_map( move |mmr_chunks| { + + // prepare to unwrap + let input_lock_scripts = input_lock_scripts.clone(); + let lock_script_witnesses = lock_script_witnesses.clone(); + let aocl_mmra = aocl_mmra.clone(); + let mmr_chunk_indices = mmr_chunk_indices.clone(); + let all_index_sets = all_index_sets.clone(); + let input_triples = input_triples.clone(); + let aocl_membership_proofs = aocl_membership_proofs.clone(); + let input_utxos = input_utxos.clone(); + let output_utxos = output_utxos.clone(); + let public_announcements = public_announcements.clone(); + + // unwrap random swbf mmra and membership proofs + let swbf_strategy = MmraAndMembershipProofs::arbitrary_with( + mmr_chunks.iter().map(Hash::hash).collect_vec(), + ); + let mmr_chunks = mmr_chunks.clone(); + swbf_strategy + .prop_flat_map(move |swbf_mmr_and_paths| { + let swbf_mmra = swbf_mmr_and_paths.mmra; + let swbf_membership_proofs = + swbf_mmr_and_paths.membership_proofs; + + let chunk_dictionary: HashMap< + u64, + (MmrMembershipProof, Chunk), + > = mmr_chunk_indices + .iter() + .cloned() + .zip( + swbf_membership_proofs + .into_iter() + .zip(mmr_chunks.iter().cloned()), + ) + .collect(); + let personalized_chunk_dictionaries = + all_index_sets + .iter() + .map(|index_set| { + let mut is = index_set + .iter() + .map(|index| { + index / (BATCH_SIZE as u128) + }) + .map(|index| index as u64) + .collect_vec(); + is.sort(); + is.dedup(); + is + }) + .map(|chunk_indices| { + ChunkDictionary::new( + chunk_indices + .iter() + .map(|chunk_index| { + ( + *chunk_index, + chunk_dictionary + .get( + chunk_index, + ) + .cloned() + .unwrap(), + ) + }) + .collect::, + Chunk, + ), + >>( + ), + ) + }) + .collect_vec(); + let input_membership_proofs = input_triples + .iter() + .zip(aocl_membership_proofs.iter()) + .zip(personalized_chunk_dictionaries.iter()) + .map( + |( + ( + (_item, sender_randomness, receiver_preimage), + auth_path, + ), + target_chunks, + )| { + MsMembershipProof { + sender_randomness: *sender_randomness, + receiver_preimage: *receiver_preimage, + auth_path_aocl: auth_path.clone(), + target_chunks: target_chunks.clone(), + } + }, + ) + .collect_vec(); + let input_removal_records = + all_index_sets + .iter() + .zip( + personalized_chunk_dictionaries + .iter(), + ) + .map(|(index_set, target_chunks)| { + RemovalRecord { + absolute_indices: + AbsoluteIndexSet::new( + index_set, + ), + target_chunks: target_chunks + .clone(), + } + }) + .collect_vec(); + let type_scripts = vec![TypeScript::new( + native_currency_program(), + )]; + + + // prepare to unwrap + let input_lock_scripts = input_lock_scripts.clone(); + let input_utxos = input_utxos.clone(); + let input_removal_records = input_removal_records.clone(); + let input_membership_proofs = input_membership_proofs.clone(); + let type_scripts = type_scripts.clone(); + let lock_script_witnesses = lock_script_witnesses.clone(); + let aocl_mmra = aocl_mmra.clone(); + let swbf_mmra = swbf_mmra.clone(); + let output_utxos = output_utxos.clone(); + let public_announcements = public_announcements.clone(); + + // unwrap sender randomnesses and receiver digests + let output_sender_randomnesses_strategy = + vec(arb::(), num_outputs); + let receiver_digest_strategy = + vec(arb::(), num_outputs); + (output_sender_randomnesses_strategy, receiver_digest_strategy) + .prop_flat_map( + move |(mut output_sender_randomnesses, mut receiver_digests)| { + let output_commitments = output_utxos + .iter() + .map(|utxo| { + commit( + Hash::hash(utxo), + output_sender_randomnesses + .pop() + .unwrap(), + receiver_digests.pop().unwrap(), + ) + }) + .collect_vec(); + + // prepare to unwrap + let input_lock_scripts = input_lock_scripts.clone(); + let input_utxos = input_utxos.clone(); + let input_removal_records = input_removal_records.clone(); + let input_membership_proofs = input_membership_proofs.clone(); + let type_scripts = type_scripts.clone(); + let lock_script_witnesses = lock_script_witnesses.clone(); + let aocl_mmra = aocl_mmra.clone(); + let swbf_mmra = swbf_mmra.clone(); + let output_utxos = output_utxos.clone(); + let public_announcements = public_announcements.clone(); + + // unwrap random active window + arb::() + .prop_map(move |active_window| { + let mutator_set_accumulator = + MutatorSetAccumulator { + kernel: MutatorSetKernel { + aocl: aocl_mmra.clone(), + swbf_inactive: + swbf_mmra.clone(), + swbf_active: + active_window, + }, + }; + + let kernel = TransactionKernel { + inputs: input_removal_records.clone(), + outputs: output_commitments.clone(), + public_announcements: public_announcements.to_vec(), + fee, + coinbase, + timestamp: BFieldElement::new( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() + as u64, + ), + mutator_set_hash: + mutator_set_accumulator.hash(), + }; + + PrimitiveWitness { + input_lock_scripts: input_lock_scripts.clone(), + input_utxos: input_utxos.clone(), + input_membership_proofs: input_membership_proofs.clone(), + type_scripts: type_scripts.clone(), + lock_script_witnesses: lock_script_witnesses.clone(), + output_utxos: output_utxos.clone(), + mutator_set_accumulator: mutator_set_accumulator.clone(), + kernel, + } + + }) + .boxed() + }, + ) + .boxed() + }) + .boxed() + }) + .boxed() + }) + .boxed() }) - .collect::, Chunk)>>(), - ) - }) - .collect_vec(); - let input_membership_proofs = input_triples - .iter() - .zip(aocl_authentication_paths.iter()) - .zip(personalized_chunk_dictionaries.iter()) - .map( - |(((_item, sender_randomness, receiver_preimage), auth_path), target_chunks)| { - MsMembershipProof { - sender_randomness: *sender_randomness, - receiver_preimage: *receiver_preimage, - auth_path_aocl: auth_path.clone(), - target_chunks: target_chunks.clone(), - } - }, - ) - .collect_vec(); - let input_removal_records = all_index_sets - .iter() - .zip(personalized_chunk_dictionaries.iter()) - .map(|(index_set, target_chunks)| RemovalRecord { - absolute_indices: AbsoluteIndexSet::new(index_set), - target_chunks: target_chunks.clone(), - }) - .collect_vec(); - let type_scripts = vec![TypeScript::new(native_currency_program())]; - let output_utxos = output_fractions - .iter() - .take(num_outputs) - .map(|f| Utxo { - lock_script_hash: u.arbitrary().unwrap(), - coins: vec![Coin { - type_script_hash: NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST, - state: NeptuneCoins::new((f * total_inputs.to_nau_f64()) as u32).encode(), - }], - }) - .collect_vec(); - let output_commitments = output_utxos - .iter() - .map(|utxo| { - commit( - Hash::hash(utxo), - u.arbitrary().unwrap(), - u.arbitrary().unwrap(), - ) + .boxed() + }) - .collect_vec(); - let mutator_set_accumulator = MutatorSetAccumulator { - kernel: MutatorSetKernel { - aocl: aocl_mmr, - swbf_inactive: swbf_mmr, - swbf_active: u.arbitrary()?, - }, - }; - let has_coinbase: bool = u.arbitrary()?; - let coinbase = if !has_coinbase { - None - } else { - let fraction_to_coinbase: f64 = u.int_in_range(0..=99)? as f64 / 100.0; - let coinbase_amount = - total_inputs.to_nau_f64() * output_fractions.last().unwrap() * fraction_to_coinbase; - Some(NeptuneCoins::new(coinbase_amount as u32)) - }; - let fee = match coinbase { - Some(cb) => { - NeptuneCoins::new( - (total_inputs.to_nau_f64() * output_fractions.last().unwrap()) as u32, - ) - cb - } - None => NeptuneCoins::new( - (total_inputs.to_nau_f64() * output_fractions.last().unwrap()) as u32, - ), - }; - let public_announcements = (0..num_public_announcements) - .map(|_| u.arbitrary().unwrap()) - .collect_vec(); - let kernel = TransactionKernel { - inputs: input_removal_records, - outputs: output_commitments, - public_announcements, - fee, - coinbase, - timestamp: BFieldElement::new( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as u64, - ), - mutator_set_hash: mutator_set_accumulator.hash(), - }; - let primitive_witness = PrimitiveWitness { - input_lock_scripts, - input_utxos, - input_membership_proofs, - type_scripts, - lock_script_witnesses, - output_utxos, - mutator_set_accumulator, - kernel, - }; - Ok(primitive_witness) - } + .boxed() } diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index e533f079..2718fb62 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -9,9 +9,11 @@ use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; use crate::util_types::mutator_set::mutator_set_kernel::get_swbf_indices; use crate::util_types::mutator_set::shared::NUM_TRIALS; use crate::Hash; -use arbitrary::Arbitrary; use get_size::GetSize; use itertools::Itertools; +use proptest::arbitrary::Arbitrary; +use proptest::strategy::BoxedStrategy; +use proptest::strategy::Strategy; use serde::{Deserialize, Serialize}; use tasm_lib::twenty_first::prelude::AlgebraicHasher; use tasm_lib::{ @@ -449,11 +451,18 @@ impl SecretWitness for TimeLockWitness { } } -impl<'a> Arbitrary<'a> for TimeLockWitness { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let transaction_primitive_witness: PrimitiveWitness = u.arbitrary()?; - Ok(TimeLockWitness::from_primitive_witness( - &transaction_primitive_witness, - )) +impl Arbitrary for TimeLockWitness { + type Parameters = (Vec>, usize, usize); + + type Strategy = BoxedStrategy; + + fn arbitrary_with(parameters: Self::Parameters) -> Self::Strategy { + let (release_dates, num_outputs, num_public_announcements) = parameters; + let num_inputs = release_dates.len(); + PrimitiveWitness::arbitrary_with((num_inputs, num_outputs, num_public_announcements)) + .prop_map(|transaction_primitive_witness| { + TimeLockWitness::from_primitive_witness(&transaction_primitive_witness) + }) + .boxed() } } From cd05d8283a4c0b49a407180bb0e516d3b205b4e5 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Tue, 20 Feb 2024 15:54:41 +0100 Subject: [PATCH 18/33] test: Implement `arbitrary_with` for `TimeLockWitness` --- .../transaction/primitive_witness.rs | 592 +++++++++--------- .../blockchain/type_scripts/time_lock.rs | 42 +- 2 files changed, 345 insertions(+), 289 deletions(-) diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index a616e9c9..54f177c1 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -29,9 +29,9 @@ use tasm_lib::{ }; use crate::{ - models::blockchain::{type_scripts::native_currency::{ + models::blockchain::type_scripts::native_currency::{ native_currency_program, NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST, - }}, + }, util_types::mutator_set::{ active_window::ActiveWindow, chunk::Chunk, @@ -57,7 +57,7 @@ use crate::{ use super::{ transaction_kernel::TransactionKernel, - utxo::{ LockScript, Utxo}, + utxo::{LockScript, Utxo}, PublicAnnouncement, }; @@ -275,8 +275,24 @@ impl Arbitrary for PrimitiveWitness { ) .prop_flat_map( |(input_utxos, mut output_utxos, public_announcements, mut fee, maybe_coinbase)| { - let total_inputs = input_utxos.iter().flat_map(|utxo| utxo.coins.clone()).filter(|coin|coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST).map(|coin|coin.state).map(|state| NeptuneCoins::decode(&state)).filter(|r|r.is_ok()).map(|r|*r.unwrap()).sum::(); - let mut total_outputs = output_utxos.iter().flat_map(|utxo| utxo.coins.clone()).filter(|coin|coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST).map(|coin|coin.state).map(|state| NeptuneCoins::decode(&state)).filter(|r|r.is_ok()).map(|r|*r.unwrap()).sum::(); + let total_inputs = input_utxos + .iter() + .flat_map(|utxo| utxo.coins.clone()) + .filter(|coin| coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST) + .map(|coin| coin.state) + .map(|state| NeptuneCoins::decode(&state)) + .filter(|r| r.is_ok()) + .map(|r| *r.unwrap()) + .sum::(); + let mut total_outputs = output_utxos + .iter() + .flat_map(|utxo| utxo.coins.clone()) + .filter(|coin| coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST) + .map(|coin| coin.state) + .map(|state| NeptuneCoins::decode(&state)) + .filter(|r| r.is_ok()) + .map(|r| *r.unwrap()) + .sum::(); let mut some_coinbase = match maybe_coinbase { Some(coinbase) => coinbase, None => NeptuneCoins::zero(), @@ -296,7 +312,17 @@ impl Arbitrary for PrimitiveWitness { some_coinbase.div_two(); } fee.div_two(); - total_outputs = output_utxos.iter().flat_map(|utxo| utxo.coins.clone()).filter(|coin|coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST).map(|coin|coin.state).map(|state| NeptuneCoins::decode(&state)).filter(|r|r.is_ok()).map(|r|*r.unwrap()).sum::(); + total_outputs = output_utxos + .iter() + .flat_map(|utxo| utxo.coins.clone()) + .filter(|coin| { + coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST + }) + .map(|coin| coin.state) + .map(|state| NeptuneCoins::decode(&state)) + .filter(|r| r.is_ok()) + .map(|r| *r.unwrap()) + .sum::(); } arbitrary_primitive_witness_with( &input_utxos, @@ -351,86 +377,104 @@ pub(crate) fn arbitrary_primitive_witness_with( let lock_script_witnesses = lock_script_witnesses.clone(); let input_lock_scripts = input_lock_scripts.clone(); + // prepare to unwrap + let lock_script_witnesses = lock_script_witnesses.clone(); + let input_lock_scripts = input_lock_scripts.clone(); + let input_utxos = input_utxos.clone(); + let output_utxos = output_utxos.clone(); + let public_announcements = public_announcements.clone(); + + // unwrap random sender randomness and receiver preimages + let sender_randomnesses_strategy = vec(arb::(), num_inputs); + let receiver_preimages_strategy = vec(arb::(), num_inputs); + (sender_randomnesses_strategy, receiver_preimages_strategy) + .prop_flat_map(move |(mut sender_randomnesses, mut receiver_preimages)| { + let input_triples = input_utxos + .iter() + .map(|utxo| { + ( + Hash::hash(utxo), + sender_randomnesses.pop().unwrap(), + receiver_preimages.pop().unwrap(), + ) + }) + .collect_vec(); + let input_commitments = input_triples + .iter() + .map(|(item, sender_randomness, receiver_preimage)| { + commit( + *item, + *sender_randomness, + Hash::hash(receiver_preimage), + ) + }) + .map(|ar| ar.canonical_commitment) + .collect_vec(); + // prepare to unwrap - let lock_script_witnesses = lock_script_witnesses.clone(); let input_lock_scripts = input_lock_scripts.clone(); + let lock_script_witnesses = lock_script_witnesses.clone(); let input_utxos = input_utxos.clone(); let output_utxos = output_utxos.clone(); let public_announcements = public_announcements.clone(); - // unwrap random sender randomness and receiver preimages - let sender_randomnesses_strategy = vec(arb::(), num_inputs); - let receiver_preimages_strategy = vec(arb::(), num_inputs); - (sender_randomnesses_strategy, receiver_preimages_strategy) - .prop_flat_map(move |(mut sender_randomnesses, mut receiver_preimages)| { - let input_triples = input_utxos + // unwrap random aocl mmr with membership proofs + MmraAndMembershipProofs::arbitrary_with(input_commitments) + .prop_flat_map(move |aocl_mmra_and_membership_proofs| { + let aocl_mmra = aocl_mmra_and_membership_proofs.mmra; + let aocl_membership_proofs = + aocl_mmra_and_membership_proofs.membership_proofs; + let all_index_sets = input_triples .iter() - .map(|utxo| { - ( - Hash::hash(utxo), - sender_randomnesses.pop().unwrap(), - receiver_preimages.pop().unwrap(), - ) - }) + .zip(aocl_membership_proofs.iter()) + .map( + |( + (item, sender_randomness, receiver_preimage), + authentication_path, + )| { + get_swbf_indices( + *item, + *sender_randomness, + *receiver_preimage, + authentication_path.leaf_index, + ) + }, + ) .collect_vec(); - let input_commitments = input_triples + let mut all_indices = + all_index_sets.iter().flatten().cloned().collect_vec(); + all_indices.sort(); + let mut all_chunk_indices = all_indices .iter() - .map(|(item, sender_randomness, receiver_preimage)| { - commit( - *item, - *sender_randomness, - Hash::hash(receiver_preimage), - ) + .map(|index| index / (CHUNK_SIZE as u128)) + .map(|index| index as u64) + .collect_vec(); + all_chunk_indices.dedup(); + let mmr_chunk_indices = all_chunk_indices + .iter() + .cloned() + .filter(|ci| { + *ci < aocl_mmra.count_leaves() / (BATCH_SIZE as u64) }) - .map(|ar| ar.canonical_commitment) .collect_vec(); // prepare to unwrap let input_lock_scripts = input_lock_scripts.clone(); let lock_script_witnesses = lock_script_witnesses.clone(); + let aocl_mmra = aocl_mmra.clone(); + let mmr_chunk_indices = mmr_chunk_indices.clone(); + let all_index_sets = all_index_sets.clone(); + let input_triples = input_triples.clone(); + let aocl_membership_proofs = aocl_membership_proofs.clone(); let input_utxos = input_utxos.clone(); let output_utxos = output_utxos.clone(); let public_announcements = public_announcements.clone(); - // unwrap random aocl mmr with membership proofs - MmraAndMembershipProofs::arbitrary_with(input_commitments) - .prop_flat_map(move |aocl_mmra_and_membership_proofs| { - let aocl_mmra = aocl_mmra_and_membership_proofs.mmra; - let aocl_membership_proofs = - aocl_mmra_and_membership_proofs.membership_proofs; - let all_index_sets = input_triples - .iter() - .zip(aocl_membership_proofs.iter()) - .map( - |( - (item, sender_randomness, receiver_preimage), - authentication_path, - )| { - get_swbf_indices( - *item, - *sender_randomness, - *receiver_preimage, - authentication_path.leaf_index, - ) - }, - ) - .collect_vec(); - let mut all_indices = - all_index_sets.iter().flatten().cloned().collect_vec(); - all_indices.sort(); - let mut all_chunk_indices = all_indices - .iter() - .map(|index| index / (CHUNK_SIZE as u128)) - .map(|index| index as u64) - .collect_vec(); - all_chunk_indices.dedup(); - let mmr_chunk_indices = all_chunk_indices - .iter() - .cloned() - .filter(|ci| { - *ci < aocl_mmra.count_leaves() / (BATCH_SIZE as u64) - }) - .collect_vec(); + // unwrap random mmr_chunks + (0..mmr_chunk_indices.len()) + .map(|_| arb::()) + .collect_vec() + .prop_flat_map( move |mmr_chunks| { // prepare to unwrap let input_lock_scripts = input_lock_scripts.clone(); @@ -444,235 +488,215 @@ pub(crate) fn arbitrary_primitive_witness_with( let output_utxos = output_utxos.clone(); let public_announcements = public_announcements.clone(); - // unwrap random mmr_chunks - (0..mmr_chunk_indices.len()) - .map(|_| arb::()) - .collect_vec() - .prop_flat_map( move |mmr_chunks| { - - // prepare to unwrap - let input_lock_scripts = input_lock_scripts.clone(); - let lock_script_witnesses = lock_script_witnesses.clone(); - let aocl_mmra = aocl_mmra.clone(); - let mmr_chunk_indices = mmr_chunk_indices.clone(); - let all_index_sets = all_index_sets.clone(); - let input_triples = input_triples.clone(); - let aocl_membership_proofs = aocl_membership_proofs.clone(); - let input_utxos = input_utxos.clone(); - let output_utxos = output_utxos.clone(); - let public_announcements = public_announcements.clone(); - - // unwrap random swbf mmra and membership proofs - let swbf_strategy = MmraAndMembershipProofs::arbitrary_with( - mmr_chunks.iter().map(Hash::hash).collect_vec(), - ); - let mmr_chunks = mmr_chunks.clone(); - swbf_strategy - .prop_flat_map(move |swbf_mmr_and_paths| { - let swbf_mmra = swbf_mmr_and_paths.mmra; - let swbf_membership_proofs = - swbf_mmr_and_paths.membership_proofs; - - let chunk_dictionary: HashMap< - u64, - (MmrMembershipProof, Chunk), - > = mmr_chunk_indices - .iter() - .cloned() - .zip( - swbf_membership_proofs - .into_iter() - .zip(mmr_chunks.iter().cloned()), - ) - .collect(); - let personalized_chunk_dictionaries = - all_index_sets + // unwrap random swbf mmra and membership proofs + let swbf_strategy = MmraAndMembershipProofs::arbitrary_with( + mmr_chunks.iter().map(Hash::hash).collect_vec(), + ); + let mmr_chunks = mmr_chunks.clone(); + swbf_strategy + .prop_flat_map(move |swbf_mmr_and_paths| { + let swbf_mmra = swbf_mmr_and_paths.mmra; + let swbf_membership_proofs = + swbf_mmr_and_paths.membership_proofs; + + let chunk_dictionary: HashMap< + u64, + (MmrMembershipProof, Chunk), + > = mmr_chunk_indices + .iter() + .cloned() + .zip( + swbf_membership_proofs + .into_iter() + .zip(mmr_chunks.iter().cloned()), + ) + .collect(); + let personalized_chunk_dictionaries = + all_index_sets + .iter() + .map(|index_set| { + let mut is = index_set .iter() - .map(|index_set| { - let mut is = index_set - .iter() - .map(|index| { - index / (BATCH_SIZE as u128) - }) - .map(|index| index as u64) - .collect_vec(); - is.sort(); - is.dedup(); - is - }) - .map(|chunk_indices| { - ChunkDictionary::new( - chunk_indices - .iter() - .map(|chunk_index| { - ( - *chunk_index, - chunk_dictionary - .get( - chunk_index, - ) - .cloned() - .unwrap(), - ) - }) - .collect::, - Chunk, - ), - >>( - ), - ) + .map(|index| { + index / (BATCH_SIZE as u128) }) + .map(|index| index as u64) .collect_vec(); - let input_membership_proofs = input_triples - .iter() - .zip(aocl_membership_proofs.iter()) - .zip(personalized_chunk_dictionaries.iter()) - .map( - |( - ( - (_item, sender_randomness, receiver_preimage), - auth_path, + is.sort(); + is.dedup(); + is + }) + .map(|chunk_indices| { + ChunkDictionary::new( + chunk_indices + .iter() + .map(|chunk_index| { + ( + *chunk_index, + chunk_dictionary + .get( + chunk_index, + ) + .cloned() + .unwrap(), + ) + }) + .collect::, + Chunk, + ), + >>( ), - target_chunks, - )| { - MsMembershipProof { - sender_randomness: *sender_randomness, - receiver_preimage: *receiver_preimage, - auth_path_aocl: auth_path.clone(), - target_chunks: target_chunks.clone(), - } - }, ) - .collect_vec(); - let input_removal_records = - all_index_sets + }) + .collect_vec(); + let input_membership_proofs = input_triples + .iter() + .zip(aocl_membership_proofs.iter()) + .zip(personalized_chunk_dictionaries.iter()) + .map( + |( + ( + (_item, sender_randomness, receiver_preimage), + auth_path, + ), + target_chunks, + )| { + MsMembershipProof { + sender_randomness: *sender_randomness, + receiver_preimage: *receiver_preimage, + auth_path_aocl: auth_path.clone(), + target_chunks: target_chunks.clone(), + } + }, + ) + .collect_vec(); + let input_removal_records = + all_index_sets + .iter() + .zip( + personalized_chunk_dictionaries + .iter(), + ) + .map(|(index_set, target_chunks)| { + RemovalRecord { + absolute_indices: + AbsoluteIndexSet::new( + index_set, + ), + target_chunks: target_chunks + .clone(), + } + }) + .collect_vec(); + let type_scripts = vec![TypeScript::new( + native_currency_program(), + )]; + + // prepare to unwrap + let input_lock_scripts = input_lock_scripts.clone(); + let input_utxos = input_utxos.clone(); + let input_removal_records = input_removal_records.clone(); + let input_membership_proofs = input_membership_proofs.clone(); + let type_scripts = type_scripts.clone(); + let lock_script_witnesses = lock_script_witnesses.clone(); + let aocl_mmra = aocl_mmra.clone(); + let swbf_mmra = swbf_mmra.clone(); + let output_utxos = output_utxos.clone(); + let public_announcements = public_announcements.clone(); + + // unwrap sender randomnesses and receiver digests + let output_sender_randomnesses_strategy = + vec(arb::(), num_outputs); + let receiver_digest_strategy = + vec(arb::(), num_outputs); + (output_sender_randomnesses_strategy, receiver_digest_strategy) + .prop_flat_map( + move |(mut output_sender_randomnesses, mut receiver_digests)| { + let output_commitments = output_utxos .iter() - .zip( - personalized_chunk_dictionaries - .iter(), - ) - .map(|(index_set, target_chunks)| { - RemovalRecord { - absolute_indices: - AbsoluteIndexSet::new( - index_set, - ), - target_chunks: target_chunks - .clone(), - } + .map(|utxo| { + commit( + Hash::hash(utxo), + output_sender_randomnesses + .pop() + .unwrap(), + receiver_digests.pop().unwrap(), + ) }) .collect_vec(); - let type_scripts = vec![TypeScript::new( - native_currency_program(), - )]; - - - // prepare to unwrap - let input_lock_scripts = input_lock_scripts.clone(); - let input_utxos = input_utxos.clone(); - let input_removal_records = input_removal_records.clone(); - let input_membership_proofs = input_membership_proofs.clone(); - let type_scripts = type_scripts.clone(); - let lock_script_witnesses = lock_script_witnesses.clone(); - let aocl_mmra = aocl_mmra.clone(); - let swbf_mmra = swbf_mmra.clone(); - let output_utxos = output_utxos.clone(); - let public_announcements = public_announcements.clone(); - - // unwrap sender randomnesses and receiver digests - let output_sender_randomnesses_strategy = - vec(arb::(), num_outputs); - let receiver_digest_strategy = - vec(arb::(), num_outputs); - (output_sender_randomnesses_strategy, receiver_digest_strategy) - .prop_flat_map( - move |(mut output_sender_randomnesses, mut receiver_digests)| { - let output_commitments = output_utxos - .iter() - .map(|utxo| { - commit( - Hash::hash(utxo), - output_sender_randomnesses - .pop() - .unwrap(), - receiver_digests.pop().unwrap(), - ) - }) - .collect_vec(); - - // prepare to unwrap - let input_lock_scripts = input_lock_scripts.clone(); - let input_utxos = input_utxos.clone(); - let input_removal_records = input_removal_records.clone(); - let input_membership_proofs = input_membership_proofs.clone(); - let type_scripts = type_scripts.clone(); - let lock_script_witnesses = lock_script_witnesses.clone(); - let aocl_mmra = aocl_mmra.clone(); - let swbf_mmra = swbf_mmra.clone(); - let output_utxos = output_utxos.clone(); - let public_announcements = public_announcements.clone(); - - // unwrap random active window - arb::() - .prop_map(move |active_window| { - let mutator_set_accumulator = - MutatorSetAccumulator { - kernel: MutatorSetKernel { - aocl: aocl_mmra.clone(), - swbf_inactive: - swbf_mmra.clone(), - swbf_active: - active_window, - }, - }; - - let kernel = TransactionKernel { - inputs: input_removal_records.clone(), - outputs: output_commitments.clone(), - public_announcements: public_announcements.to_vec(), - fee, - coinbase, - timestamp: BFieldElement::new( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() - as u64, - ), - mutator_set_hash: - mutator_set_accumulator.hash(), - }; - - PrimitiveWitness { - input_lock_scripts: input_lock_scripts.clone(), - input_utxos: input_utxos.clone(), - input_membership_proofs: input_membership_proofs.clone(), - type_scripts: type_scripts.clone(), - lock_script_witnesses: lock_script_witnesses.clone(), - output_utxos: output_utxos.clone(), - mutator_set_accumulator: mutator_set_accumulator.clone(), - kernel, - } - - }) - .boxed() - }, - ) + + // prepare to unwrap + let input_lock_scripts = input_lock_scripts.clone(); + let input_utxos = input_utxos.clone(); + let input_removal_records = input_removal_records.clone(); + let input_membership_proofs = input_membership_proofs.clone(); + let type_scripts = type_scripts.clone(); + let lock_script_witnesses = lock_script_witnesses.clone(); + let aocl_mmra = aocl_mmra.clone(); + let swbf_mmra = swbf_mmra.clone(); + let output_utxos = output_utxos.clone(); + let public_announcements = public_announcements.clone(); + + // unwrap random active window + arb::() + .prop_map(move |active_window| { + let mutator_set_accumulator = + MutatorSetAccumulator { + kernel: MutatorSetKernel { + aocl: aocl_mmra.clone(), + swbf_inactive: + swbf_mmra.clone(), + swbf_active: + active_window, + }, + }; + + let kernel = TransactionKernel { + inputs: input_removal_records.clone(), + outputs: output_commitments.clone(), + public_announcements: public_announcements.to_vec(), + fee, + coinbase, + timestamp: BFieldElement::new( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() + as u64, + ), + mutator_set_hash: + mutator_set_accumulator.hash(), + }; + + PrimitiveWitness { + input_lock_scripts: input_lock_scripts.clone(), + input_utxos: input_utxos.clone(), + input_membership_proofs: input_membership_proofs.clone(), + type_scripts: type_scripts.clone(), + lock_script_witnesses: lock_script_witnesses.clone(), + output_utxos: output_utxos.clone(), + mutator_set_accumulator: mutator_set_accumulator.clone(), + kernel, + } + + }) .boxed() - }) - .boxed() - }) + }, + ) .boxed() }) .boxed() - }) - .boxed() - + }) + .boxed() + }) + .boxed() + }) + .boxed() }) .boxed() } diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index 2718fb62..cae20803 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -1,8 +1,10 @@ +use crate::models::blockchain::transaction::primitive_witness::arbitrary_primitive_witness_with; use crate::models::blockchain::transaction::primitive_witness::PrimitiveWitness; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernelField; use crate::models::blockchain::transaction::utxo::Coin; use crate::models::blockchain::transaction::utxo::Utxo; +use crate::models::blockchain::transaction::PublicAnnouncement; use crate::models::consensus::mast_hash::MastHash; use crate::models::consensus::SecretWitness; use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; @@ -11,9 +13,12 @@ use crate::util_types::mutator_set::shared::NUM_TRIALS; use crate::Hash; use get_size::GetSize; use itertools::Itertools; +use num_traits::Zero; use proptest::arbitrary::Arbitrary; +use proptest::collection::vec; use proptest::strategy::BoxedStrategy; use proptest::strategy::Strategy; +use proptest_arbitrary_interop::arb; use serde::{Deserialize, Serialize}; use tasm_lib::twenty_first::prelude::AlgebraicHasher; use tasm_lib::{ @@ -29,6 +34,8 @@ use tasm_lib::{ use crate::models::consensus::tasm::builtins as tasm; use crate::models::consensus::tasm::program::ConsensusProgram; +use super::neptune_coins::NeptuneCoins; + #[derive(Debug, Clone, Deserialize, Serialize, BFieldCodec, GetSize, PartialEq, Eq)] pub struct TimeLock {} @@ -452,17 +459,42 @@ impl SecretWitness for TimeLockWitness { } impl Arbitrary for TimeLockWitness { - type Parameters = (Vec>, usize, usize); + type Parameters = (Vec, usize, usize); type Strategy = BoxedStrategy; fn arbitrary_with(parameters: Self::Parameters) -> Self::Strategy { let (release_dates, num_outputs, num_public_announcements) = parameters; let num_inputs = release_dates.len(); - PrimitiveWitness::arbitrary_with((num_inputs, num_outputs, num_public_announcements)) - .prop_map(|transaction_primitive_witness| { - TimeLockWitness::from_primitive_witness(&transaction_primitive_witness) - }) + ( + vec(arb::(), num_inputs), + vec(arb::(), num_outputs), + vec(arb::(), num_public_announcements), + ) + .prop_flat_map( + move |(mut input_utxos, output_utxos, public_announcements)| { + // add time locks to input utxos + for (utxo, release_date) in input_utxos.iter_mut().zip(release_dates.iter()) { + if *release_date != 0 { + let time_lock_coin = TimeLock::until(*release_date); + utxo.coins.push(time_lock_coin); + } + } + + // generate primitive transaction witness and time lock witness from there + arbitrary_primitive_witness_with( + &input_utxos, + &output_utxos, + &public_announcements, + NeptuneCoins::zero(), + None, + ) + .prop_map(move |transaction_primitive_witness| { + TimeLockWitness::from_primitive_witness(&transaction_primitive_witness) + }) + .boxed() + }, + ) .boxed() } } From 9ab9f9c455ddc6f437e685f13063489d97f0321a Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Tue, 20 Feb 2024 18:05:50 +0100 Subject: [PATCH 19/33] wip --- .../transaction/primitive_witness.rs | 23 ++ src/util_types/mutator_set.rs | 1 + .../mutator_set/mmra_and_membership_proofs.rs | 343 ++++++++---------- 3 files changed, 176 insertions(+), 191 deletions(-) diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index 54f177c1..9d6024d1 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -700,3 +700,26 @@ pub(crate) fn arbitrary_primitive_witness_with( }) .boxed() } + +#[cfg(test)] +mod test { + use crate::models::blockchain::transaction::validity::TransactionValidationLogic; + + use super::PrimitiveWitness; + use proptest::prop_assert; + use test_strategy::proptest; + + #[proptest] + fn arbitrary_transaction_is_valid( + #[strategy(1usize..3)] _num_inputs: usize, + #[strategy(1usize..3)] _num_outputs: usize, + #[strategy(0usize..3)] _num_public_announcements: usize, + #[strategy(PrimitiveWitness::arbitrary_with((#_num_inputs, #_num_outputs, #_num_public_announcements)))] + transaction_primitive_witness: PrimitiveWitness, + ) { + prop_assert!(TransactionValidationLogic::new_from_primitive_witness( + &transaction_primitive_witness + ) + .verify()); + } +} diff --git a/src/util_types/mutator_set.rs b/src/util_types/mutator_set.rs index fec1cf28..ff95c881 100644 --- a/src/util_types/mutator_set.rs +++ b/src/util_types/mutator_set.rs @@ -6,6 +6,7 @@ pub mod chunk; pub mod chunk_dictionary; pub mod mmra_and_membership_proofs; pub mod ms_membership_proof; +pub mod msa_and_records; pub mod mutator_set_accumulator; pub mod mutator_set_kernel; pub mod mutator_set_trait; diff --git a/src/util_types/mutator_set/mmra_and_membership_proofs.rs b/src/util_types/mutator_set/mmra_and_membership_proofs.rs index bd95fbb3..c4db35e9 100644 --- a/src/util_types/mutator_set/mmra_and_membership_proofs.rs +++ b/src/util_types/mutator_set/mmra_and_membership_proofs.rs @@ -3,7 +3,6 @@ use std::marker::PhantomData; use itertools::Itertools; use proptest::{ arbitrary::Arbitrary, - collection::vec, strategy::{BoxedStrategy, Just, Strategy}, }; use tasm_lib::twenty_first::util_types::{ @@ -27,193 +26,142 @@ pub struct MmraAndMembershipProofs { } impl Arbitrary for MmraAndMembershipProofs { - type Parameters = Vec; - - fn arbitrary_with(leafs_proper: Self::Parameters) -> Self::Strategy { - let num_paths = leafs_proper.len() as u64; - // let total_leaf_count_strategy = num_paths..u64::MAX; - let total_leaf_count_strategy = num_paths..num_paths + 100; - - // unwrap total leaf count - total_leaf_count_strategy - .prop_flat_map(move |total_leaf_count| { - let indices_strategy = vec(0u64..total_leaf_count, num_paths as usize) - .prop_filter("indices must be unique", |indices| { - indices.iter().all_unique() - }) - .boxed(); - - // unwrap leafs, indices, digests - (Just(leafs_proper.clone()), indices_strategy) - .prop_flat_map(move |(leafs, mut indices)| { - let num_peaks = total_leaf_count.count_ones(); - let mut tree_heights = vec![]; - for shift in (0..63).rev() { - if total_leaf_count & (1u64 << shift) != 0 { - tree_heights.push(shift); - } - } - - // sample mmr leaf indices and calculate matching derived indices - let index_sets = leafs - .iter() - .enumerate() - .map(|(enumeration_index, _leaf)| { - (enumeration_index, indices.pop().unwrap()) - }) - .map(|(enumeration_index, mmr_leaf_index)| { - let (mt_node_index, peak_index) = - leaf_index_to_mt_index_and_peak_index( - mmr_leaf_index, - total_leaf_count, - ); - (enumeration_index, mt_node_index, mmr_leaf_index, peak_index) - }) - .collect_vec(); - let leafs_and_indices = leafs - .iter() - .copied() - .zip(index_sets.iter().cloned()) - .collect_vec(); - - // segregate by tree - let mut indices_and_leafs_by_tree = vec![]; - for tree in 0..num_peaks { - let local_indices_and_leafs = leafs_and_indices - .iter() - .filter( - |( - _leaf, - ( - _enumeration_index, - _mt_node_index, - _mmr_leaf_index, - peak_index, - ), - )| { *peak_index == tree }, - ) - .cloned() - .map( - |( - leaf, - ( - enumeration_index, - mt_node_index, - mmr_leaf_index, - _peak_index, - ), - )| { - (enumeration_index, mt_node_index, mmr_leaf_index, leaf) - }, - ) - .collect_vec(); - - indices_and_leafs_by_tree.push(local_indices_and_leafs); - } - - // for each tree generate root and paths - let mut root_and_paths_strategies = vec![]; - for (tree, local_indices_and_leafs) in - indices_and_leafs_by_tree.iter().enumerate() - { - let tree_height = tree_heights[tree]; - let root_and_paths_strategy = RootAndPaths::arbitrary_with(( - tree_height, - local_indices_and_leafs - .iter() - .copied() - .map(|(_ei, mtni, _mmri, leaf)| { - (mtni ^ (1 << tree_height), leaf) - }) - .collect_vec(), - )); - root_and_paths_strategies.push(root_and_paths_strategy); - } - - // unwrap vector of roots and pathses - (root_and_paths_strategies, Just(indices_and_leafs_by_tree)).prop_map( - move |(roots_and_pathses, indices_and_leafs_by_tree_)| { - // sanity check for roots and pathses - for (root_and_paths, indices_and_leafs) in roots_and_pathses - .iter() - .zip(indices_and_leafs_by_tree_.iter()) - { - let root = root_and_paths.root; - for ( - path, - ( - _enumeration_index, - merkle_tree_node_index, - _mmr_index, - leaf, - ), - ) in - root_and_paths.paths.iter().zip(indices_and_leafs.iter()) - { - let mip = MerkleTreeInclusionProof { - tree_height: path.len(), - indexed_leaves: vec![( - *merkle_tree_node_index as usize - ^ (1 << path.len()), - *leaf, - )], - authentication_structure: path.clone(), - _hasher: PhantomData::, - }; - assert!(mip.verify(root)); - } - } - - // extract peaks - let peaks = roots_and_pathses - .iter() - .map(|root_and_paths| root_and_paths.root) - .collect_vec(); - - // prepare to extract membership proofs - let mut membership_proofs = vec![ - MmrMembershipProof { - leaf_index: 0, - authentication_path: vec![], - _hasher: std::marker::PhantomData:: - }; - num_paths as usize - ]; - - // loop over all leaf indices and look up membership proof - for (root_and_paths, indices_and_leafs) in roots_and_pathses - .into_iter() - .zip(indices_and_leafs_by_tree_.iter()) - { - let paths = root_and_paths.paths; - for ( - path, - &(enumeration_index, _merkle_tree_index, mmr_index, _leaf), - ) in paths.into_iter().zip(indices_and_leafs.iter()) - { - membership_proofs[enumeration_index].authentication_path = - path; - membership_proofs[enumeration_index].leaf_index = mmr_index; - } - } - - // sanity check - for (mmr_mp, leaf) in membership_proofs.iter().zip(leafs.iter()) { - let (_mti, _pi) = leaf_index_to_mt_index_and_peak_index( - mmr_mp.leaf_index, - total_leaf_count, - ); - assert!(mmr_mp.verify(&peaks, *leaf, total_leaf_count).0); - } - - MmraAndMembershipProofs { - mmra: MmrAccumulator::init(peaks, total_leaf_count), - membership_proofs, - } - }, - ) - }) - .boxed() + type Parameters = (Vec<(u64, Digest)>, u64); + + fn arbitrary_with(parameters: Self::Parameters) -> Self::Strategy { + let (indices_and_leafs, total_leaf_count) = parameters; + let indices = indices_and_leafs.iter().map(|(i, d)| *i).collect_vec(); + let max_index = indices.iter().max().copied().unwrap_or(0u64); + let leafs = indices_and_leafs.iter().map(|(i, d)| *d).collect_vec(); + let num_paths = leafs.len() as u64; + + let num_peaks = total_leaf_count.count_ones(); + let mut tree_heights = vec![]; + for shift in (0..63).rev() { + if total_leaf_count & (1u64 << shift) != 0 { + tree_heights.push(shift); + } + } + + // sample mmr leaf indices and calculate matching derived indices + let index_sets = leafs + .iter() + .enumerate() + .map(|(enumeration_index, _leaf)| (enumeration_index, indices.pop().unwrap())) + .map(|(enumeration_index, mmr_leaf_index)| { + let (mt_node_index, peak_index) = + leaf_index_to_mt_index_and_peak_index(mmr_leaf_index, total_leaf_count); + (enumeration_index, mt_node_index, mmr_leaf_index, peak_index) + }) + .collect_vec(); + let leafs_and_indices = leafs + .iter() + .copied() + .zip(index_sets.iter().cloned()) + .collect_vec(); + + // segregate by tree + let mut indices_and_leafs_by_tree = vec![]; + for tree in 0..num_peaks { + let local_indices_and_leafs = leafs_and_indices + .iter() + .filter( + |(_leaf, (_enumeration_index, _mt_node_index, _mmr_leaf_index, peak_index))| { + *peak_index == tree + }, + ) + .cloned() + .map( + |(leaf, (enumeration_index, mt_node_index, mmr_leaf_index, _peak_index))| { + (enumeration_index, mt_node_index, mmr_leaf_index, leaf) + }, + ) + .collect_vec(); + + indices_and_leafs_by_tree.push(local_indices_and_leafs); + } + + // for each tree generate root and paths + let mut root_and_paths_strategies = vec![]; + for (tree, local_indices_and_leafs) in indices_and_leafs_by_tree.iter().enumerate() { + let tree_height = tree_heights[tree]; + let root_and_paths_strategy = RootAndPaths::arbitrary_with(( + tree_height, + local_indices_and_leafs + .iter() + .copied() + .map(|(_ei, mtni, _mmri, leaf)| (mtni ^ (1 << tree_height), leaf)) + .collect_vec(), + )); + root_and_paths_strategies.push(root_and_paths_strategy); + } + + // unwrap vector of roots and pathses + (root_and_paths_strategies, Just(indices_and_leafs_by_tree)) + .prop_map(move |(roots_and_pathses, indices_and_leafs_by_tree_)| { + // sanity check for roots and pathses + for (root_and_paths, indices_and_leafs) in roots_and_pathses + .iter() + .zip(indices_and_leafs_by_tree_.iter()) + { + let root = root_and_paths.root; + for (path, (_enumeration_index, merkle_tree_node_index, _mmr_index, leaf)) in + root_and_paths.paths.iter().zip(indices_and_leafs.iter()) + { + let mip = MerkleTreeInclusionProof { + tree_height: path.len(), + indexed_leaves: vec![( + *merkle_tree_node_index as usize ^ (1 << path.len()), + *leaf, + )], + authentication_structure: path.clone(), + _hasher: PhantomData::, + }; + assert!(mip.verify(root)); + } + } + + // extract peaks + let peaks = roots_and_pathses + .iter() + .map(|root_and_paths| root_and_paths.root) + .collect_vec(); + + // prepare to extract membership proofs + let mut membership_proofs = vec![ + MmrMembershipProof { + leaf_index: 0, + authentication_path: vec![], + _hasher: std::marker::PhantomData:: + }; + num_paths as usize + ]; + + // loop over all leaf indices and look up membership proof + for (root_and_paths, indices_and_leafs) in roots_and_pathses + .into_iter() + .zip(indices_and_leafs_by_tree_.iter()) + { + let paths = root_and_paths.paths; + for (path, &(enumeration_index, _merkle_tree_index, mmr_index, _leaf)) in + paths.into_iter().zip(indices_and_leafs.iter()) + { + membership_proofs[enumeration_index].authentication_path = path; + membership_proofs[enumeration_index].leaf_index = mmr_index; + } + } + + // sanity check + for (mmr_mp, leaf) in membership_proofs.iter().zip(leafs.iter()) { + let (_mti, _pi) = + leaf_index_to_mt_index_and_peak_index(mmr_mp.leaf_index, total_leaf_count); + assert!(mmr_mp.verify(&peaks, *leaf, total_leaf_count).0); + } + + MmraAndMembershipProofs { + mmra: MmrAccumulator::init(peaks, total_leaf_count), + membership_proofs, + } }) .boxed() } @@ -226,18 +174,31 @@ mod test { use super::*; use crate::twenty_first::shared_math::tip5::Digest; + use proptest::collection::vec; use proptest::prelude::*; use proptest_arbitrary_interop::arb; use tasm_lib::twenty_first::util_types::mmr::mmr_trait::Mmr; use test_strategy::proptest; + fn indices_and_leafs_strategy(num: usize) -> BoxedStrategy> { + vec(arb::<(u64, Digest)>(), num) + .prop_filter("indices must all be unique", |indices_and_leafs| { + indices_and_leafs.iter().map(|(i, l)| *i).all_unique() + }) + .boxed() + } + #[proptest(cases = 20)] fn integrity( - #[strategy(vec(arb(), 0..10))] leafs: Vec, - #[strategy(MmraAndMembershipProofs::arbitrary_with(#leafs))] + #[strategy(1usize..10)] _num_paths: usize, + #[strategy(indices_and_leafs_strategy(#_num_paths))] indices_and_leafs: Vec<(u64, Digest)>, + #[filter(#indices_and_leafs.iter().map(|(i,_l)|*i).max().unwrap() < #_total_leaf_count)] + #[strategy(0..u64::MAX)] + _total_leaf_count: u64, + #[strategy(MmraAndMembershipProofs::arbitrary_with((#indices_and_leafs, #_total_leaf_count)))] mmra_and_membership_proofs: MmraAndMembershipProofs, ) { - for (leaf, mp) in leafs + for ((index, leaf), mp) in indices_and_leafs .into_iter() .zip(mmra_and_membership_proofs.membership_proofs) { From c8e3ba873acd2c530b6ba0c8ff6cf331e452425b Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Tue, 20 Feb 2024 18:13:27 +0100 Subject: [PATCH 20/33] more wip --- src/util_types/mutator_set/msa_and_records.rs | 291 ++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 src/util_types/mutator_set/msa_and_records.rs diff --git a/src/util_types/mutator_set/msa_and_records.rs b/src/util_types/mutator_set/msa_and_records.rs new file mode 100644 index 00000000..f6d0972f --- /dev/null +++ b/src/util_types/mutator_set/msa_and_records.rs @@ -0,0 +1,291 @@ +use std::collections::HashMap; + +use itertools::Itertools; +use proptest::{ + arbitrary::Arbitrary, + strategy::{BoxedStrategy, Just, Strategy}, +}; +use proptest_arbitrary_interop::arb; +use tasm_lib::{ + twenty_first::util_types::{ + algebraic_hasher::AlgebraicHasher, + mmr::{mmr_membership_proof::MmrMembershipProof, mmr_trait::Mmr}, + }, + Digest, +}; + +use crate::models::blockchain::shared::Hash; + +use super::{ + active_window::ActiveWindow, + addition_record::AdditionRecord, + chunk::Chunk, + chunk_dictionary::ChunkDictionary, + mmra_and_membership_proofs::MmraAndMembershipProofs, + ms_membership_proof::MsMembershipProof, + mutator_set_accumulator::MutatorSetAccumulator, + mutator_set_kernel::{get_swbf_indices, MutatorSetKernel}, + mutator_set_trait::{commit, MutatorSet}, + removal_record::{AbsoluteIndexSet, RemovalRecord}, + shared::{BATCH_SIZE, CHUNK_SIZE}, +}; +use proptest::collection::vec; + +#[derive(Debug, Clone)] +pub struct MsaAndRecords { + pub mutator_set_accumulator: MutatorSetAccumulator, + pub addition_records: Vec, + pub removal_records: Vec, + pub membership_proofs: Vec, +} + +impl MsaAndRecords { + pub fn verify(&self, items: &[Digest]) -> bool { + self.removal_records + .iter() + .all(|rr| self.mutator_set_accumulator.kernel.can_remove(rr)) + && self + .membership_proofs + .iter() + .zip_eq(items.iter()) + .all(|(mp, item)| self.mutator_set_accumulator.verify(*item, mp)) + } +} + +impl Arbitrary for MsaAndRecords { + type Parameters = (Vec<(Digest, Digest, Digest)>, Vec<(Digest, Digest, Digest)>); + type Strategy = BoxedStrategy; + + fn arbitrary_with(parameters: Self::Parameters) -> Self::Strategy { + let (removables, addeds) = parameters; + + let removable_commitments = removables + .iter() + .map(|(item, sender_randomness, receiver_preimage)| { + commit(*item, *sender_randomness, Hash::hash(receiver_preimage)) + }) + .collect_vec(); + + // unwrap aocl indices + let aocl_size = (1u64 << 33); + vec(0u64..aocl_size, addeds.len()).prop_flat_map(|aocl_indices| { + + // unwrap random aocl mmr with membership proofs + MmraAndMembershipProofs::arbitrary_with(( + aocl_indices.iter().zip(removable_commitments + .iter()) + .map(|(i, ar)| (*i, ar.canonical_commitment)) + .collect_vec(),aocl_size)) + .prop_flat_map(move |aocl_mmra_and_membership_proofs| { + let aocl_mmra = aocl_mmra_and_membership_proofs.mmra; + let aocl_membership_proofs = aocl_mmra_and_membership_proofs.membership_proofs; + let all_index_sets = removables + .iter() + .zip(aocl_membership_proofs.iter()) + .map( + |((item, sender_randomness, receiver_preimage), authentication_path)| { + get_swbf_indices( + *item, + *sender_randomness, + *receiver_preimage, + authentication_path.leaf_index, + ) + }, + ) + .collect_vec(); + let mut all_indices = all_index_sets.iter().flatten().cloned().collect_vec(); + all_indices.sort(); + let mut all_chunk_indices = all_indices + .iter() + .map(|index| *index / (CHUNK_SIZE as u128)) + .map(|index| index as u64) + .collect_vec(); + all_chunk_indices.sort(); + all_chunk_indices.dedup(); + let mmr_chunk_indices = all_chunk_indices + .iter() + .cloned() + .filter(|ci| *ci < aocl_mmra.count_leaves() / (BATCH_SIZE as u64)) + .collect_vec(); + + // prepare to unwrap + let aocl_mmra = aocl_mmra.clone(); + let mmr_chunk_indices = mmr_chunk_indices.clone(); + let all_index_sets = all_index_sets.clone(); + let aocl_membership_proofs = aocl_membership_proofs.clone(); + let removables = removables.clone(); + let addeds = addeds.clone(); + + // unwrap random mmr_chunks + mmr_chunk_indices.into_iter() + .map(|index| (Just(index), arb::())) + .collect_vec() + .prop_flat_map(move |mmr_indices_and_chunks| { + // prepare to unwrap + let aocl_mmra = aocl_mmra.clone(); + let mmr_chunk_indices = mmr_chunk_indices.clone(); + let all_index_sets = all_index_sets.clone(); + let aocl_membership_proofs = aocl_membership_proofs.clone(); + let removables = removables.clone(); + let addeds = addeds.clone(); + let swbf_mmr_leaf_count = aocl_mmra.count_leaves() / (BATCH_SIZE as u64); + + // unwrap random swbf mmra and membership proofs + let swbf_strategy = MmraAndMembershipProofs::arbitrary_with(( + mmr_indices_and_chunks.iter().map(|(i, c)| (*i, Hash::hash(c))).collect_vec(), swbf_mmr_leaf_count + )); + let mmr_indices_and_chunks = mmr_indices_and_chunks.clone(); + swbf_strategy + .prop_flat_map(move |swbf_mmr_and_paths| { + let swbf_mmra = swbf_mmr_and_paths.mmra; + let swbf_membership_proofs = swbf_mmr_and_paths.membership_proofs; + + let chunk_dictionary: HashMap, Chunk)> = + mmr_chunk_indices + .iter() + .cloned() + .zip( + swbf_membership_proofs + .into_iter() + .zip(mmr_indices_and_chunks.iter().cloned()), + ) + .collect(); + println!("made chunk dictionary with indices: {} where aocl mmra leaf count is {}", chunk_dictionary.keys().join(", "), aocl_mmra.count_leaves()); + let personalized_chunk_dictionaries = all_index_sets + .iter() + .map(|index_set| { + let mut is = index_set + .iter() + .map(|index| *index / (BATCH_SIZE as u128)) + .map(|index| index as u64) + .collect_vec(); + is.sort(); + is.dedup(); + is + }) + .map(|chunk_indices| { + ChunkDictionary::new( + chunk_indices + .iter() + .map(|chunk_index| { + ( + *chunk_index, + chunk_dictionary + .get( + chunk_index, + ) + ).filter(|r|r.is_some()).cloned() + }) + .collect::, + Chunk, + ), + >>( + ), + ) + }) + .collect_vec(); + let membership_proofs = removables + .clone() + .iter() + .zip(aocl_membership_proofs.iter()) + .zip(personalized_chunk_dictionaries.iter()) + .map( + |( + ((_item, sender_randomness, receiver_preimage), auth_path), + target_chunks, + )| { + MsMembershipProof { + sender_randomness: *sender_randomness, + receiver_preimage: *receiver_preimage, + auth_path_aocl: auth_path.clone(), + target_chunks: target_chunks.clone(), + } + }, + ) + .collect_vec(); + let removal_records = all_index_sets + .iter() + .zip(personalized_chunk_dictionaries.iter()) + .map(|(index_set, target_chunks)| RemovalRecord { + absolute_indices: AbsoluteIndexSet::new(index_set), + target_chunks: target_chunks.clone(), + }) + .collect_vec(); + + let addition_records = addeds + .iter() + .map(|(item, sender_randomness, receiver_preimage)| { + commit(*item, *sender_randomness, Hash::hash(receiver_preimage)) + }) + .collect_vec(); + + // prepare to unwrap + let aocl_mmra = aocl_mmra.clone(); + let swbf_mmra = swbf_mmra.clone(); + let addition_records = addition_records.clone(); + let removal_records = removal_records.clone(); + let membership_proofs = membership_proofs.clone(); + + // unwrap random active window + arb::() + .prop_map(move |active_window| { + let mutator_set_accumulator = MutatorSetAccumulator { + kernel: MutatorSetKernel { + aocl: aocl_mmra.clone(), + swbf_inactive: swbf_mmra.clone(), + swbf_active: active_window, + }, + }; + MsaAndRecords { + mutator_set_accumulator, + addition_records: addition_records.clone(), + removal_records: removal_records.clone(), + membership_proofs: membership_proofs.clone(), + } + }) + .boxed() + }) + .boxed() + }) + .boxed() + }) + .boxed() + }).boxed() + } +} + +#[cfg(test)] +mod test { + use itertools::Itertools; + use proptest::collection::vec; + use proptest::prop_assert; + use proptest_arbitrary_interop::arb; + use tasm_lib::Digest; + use test_strategy::proptest; + + use super::MsaAndRecords; + + #[proptest] + fn msa_and_records_is_valid( + #[strategy(0usize..10)] _num_removals: usize, + #[strategy(0usize..10)] _num_additions: usize, + #[strategy(vec((arb::(), arb::(), arb::()), #_num_removals))] + removables: Vec<(Digest, Digest, Digest)>, + #[strategy(vec((arb::(), arb::(), arb::()), #_num_additions))] + _addeds: Vec<(Digest, Digest, Digest)>, + #[strategy(MsaAndRecords::arbitrary_with((#removables, #_addeds)))] + msa_and_records: MsaAndRecords, + ) { + prop_assert!(msa_and_records.verify( + &removables + .iter() + .map(|(item, _sr, _rp)| *item) + .collect_vec() + )); + } +} From 4e1bccb3768bdef1695be5260127edd5032ec863 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Tue, 20 Feb 2024 19:14:06 +0100 Subject: [PATCH 21/33] wip wip wip --- .../blockchain/transaction/primitive_witness.rs | 15 +++++++++------ src/util_types/mutator_set.rs | 2 +- .../mutator_set/mmra_and_membership_proofs.rs | 10 +++++----- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index 9d6024d1..33f6d29b 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -387,8 +387,10 @@ pub(crate) fn arbitrary_primitive_witness_with( // unwrap random sender randomness and receiver preimages let sender_randomnesses_strategy = vec(arb::(), num_inputs); let receiver_preimages_strategy = vec(arb::(), num_inputs); - (sender_randomnesses_strategy, receiver_preimages_strategy) - .prop_flat_map(move |(mut sender_randomnesses, mut receiver_preimages)| { + let aocl_size = 1u64<<33; + let aocl_indices_strategy = vec(0u64..aocl_size, num_inputs); + (sender_randomnesses_strategy, receiver_preimages_strategy, aocl_indices_strategy) + .prop_flat_map(move |(mut sender_randomnesses, mut receiver_preimages, aocl_indices)| { let input_triples = input_utxos .iter() .map(|utxo| { @@ -419,7 +421,7 @@ pub(crate) fn arbitrary_primitive_witness_with( let public_announcements = public_announcements.clone(); // unwrap random aocl mmr with membership proofs - MmraAndMembershipProofs::arbitrary_with(input_commitments) + MmraAndMembershipProofs::arbitrary_with((aocl_indices.into_iter().zip(input_commitments).collect_vec(), aocl_size)) .prop_flat_map(move |aocl_mmra_and_membership_proofs| { let aocl_mmra = aocl_mmra_and_membership_proofs.mmra; let aocl_membership_proofs = @@ -489,9 +491,10 @@ pub(crate) fn arbitrary_primitive_witness_with( let public_announcements = public_announcements.clone(); // unwrap random swbf mmra and membership proofs - let swbf_strategy = MmraAndMembershipProofs::arbitrary_with( - mmr_chunks.iter().map(Hash::hash).collect_vec(), - ); + let swbf_size = aocl_size / (BATCH_SIZE as u64); + let swbf_strategy = MmraAndMembershipProofs::arbitrary_with(( + mmr_chunk_indices.iter().zip(mmr_chunks.iter()).map(|(i,c)|(*i,Hash::hash(c))).collect_vec(),swbf_size + )); let mmr_chunks = mmr_chunks.clone(); swbf_strategy .prop_flat_map(move |swbf_mmr_and_paths| { diff --git a/src/util_types/mutator_set.rs b/src/util_types/mutator_set.rs index ff95c881..1429d8a3 100644 --- a/src/util_types/mutator_set.rs +++ b/src/util_types/mutator_set.rs @@ -6,7 +6,7 @@ pub mod chunk; pub mod chunk_dictionary; pub mod mmra_and_membership_proofs; pub mod ms_membership_proof; -pub mod msa_and_records; +// pub mod msa_and_records; pub mod mutator_set_accumulator; pub mod mutator_set_kernel; pub mod mutator_set_trait; diff --git a/src/util_types/mutator_set/mmra_and_membership_proofs.rs b/src/util_types/mutator_set/mmra_and_membership_proofs.rs index c4db35e9..e047222f 100644 --- a/src/util_types/mutator_set/mmra_and_membership_proofs.rs +++ b/src/util_types/mutator_set/mmra_and_membership_proofs.rs @@ -47,7 +47,7 @@ impl Arbitrary for MmraAndMembershipProofs { let index_sets = leafs .iter() .enumerate() - .map(|(enumeration_index, _leaf)| (enumeration_index, indices.pop().unwrap())) + .map(|(enumeration_index, _leaf)| (enumeration_index, indices[enumeration_index])) .map(|(enumeration_index, mmr_leaf_index)| { let (mt_node_index, peak_index) = leaf_index_to_mt_index_and_peak_index(mmr_leaf_index, total_leaf_count); @@ -100,13 +100,13 @@ impl Arbitrary for MmraAndMembershipProofs { (root_and_paths_strategies, Just(indices_and_leafs_by_tree)) .prop_map(move |(roots_and_pathses, indices_and_leafs_by_tree_)| { // sanity check for roots and pathses - for (root_and_paths, indices_and_leafs) in roots_and_pathses + for (root_and_paths, indices_and_leafs__) in roots_and_pathses .iter() .zip(indices_and_leafs_by_tree_.iter()) { let root = root_and_paths.root; for (path, (_enumeration_index, merkle_tree_node_index, _mmr_index, leaf)) in - root_and_paths.paths.iter().zip(indices_and_leafs.iter()) + root_and_paths.paths.iter().zip(indices_and_leafs__.iter()) { let mip = MerkleTreeInclusionProof { tree_height: path.len(), @@ -138,13 +138,13 @@ impl Arbitrary for MmraAndMembershipProofs { ]; // loop over all leaf indices and look up membership proof - for (root_and_paths, indices_and_leafs) in roots_and_pathses + for (root_and_paths, indices_and_leafs_) in roots_and_pathses .into_iter() .zip(indices_and_leafs_by_tree_.iter()) { let paths = root_and_paths.paths; for (path, &(enumeration_index, _merkle_tree_index, mmr_index, _leaf)) in - paths.into_iter().zip(indices_and_leafs.iter()) + paths.into_iter().zip(indices_and_leafs_.iter()) { membership_proofs[enumeration_index].authentication_path = path; membership_proofs[enumeration_index].leaf_index = mmr_index; From 7d27ba140c325361d323efc9a31904ded087dae3 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Tue, 20 Feb 2024 22:05:15 +0100 Subject: [PATCH 22/33] test: Implement `arbitrary_with` for `MsaAndRecords` Factoring out mutator set part of random witness generation. --- src/util_types/mutator_set.rs | 2 +- src/util_types/mutator_set/chunk.rs | 44 +++---- .../mutator_set/mmra_and_membership_proofs.rs | 50 ++----- src/util_types/mutator_set/msa_and_records.rs | 122 +++++++++++++----- 4 files changed, 119 insertions(+), 99 deletions(-) diff --git a/src/util_types/mutator_set.rs b/src/util_types/mutator_set.rs index 1429d8a3..ff95c881 100644 --- a/src/util_types/mutator_set.rs +++ b/src/util_types/mutator_set.rs @@ -6,7 +6,7 @@ pub mod chunk; pub mod chunk_dictionary; pub mod mmra_and_membership_proofs; pub mod ms_membership_proof; -// pub mod msa_and_records; +pub mod msa_and_records; pub mod mutator_set_accumulator; pub mod mutator_set_kernel; pub mod mutator_set_trait; diff --git a/src/util_types/mutator_set/chunk.rs b/src/util_types/mutator_set/chunk.rs index aa869bf3..3c809c42 100644 --- a/src/util_types/mutator_set/chunk.rs +++ b/src/util_types/mutator_set/chunk.rs @@ -8,7 +8,7 @@ use twenty_first::shared_math::bfield_codec::BFieldCodec; use super::shared::CHUNK_SIZE; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, GetSize, BFieldCodec, Arbitrary)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, GetSize, BFieldCodec)] pub struct Chunk { pub relative_indices: Vec, } @@ -109,32 +109,22 @@ impl Chunk { } } -// impl InvertibleBloomFilter for Chunk { -// fn increment(&mut self, location: u128) { -// self.relative_indices.push(location as u32); -// self.relative_indices.sort(); -// } - -// fn decrement(&mut self, location: u128) { -// let mut drop_index = 0; -// let mut found = false; -// for (i, b) in self.relative_indices.iter().enumerate() { -// if *b == location as u32 { -// drop_index = i; -// found = true; -// } -// } -// if found { -// self.relative_indices.remove(drop_index); -// } else { -// panic!("Cannot decrement integer that is already zero."); -// } -// } - -// fn isset(&self, location: u128) -> bool { -// self.relative_indices.contains(&(location as u32)) -// } -// } +impl<'a> Arbitrary<'a> for Chunk { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let relative_indices = (0..10) + .map(|_| u.int_in_range(0..=(CHUNK_SIZE - 1))) + .collect_vec(); + if relative_indices.iter().any(|index| index.is_err()) { + return arbitrary::Result::::Err(arbitrary::Error::IncorrectFormat); + } + Ok(Chunk { + relative_indices: relative_indices + .into_iter() + .map(|i| i.unwrap()) + .collect_vec(), + }) + } +} #[cfg(test)] mod chunk_tests { diff --git a/src/util_types/mutator_set/mmra_and_membership_proofs.rs b/src/util_types/mutator_set/mmra_and_membership_proofs.rs index e047222f..47d34fd2 100644 --- a/src/util_types/mutator_set/mmra_and_membership_proofs.rs +++ b/src/util_types/mutator_set/mmra_and_membership_proofs.rs @@ -1,13 +1,9 @@ -use std::marker::PhantomData; - use itertools::Itertools; use proptest::{ arbitrary::Arbitrary, strategy::{BoxedStrategy, Just, Strategy}, }; -use tasm_lib::twenty_first::util_types::{ - merkle_tree::MerkleTreeInclusionProof, mmr::shared_basic::leaf_index_to_mt_index_and_peak_index, -}; +use tasm_lib::twenty_first::util_types::mmr::shared_basic::leaf_index_to_mt_index_and_peak_index; use tasm_lib::{ twenty_first::util_types::mmr::{ mmr_accumulator::MmrAccumulator, mmr_membership_proof::MmrMembershipProof, @@ -30,14 +26,13 @@ impl Arbitrary for MmraAndMembershipProofs { fn arbitrary_with(parameters: Self::Parameters) -> Self::Strategy { let (indices_and_leafs, total_leaf_count) = parameters; - let indices = indices_and_leafs.iter().map(|(i, d)| *i).collect_vec(); - let max_index = indices.iter().max().copied().unwrap_or(0u64); - let leafs = indices_and_leafs.iter().map(|(i, d)| *d).collect_vec(); + let indices = indices_and_leafs.iter().map(|(i, _d)| *i).collect_vec(); + let leafs = indices_and_leafs.iter().map(|(_i, d)| *d).collect_vec(); let num_paths = leafs.len() as u64; let num_peaks = total_leaf_count.count_ones(); let mut tree_heights = vec![]; - for shift in (0..63).rev() { + for shift in (0..64).rev() { if total_leaf_count & (1u64 << shift) != 0 { tree_heights.push(shift); } @@ -99,28 +94,6 @@ impl Arbitrary for MmraAndMembershipProofs { // unwrap vector of roots and pathses (root_and_paths_strategies, Just(indices_and_leafs_by_tree)) .prop_map(move |(roots_and_pathses, indices_and_leafs_by_tree_)| { - // sanity check for roots and pathses - for (root_and_paths, indices_and_leafs__) in roots_and_pathses - .iter() - .zip(indices_and_leafs_by_tree_.iter()) - { - let root = root_and_paths.root; - for (path, (_enumeration_index, merkle_tree_node_index, _mmr_index, leaf)) in - root_and_paths.paths.iter().zip(indices_and_leafs__.iter()) - { - let mip = MerkleTreeInclusionProof { - tree_height: path.len(), - indexed_leaves: vec![( - *merkle_tree_node_index as usize ^ (1 << path.len()), - *leaf, - )], - authentication_structure: path.clone(), - _hasher: PhantomData::, - }; - assert!(mip.verify(root)); - } - } - // extract peaks let peaks = roots_and_pathses .iter() @@ -180,10 +153,10 @@ mod test { use tasm_lib::twenty_first::util_types::mmr::mmr_trait::Mmr; use test_strategy::proptest; - fn indices_and_leafs_strategy(num: usize) -> BoxedStrategy> { - vec(arb::<(u64, Digest)>(), num) + fn indices_and_leafs_strategy(max: u64, num: usize) -> BoxedStrategy> { + vec((0u64..max, arb::()), num) .prop_filter("indices must all be unique", |indices_and_leafs| { - indices_and_leafs.iter().map(|(i, l)| *i).all_unique() + indices_and_leafs.iter().map(|(i, _l)| *i).all_unique() }) .boxed() } @@ -191,14 +164,13 @@ mod test { #[proptest(cases = 20)] fn integrity( #[strategy(1usize..10)] _num_paths: usize, - #[strategy(indices_and_leafs_strategy(#_num_paths))] indices_and_leafs: Vec<(u64, Digest)>, - #[filter(#indices_and_leafs.iter().map(|(i,_l)|*i).max().unwrap() < #_total_leaf_count)] - #[strategy(0..u64::MAX)] - _total_leaf_count: u64, + #[strategy(0..u64::MAX)] _total_leaf_count: u64, + #[strategy(indices_and_leafs_strategy(#_total_leaf_count, #_num_paths))] + indices_and_leafs: Vec<(u64, Digest)>, #[strategy(MmraAndMembershipProofs::arbitrary_with((#indices_and_leafs, #_total_leaf_count)))] mmra_and_membership_proofs: MmraAndMembershipProofs, ) { - for ((index, leaf), mp) in indices_and_leafs + for ((_index, leaf), mp) in indices_and_leafs .into_iter() .zip(mmra_and_membership_proofs.membership_proofs) { diff --git a/src/util_types/mutator_set/msa_and_records.rs b/src/util_types/mutator_set/msa_and_records.rs index f6d0972f..96c6a2d1 100644 --- a/src/util_types/mutator_set/msa_and_records.rs +++ b/src/util_types/mutator_set/msa_and_records.rs @@ -9,7 +9,10 @@ use proptest_arbitrary_interop::arb; use tasm_lib::{ twenty_first::util_types::{ algebraic_hasher::AlgebraicHasher, - mmr::{mmr_membership_proof::MmrMembershipProof, mmr_trait::Mmr}, + mmr::{ + mmr_accumulator::MmrAccumulator, mmr_membership_proof::MmrMembershipProof, + mmr_trait::Mmr, + }, }, Digest, }; @@ -39,25 +42,81 @@ pub struct MsaAndRecords { pub membership_proofs: Vec, } +fn can_remove_verbose( + mutator_set_kernel: &MutatorSetKernel>, + removal_record: &RemovalRecord, +) -> bool { + let mut have_absent_index = false; + if !removal_record.validate(mutator_set_kernel) { + panic!("unsynchronized"); + return false; + } + + for inserted_index in removal_record.absolute_indices.to_vec().into_iter() { + // determine if inserted index lives in active window + let active_window_start = (mutator_set_kernel.aocl.count_leaves() / BATCH_SIZE as u64) + as u128 + * CHUNK_SIZE as u128; + if inserted_index < active_window_start { + let inserted_index_chunkidx = (inserted_index / CHUNK_SIZE as u128) as u64; + if let Some((_mmr_mp, chunk)) = removal_record + .target_chunks + .dictionary + .get(&inserted_index_chunkidx) + { + let relative_index = (inserted_index % CHUNK_SIZE as u128) as u32; + if !chunk.contains(relative_index) { + have_absent_index = true; + break; + } + } + } else { + let relative_index = (inserted_index - active_window_start) as u32; + if !mutator_set_kernel.swbf_active.contains(relative_index) { + have_absent_index = true; + break; + } + } + } + + assert!(have_absent_index, "no indices absent!"); + + have_absent_index +} + impl MsaAndRecords { pub fn verify(&self, items: &[Digest]) -> bool { - self.removal_records + let all_removal_records_can_remove = self + .removal_records .iter() - .all(|rr| self.mutator_set_accumulator.kernel.can_remove(rr)) - && self - .membership_proofs - .iter() - .zip_eq(items.iter()) - .all(|(mp, item)| self.mutator_set_accumulator.verify(*item, mp)) + .all(|rr| can_remove_verbose(&self.mutator_set_accumulator.kernel, rr)); + assert!( + all_removal_records_can_remove, + "Some removal records cannot be removed!" + ); + let all_membership_proofs_are_valid = self + .membership_proofs + .iter() + .zip_eq(items.iter()) + .all(|(mp, item)| self.mutator_set_accumulator.verify(*item, mp)); + assert!( + all_membership_proofs_are_valid, + "some membership proofs are not valid!" + ); + all_removal_records_can_remove && all_membership_proofs_are_valid } } impl Arbitrary for MsaAndRecords { - type Parameters = (Vec<(Digest, Digest, Digest)>, Vec<(Digest, Digest, Digest)>); + type Parameters = ( + Vec<(Digest, Digest, Digest)>, + Vec<(Digest, Digest, Digest)>, + u64, + ); type Strategy = BoxedStrategy; fn arbitrary_with(parameters: Self::Parameters) -> Self::Strategy { - let (removables, addeds) = parameters; + let (removables, addeds, aocl_size) = parameters; let removable_commitments = removables .iter() @@ -67,8 +126,11 @@ impl Arbitrary for MsaAndRecords { .collect_vec(); // unwrap aocl indices - let aocl_size = (1u64 << 33); - vec(0u64..aocl_size, addeds.len()).prop_flat_map(|aocl_indices| { + vec(0u64..aocl_size, addeds.len()).prop_flat_map(move |aocl_indices| { + + // prepare unwrap + let removables = removables.clone(); + let addeds = addeds.clone(); // unwrap random aocl mmr with membership proofs MmraAndMembershipProofs::arbitrary_with(( @@ -117,9 +179,11 @@ impl Arbitrary for MsaAndRecords { let addeds = addeds.clone(); // unwrap random mmr_chunks - mmr_chunk_indices.into_iter() - .map(|index| (Just(index), arb::())) - .collect_vec() + let mmr_indices_and_chunks_strategy = mmr_chunk_indices.iter() + .map(|index| (Just(*index), arb::())) + .collect_vec(); + let mmr_chunk_indices = mmr_chunk_indices.clone(); + mmr_indices_and_chunks_strategy .prop_flat_map(move |mmr_indices_and_chunks| { // prepare to unwrap let aocl_mmra = aocl_mmra.clone(); @@ -148,9 +212,8 @@ impl Arbitrary for MsaAndRecords { swbf_membership_proofs .into_iter() .zip(mmr_indices_and_chunks.iter().cloned()), - ) + ).map(|(mmr_chunk_index, (swbf_membership_proof, (_mmr_index, chunk)))|(mmr_chunk_index, (swbf_membership_proof, chunk))) .collect(); - println!("made chunk dictionary with indices: {} where aocl mmra leaf count is {}", chunk_dictionary.keys().join(", "), aocl_mmra.count_leaves()); let personalized_chunk_dictionaries = all_index_sets .iter() .map(|index_set| { @@ -167,25 +230,17 @@ impl Arbitrary for MsaAndRecords { ChunkDictionary::new( chunk_indices .iter() + .filter(|chunk_index| {chunk_dictionary.contains_key(*chunk_index,)}) .map(|chunk_index| { ( - *chunk_index, + chunk_index, chunk_dictionary .get( chunk_index, - ) - ).filter(|r|r.is_some()).cloned() - }) - .collect::, - Chunk, - ), - >>( - ), + ).unwrap() + ) + }).map(|(chunk_index, (membership_proof, chunk))| (*chunk_index, (membership_proof.clone(), chunk.clone()))) + .collect::,Chunk)>>(), ) }) .collect_vec(); @@ -208,6 +263,7 @@ impl Arbitrary for MsaAndRecords { }, ) .collect_vec(); + let removal_records = all_index_sets .iter() .zip(personalized_chunk_dictionaries.iter()) @@ -241,6 +297,7 @@ impl Arbitrary for MsaAndRecords { swbf_active: active_window, }, }; + MsaAndRecords { mutator_set_accumulator, addition_records: addition_records.clone(), @@ -274,11 +331,12 @@ mod test { fn msa_and_records_is_valid( #[strategy(0usize..10)] _num_removals: usize, #[strategy(0usize..10)] _num_additions: usize, + #[strategy(0u64..=u64::MAX)] _aocl_size: u64, #[strategy(vec((arb::(), arb::(), arb::()), #_num_removals))] removables: Vec<(Digest, Digest, Digest)>, #[strategy(vec((arb::(), arb::(), arb::()), #_num_additions))] _addeds: Vec<(Digest, Digest, Digest)>, - #[strategy(MsaAndRecords::arbitrary_with((#removables, #_addeds)))] + #[strategy(MsaAndRecords::arbitrary_with((#removables, #_addeds, #_aocl_size)))] msa_and_records: MsaAndRecords, ) { prop_assert!(msa_and_records.verify( From 5b60c62476c7bfa01d1572a114a63c217c662077 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Wed, 21 Feb 2024 16:35:30 +0100 Subject: [PATCH 23/33] test: Fix `arbitrary_with` for `MsaAndRecords` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Thorkil Værge --- src/util_types/mutator_set/msa_and_records.rs | 436 ++++++++---------- 1 file changed, 199 insertions(+), 237 deletions(-) diff --git a/src/util_types/mutator_set/msa_and_records.rs b/src/util_types/mutator_set/msa_and_records.rs index 96c6a2d1..1029822c 100644 --- a/src/util_types/mutator_set/msa_and_records.rs +++ b/src/util_types/mutator_set/msa_and_records.rs @@ -3,32 +3,30 @@ use std::collections::HashMap; use itertools::Itertools; use proptest::{ arbitrary::Arbitrary, - strategy::{BoxedStrategy, Just, Strategy}, + strategy::{BoxedStrategy, Strategy}, }; use proptest_arbitrary_interop::arb; use tasm_lib::{ twenty_first::util_types::{ algebraic_hasher::AlgebraicHasher, - mmr::{ - mmr_accumulator::MmrAccumulator, mmr_membership_proof::MmrMembershipProof, - mmr_trait::Mmr, - }, + mmr::{mmr_membership_proof::MmrMembershipProof, mmr_trait::Mmr}, }, Digest, }; -use crate::models::blockchain::shared::Hash; +use crate::{ + models::blockchain::shared::Hash, util_types::mutator_set::mutator_set_trait::MutatorSet, +}; use super::{ active_window::ActiveWindow, - addition_record::AdditionRecord, chunk::Chunk, chunk_dictionary::ChunkDictionary, mmra_and_membership_proofs::MmraAndMembershipProofs, ms_membership_proof::MsMembershipProof, mutator_set_accumulator::MutatorSetAccumulator, mutator_set_kernel::{get_swbf_indices, MutatorSetKernel}, - mutator_set_trait::{commit, MutatorSet}, + mutator_set_trait::commit, removal_record::{AbsoluteIndexSet, RemovalRecord}, shared::{BATCH_SIZE, CHUNK_SIZE}, }; @@ -37,59 +35,16 @@ use proptest::collection::vec; #[derive(Debug, Clone)] pub struct MsaAndRecords { pub mutator_set_accumulator: MutatorSetAccumulator, - pub addition_records: Vec, pub removal_records: Vec, pub membership_proofs: Vec, } -fn can_remove_verbose( - mutator_set_kernel: &MutatorSetKernel>, - removal_record: &RemovalRecord, -) -> bool { - let mut have_absent_index = false; - if !removal_record.validate(mutator_set_kernel) { - panic!("unsynchronized"); - return false; - } - - for inserted_index in removal_record.absolute_indices.to_vec().into_iter() { - // determine if inserted index lives in active window - let active_window_start = (mutator_set_kernel.aocl.count_leaves() / BATCH_SIZE as u64) - as u128 - * CHUNK_SIZE as u128; - if inserted_index < active_window_start { - let inserted_index_chunkidx = (inserted_index / CHUNK_SIZE as u128) as u64; - if let Some((_mmr_mp, chunk)) = removal_record - .target_chunks - .dictionary - .get(&inserted_index_chunkidx) - { - let relative_index = (inserted_index % CHUNK_SIZE as u128) as u32; - if !chunk.contains(relative_index) { - have_absent_index = true; - break; - } - } - } else { - let relative_index = (inserted_index - active_window_start) as u32; - if !mutator_set_kernel.swbf_active.contains(relative_index) { - have_absent_index = true; - break; - } - } - } - - assert!(have_absent_index, "no indices absent!"); - - have_absent_index -} - impl MsaAndRecords { pub fn verify(&self, items: &[Digest]) -> bool { let all_removal_records_can_remove = self .removal_records .iter() - .all(|rr| can_remove_verbose(&self.mutator_set_accumulator.kernel, rr)); + .all(|rr| self.mutator_set_accumulator.kernel.can_remove(rr)); assert!( all_removal_records_can_remove, "Some removal records cannot be removed!" @@ -108,211 +63,221 @@ impl MsaAndRecords { } impl Arbitrary for MsaAndRecords { - type Parameters = ( - Vec<(Digest, Digest, Digest)>, - Vec<(Digest, Digest, Digest)>, - u64, - ); + /// Parameters: + /// - removables : Vec<(Digest, Digest, Digest)> where each triple contains: + /// - an item + /// - sender randomness + /// - receiver preimage + /// - aocl_size : u64 which counts the total number of items added to the mutator + /// set. + type Parameters = (Vec<(Digest, Digest, Digest)>, u64); type Strategy = BoxedStrategy; fn arbitrary_with(parameters: Self::Parameters) -> Self::Strategy { - let (removables, addeds, aocl_size) = parameters; + let (removables, aocl_size) = parameters; + // compute the canonical commitments that were added to the aocl at some prior, + // as this is information needed to produce membership proofs for the items that + // are going to be removed let removable_commitments = removables .iter() .map(|(item, sender_randomness, receiver_preimage)| { - commit(*item, *sender_randomness, Hash::hash(receiver_preimage)) + commit(*item, *sender_randomness, receiver_preimage.hash::()) }) .collect_vec(); - // unwrap aocl indices - vec(0u64..aocl_size, addeds.len()).prop_flat_map(move |aocl_indices| { + let removable_commitments = removable_commitments.clone(); - // prepare unwrap - let removables = removables.clone(); - let addeds = addeds.clone(); + // sample random aocl indices + vec(0u64..aocl_size, removables.len()) + .prop_flat_map(move |removed_aocl_indices| { - // unwrap random aocl mmr with membership proofs - MmraAndMembershipProofs::arbitrary_with(( - aocl_indices.iter().zip(removable_commitments - .iter()) - .map(|(i, ar)| (*i, ar.canonical_commitment)) - .collect_vec(),aocl_size)) - .prop_flat_map(move |aocl_mmra_and_membership_proofs| { - let aocl_mmra = aocl_mmra_and_membership_proofs.mmra; - let aocl_membership_proofs = aocl_mmra_and_membership_proofs.membership_proofs; - let all_index_sets = removables - .iter() - .zip(aocl_membership_proofs.iter()) - .map( - |((item, sender_randomness, receiver_preimage), authentication_path)| { - get_swbf_indices( - *item, - *sender_randomness, - *receiver_preimage, - authentication_path.leaf_index, - ) - }, - ) - .collect_vec(); - let mut all_indices = all_index_sets.iter().flatten().cloned().collect_vec(); - all_indices.sort(); - let mut all_chunk_indices = all_indices - .iter() - .map(|index| *index / (CHUNK_SIZE as u128)) - .map(|index| index as u64) - .collect_vec(); - all_chunk_indices.sort(); - all_chunk_indices.dedup(); - let mmr_chunk_indices = all_chunk_indices - .iter() - .cloned() - .filter(|ci| *ci < aocl_mmra.count_leaves() / (BATCH_SIZE as u64)) - .collect_vec(); - - // prepare to unwrap - let aocl_mmra = aocl_mmra.clone(); - let mmr_chunk_indices = mmr_chunk_indices.clone(); - let all_index_sets = all_index_sets.clone(); - let aocl_membership_proofs = aocl_membership_proofs.clone(); + // prepare unwrap let removables = removables.clone(); - let addeds = addeds.clone(); - // unwrap random mmr_chunks - let mmr_indices_and_chunks_strategy = mmr_chunk_indices.iter() - .map(|index| (Just(*index), arb::())) - .collect_vec(); - let mmr_chunk_indices = mmr_chunk_indices.clone(); - mmr_indices_and_chunks_strategy - .prop_flat_map(move |mmr_indices_and_chunks| { - // prepare to unwrap - let aocl_mmra = aocl_mmra.clone(); - let mmr_chunk_indices = mmr_chunk_indices.clone(); - let all_index_sets = all_index_sets.clone(); - let aocl_membership_proofs = aocl_membership_proofs.clone(); - let removables = removables.clone(); - let addeds = addeds.clone(); - let swbf_mmr_leaf_count = aocl_mmra.count_leaves() / (BATCH_SIZE as u64); + // bundle all indices and leafs for pseudorandom aocl + let all_aocl_indices_and_leafs = removed_aocl_indices.into_iter().zip(removable_commitments.iter().map(|ar|ar.canonical_commitment)).collect_vec(); - // unwrap random swbf mmra and membership proofs - let swbf_strategy = MmraAndMembershipProofs::arbitrary_with(( - mmr_indices_and_chunks.iter().map(|(i, c)| (*i, Hash::hash(c))).collect_vec(), swbf_mmr_leaf_count - )); - let mmr_indices_and_chunks = mmr_indices_and_chunks.clone(); - swbf_strategy - .prop_flat_map(move |swbf_mmr_and_paths| { - let swbf_mmra = swbf_mmr_and_paths.mmra; - let swbf_membership_proofs = swbf_mmr_and_paths.membership_proofs; + // unwrap random aocl mmr with membership proofs + MmraAndMembershipProofs::arbitrary_with((all_aocl_indices_and_leafs, aocl_size)) + .prop_flat_map(move |aocl_mmra_and_membership_proofs| { + let aocl_mmra = aocl_mmra_and_membership_proofs.mmra; + let aocl_membership_proofs = aocl_mmra_and_membership_proofs.membership_proofs; - let chunk_dictionary: HashMap, Chunk)> = - mmr_chunk_indices - .iter() - .cloned() - .zip( - swbf_membership_proofs - .into_iter() - .zip(mmr_indices_and_chunks.iter().cloned()), - ).map(|(mmr_chunk_index, (swbf_membership_proof, (_mmr_index, chunk)))|(mmr_chunk_index, (swbf_membership_proof, chunk))) - .collect(); - let personalized_chunk_dictionaries = all_index_sets - .iter() - .map(|index_set| { - let mut is = index_set - .iter() - .map(|index| *index / (BATCH_SIZE as u128)) - .map(|index| index as u64) - .collect_vec(); - is.sort(); - is.dedup(); - is - }) - .map(|chunk_indices| { - ChunkDictionary::new( - chunk_indices + // assemble all indices of all removal records + let all_index_sets = removables + .iter() + .zip(aocl_membership_proofs.iter()) + .map( + |((item, sender_randomness, receiver_preimage), membership_proof)| { + get_swbf_indices( + *item, + *sender_randomness, + *receiver_preimage, + membership_proof.leaf_index, + ) + }, + ) + .collect_vec(); + let mut all_indices = all_index_sets.iter().flatten().cloned().collect_vec(); + all_indices.sort(); + + // assemble all chunk indices + let mut all_chunk_indices = all_indices + .iter() + .map(|index| *index / (CHUNK_SIZE as u128)) + .map(|index| index as u64) + .collect_vec(); + all_chunk_indices.sort(); + all_chunk_indices.dedup(); + + // filter by swbf mmr size + let swbf_mmr_size = aocl_mmra.count_leaves() / (BATCH_SIZE as u64); + let mmr_chunk_indices = all_chunk_indices + .iter() + .cloned() + .filter(|ci| *ci < swbf_mmr_size) + .collect_vec(); + + // prepare to unwrap + let aocl_mmra = aocl_mmra.clone(); + let swbf_chunk_indices = mmr_chunk_indices.clone(); + let all_index_sets = all_index_sets.clone(); + let aocl_membership_proofs = aocl_membership_proofs.clone(); + let removables = removables.clone(); + + // unwrap random swbf chunks + swbf_chunk_indices.iter() + .map(|_| arb::()) + .collect_vec() + .prop_flat_map(move |swbf_chunks| { + // prepare input to pseudorandom mmr generator + let swbf_leafs = swbf_chunks.iter().map(Hash::hash).collect_vec(); + let swbf_indices_and_leafs = swbf_chunk_indices.iter().copied().zip(swbf_leafs.iter().copied()).collect_vec(); + + // prepare to unwrap + let aocl_mmra = aocl_mmra.clone(); + let swbf_chunk_indices = swbf_chunk_indices.clone(); + let all_index_sets = all_index_sets.clone(); + let aocl_membership_proofs = aocl_membership_proofs.clone(); + let removables = removables.clone(); + let swbf_mmr_leaf_count = aocl_mmra.count_leaves() / (BATCH_SIZE as u64); + + // unwrap random swbf mmra and membership proofs + MmraAndMembershipProofs::arbitrary_with(( + swbf_indices_and_leafs, swbf_mmr_leaf_count + )).prop_flat_map(move |swbf_mmr_and_paths| { + let swbf_mmra = swbf_mmr_and_paths.mmra; + let swbf_membership_proofs = swbf_mmr_and_paths.membership_proofs; + + let universal_chunk_dictionary: HashMap, Chunk)> = + swbf_chunk_indices .iter() - .filter(|chunk_index| {chunk_dictionary.contains_key(*chunk_index,)}) - .map(|chunk_index| { - ( - chunk_index, - chunk_dictionary - .get( - chunk_index, - ).unwrap() + .cloned() + .zip( + swbf_membership_proofs + .into_iter() + .zip(swbf_chunks.iter().cloned()) ) - }).map(|(chunk_index, (membership_proof, chunk))| (*chunk_index, (membership_proof.clone(), chunk.clone()))) - .collect::,Chunk)>>(), + .collect(); + let personalized_chunk_dictionaries = all_index_sets + .iter() + .map(|index_set| { + let mut is = index_set + .iter() + .map(|index| *index / (CHUNK_SIZE as u128)) + .map(|index| index as u64) + .collect_vec(); + is.sort(); + is.dedup(); + is + }) + .map(|chunk_indices| { + ChunkDictionary::new( + chunk_indices + .iter() + .filter(|chunk_index| **chunk_index < swbf_mmr_size) + .map(|chunk_index| { + ( + chunk_index, + universal_chunk_dictionary + .get( + chunk_index, + ).unwrap_or_else(|| panic!("Could not find chunk index {chunk_index} in universal chunk dictionary")) + ) + }) + .map(|(chunk_index, (membership_proof, chunk))| (*chunk_index, (membership_proof.clone(), chunk.clone()))) + .collect::,Chunk)>>(), + ) + }) + .collect_vec(); + let membership_proofs = removables + .clone() + .iter() + .zip(aocl_membership_proofs.iter()) + .zip(personalized_chunk_dictionaries.iter()) + .map(|(((item, sender_randomness, receiver_preimage), aocl_auth_path), target_chunks)| { + let leaf = commit(*item, *sender_randomness, receiver_preimage.hash::()).canonical_commitment; + assert!(aocl_auth_path.verify(&aocl_mmra.get_peaks(), leaf, aocl_mmra.count_leaves()).0); + println!("verified AOCL membership of leaf {leaf} at index {}", aocl_auth_path.leaf_index); + (((item, sender_randomness, receiver_preimage), aocl_auth_path), target_chunks) + }) + .map( + |( + ((_item, sender_randomness, receiver_preimage), aocl_auth_path), + target_chunks, + )| { + MsMembershipProof { + sender_randomness: *sender_randomness, + receiver_preimage: *receiver_preimage, + auth_path_aocl: aocl_auth_path.clone(), + target_chunks: target_chunks.clone(), + } + }, ) - }) - .collect_vec(); - let membership_proofs = removables - .clone() - .iter() - .zip(aocl_membership_proofs.iter()) - .zip(personalized_chunk_dictionaries.iter()) - .map( - |( - ((_item, sender_randomness, receiver_preimage), auth_path), - target_chunks, - )| { - MsMembershipProof { - sender_randomness: *sender_randomness, - receiver_preimage: *receiver_preimage, - auth_path_aocl: auth_path.clone(), - target_chunks: target_chunks.clone(), - } - }, - ) - .collect_vec(); + .collect_vec(); - let removal_records = all_index_sets - .iter() - .zip(personalized_chunk_dictionaries.iter()) - .map(|(index_set, target_chunks)| RemovalRecord { - absolute_indices: AbsoluteIndexSet::new(index_set), - target_chunks: target_chunks.clone(), - }) - .collect_vec(); + let removal_records = all_index_sets + .iter() + .zip(personalized_chunk_dictionaries.iter()) + .map(|(index_set, target_chunks)| RemovalRecord { + absolute_indices: AbsoluteIndexSet::new(index_set), + target_chunks: target_chunks.clone(), + }) + .collect_vec(); - let addition_records = addeds - .iter() - .map(|(item, sender_randomness, receiver_preimage)| { - commit(*item, *sender_randomness, Hash::hash(receiver_preimage)) - }) - .collect_vec(); + // prepare to unwrap + let aocl_mmra = aocl_mmra.clone(); + let swbf_mmra = swbf_mmra.clone(); + let removal_records = removal_records.clone(); + let membership_proofs = membership_proofs.clone(); - // prepare to unwrap - let aocl_mmra = aocl_mmra.clone(); - let swbf_mmra = swbf_mmra.clone(); - let addition_records = addition_records.clone(); - let removal_records = removal_records.clone(); - let membership_proofs = membership_proofs.clone(); + // unwrap random active window + arb::() + .prop_map(move |active_window| { + let mutator_set_accumulator = MutatorSetAccumulator { + kernel: MutatorSetKernel { + aocl: aocl_mmra.clone(), + swbf_inactive: swbf_mmra.clone(), + swbf_active: active_window, + }, + }; - // unwrap random active window - arb::() - .prop_map(move |active_window| { - let mutator_set_accumulator = MutatorSetAccumulator { - kernel: MutatorSetKernel { - aocl: aocl_mmra.clone(), - swbf_inactive: swbf_mmra.clone(), - swbf_active: active_window, - }, - }; - - MsaAndRecords { - mutator_set_accumulator, - addition_records: addition_records.clone(), - removal_records: removal_records.clone(), - membership_proofs: membership_proofs.clone(), - } - }) - .boxed() - }) - .boxed() - }) - .boxed() + MsaAndRecords { + mutator_set_accumulator, + removal_records: removal_records.clone(), + membership_proofs: membership_proofs.clone(), + } + }) + .boxed() + }) + .boxed() + }) + .boxed() + }) + .boxed() }) .boxed() - }).boxed() } } @@ -327,16 +292,13 @@ mod test { use super::MsaAndRecords; - #[proptest] + #[proptest(cases = 1)] fn msa_and_records_is_valid( #[strategy(0usize..10)] _num_removals: usize, - #[strategy(0usize..10)] _num_additions: usize, #[strategy(0u64..=u64::MAX)] _aocl_size: u64, #[strategy(vec((arb::(), arb::(), arb::()), #_num_removals))] removables: Vec<(Digest, Digest, Digest)>, - #[strategy(vec((arb::(), arb::(), arb::()), #_num_additions))] - _addeds: Vec<(Digest, Digest, Digest)>, - #[strategy(MsaAndRecords::arbitrary_with((#removables, #_addeds, #_aocl_size)))] + #[strategy(MsaAndRecords::arbitrary_with((#removables, #_aocl_size)))] msa_and_records: MsaAndRecords, ) { prop_assert!(msa_and_records.verify( From 16c476f1cda9ac2afac3ddeb71e7cc8724107a9b Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Wed, 21 Feb 2024 18:00:58 +0100 Subject: [PATCH 24/33] wip: Implement arbitrary for `PrimitiveWitness` for `Transaction` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Thorkil Værge --- .../transaction/primitive_witness.rs | 512 ++++++------------ src/models/blockchain/transaction/validity.rs | 14 +- src/util_types/mutator_set/msa_and_records.rs | 1 - 3 files changed, 166 insertions(+), 361 deletions(-) diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index 33f6d29b..326ec122 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -33,14 +33,8 @@ use crate::{ native_currency_program, NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST, }, util_types::mutator_set::{ - active_window::ActiveWindow, - chunk::Chunk, - chunk_dictionary::ChunkDictionary, - mmra_and_membership_proofs::MmraAndMembershipProofs, - mutator_set_kernel::{get_swbf_indices, MutatorSetKernel}, + msa_and_records::MsaAndRecords, mutator_set_trait::{commit, MutatorSet}, - removal_record::{AbsoluteIndexSet, RemovalRecord}, - shared::{BATCH_SIZE, CHUNK_SIZE}, }, Hash, }; @@ -141,15 +135,6 @@ impl PrimitiveWitness { .collect_vec(), ); - // sanity check - // for ((leaf, mt_index, _original_index), auth_path) in - // leafs_and_mt_indices.iter().zip(authentication_paths.iter()) - // { - // assert!(merkle_verify_tester_helper::( - // root, *mt_index, auth_path, *leaf - // )); - // } - // update peaks list peaks.push(root); @@ -266,15 +251,48 @@ impl Arbitrary for PrimitiveWitness { fn arbitrary_with(parameters: Self::Parameters) -> Self::Strategy { let (num_inputs, num_outputs, num_public_announcements) = parameters; + + // unwrap: + // - lock script preimages (inputs) + // - amounts (inputs) + // - lock script preimages (outputs) + // - amounts (outputs) + // - public announcements + // - fee + // - coinbase (option) ( - vec(arb::(), num_inputs), - vec(arb::(), num_outputs), + vec(arb::(), num_inputs), + vec(arb::(), num_inputs), + vec(arb::(), num_outputs), + vec(arb::(), num_outputs), vec(arb::(), num_public_announcements), arb::(), arb::>(), ) .prop_flat_map( - |(input_utxos, mut output_utxos, public_announcements, mut fee, maybe_coinbase)| { + |( + input_lock_script_preimages, + input_amounts, + output_lock_script_preimages, + mut output_amounts, + public_announcements, + mut fee, + maybe_coinbase, + )| { + let input_utxos = input_lock_script_preimages + .into_iter() + .zip(input_amounts) + .map(|(lock_script_preimage, amount)| { + Utxo::new( + generation_address::SpendingKey::derive_from_seed( + lock_script_preimage, + ) + .to_address() + .lock_script(), + amount.to_native_coins(), + ) + }) + .collect_vec(); let total_inputs = input_utxos .iter() .flat_map(|utxo| utxo.coins.clone()) @@ -284,46 +302,36 @@ impl Arbitrary for PrimitiveWitness { .filter(|r| r.is_ok()) .map(|r| *r.unwrap()) .sum::(); - let mut total_outputs = output_utxos - .iter() - .flat_map(|utxo| utxo.coins.clone()) - .filter(|coin| coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST) - .map(|coin| coin.state) - .map(|state| NeptuneCoins::decode(&state)) - .filter(|r| r.is_ok()) - .map(|r| *r.unwrap()) - .sum::(); + let mut total_outputs = output_amounts.iter().cloned().sum::(); let mut some_coinbase = match maybe_coinbase { Some(coinbase) => coinbase, None => NeptuneCoins::zero(), }; while total_inputs < total_outputs + fee + some_coinbase { - for output_utxo in output_utxos.iter_mut() { - for coin in output_utxo.coins.iter_mut() { - if coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST { - let mut amount = *NeptuneCoins::decode(&coin.state).unwrap(); - amount.div_two(); - coin.state = amount.encode(); - } - } + for amount in output_amounts.iter_mut() { + amount.div_two(); } if let Some(mut coinbase) = maybe_coinbase { coinbase.div_two(); some_coinbase.div_two(); } fee.div_two(); - total_outputs = output_utxos - .iter() - .flat_map(|utxo| utxo.coins.clone()) - .filter(|coin| { - coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST - }) - .map(|coin| coin.state) - .map(|state| NeptuneCoins::decode(&state)) - .filter(|r| r.is_ok()) - .map(|r| *r.unwrap()) - .sum::(); + total_outputs = output_amounts.iter().cloned().sum::(); } + let output_utxos = output_lock_script_preimages + .into_iter() + .zip(output_amounts) + .map(|(lock_script_preimage, amount)| { + Utxo::new( + generation_address::SpendingKey::derive_from_seed( + lock_script_preimage, + ) + .to_address() + .lock_script(), + amount.to_native_coins(), + ) + }) + .collect_vec(); arbitrary_primitive_witness_with( &input_utxos, &output_utxos, @@ -350,9 +358,30 @@ pub(crate) fn arbitrary_primitive_witness_with( let output_utxos = output_utxos.to_vec(); let public_announcements = public_announcements.to_vec(); - // unwrap spending key seeds - vec(arb::(), num_inputs) - .prop_flat_map(move |mut spending_key_seeds| { + // unwrap: + // - spending key seeds + // - sender randomness (input) + // - receiver preimage (input) + // - sender randomness (output) + // - receiver preimage (output) + // - aocl size + ( + vec(arb::(), num_inputs), + vec(arb::(), num_inputs), + vec(arb::(), num_inputs), + vec(arb::(), num_outputs), + vec(arb::(), num_outputs), + 0u64..=u64::MAX, + ) + .prop_flat_map( + move |( + mut spending_key_seeds, + mut sender_randomnesses_input, + mut receiver_preimages_input, + sender_randomnesses_output, + receiver_preimages_output, + aocl_size, + )| { let sender_spending_keys = (0..num_inputs) .map(|_| { generation_address::SpendingKey::derive_from_seed( @@ -376,332 +405,101 @@ pub(crate) fn arbitrary_primitive_witness_with( // prepare to unwrap let lock_script_witnesses = lock_script_witnesses.clone(); let input_lock_scripts = input_lock_scripts.clone(); + let input_utxos = input_utxos.clone(); + let output_utxos = output_utxos.clone(); + let public_announcements = public_announcements.clone(); + + let input_triples = input_utxos + .iter() + .map(|utxo| { + ( + Hash::hash(utxo), + sender_randomnesses_input.pop().unwrap(), + receiver_preimages_input.pop().unwrap(), + ) + }) + .collect_vec(); // prepare to unwrap - let lock_script_witnesses = lock_script_witnesses.clone(); let input_lock_scripts = input_lock_scripts.clone(); + let lock_script_witnesses = lock_script_witnesses.clone(); + let input_triples = input_triples.clone(); let input_utxos = input_utxos.clone(); let output_utxos = output_utxos.clone(); let public_announcements = public_announcements.clone(); - // unwrap random sender randomness and receiver preimages - let sender_randomnesses_strategy = vec(arb::(), num_inputs); - let receiver_preimages_strategy = vec(arb::(), num_inputs); - let aocl_size = 1u64<<33; - let aocl_indices_strategy = vec(0u64..aocl_size, num_inputs); - (sender_randomnesses_strategy, receiver_preimages_strategy, aocl_indices_strategy) - .prop_flat_map(move |(mut sender_randomnesses, mut receiver_preimages, aocl_indices)| { - let input_triples = input_utxos + // unwrap random mutator set accumulator with membership proofs and removal records + MsaAndRecords::arbitrary_with((input_triples, aocl_size)) + .prop_map(move |msa_and_records| { + let mutator_set_accumulator = msa_and_records.mutator_set_accumulator; + let input_membership_proofs = msa_and_records.membership_proofs; + let input_removal_records = msa_and_records.removal_records; + + let type_scripts = vec![TypeScript::new(native_currency_program())]; + + // prepare to unwrap + let input_lock_scripts = input_lock_scripts.clone(); + let input_utxos = input_utxos.clone(); + let input_removal_records = input_removal_records.clone(); + let input_membership_proofs = input_membership_proofs.clone(); + let type_scripts = type_scripts.clone(); + let lock_script_witnesses = lock_script_witnesses.clone(); + let output_utxos = output_utxos.clone(); + let public_announcements = public_announcements.clone(); + let mut sender_randomnesses_output = sender_randomnesses_output.clone(); + let mut receiver_preimages_output = receiver_preimages_output.clone(); + + let output_commitments = output_utxos .iter() .map(|utxo| { - ( - Hash::hash(utxo), - sender_randomnesses.pop().unwrap(), - receiver_preimages.pop().unwrap(), - ) - }) - .collect_vec(); - let input_commitments = input_triples - .iter() - .map(|(item, sender_randomness, receiver_preimage)| { commit( - *item, - *sender_randomness, - Hash::hash(receiver_preimage), + Hash::hash(utxo), + sender_randomnesses_output.pop().unwrap(), + receiver_preimages_output.pop().unwrap().hash::(), ) }) - .map(|ar| ar.canonical_commitment) .collect_vec(); // prepare to unwrap let input_lock_scripts = input_lock_scripts.clone(); - let lock_script_witnesses = lock_script_witnesses.clone(); let input_utxos = input_utxos.clone(); + let input_removal_records = input_removal_records.clone(); + let input_membership_proofs = input_membership_proofs.clone(); + let type_scripts = type_scripts.clone(); + let lock_script_witnesses = lock_script_witnesses.clone(); let output_utxos = output_utxos.clone(); let public_announcements = public_announcements.clone(); - // unwrap random aocl mmr with membership proofs - MmraAndMembershipProofs::arbitrary_with((aocl_indices.into_iter().zip(input_commitments).collect_vec(), aocl_size)) - .prop_flat_map(move |aocl_mmra_and_membership_proofs| { - let aocl_mmra = aocl_mmra_and_membership_proofs.mmra; - let aocl_membership_proofs = - aocl_mmra_and_membership_proofs.membership_proofs; - let all_index_sets = input_triples - .iter() - .zip(aocl_membership_proofs.iter()) - .map( - |( - (item, sender_randomness, receiver_preimage), - authentication_path, - )| { - get_swbf_indices( - *item, - *sender_randomness, - *receiver_preimage, - authentication_path.leaf_index, - ) - }, - ) - .collect_vec(); - let mut all_indices = - all_index_sets.iter().flatten().cloned().collect_vec(); - all_indices.sort(); - let mut all_chunk_indices = all_indices - .iter() - .map(|index| index / (CHUNK_SIZE as u128)) - .map(|index| index as u64) - .collect_vec(); - all_chunk_indices.dedup(); - let mmr_chunk_indices = all_chunk_indices - .iter() - .cloned() - .filter(|ci| { - *ci < aocl_mmra.count_leaves() / (BATCH_SIZE as u64) - }) - .collect_vec(); - - // prepare to unwrap - let input_lock_scripts = input_lock_scripts.clone(); - let lock_script_witnesses = lock_script_witnesses.clone(); - let aocl_mmra = aocl_mmra.clone(); - let mmr_chunk_indices = mmr_chunk_indices.clone(); - let all_index_sets = all_index_sets.clone(); - let input_triples = input_triples.clone(); - let aocl_membership_proofs = aocl_membership_proofs.clone(); - let input_utxos = input_utxos.clone(); - let output_utxos = output_utxos.clone(); - let public_announcements = public_announcements.clone(); - - // unwrap random mmr_chunks - (0..mmr_chunk_indices.len()) - .map(|_| arb::()) - .collect_vec() - .prop_flat_map( move |mmr_chunks| { - - // prepare to unwrap - let input_lock_scripts = input_lock_scripts.clone(); - let lock_script_witnesses = lock_script_witnesses.clone(); - let aocl_mmra = aocl_mmra.clone(); - let mmr_chunk_indices = mmr_chunk_indices.clone(); - let all_index_sets = all_index_sets.clone(); - let input_triples = input_triples.clone(); - let aocl_membership_proofs = aocl_membership_proofs.clone(); - let input_utxos = input_utxos.clone(); - let output_utxos = output_utxos.clone(); - let public_announcements = public_announcements.clone(); - - // unwrap random swbf mmra and membership proofs - let swbf_size = aocl_size / (BATCH_SIZE as u64); - let swbf_strategy = MmraAndMembershipProofs::arbitrary_with(( - mmr_chunk_indices.iter().zip(mmr_chunks.iter()).map(|(i,c)|(*i,Hash::hash(c))).collect_vec(),swbf_size - )); - let mmr_chunks = mmr_chunks.clone(); - swbf_strategy - .prop_flat_map(move |swbf_mmr_and_paths| { - let swbf_mmra = swbf_mmr_and_paths.mmra; - let swbf_membership_proofs = - swbf_mmr_and_paths.membership_proofs; - - let chunk_dictionary: HashMap< - u64, - (MmrMembershipProof, Chunk), - > = mmr_chunk_indices - .iter() - .cloned() - .zip( - swbf_membership_proofs - .into_iter() - .zip(mmr_chunks.iter().cloned()), - ) - .collect(); - let personalized_chunk_dictionaries = - all_index_sets - .iter() - .map(|index_set| { - let mut is = index_set - .iter() - .map(|index| { - index / (BATCH_SIZE as u128) - }) - .map(|index| index as u64) - .collect_vec(); - is.sort(); - is.dedup(); - is - }) - .map(|chunk_indices| { - ChunkDictionary::new( - chunk_indices - .iter() - .map(|chunk_index| { - ( - *chunk_index, - chunk_dictionary - .get( - chunk_index, - ) - .cloned() - .unwrap(), - ) - }) - .collect::, - Chunk, - ), - >>( - ), - ) - }) - .collect_vec(); - let input_membership_proofs = input_triples - .iter() - .zip(aocl_membership_proofs.iter()) - .zip(personalized_chunk_dictionaries.iter()) - .map( - |( - ( - (_item, sender_randomness, receiver_preimage), - auth_path, - ), - target_chunks, - )| { - MsMembershipProof { - sender_randomness: *sender_randomness, - receiver_preimage: *receiver_preimage, - auth_path_aocl: auth_path.clone(), - target_chunks: target_chunks.clone(), - } - }, - ) - .collect_vec(); - let input_removal_records = - all_index_sets - .iter() - .zip( - personalized_chunk_dictionaries - .iter(), - ) - .map(|(index_set, target_chunks)| { - RemovalRecord { - absolute_indices: - AbsoluteIndexSet::new( - index_set, - ), - target_chunks: target_chunks - .clone(), - } - }) - .collect_vec(); - let type_scripts = vec![TypeScript::new( - native_currency_program(), - )]; - - // prepare to unwrap - let input_lock_scripts = input_lock_scripts.clone(); - let input_utxos = input_utxos.clone(); - let input_removal_records = input_removal_records.clone(); - let input_membership_proofs = input_membership_proofs.clone(); - let type_scripts = type_scripts.clone(); - let lock_script_witnesses = lock_script_witnesses.clone(); - let aocl_mmra = aocl_mmra.clone(); - let swbf_mmra = swbf_mmra.clone(); - let output_utxos = output_utxos.clone(); - let public_announcements = public_announcements.clone(); - - // unwrap sender randomnesses and receiver digests - let output_sender_randomnesses_strategy = - vec(arb::(), num_outputs); - let receiver_digest_strategy = - vec(arb::(), num_outputs); - (output_sender_randomnesses_strategy, receiver_digest_strategy) - .prop_flat_map( - move |(mut output_sender_randomnesses, mut receiver_digests)| { - let output_commitments = output_utxos - .iter() - .map(|utxo| { - commit( - Hash::hash(utxo), - output_sender_randomnesses - .pop() - .unwrap(), - receiver_digests.pop().unwrap(), - ) - }) - .collect_vec(); - - // prepare to unwrap - let input_lock_scripts = input_lock_scripts.clone(); - let input_utxos = input_utxos.clone(); - let input_removal_records = input_removal_records.clone(); - let input_membership_proofs = input_membership_proofs.clone(); - let type_scripts = type_scripts.clone(); - let lock_script_witnesses = lock_script_witnesses.clone(); - let aocl_mmra = aocl_mmra.clone(); - let swbf_mmra = swbf_mmra.clone(); - let output_utxos = output_utxos.clone(); - let public_announcements = public_announcements.clone(); - - // unwrap random active window - arb::() - .prop_map(move |active_window| { - let mutator_set_accumulator = - MutatorSetAccumulator { - kernel: MutatorSetKernel { - aocl: aocl_mmra.clone(), - swbf_inactive: - swbf_mmra.clone(), - swbf_active: - active_window, - }, - }; - - let kernel = TransactionKernel { - inputs: input_removal_records.clone(), - outputs: output_commitments.clone(), - public_announcements: public_announcements.to_vec(), - fee, - coinbase, - timestamp: BFieldElement::new( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() - as u64, - ), - mutator_set_hash: - mutator_set_accumulator.hash(), - }; - - PrimitiveWitness { - input_lock_scripts: input_lock_scripts.clone(), - input_utxos: input_utxos.clone(), - input_membership_proofs: input_membership_proofs.clone(), - type_scripts: type_scripts.clone(), - lock_script_witnesses: lock_script_witnesses.clone(), - output_utxos: output_utxos.clone(), - mutator_set_accumulator: mutator_set_accumulator.clone(), - kernel, - } - - }) - .boxed() - }, - ) - .boxed() - }) - .boxed() - }) - .boxed() - }) - .boxed() - }) - .boxed() - }) - .boxed() + let kernel = TransactionKernel { + inputs: input_removal_records.clone(), + outputs: output_commitments.clone(), + public_announcements: public_announcements.to_vec(), + fee, + coinbase, + timestamp: BFieldElement::new( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64, + ), + mutator_set_hash: mutator_set_accumulator.hash(), + }; + + PrimitiveWitness { + input_lock_scripts: input_lock_scripts.clone(), + input_utxos: input_utxos.clone(), + input_membership_proofs: input_membership_proofs.clone(), + type_scripts: type_scripts.clone(), + lock_script_witnesses: lock_script_witnesses.clone(), + output_utxos: output_utxos.clone(), + mutator_set_accumulator: mutator_set_accumulator.clone(), + kernel, + } + }) + .boxed() + }, + ) + .boxed() } #[cfg(test)] @@ -712,7 +510,7 @@ mod test { use proptest::prop_assert; use test_strategy::proptest; - #[proptest] + #[proptest(cases = 1)] fn arbitrary_transaction_is_valid( #[strategy(1usize..3)] _num_inputs: usize, #[strategy(1usize..3)] _num_outputs: usize, diff --git a/src/models/blockchain/transaction/validity.rs b/src/models/blockchain/transaction/validity.rs index edbcd3b7..6f399658 100644 --- a/src/models/blockchain/transaction/validity.rs +++ b/src/models/blockchain/transaction/validity.rs @@ -76,10 +76,18 @@ impl TransactionValidationLogic { pub fn verify(&self) -> bool { info!("validity logic for 'kernel_to_lock_scripts', 'kernel_to_type_scripts', 'type_scripts_halt' not implemented yet."); - self.lock_scripts_halt.verify() - // && self.kernel_to_lock_scripts.verify() - && self.removal_records_integrity.verify() + let lock_scripts_halt = self.lock_scripts_halt.verify(); + let removal_records_integral = self.removal_records_integrity.verify(); + if !lock_scripts_halt { + eprintln!("Lock scripts don't halt."); + } + if !removal_records_integral { + eprintln!("Removal records are not integral."); + } + // && self.kernel_to_lock_scripts.verify() + // && self.kernel_to_typescripts.verify() // && self.type_scripts_halt.verify() + lock_scripts_halt && removal_records_integral } } diff --git a/src/util_types/mutator_set/msa_and_records.rs b/src/util_types/mutator_set/msa_and_records.rs index 1029822c..9fdcecd0 100644 --- a/src/util_types/mutator_set/msa_and_records.rs +++ b/src/util_types/mutator_set/msa_and_records.rs @@ -219,7 +219,6 @@ impl Arbitrary for MsaAndRecords { .map(|(((item, sender_randomness, receiver_preimage), aocl_auth_path), target_chunks)| { let leaf = commit(*item, *sender_randomness, receiver_preimage.hash::()).canonical_commitment; assert!(aocl_auth_path.verify(&aocl_mmra.get_peaks(), leaf, aocl_mmra.count_leaves()).0); - println!("verified AOCL membership of leaf {leaf} at index {}", aocl_auth_path.leaf_index); (((item, sender_randomness, receiver_preimage), aocl_auth_path), target_chunks) }) .map( From b0d2cc8bbc1f93d9463b3c02f17efcf1910afb13 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Wed, 21 Feb 2024 22:02:28 +0100 Subject: [PATCH 25/33] wip: Fix arbitrary for `PrimitiveWitness` --- .../transaction/primitive_witness.rs | 77 +++++++------------ .../blockchain/type_scripts/time_lock.rs | 2 + src/models/consensus/mod.rs | 6 +- 3 files changed, 35 insertions(+), 50 deletions(-) diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index 326ec122..708fe902 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -271,7 +271,7 @@ impl Arbitrary for PrimitiveWitness { ) .prop_flat_map( |( - input_lock_script_preimages, + address_seeds, input_amounts, output_lock_script_preimages, mut output_amounts, @@ -279,18 +279,25 @@ impl Arbitrary for PrimitiveWitness { mut fee, maybe_coinbase, )| { - let input_utxos = input_lock_script_preimages - .into_iter() + let input_spending_keys = address_seeds + .iter() + .map(|address_seed| { + generation_address::SpendingKey::derive_from_seed(*address_seed) + }) + .collect_vec(); + let input_lock_scripts = input_spending_keys + .iter() + .map(|spending_key| spending_key.to_address().lock_script()) + .collect_vec(); + let input_lock_script_witnesses = input_spending_keys + .iter() + .map(|spending_key| spending_key.unlock_key.values().to_vec()) + .collect_vec(); + let input_utxos = input_lock_scripts + .iter() .zip(input_amounts) - .map(|(lock_script_preimage, amount)| { - Utxo::new( - generation_address::SpendingKey::derive_from_seed( - lock_script_preimage, - ) - .to_address() - .lock_script(), - amount.to_native_coins(), - ) + .map(|(lock_script, amount)| { + Utxo::new(lock_script.clone(), amount.to_native_coins()) }) .collect_vec(); let total_inputs = input_utxos @@ -334,6 +341,8 @@ impl Arbitrary for PrimitiveWitness { .collect_vec(); arbitrary_primitive_witness_with( &input_utxos, + &input_lock_scripts, + &input_lock_script_witnesses, &output_utxos, &public_announcements, fee, @@ -347,6 +356,8 @@ impl Arbitrary for PrimitiveWitness { pub(crate) fn arbitrary_primitive_witness_with( input_utxos: &[Utxo], + input_lock_scripts: &[LockScript], + input_lock_script_witnesses: &[Vec], output_utxos: &[Utxo], public_announcements: &[PublicAnnouncement], fee: NeptuneCoins, @@ -357,16 +368,16 @@ pub(crate) fn arbitrary_primitive_witness_with( let input_utxos = input_utxos.to_vec(); let output_utxos = output_utxos.to_vec(); let public_announcements = public_announcements.to_vec(); + let input_lock_scripts = input_lock_scripts.to_vec(); + let input_lock_script_witnesses = input_lock_script_witnesses.to_vec(); // unwrap: - // - spending key seeds // - sender randomness (input) // - receiver preimage (input) // - sender randomness (output) // - receiver preimage (output) // - aocl size ( - vec(arb::(), num_inputs), vec(arb::(), num_inputs), vec(arb::(), num_inputs), vec(arb::(), num_outputs), @@ -375,40 +386,12 @@ pub(crate) fn arbitrary_primitive_witness_with( ) .prop_flat_map( move |( - mut spending_key_seeds, mut sender_randomnesses_input, mut receiver_preimages_input, sender_randomnesses_output, receiver_preimages_output, aocl_size, )| { - let sender_spending_keys = (0..num_inputs) - .map(|_| { - generation_address::SpendingKey::derive_from_seed( - spending_key_seeds.pop().unwrap(), - ) - }) - .collect_vec(); - let sender_receiving_addresses = sender_spending_keys - .iter() - .map(|ssk| ssk.to_address()) - .collect_vec(); - let input_lock_scripts = sender_receiving_addresses - .iter() - .map(|sra| sra.lock_script()) - .collect_vec(); - let lock_script_witnesses = sender_spending_keys - .iter() - .map(|ssk| ssk.unlock_key.values().to_vec()) - .collect_vec(); - - // prepare to unwrap - let lock_script_witnesses = lock_script_witnesses.clone(); - let input_lock_scripts = input_lock_scripts.clone(); - let input_utxos = input_utxos.clone(); - let output_utxos = output_utxos.clone(); - let public_announcements = public_announcements.clone(); - let input_triples = input_utxos .iter() .map(|utxo| { @@ -421,9 +404,9 @@ pub(crate) fn arbitrary_primitive_witness_with( .collect_vec(); // prepare to unwrap - let input_lock_scripts = input_lock_scripts.clone(); - let lock_script_witnesses = lock_script_witnesses.clone(); let input_triples = input_triples.clone(); + let input_lock_scripts = input_lock_scripts.to_vec(); + let input_lock_script_witnesses = input_lock_script_witnesses.to_vec(); let input_utxos = input_utxos.clone(); let output_utxos = output_utxos.clone(); let public_announcements = public_announcements.clone(); @@ -438,12 +421,10 @@ pub(crate) fn arbitrary_primitive_witness_with( let type_scripts = vec![TypeScript::new(native_currency_program())]; // prepare to unwrap - let input_lock_scripts = input_lock_scripts.clone(); let input_utxos = input_utxos.clone(); let input_removal_records = input_removal_records.clone(); let input_membership_proofs = input_membership_proofs.clone(); let type_scripts = type_scripts.clone(); - let lock_script_witnesses = lock_script_witnesses.clone(); let output_utxos = output_utxos.clone(); let public_announcements = public_announcements.clone(); let mut sender_randomnesses_output = sender_randomnesses_output.clone(); @@ -461,12 +442,10 @@ pub(crate) fn arbitrary_primitive_witness_with( .collect_vec(); // prepare to unwrap - let input_lock_scripts = input_lock_scripts.clone(); let input_utxos = input_utxos.clone(); let input_removal_records = input_removal_records.clone(); let input_membership_proofs = input_membership_proofs.clone(); let type_scripts = type_scripts.clone(); - let lock_script_witnesses = lock_script_witnesses.clone(); let output_utxos = output_utxos.clone(); let public_announcements = public_announcements.clone(); @@ -490,7 +469,7 @@ pub(crate) fn arbitrary_primitive_witness_with( input_utxos: input_utxos.clone(), input_membership_proofs: input_membership_proofs.clone(), type_scripts: type_scripts.clone(), - lock_script_witnesses: lock_script_witnesses.clone(), + lock_script_witnesses: input_lock_script_witnesses.clone(), output_utxos: output_utxos.clone(), mutator_set_accumulator: mutator_set_accumulator.clone(), kernel, diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index cae20803..d2830c4a 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -484,6 +484,8 @@ impl Arbitrary for TimeLockWitness { // generate primitive transaction witness and time lock witness from there arbitrary_primitive_witness_with( &input_utxos, + &[], + &[], &output_utxos, &public_announcements, NeptuneCoins::zero(), diff --git a/src/models/consensus/mod.rs b/src/models/consensus/mod.rs index a206c145..3cd221d9 100644 --- a/src/models/consensus/mod.rs +++ b/src/models/consensus/mod.rs @@ -196,15 +196,19 @@ pub trait ValidationLogic { } ClaimSupport::MultipleSupports(secret_witnesses) => { let claim = self.claim(); + #[allow(clippy::never_loop)] for witness in secret_witnesses.iter() { let vm_result = witness.subprogram().run( PublicInput::new(claim.input.to_vec()), witness.nondeterminism(), ); match vm_result { - Ok(_) => (), + Ok(_) => { + panic!("success!"); + } Err(err) => { warn!("Multiple-support witness failed to validate: {err}"); + panic!("failed: {err}"); return false; } } From 64434d29efd27d688d87a73133cee3d343bcd938 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Thu, 22 Feb 2024 14:29:28 +0100 Subject: [PATCH 26/33] test: Fix arbitrary for `PrimitiveWitness` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Thorkil Værge --- .../transaction/primitive_witness.rs | 1 + .../transaction/validity/lockscripts_halt.rs | 33 ++++++++++++------- src/models/consensus/mod.rs | 23 ++++++++----- src/models/state/mod.rs | 6 +--- .../wallet/address/generation_address.rs | 9 +++++ 5 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index 708fe902..d28a02e9 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -293,6 +293,7 @@ impl Arbitrary for PrimitiveWitness { .iter() .map(|spending_key| spending_key.unlock_key.values().to_vec()) .collect_vec(); + let input_utxos = input_lock_scripts .iter() .zip(input_amounts) diff --git a/src/models/blockchain/transaction/validity/lockscripts_halt.rs b/src/models/blockchain/transaction/validity/lockscripts_halt.rs index eafecc67..543b30b1 100644 --- a/src/models/blockchain/transaction/validity/lockscripts_halt.rs +++ b/src/models/blockchain/transaction/validity/lockscripts_halt.rs @@ -17,12 +17,17 @@ use crate::models::{ #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] pub struct LockScriptHaltsWitness { lock_script: LockScript, - preimage: Vec, + nondeterministic_tokens: Vec, } impl SecretWitness for LockScriptHaltsWitness { fn nondeterminism(&self) -> NonDeterminism { - NonDeterminism::new(self.preimage.clone().into_iter().collect_vec()) + NonDeterminism::new( + self.nondeterministic_tokens + .clone() + .into_iter() + .collect_vec(), + ) } fn subprogram(&self) -> Program { @@ -50,16 +55,20 @@ impl ValidationLogic for LockScriptsHalt { Self { supported_claims: program_and_program_digests_and_spending_keys .into_iter() - .map(|(lockscript, lockscript_digest, spendkey)| SupportedClaim { - claim: triton_vm::prelude::Claim { - program_digest: lockscript_digest, - input: tx_kernel_mast_hash.values().to_vec(), - output: empty_string.clone(), - }, - support: ClaimSupport::SecretWitness(LockScriptHaltsWitness { - lock_script: lockscript.to_owned(), - preimage: spendkey.to_owned(), - }), + .map(|(lockscript, lockscript_digest, spendkey)| { + let mut nondeterministic_tokens = spendkey.to_owned(); + nondeterministic_tokens.reverse(); + SupportedClaim { + claim: triton_vm::prelude::Claim { + program_digest: lockscript_digest, + input: tx_kernel_mast_hash.values().to_vec(), + output: empty_string.clone(), + }, + support: ClaimSupport::SecretWitness(LockScriptHaltsWitness { + lock_script: lockscript.to_owned(), + nondeterministic_tokens, + }), + } }) .collect(), } diff --git a/src/models/consensus/mod.rs b/src/models/consensus/mod.rs index 3cd221d9..a21c6369 100644 --- a/src/models/consensus/mod.rs +++ b/src/models/consensus/mod.rs @@ -2,7 +2,9 @@ use anyhow::Result; use get_size::GetSize; use serde::Deserialize; use serde::Serialize; +use tasm_lib::maybe_write_debuggable_program_to_disk; use tasm_lib::triton_vm; +use tasm_lib::triton_vm::vm::VMState; use tasm_lib::twenty_first::shared_math::b_field_element::BFieldElement; use tasm_lib::twenty_first::shared_math::bfield_codec::BFieldCodec; use tracing::{debug, warn}; @@ -198,17 +200,22 @@ pub trait ValidationLogic { let claim = self.claim(); #[allow(clippy::never_loop)] for witness in secret_witnesses.iter() { - let vm_result = witness.subprogram().run( - PublicInput::new(claim.input.to_vec()), - witness.nondeterminism(), - ); + let public_input = PublicInput::new(claim.input.to_vec()); + let vm_result = witness + .subprogram() + .run(public_input.clone(), witness.nondeterminism()); match vm_result { - Ok(_) => { - panic!("success!"); - } + Ok(_) => {} Err(err) => { warn!("Multiple-support witness failed to validate: {err}"); - panic!("failed: {err}"); + maybe_write_debuggable_program_to_disk( + &witness.subprogram(), + &VMState::new( + &witness.subprogram(), + public_input, + witness.nondeterminism(), + ), + ); return false; } } diff --git a/src/models/state/mod.rs b/src/models/state/mod.rs index d10cbe56..96027a37 100644 --- a/src/models/state/mod.rs +++ b/src/models/state/mod.rs @@ -546,11 +546,7 @@ impl GlobalState { .mutator_set_accumulator .clone(); - // When reading a digest from secret and standard-in, the digest's - // zeroth element must be on top of the stack. So the secret-in - // is here the spending key reversed. - let mut secret_input = spending_key.unlock_key.encode(); - secret_input.reverse(); + let secret_input = spending_key.unlock_key.encode(); let mut primitive_witness = PrimitiveWitness { input_utxos, input_lock_scripts, diff --git a/src/models/state/wallet/address/generation_address.rs b/src/models/state/wallet/address/generation_address.rs index 0da98b3c..d17a8e5f 100644 --- a/src/models/state/wallet/address/generation_address.rs +++ b/src/models/state/wallet/address/generation_address.rs @@ -308,6 +308,15 @@ impl ReceivingAddress { Self::from_spending_key(&spending_key) } + /// Determine whether the given witness unlocks the lock defined by this receiving + /// address. + pub fn can_unlock_with(&self, witness: &[BFieldElement]) -> bool { + match witness.try_into() { + Ok(witness_array) => Digest::new(witness_array).hash::() == self.spending_lock, + Err(_) => false, + } + } + pub fn encrypt(&self, utxo: &Utxo, sender_randomness: Digest) -> Result> { // derive shared key let mut randomness = [0u8; 32]; From b36a55261e27364bb8b2e8e19ee5b528db1557a0 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Thu, 22 Feb 2024 15:39:57 +0100 Subject: [PATCH 27/33] cleanup: Remove old unused helper functions --- .../transaction/primitive_witness.rs | 195 +----------------- 1 file changed, 3 insertions(+), 192 deletions(-) diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index d28a02e9..9099a6f5 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -1,7 +1,4 @@ -use std::{ - collections::HashMap, - time::{SystemTime, UNIX_EPOCH}, -}; +use std::time::{SystemTime, UNIX_EPOCH}; use get_size::GetSize; use itertools::Itertools; @@ -12,22 +9,16 @@ use proptest::{ strategy::{BoxedStrategy, Strategy}, }; use proptest_arbitrary_interop::arb; -use rand::{rngs::StdRng, Rng, RngCore, SeedableRng}; use serde::{Deserialize, Serialize}; use tasm_lib::{ twenty_first::{ shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, - util_types::{ - algebraic_hasher::AlgebraicHasher, - mmr::{ - mmr_accumulator::MmrAccumulator, mmr_membership_proof::MmrMembershipProof, - mmr_trait::Mmr, - }, - }, + util_types::algebraic_hasher::AlgebraicHasher, }, Digest, }; +use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::{ models::blockchain::type_scripts::native_currency::{ native_currency_program, NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST, @@ -38,10 +29,6 @@ use crate::{ }, Hash, }; -use crate::{ - models::blockchain::type_scripts::neptune_coins::NeptuneCoins, - twenty_first::util_types::mmr::shared_basic::leaf_index_to_mt_index_and_peak_index, -}; use crate::{ models::{blockchain::type_scripts::TypeScript, state::wallet::address::generation_address}, util_types::mutator_set::{ @@ -69,182 +56,6 @@ pub struct PrimitiveWitness { pub kernel: TransactionKernel, } -impl PrimitiveWitness { - pub fn pseudorandom_mmra_with_mps( - seed: [u8; 32], - leafs: &[Digest], - ) -> (MmrAccumulator, Vec>) { - let mut rng: StdRng = SeedableRng::from_seed(seed); - - // sample size of MMR - let mut leaf_count = rng.next_u64(); - while leaf_count < leafs.len() as u64 { - leaf_count = rng.next_u64(); - } - let num_peaks = leaf_count.count_ones(); - - // sample mmr leaf indices and calculate matching derived indices - let leaf_indices = leafs - .iter() - .enumerate() - .map(|(original_index, _leaf)| (original_index, rng.next_u64() % leaf_count)) - .map(|(original_index, mmr_index)| { - let (mt_index, peak_index) = - leaf_index_to_mt_index_and_peak_index(mmr_index, leaf_count); - (original_index, mmr_index, mt_index, peak_index) - }) - .collect_vec(); - let leafs_and_indices = leafs.iter().copied().zip(leaf_indices).collect_vec(); - - // iterate over all trees - let mut peaks = vec![]; - let dummy_mp = MmrMembershipProof::new(0u64, vec![]); - let mut mps: Vec> = - (0..leafs.len()).map(|_| dummy_mp.clone()).collect_vec(); - for tree in 0..num_peaks { - // select all leafs and merkle tree indices for this tree - let leafs_and_mt_indices = leafs_and_indices - .iter() - .copied() - .filter( - |(_leaf, (_original_index, _mmr_index, _mt_index, peak_index))| { - *peak_index == tree - }, - ) - .map( - |(leaf, (original_index, _mmr_index, mt_index, _peak_index))| { - (leaf, mt_index, original_index) - }, - ) - .collect_vec(); - if leafs_and_mt_indices.is_empty() { - peaks.push(rng.gen()); - continue; - } - - // generate root and authentication paths - let tree_height = (*leafs_and_mt_indices.first().map(|(_l, i, _o)| i).unwrap() as u128) - .ilog2() as usize; - let (root, authentication_paths) = - Self::pseudorandom_merkle_root_with_authentication_paths( - rng.gen(), - tree_height, - &leafs_and_mt_indices - .iter() - .map(|(l, i, _o)| (*l, *i)) - .collect_vec(), - ); - - // update peaks list - peaks.push(root); - - // generate membership proof objects - let membership_proofs = leafs_and_indices - .iter() - .copied() - .filter( - |(_leaf, (_original_index, _mmr_index, _mt_index, peak_index))| { - *peak_index == tree - }, - ) - .zip(authentication_paths.into_iter()) - .map( - |( - (_leaf, (_original_index, mmr_index, _mt_index, _peak_index)), - authentication_path, - )| { - MmrMembershipProof::::new(mmr_index, authentication_path) - }, - ) - .collect_vec(); - - // sanity check: test if membership proofs agree with peaks list (up until now) - let dummy_remainder: Vec = (peaks.len()..num_peaks as usize) - .map(|_| rng.gen()) - .collect_vec(); - let dummy_peaks = [peaks.clone(), dummy_remainder].concat(); - for (&(leaf, _mt_index, _original_index), mp) in - leafs_and_mt_indices.iter().zip(membership_proofs.iter()) - { - assert!(mp.verify(&dummy_peaks, leaf, leaf_count).0); - } - - // collect membership proofs in vector, with indices matching those of the supplied leafs - for ((_leaf, _mt_index, original_index), mp) in - leafs_and_mt_indices.iter().zip(membership_proofs.iter()) - { - mps[*original_index] = mp.clone(); - } - } - - let mmra = MmrAccumulator::::init(peaks, leaf_count); - - // sanity check - for (&leaf, mp) in leafs.iter().zip(mps.iter()) { - assert!(mp.verify(&mmra.get_peaks(), leaf, mmra.count_leaves()).0); - } - - (mmra, mps) - } - - pub fn pseudorandom_merkle_root_with_authentication_paths( - seed: [u8; 32], - tree_height: usize, - leafs_and_indices: &[(Digest, u64)], - ) -> (Digest, Vec>) { - let mut rng: StdRng = SeedableRng::from_seed(seed); - let mut nodes: HashMap = HashMap::new(); - - // populate nodes dictionary with leafs - for (leaf, index) in leafs_and_indices.iter() { - nodes.insert(*index, *leaf); - } - - // walk up tree layer by layer - // when we need nodes not already present, sample at random - let mut depth = tree_height + 1; - while depth > 0 { - let mut working_indices = nodes - .keys() - .copied() - .filter(|i| { - (*i as u128) < (1u128 << (depth)) && (*i as u128) >= (1u128 << (depth - 1)) - }) - .collect_vec(); - working_indices.sort(); - working_indices.dedup(); - for wi in working_indices { - let wi_odd = wi | 1; - if nodes.get(&wi_odd).is_none() { - nodes.insert(wi_odd, rng.gen::()); - } - let wi_even = wi_odd ^ 1; - if nodes.get(&wi_even).is_none() { - nodes.insert(wi_even, rng.gen::()); - } - let hash = Hash::hash_pair(nodes[&wi_even], nodes[&wi_odd]); - nodes.insert(wi >> 1, hash); - } - depth -= 1; - } - - // read out root - let root = *nodes.get(&1).unwrap_or(&rng.gen()); - - // read out paths - let paths = leafs_and_indices - .iter() - .map(|(_d, i)| { - (0..tree_height) - .map(|j| *nodes.get(&((*i >> j) ^ 1)).unwrap()) - .collect_vec() - }) - .collect_vec(); - - (root, paths) - } -} - impl Arbitrary for PrimitiveWitness { type Parameters = (usize, usize, usize); type Strategy = BoxedStrategy; From e903689fd136211a007aa7c9a65a55e65ebb8ae9 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Thu, 22 Feb 2024 16:09:49 +0100 Subject: [PATCH 28/33] refactor: Factor out inputs and outputs generation The generation of the input UTXOs (with witnesses) and output UTXOs (where we don't care about witnesses) for `arbitrary` from seeds and amounts has been moved to separate functions, enabling code reuse. --- .../transaction/primitive_witness.rs | 147 ++++++++++-------- 1 file changed, 81 insertions(+), 66 deletions(-) diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index 9099a6f5..cd4e9132 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -20,9 +20,7 @@ use tasm_lib::{ use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::{ - models::blockchain::type_scripts::native_currency::{ - native_currency_program, NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST, - }, + models::blockchain::type_scripts::native_currency::native_currency_program, util_types::mutator_set::{ msa_and_records::MsaAndRecords, mutator_set_trait::{commit, MutatorSet}, @@ -56,6 +54,70 @@ pub struct PrimitiveWitness { pub kernel: TransactionKernel, } +impl PrimitiveWitness { + pub fn transaction_inputs_from_address_seeds_and_amounts( + address_seeds: &[Digest], + input_amounts: &[NeptuneCoins], + ) -> (Vec, Vec, Vec>) { + let input_spending_keys = address_seeds + .iter() + .map(|address_seed| generation_address::SpendingKey::derive_from_seed(*address_seed)) + .collect_vec(); + let input_lock_scripts = input_spending_keys + .iter() + .map(|spending_key| spending_key.to_address().lock_script()) + .collect_vec(); + let input_lock_script_witnesses = input_spending_keys + .iter() + .map(|spending_key| spending_key.unlock_key.values().to_vec()) + .collect_vec(); + + let input_utxos = input_lock_scripts + .iter() + .zip(input_amounts) + .map(|(lock_script, amount)| Utxo::new(lock_script.clone(), amount.to_native_coins())) + .collect_vec(); + (input_utxos, input_lock_scripts, input_lock_script_witnesses) + } + + pub fn valid_transaction_outputs_from_amounts_and_address_seeds( + total_inputs: NeptuneCoins, + maybe_coinbase: Option, + fee: &mut NeptuneCoins, + output_amounts: &mut [NeptuneCoins], + address_seeds: &[Digest], + ) -> Vec { + let mut total_outputs = output_amounts.iter().cloned().sum::(); + let mut some_coinbase = match maybe_coinbase { + Some(coinbase) => coinbase, + None => NeptuneCoins::zero(), + }; + while total_inputs < total_outputs + *fee + some_coinbase { + for amount in output_amounts.iter_mut() { + amount.div_two(); + } + if let Some(mut coinbase) = maybe_coinbase { + coinbase.div_two(); + some_coinbase.div_two(); + } + fee.div_two(); + total_outputs = output_amounts.iter().cloned().sum::(); + } + address_seeds + .iter() + .zip(output_amounts) + .map(|(seed, amount)| { + Utxo::new( + generation_address::SpendingKey::derive_from_seed(*seed) + .to_address() + .lock_script(), + amount.to_native_coins(), + ) + }) + .collect_vec() + } +} + impl Arbitrary for PrimitiveWitness { type Parameters = (usize, usize, usize); type Strategy = BoxedStrategy; @@ -82,75 +144,28 @@ impl Arbitrary for PrimitiveWitness { ) .prop_flat_map( |( - address_seeds, + input_address_seeds, input_amounts, - output_lock_script_preimages, + output_address_seeds, mut output_amounts, public_announcements, mut fee, maybe_coinbase, )| { - let input_spending_keys = address_seeds - .iter() - .map(|address_seed| { - generation_address::SpendingKey::derive_from_seed(*address_seed) - }) - .collect_vec(); - let input_lock_scripts = input_spending_keys - .iter() - .map(|spending_key| spending_key.to_address().lock_script()) - .collect_vec(); - let input_lock_script_witnesses = input_spending_keys - .iter() - .map(|spending_key| spending_key.unlock_key.values().to_vec()) - .collect_vec(); - - let input_utxos = input_lock_scripts - .iter() - .zip(input_amounts) - .map(|(lock_script, amount)| { - Utxo::new(lock_script.clone(), amount.to_native_coins()) - }) - .collect_vec(); - let total_inputs = input_utxos - .iter() - .flat_map(|utxo| utxo.coins.clone()) - .filter(|coin| coin.type_script_hash == NATIVE_CURRENCY_TYPE_SCRIPT_DIGEST) - .map(|coin| coin.state) - .map(|state| NeptuneCoins::decode(&state)) - .filter(|r| r.is_ok()) - .map(|r| *r.unwrap()) - .sum::(); - let mut total_outputs = output_amounts.iter().cloned().sum::(); - let mut some_coinbase = match maybe_coinbase { - Some(coinbase) => coinbase, - None => NeptuneCoins::zero(), - }; - while total_inputs < total_outputs + fee + some_coinbase { - for amount in output_amounts.iter_mut() { - amount.div_two(); - } - if let Some(mut coinbase) = maybe_coinbase { - coinbase.div_two(); - some_coinbase.div_two(); - } - fee.div_two(); - total_outputs = output_amounts.iter().cloned().sum::(); - } - let output_utxos = output_lock_script_preimages - .into_iter() - .zip(output_amounts) - .map(|(lock_script_preimage, amount)| { - Utxo::new( - generation_address::SpendingKey::derive_from_seed( - lock_script_preimage, - ) - .to_address() - .lock_script(), - amount.to_native_coins(), - ) - }) - .collect_vec(); + let (input_utxos, input_lock_scripts, input_lock_script_witnesses) = + Self::transaction_inputs_from_address_seeds_and_amounts( + &input_address_seeds, + &input_amounts, + ); + let total_inputs = input_amounts.iter().copied().sum::(); + let output_utxos = + Self::valid_transaction_outputs_from_amounts_and_address_seeds( + total_inputs, + maybe_coinbase, + &mut fee, + &mut output_amounts, + &output_address_seeds, + ); arbitrary_primitive_witness_with( &input_utxos, &input_lock_scripts, From 66a941491f1ce6b5a0a6de6db225094a4a2eedeb Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Thu, 22 Feb 2024 17:07:58 +0100 Subject: [PATCH 29/33] refactor: Factor out valid output amounts finding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Thorkil Værge --- .../transaction/primitive_witness.rs | 62 ++++++++++++++----- .../blockchain/type_scripts/time_lock.rs | 49 ++++++++++++--- 2 files changed, 88 insertions(+), 23 deletions(-) diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index cd4e9132..794c4be5 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -2,7 +2,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use get_size::GetSize; use itertools::Itertools; -use num_traits::Zero; +use num_traits::{CheckedSub, Zero}; use proptest::{ arbitrary::Arbitrary, collection::vec, @@ -80,29 +80,56 @@ impl PrimitiveWitness { (input_utxos, input_lock_scripts, input_lock_script_witnesses) } - pub fn valid_transaction_outputs_from_amounts_and_address_seeds( - total_inputs: NeptuneCoins, + /// Obtain a *valid* set of outputs (and fee) given a fixed total input amount + /// and (optional) coinbase. This function takes a suggestion for the output + /// amounts and fee and mutates these values until they satisfy the no-inflation + /// requirement. + pub fn find_valid_output_amounts_and_fee( + total_input_amount: NeptuneCoins, maybe_coinbase: Option, - fee: &mut NeptuneCoins, - output_amounts: &mut [NeptuneCoins], - address_seeds: &[Digest], - ) -> Vec { - let mut total_outputs = output_amounts.iter().cloned().sum::(); + output_amounts_suggestion: &mut [NeptuneCoins], + fee_suggestion: &mut NeptuneCoins, + ) { + let mut total_output_amount = output_amounts_suggestion + .iter() + .cloned() + .sum::(); let mut some_coinbase = match maybe_coinbase { Some(coinbase) => coinbase, None => NeptuneCoins::zero(), }; - while total_inputs < total_outputs + *fee + some_coinbase { - for amount in output_amounts.iter_mut() { + let mut inflationary = false; + while total_input_amount + some_coinbase != total_output_amount + *fee_suggestion + || inflationary + { + for amount in output_amounts_suggestion.iter_mut() { amount.div_two(); } if let Some(mut coinbase) = maybe_coinbase { coinbase.div_two(); some_coinbase.div_two(); } - fee.div_two(); - total_outputs = output_amounts.iter().cloned().sum::(); + total_output_amount = output_amounts_suggestion + .iter() + .cloned() + .sum::(); + match (total_input_amount + some_coinbase).checked_sub(&total_output_amount) { + Some(number) => { + *fee_suggestion = number; + inflationary = false; + } + None => { + inflationary = true; + } + } } + } + + /// Generate valid output UTXOs from the amounts and seeds for the addresses + pub fn valid_transaction_outputs_from_amounts_and_address_seeds( + output_amounts: &[NeptuneCoins], + address_seeds: &[Digest], + ) -> Vec { address_seeds .iter() .zip(output_amounts) @@ -158,12 +185,15 @@ impl Arbitrary for PrimitiveWitness { &input_amounts, ); let total_inputs = input_amounts.iter().copied().sum::(); + Self::find_valid_output_amounts_and_fee( + total_inputs, + maybe_coinbase, + &mut output_amounts, + &mut fee, + ); let output_utxos = Self::valid_transaction_outputs_from_amounts_and_address_seeds( - total_inputs, - maybe_coinbase, - &mut fee, - &mut output_amounts, + &output_amounts, &output_address_seeds, ); arbitrary_primitive_witness_with( diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index d2830c4a..fff83f30 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -467,13 +467,33 @@ impl Arbitrary for TimeLockWitness { let (release_dates, num_outputs, num_public_announcements) = parameters; let num_inputs = release_dates.len(); ( - vec(arb::(), num_inputs), - vec(arb::(), num_outputs), + vec(arb::(), num_inputs), + vec(arb::(), num_inputs), + vec(arb::(), num_outputs), + vec(arb::(), num_outputs), vec(arb::(), num_public_announcements), + arb::>(), + arb::(), ) .prop_flat_map( - move |(mut input_utxos, output_utxos, public_announcements)| { - // add time locks to input utxos + move |( + input_address_seeds, + input_amounts, + output_address_seeds, + mut output_amounts, + public_announcements, + maybe_coinbase, + mut fee, + )| { + // generate inputs + let (mut input_utxos, input_lock_scripts, input_lock_script_witnesses) = + PrimitiveWitness::transaction_inputs_from_address_seeds_and_amounts( + &input_address_seeds, + &input_amounts, + ); + let total_inputs = input_amounts.into_iter().sum::(); + + // add time locks to input UTXOs for (utxo, release_date) in input_utxos.iter_mut().zip(release_dates.iter()) { if *release_date != 0 { let time_lock_coin = TimeLock::until(*release_date); @@ -481,15 +501,30 @@ impl Arbitrary for TimeLockWitness { } } + // generate valid output amounts + PrimitiveWitness::find_valid_output_amounts_and_fee( + total_inputs, + maybe_coinbase, + &mut output_amounts, + &mut fee, + ); + + // generate output UTXOs + let output_utxos = + PrimitiveWitness::valid_transaction_outputs_from_amounts_and_address_seeds( + &output_amounts, + &output_address_seeds, + ); + // generate primitive transaction witness and time lock witness from there arbitrary_primitive_witness_with( &input_utxos, - &[], - &[], + &input_lock_scripts, + &input_lock_script_witnesses, &output_utxos, &public_announcements, NeptuneCoins::zero(), - None, + maybe_coinbase, ) .prop_map(move |transaction_primitive_witness| { TimeLockWitness::from_primitive_witness(&transaction_primitive_witness) From ec8dd094c4ecee3ef58e3ca5c6b28c3c682ab453 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Thu, 22 Feb 2024 17:46:48 +0100 Subject: [PATCH 30/33] wip: Test `arbitrary_with` for `TimeLockWitness` --- .../transaction/transaction_kernel.rs | 2 +- .../blockchain/type_scripts/time_lock.rs | 41 ++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/models/blockchain/transaction/transaction_kernel.rs b/src/models/blockchain/transaction/transaction_kernel.rs index 95466f9b..41050b3b 100644 --- a/src/models/blockchain/transaction/transaction_kernel.rs +++ b/src/models/blockchain/transaction/transaction_kernel.rs @@ -60,7 +60,7 @@ impl TransactionKernel { pub enum TransactionKernelField { InputUtxos, OutputUtxos, - Pubscript, + PublicAnnouncements, Fee, Coinbase, Timestamp, diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index fff83f30..18c045eb 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -446,7 +446,12 @@ impl TimeLockWitness { impl SecretWitness for TimeLockWitness { fn nondeterminism(&self) -> NonDeterminism { - NonDeterminism::new(self.release_dates.encode()).with_digests( + let individual_tokens = [ + vec![self.transaction_kernel.timestamp], + self.release_dates.encode(), + ] + .concat(); + NonDeterminism::new(individual_tokens).with_digests( self.transaction_kernel .mast_path(TransactionKernelField::Timestamp) .clone(), @@ -459,6 +464,11 @@ impl SecretWitness for TimeLockWitness { } impl Arbitrary for TimeLockWitness { + /// Parameters are: + /// - release_dates : Vec One release date per input UTXO. 0 if the time lock + /// coin is absent. + /// - num_outputs : usize Number of outputs. + /// - num_public_announcements : usize Number of public announcements. type Parameters = (Vec, usize, usize); type Strategy = BoxedStrategy; @@ -535,3 +545,32 @@ impl Arbitrary for TimeLockWitness { .boxed() } } + +#[cfg(test)] +mod test { + use proptest::{collection::vec, strategy::Just}; + use test_strategy::proptest; + + use crate::models::{ + blockchain::type_scripts::time_lock::TimeLock, + consensus::{mast_hash::MastHash, tasm::program::ConsensusProgram, SecretWitness}, + }; + + use super::TimeLockWitness; + + #[proptest] + fn test_unlocked( + #[strategy(1usize..=3)] _num_inputs: usize, + #[strategy(1usize..=3)] _num_outputs: usize, + #[strategy(1usize..=3)] _num_public_announcements: usize, + #[strategy(vec(Just(0u64), #_num_inputs))] _release_dates: Vec, + #[strategy(TimeLockWitness::arbitrary_with((#_release_dates, #_num_outputs, #_num_public_announcements)))] + time_lock_witness: TimeLockWitness, + ) { + let transaction_kernel = &time_lock_witness.transaction_kernel; + TimeLock::run( + transaction_kernel.mast_hash().reversed().values().as_ref(), + time_lock_witness.nondeterminism(), + ); + } +} From eda0fa86f99690c8c543f35fb7c9638e2bdcdc4e Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Fri, 23 Feb 2024 11:34:11 +0100 Subject: [PATCH 31/33] refactor: Wrap witness UTXOs with salt Witness objects such as `transaction::PrimitiveWitness` can contain lists of input and output UTXOs. This commit wraps these lists of into a new dedicated struct `SaltedUtxos` which adds a `salt` field. The purpose is such that consensus programs can reduce a claim about the set of input and/or output UTXOs to a claim about a single hash digest. The salt ensures that claims related to different transactions or blocks but happen to pertain to the same list of UTXOs (e.g., the empty list) reduce to claims about distinct digests. --- src/mine_loop.rs | 5 +- src/models/blockchain/transaction/mod.rs | 30 +++++---- .../transaction/primitive_witness.rs | 62 +++++++++++++++++-- .../validity/kernel_to_lock_scripts.rs | 2 +- .../validity/kernel_to_type_scripts.rs | 3 +- .../validity/removal_records_integrity.rs | 2 +- .../transaction/validity/typescripts_halt.rs | 4 +- .../blockchain/type_scripts/time_lock.rs | 4 +- src/models/state/mod.rs | 6 +- src/tests/shared.rs | 9 +-- 10 files changed, 95 insertions(+), 32 deletions(-) diff --git a/src/mine_loop.rs b/src/mine_loop.rs index 407193ce..9f75c5ff 100644 --- a/src/mine_loop.rs +++ b/src/mine_loop.rs @@ -43,6 +43,7 @@ use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; use twenty_first::util_types::emojihash_trait::Emojihash; use self::primitive_witness::PrimitiveWitness; +use self::primitive_witness::SaltedUtxos; const MOCK_MAX_BLOCK_SIZE: u32 = 1_000_000; @@ -217,12 +218,12 @@ fn make_coinbase_transaction( }; let primitive_witness = PrimitiveWitness { - input_utxos: vec![], + input_utxos: SaltedUtxos::empty(), type_scripts: vec![TypeScript::native_coin()], input_lock_scripts: vec![], lock_script_witnesses: vec![], input_membership_proofs: vec![], - output_utxos: vec![coinbase_utxo.clone()], + output_utxos: SaltedUtxos::new(vec![coinbase_utxo.clone()]), mutator_set_accumulator, kernel, }; diff --git a/src/models/blockchain/transaction/mod.rs b/src/models/blockchain/transaction/mod.rs index aa0f5391..d47a7a35 100644 --- a/src/models/blockchain/transaction/mod.rs +++ b/src/models/blockchain/transaction/mod.rs @@ -110,7 +110,12 @@ impl Transaction { if let Witness::Primitive(witness) = &mut self.witness { let membership_proofs = &mut witness.input_membership_proofs.iter_mut().collect_vec(); - let own_items = witness.input_utxos.iter().map(Hash::hash).collect_vec(); + let own_items = witness + .input_utxos + .utxos + .iter() + .map(Hash::hash) + .collect_vec(); MsMembershipProof::batch_update_from_addition( membership_proofs, &own_items, @@ -235,11 +240,9 @@ impl Transaction { error!("Cannot merge two transactions with distinct mutator set hashes."); } Witness::Primitive(PrimitiveWitness { - input_utxos: [ - self_witness.input_utxos.clone(), - other_witness.input_utxos.clone(), - ] - .concat(), + input_utxos: self_witness + .input_utxos + .cat(other_witness.input_utxos.clone()), input_lock_scripts: [ self_witness.input_lock_scripts.clone(), other_witness.input_lock_scripts.clone(), @@ -262,11 +265,9 @@ impl Transaction { other_witness.input_membership_proofs.clone(), ] .concat(), - output_utxos: [ - self_witness.output_utxos.clone(), - other_witness.output_utxos.clone(), - ] - .concat(), + output_utxos: self_witness + .output_utxos + .cat(other_witness.output_utxos.clone()), mutator_set_accumulator: self_witness.mutator_set_accumulator.clone(), kernel: merged_kernel.clone(), }) @@ -347,6 +348,7 @@ impl Transaction { let mut witnessed_removal_records = vec![]; for (input_utxo, msmp) in primitive_witness .input_utxos + .utxos .iter() .zip(primitive_witness.input_membership_proofs.iter()) { @@ -373,6 +375,7 @@ impl Transaction { // collect type script hashes let type_script_hashes = primitive_witness .output_utxos + .utxos .iter() .flat_map(|utxo| utxo.coins.iter().map(|coin| coin.type_script_hash)) .sorted_by_key(|d| d.values().map(|b| b.value())) @@ -494,6 +497,7 @@ impl Transaction { #[cfg(test)] mod witness_tests { use tasm_lib::Digest; + use witness_tests::primitive_witness::SaltedUtxos; use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; @@ -511,12 +515,12 @@ mod witness_tests { mutator_set_hash: Digest::default(), }; let primitive_witness = PrimitiveWitness { - input_utxos: vec![], + input_utxos: SaltedUtxos::empty(), type_scripts: vec![], input_lock_scripts: vec![], lock_script_witnesses: vec![], input_membership_proofs: vec![], - output_utxos: vec![], + output_utxos: SaltedUtxos::empty(), mutator_set_accumulator: MutatorSetAccumulator::new(), kernel: empty_kernel, }; diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index 794c4be5..1f0e1df2 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -9,6 +9,7 @@ use proptest::{ strategy::{BoxedStrategy, Strategy}, }; use proptest_arbitrary_interop::arb; +use rand::{thread_rng, Rng}; use serde::{Deserialize, Serialize}; use tasm_lib::{ twenty_first::{ @@ -40,16 +41,57 @@ use super::{ PublicAnnouncement, }; +/// `SaltedUtxos` is a struct for representing a list of UTXOs in a witness object when it +/// is desirable to associate a random but consistent salt for the entire list of UTXOs. +/// This situation arises when two distinct consensus programs prove different features +/// about the same list of UTXOs. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, GetSize, BFieldCodec)] +pub struct SaltedUtxos { + pub utxos: Vec, + pub salt: [BFieldElement; 3], +} + +impl SaltedUtxos { + /// Takes a Vec of UTXOs and returns a `SaltedUtxos` object. The salt comes from + /// `thread_rng`. + pub fn new(utxos: Vec) -> Self { + Self { + utxos, + salt: thread_rng().gen(), + } + } + + /// Generate a `SaltedUtxos` object that contains no UTXOs. There is a random salt + /// though, which comes from `thread_rng`. + pub fn empty() -> Self { + Self { + utxos: vec![], + salt: thread_rng().gen(), + } + } + + /// Concatenate two `SaltedUtxos` objects. Derives the salt from hashing the + /// concatenation of that of the operands. + pub fn cat(&self, other: SaltedUtxos) -> Self { + Self { + utxos: [self.utxos.clone(), other.utxos].concat(), + salt: Hash::hash_varlen(&[self.salt, other.salt].concat().to_vec()).values()[0..3] + .try_into() + .unwrap(), + } + } +} + /// The raw witness is the most primitive type of transaction witness. /// It exposes secret data and is therefore not for broadcasting. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] pub struct PrimitiveWitness { - pub input_utxos: Vec, + pub input_utxos: SaltedUtxos, pub input_lock_scripts: Vec, pub type_scripts: Vec, pub lock_script_witnesses: Vec>, pub input_membership_proofs: Vec, - pub output_utxos: Vec, + pub output_utxos: SaltedUtxos, pub mutator_set_accumulator: MutatorSetAccumulator, pub kernel: TransactionKernel, } @@ -231,22 +273,28 @@ pub(crate) fn arbitrary_primitive_witness_with( // unwrap: // - sender randomness (input) // - receiver preimage (input) + // - salt (input) // - sender randomness (output) // - receiver preimage (output) + // - salt (output) // - aocl size ( vec(arb::(), num_inputs), vec(arb::(), num_inputs), + vec(arb::(), 3), vec(arb::(), num_outputs), vec(arb::(), num_outputs), + vec(arb::(), 3), 0u64..=u64::MAX, ) .prop_flat_map( move |( mut sender_randomnesses_input, mut receiver_preimages_input, + inputs_salt, sender_randomnesses_output, receiver_preimages_output, + outputs_salt, aocl_size, )| { let input_triples = input_utxos @@ -323,11 +371,17 @@ pub(crate) fn arbitrary_primitive_witness_with( PrimitiveWitness { input_lock_scripts: input_lock_scripts.clone(), - input_utxos: input_utxos.clone(), + input_utxos: SaltedUtxos { + utxos: input_utxos.clone(), + salt: inputs_salt.clone().try_into().unwrap(), + }, input_membership_proofs: input_membership_proofs.clone(), type_scripts: type_scripts.clone(), lock_script_witnesses: input_lock_script_witnesses.clone(), - output_utxos: output_utxos.clone(), + output_utxos: SaltedUtxos { + utxos: output_utxos.clone(), + salt: outputs_salt.clone().try_into().unwrap(), + }, mutator_set_accumulator: mutator_set_accumulator.clone(), kernel, } diff --git a/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs b/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs index 0a8e51a1..e732b77b 100644 --- a/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs +++ b/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs @@ -61,7 +61,7 @@ impl ValidationLogic for KernelToLockScripts { program_digest: Digest::default(), }; let _kernel_to_lock_scripts_witness = KernelToLockScriptsWitness { - input_utxos: primitive_witness.input_utxos.clone(), + input_utxos: primitive_witness.input_utxos.utxos.clone(), mast_path: primitive_witness .kernel .mast_path(TransactionKernelField::InputUtxos), diff --git a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs index 2785e391..fc8772b5 100644 --- a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs +++ b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs @@ -49,8 +49,9 @@ impl ValidationLogic for KernelToTypeScripts { fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> Self { let mut type_script_digests = primitive_witness .input_utxos + .utxos .iter() - .chain(primitive_witness.output_utxos.iter()) + .chain(primitive_witness.output_utxos.utxos.iter()) .flat_map(|utxo| { utxo.coins .iter() diff --git a/src/models/blockchain/transaction/validity/removal_records_integrity.rs b/src/models/blockchain/transaction/validity/removal_records_integrity.rs index 188c3a19..10e60e6c 100644 --- a/src/models/blockchain/transaction/validity/removal_records_integrity.rs +++ b/src/models/blockchain/transaction/validity/removal_records_integrity.rs @@ -58,7 +58,7 @@ pub struct RemovalRecordsIntegrityWitness { impl RemovalRecordsIntegrityWitness { pub fn new(primitive_witness: &PrimitiveWitness) -> Self { Self { - input_utxos: primitive_witness.input_utxos.clone(), + input_utxos: primitive_witness.input_utxos.utxos.clone(), membership_proofs: primitive_witness.input_membership_proofs.clone(), kernel: primitive_witness.kernel.clone(), aocl: primitive_witness diff --git a/src/models/blockchain/transaction/validity/typescripts_halt.rs b/src/models/blockchain/transaction/validity/typescripts_halt.rs index 8179d5ec..147ff964 100644 --- a/src/models/blockchain/transaction/validity/typescripts_halt.rs +++ b/src/models/blockchain/transaction/validity/typescripts_halt.rs @@ -56,8 +56,8 @@ impl ValidationLogic for TypeScriptsHalt { }; let witness = TypeScriptHaltsWitness { type_script: TypeScript::native_coin(), - input_utxos: primitive_witness.input_utxos.clone(), - output_utxos: primitive_witness.output_utxos.clone(), + input_utxos: primitive_witness.input_utxos.utxos.clone(), + output_utxos: primitive_witness.output_utxos.utxos.clone(), }; let amount_logic: SupportedClaim = SupportedClaim { claim, diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index 18c045eb..f8e4dec8 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -1,5 +1,6 @@ use crate::models::blockchain::transaction::primitive_witness::arbitrary_primitive_witness_with; use crate::models::blockchain::transaction::primitive_witness::PrimitiveWitness; +use crate::models::blockchain::transaction::primitive_witness::SaltedUtxos; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernelField; use crate::models::blockchain::transaction::utxo::Coin; @@ -404,7 +405,7 @@ pub struct TimeLockWitness { /// One timestamp for every input UTXO. Inputs that do not have a time lock are /// assigned timestamp 0, which is automatically satisfied. release_dates: Vec, - input_utxos: Vec, + input_utxos: SaltedUtxos, input_membership_proofs: Vec, transaction_kernel: TransactionKernel, } @@ -413,6 +414,7 @@ impl TimeLockWitness { pub fn from_primitive_witness(transaction_primitive_witness: &PrimitiveWitness) -> Self { let release_dates = transaction_primitive_witness .input_utxos + .utxos .iter() .map(|utxo| { utxo.coins diff --git a/src/models/state/mod.rs b/src/models/state/mod.rs index 96027a37..edb5bb00 100644 --- a/src/models/state/mod.rs +++ b/src/models/state/mod.rs @@ -26,7 +26,7 @@ use self::wallet::wallet_state::WalletState; use self::wallet::wallet_status::WalletStatus; use super::blockchain::block::block_height::BlockHeight; use super::blockchain::block::Block; -use super::blockchain::transaction::primitive_witness::PrimitiveWitness; +use super::blockchain::transaction::primitive_witness::{PrimitiveWitness, SaltedUtxos}; use super::blockchain::transaction::transaction_kernel::TransactionKernel; use super::blockchain::transaction::utxo::{LockScript, Utxo}; use super::blockchain::transaction::validity::TransactionValidationLogic; @@ -548,12 +548,12 @@ impl GlobalState { let secret_input = spending_key.unlock_key.encode(); let mut primitive_witness = PrimitiveWitness { - input_utxos, + input_utxos: SaltedUtxos::new(input_utxos.clone()), input_lock_scripts, type_scripts, lock_script_witnesses: vec![secret_input; spendable_utxos_and_mps.len()], input_membership_proofs, - output_utxos: output_utxos.clone(), + output_utxos: SaltedUtxos::new(output_utxos.clone()), mutator_set_accumulator, kernel: kernel.clone(), }; diff --git a/src/tests/shared.rs b/src/tests/shared.rs index 0031f0d3..78f3a5c8 100644 --- a/src/tests/shared.rs +++ b/src/tests/shared.rs @@ -1,3 +1,4 @@ +use crate::models::blockchain::transaction::primitive_witness::SaltedUtxos; use crate::models::blockchain::type_scripts::neptune_coins::pseudorandom_amount; use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::prelude::twenty_first; @@ -800,12 +801,12 @@ pub fn make_mock_transaction_with_generation_key( .collect_vec(); let output_utxos = receiver_data.into_iter().map(|rd| rd.utxo).collect(); let primitive_witness = PrimitiveWitness { - input_utxos, + input_utxos: SaltedUtxos::new(input_utxos), type_scripts, input_lock_scripts, lock_script_witnesses: spending_key_unlock_keys, input_membership_proofs, - output_utxos, + output_utxos: SaltedUtxos::new(output_utxos), mutator_set_accumulator: tip_msa, kernel: kernel.clone(), }; @@ -926,11 +927,11 @@ pub fn make_mock_block( }; let primitive_witness = PrimitiveWitness { - input_utxos: vec![], + input_utxos: SaltedUtxos::empty(), type_scripts: vec![TypeScript::native_coin()], lock_script_witnesses: vec![], input_membership_proofs: vec![], - output_utxos: vec![coinbase_utxo.clone()], + output_utxos: SaltedUtxos::new(vec![coinbase_utxo.clone()]), mutator_set_accumulator: previous_mutator_set.clone(), input_lock_scripts: vec![], kernel: tx_kernel.clone(), From 88c26dc6a09ab66d25c0a8e9ca882af7732b6560 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Fri, 23 Feb 2024 13:56:45 +0100 Subject: [PATCH 32/33] refactor: Expand trait `SecretWitness` Trait `SecretWitness` is the interface between a blob of data that makes an atomic unit of consensus logic valid, and the VM computation induced by that validity claim. As such it supports the generation of the program, the nondeterminism, and the input. The last one in that list is the new function declarations. Also in this commit: new trait `TypeScriptWitness` specializes `SecretWitness` because for all type scripts the input and output are the same. Specifically, the output is empty, and the input is the triple (transaction kernel mast hash, salted input utxos hash, salted output utxos hash). --- src/models/blockchain/block/validity.rs | 7 +- .../block/validity/coinbase_is_valid.rs | 7 +- .../correct_control_parameter_update.rs | 7 +- .../block/validity/correct_mmr_update.rs | 7 +- .../validity/correct_mutator_set_update.rs | 7 +- .../block/validity/mmr_membership.rs | 6 + .../block/validity/predecessor_is_valid.rs | 7 +- .../block/validity/transaction_is_valid.rs | 7 +- .../transaction/primitive_witness.rs | 3 +- .../validity/kernel_to_lock_scripts.rs | 4 + .../validity/kernel_to_type_scripts.rs | 4 + .../transaction/validity/lockscripts_halt.rs | 12 ++ .../validity/removal_records_integrity.rs | 5 + .../transaction/validity/typescripts_halt.rs | 53 +++++-- src/models/blockchain/type_scripts/mod.rs | 34 ++++- .../blockchain/type_scripts/time_lock.rs | 132 ++++++++++-------- src/models/consensus/mod.rs | 3 + src/models/consensus/tasm/program.rs | 27 +++- 18 files changed, 242 insertions(+), 90 deletions(-) diff --git a/src/models/blockchain/block/validity.rs b/src/models/blockchain/block/validity.rs index 691f1617..0e569d58 100644 --- a/src/models/blockchain/block/validity.rs +++ b/src/models/blockchain/block/validity.rs @@ -1,7 +1,7 @@ use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program}, + triton_vm::program::{NonDeterminism, Program, PublicInput}, twenty_first::{self, shared_math::b_field_element::BFieldElement}, }; use twenty_first::shared_math::bfield_codec::BFieldCodec; @@ -71,4 +71,9 @@ impl SecretWitness for PrincipalBlockValidationWitness { fn subprogram(&self) -> Program { todo!() } + + fn standard_input(&self) -> PublicInput { + todo!() + } + } diff --git a/src/models/blockchain/block/validity/coinbase_is_valid.rs b/src/models/blockchain/block/validity/coinbase_is_valid.rs index 41be8d92..5566363e 100644 --- a/src/models/blockchain/block/validity/coinbase_is_valid.rs +++ b/src/models/blockchain/block/validity/coinbase_is_valid.rs @@ -1,7 +1,7 @@ use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program}, + triton_vm::program::{NonDeterminism, Program, PublicInput}, twenty_first::{self, shared_math::b_field_element::BFieldElement}, }; use twenty_first::shared_math::bfield_codec::BFieldCodec; @@ -24,6 +24,11 @@ impl SecretWitness for CoinbaseIsValidWitness { fn subprogram(&self) -> Program { todo!() } + + fn standard_input(&self) -> PublicInput { + todo!() + } + } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/block/validity/correct_control_parameter_update.rs b/src/models/blockchain/block/validity/correct_control_parameter_update.rs index a8904e6f..f3af0fc4 100644 --- a/src/models/blockchain/block/validity/correct_control_parameter_update.rs +++ b/src/models/blockchain/block/validity/correct_control_parameter_update.rs @@ -1,7 +1,7 @@ use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program}, + triton_vm::program::{NonDeterminism, Program, PublicInput}, twenty_first::shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, }; @@ -23,6 +23,11 @@ impl SecretWitness for CorrectControlParameterUpdateWitness { fn subprogram(&self) -> Program { todo!() } + + fn standard_input(&self) -> PublicInput { + todo!() + } + } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/block/validity/correct_mmr_update.rs b/src/models/blockchain/block/validity/correct_mmr_update.rs index 55738296..18801b2c 100644 --- a/src/models/blockchain/block/validity/correct_mmr_update.rs +++ b/src/models/blockchain/block/validity/correct_mmr_update.rs @@ -2,7 +2,7 @@ use crate::Hash; use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program}, + triton_vm::program::{NonDeterminism, Program, PublicInput}, twenty_first::{ shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, util_types::mmr::mmr_accumulator::MmrAccumulator, @@ -24,6 +24,11 @@ impl SecretWitness for CorrectMmrUpdateWitness { fn subprogram(&self) -> Program { todo!() } + + fn standard_input(&self) -> PublicInput { + todo!() + } + } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/block/validity/correct_mutator_set_update.rs b/src/models/blockchain/block/validity/correct_mutator_set_update.rs index b76619f3..00a4023f 100644 --- a/src/models/blockchain/block/validity/correct_mutator_set_update.rs +++ b/src/models/blockchain/block/validity/correct_mutator_set_update.rs @@ -1,7 +1,7 @@ use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program}, + triton_vm::program::{NonDeterminism, Program, PublicInput}, twenty_first::{bfieldcodec_derive::BFieldCodec, shared_math::b_field_element::BFieldElement}, }; @@ -23,6 +23,11 @@ impl SecretWitness for CorrectMutatorSetUpdateWitness { fn subprogram(&self) -> Program { todo!() } + + fn standard_input(&self) -> PublicInput { + todo!() + } + } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/block/validity/mmr_membership.rs b/src/models/blockchain/block/validity/mmr_membership.rs index 160e6871..c48046c0 100644 --- a/src/models/blockchain/block/validity/mmr_membership.rs +++ b/src/models/blockchain/block/validity/mmr_membership.rs @@ -7,6 +7,7 @@ use crate::models::consensus::SecretWitness; use crate::models::consensus::SupportedClaim; use crate::triton_vm::program::Program; use tasm_lib::triton_vm::program::NonDeterminism; +use tasm_lib::triton_vm::program::PublicInput; use tasm_lib::twenty_first::shared_math::b_field_element::BFieldElement; use tasm_lib::twenty_first::util_types::mmr::mmr_membership_proof::MmrMembershipProof; @@ -23,6 +24,11 @@ impl SecretWitness for MmrMembershipWitness { fn subprogram(&self) -> Program { todo!() } + + fn standard_input(&self) -> PublicInput { + todo!() + } + } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/block/validity/predecessor_is_valid.rs b/src/models/blockchain/block/validity/predecessor_is_valid.rs index 79481ff7..0fcb940f 100644 --- a/src/models/blockchain/block/validity/predecessor_is_valid.rs +++ b/src/models/blockchain/block/validity/predecessor_is_valid.rs @@ -1,7 +1,7 @@ use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program}, + triton_vm::program::{NonDeterminism, Program, PublicInput}, twenty_first::shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, }; @@ -23,6 +23,11 @@ impl SecretWitness for PredecessorIsValidWitness { fn subprogram(&self) -> Program { todo!() } + + fn standard_input(&self) -> PublicInput { + todo!() + } + } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/block/validity/transaction_is_valid.rs b/src/models/blockchain/block/validity/transaction_is_valid.rs index 4453588c..c087450f 100644 --- a/src/models/blockchain/block/validity/transaction_is_valid.rs +++ b/src/models/blockchain/block/validity/transaction_is_valid.rs @@ -1,7 +1,7 @@ use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program}, + triton_vm::program::{NonDeterminism, Program, PublicInput}, twenty_first::shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, }; @@ -23,6 +23,11 @@ impl SecretWitness for TransactionIsValidWitness { fn subprogram(&self) -> Program { todo!() } + + fn standard_input(&self) -> PublicInput { + todo!() + } + } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index 1f0e1df2..c3a5ec08 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -12,6 +12,7 @@ use proptest_arbitrary_interop::arb; use rand::{thread_rng, Rng}; use serde::{Deserialize, Serialize}; use tasm_lib::{ + structure::tasm_object::TasmObject, twenty_first::{ shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, util_types::algebraic_hasher::AlgebraicHasher, @@ -45,7 +46,7 @@ use super::{ /// is desirable to associate a random but consistent salt for the entire list of UTXOs. /// This situation arises when two distinct consensus programs prove different features /// about the same list of UTXOs. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, GetSize, BFieldCodec)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, GetSize, BFieldCodec, TasmObject)] pub struct SaltedUtxos { pub utxos: Vec, pub salt: [BFieldElement; 3], diff --git a/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs b/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs index e732b77b..2a5bf85c 100644 --- a/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs +++ b/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs @@ -30,6 +30,10 @@ impl SecretWitness for KernelToLockScriptsWitness { fn subprogram(&self) -> Program { todo!() } + + fn standard_input(&self) -> PublicInput { + todo!() + } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] diff --git a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs index fc8772b5..52eb9dd2 100644 --- a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs +++ b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs @@ -28,6 +28,10 @@ impl SecretWitness for KernelToTypeScriptsWitness { fn subprogram(&self) -> Program { todo!() } + + fn standard_input(&self) -> PublicInput { + todo!() + } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] pub struct KernelToTypeScripts { diff --git a/src/models/blockchain/transaction/validity/lockscripts_halt.rs b/src/models/blockchain/transaction/validity/lockscripts_halt.rs index 543b30b1..1e49c15e 100644 --- a/src/models/blockchain/transaction/validity/lockscripts_halt.rs +++ b/src/models/blockchain/transaction/validity/lockscripts_halt.rs @@ -6,6 +6,7 @@ use crate::{ use get_size::GetSize; use itertools::Itertools; use serde::{Deserialize, Serialize}; +use tasm_lib::{triton_vm::program::PublicInput, Digest}; use triton_vm::prelude::{BFieldElement, Claim, NonDeterminism, Program}; use twenty_first::shared_math::bfield_codec::BFieldCodec; @@ -18,6 +19,7 @@ use crate::models::{ pub struct LockScriptHaltsWitness { lock_script: LockScript, nondeterministic_tokens: Vec, + transaction_kernel_mast_hash: Digest, } impl SecretWitness for LockScriptHaltsWitness { @@ -33,6 +35,15 @@ impl SecretWitness for LockScriptHaltsWitness { fn subprogram(&self) -> Program { self.lock_script.program.clone() } + + fn standard_input(&self) -> PublicInput { + PublicInput::new( + self.transaction_kernel_mast_hash + .reversed() + .values() + .to_vec(), + ) + } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, Default, BFieldCodec)] @@ -67,6 +78,7 @@ impl ValidationLogic for LockScriptsHalt { support: ClaimSupport::SecretWitness(LockScriptHaltsWitness { lock_script: lockscript.to_owned(), nondeterministic_tokens, + transaction_kernel_mast_hash: tx_kernel_mast_hash, }), } }) diff --git a/src/models/blockchain/transaction/validity/removal_records_integrity.rs b/src/models/blockchain/transaction/validity/removal_records_integrity.rs index 10e60e6c..6b9186bf 100644 --- a/src/models/blockchain/transaction/validity/removal_records_integrity.rs +++ b/src/models/blockchain/transaction/validity/removal_records_integrity.rs @@ -17,6 +17,7 @@ use std::collections::HashMap; use tasm_lib::memory::{encode_to_memory, FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS}; use tasm_lib::structure::tasm_object::TasmObject; use tasm_lib::traits::compiled_program::CompiledProgram; +use tasm_lib::triton_vm::program::PublicInput; use tasm_lib::twenty_first::util_types::mmr::mmr_membership_proof::MmrMembershipProof; use tasm_lib::twenty_first::util_types::mmr::mmr_trait::Mmr; use triton_vm::prelude::{BFieldElement, Claim, NonDeterminism, Program}; @@ -90,6 +91,10 @@ impl SecretWitness for RemovalRecordsIntegrityWitness { fn subprogram(&self) -> Program { RemovalRecordsIntegrity::program() } + + fn standard_input(&self) -> PublicInput { + PublicInput::new(self.kernel.mast_hash().reversed().values().to_vec()) + } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, FieldCount, BFieldCodec)] diff --git a/src/models/blockchain/transaction/validity/typescripts_halt.rs b/src/models/blockchain/transaction/validity/typescripts_halt.rs index 147ff964..88bfe1b7 100644 --- a/src/models/blockchain/transaction/validity/typescripts_halt.rs +++ b/src/models/blockchain/transaction/validity/typescripts_halt.rs @@ -1,27 +1,35 @@ use crate::{ - models::{blockchain::type_scripts::TypeScript, consensus::mast_hash::MastHash}, + models::{ + blockchain::{ + transaction::{primitive_witness::SaltedUtxos, transaction_kernel::TransactionKernel}, + type_scripts::{TypeScript, TypeScriptWitness}, + }, + consensus::mast_hash::MastHash, + }, prelude::{triton_vm, twenty_first}, }; use get_size::GetSize; use itertools::Itertools; use serde::{Deserialize, Serialize}; +use tasm_lib::triton_vm::program::PublicInput; use triton_vm::prelude::{BFieldElement, Claim, NonDeterminism, Program}; use twenty_first::shared_math::bfield_codec::BFieldCodec; use crate::models::{ - blockchain::transaction::{utxo::Utxo, PrimitiveWitness}, + blockchain::transaction::PrimitiveWitness, consensus::{ClaimSupport, SecretWitness, SupportedClaim, ValidationLogic}, }; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] -pub struct TypeScriptHaltsWitness { +pub struct BasicTypeScriptWitness { type_script: TypeScript, - input_utxos: Vec, - output_utxos: Vec, + input_utxos: SaltedUtxos, + output_utxos: SaltedUtxos, + transaction_kernel: TransactionKernel, } -impl SecretWitness for TypeScriptHaltsWitness { +impl SecretWitness for BasicTypeScriptWitness { fn nondeterminism(&self) -> NonDeterminism { NonDeterminism::default() } @@ -29,11 +37,29 @@ impl SecretWitness for TypeScriptHaltsWitness { fn subprogram(&self) -> Program { self.type_script.program.clone() } + + fn standard_input(&self) -> PublicInput { + self.type_script_standard_input() + } +} + +impl TypeScriptWitness for BasicTypeScriptWitness { + fn transaction_kernel(&self) -> TransactionKernel { + self.transaction_kernel.clone() + } + + fn salted_input_utxos(&self) -> SaltedUtxos { + self.input_utxos.clone() + } + + fn salted_output_utxos(&self) -> SaltedUtxos { + self.output_utxos.clone() + } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] pub struct TypeScriptsHalt { - pub supported_claims: Vec>, + pub supported_claims: Vec>, } impl TypeScriptsHalt { @@ -45,7 +71,7 @@ impl TypeScriptsHalt { } } -impl ValidationLogic for TypeScriptsHalt { +impl ValidationLogic for TypeScriptsHalt { type PrimitiveWitness = PrimitiveWitness; fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> Self { @@ -54,12 +80,13 @@ impl ValidationLogic for TypeScriptsHalt { output: vec![], program_digest: TypeScript::native_coin().hash(), }; - let witness = TypeScriptHaltsWitness { + let witness = BasicTypeScriptWitness { type_script: TypeScript::native_coin(), - input_utxos: primitive_witness.input_utxos.utxos.clone(), - output_utxos: primitive_witness.output_utxos.utxos.clone(), + input_utxos: primitive_witness.input_utxos.clone(), + output_utxos: primitive_witness.output_utxos.clone(), + transaction_kernel: primitive_witness.kernel.clone(), }; - let amount_logic: SupportedClaim = SupportedClaim { + let amount_logic: SupportedClaim = SupportedClaim { claim, support: ClaimSupport::SecretWitness(witness), }; @@ -72,7 +99,7 @@ impl ValidationLogic for TypeScriptsHalt { todo!() } - fn support(&self) -> ClaimSupport { + fn support(&self) -> ClaimSupport { ClaimSupport::MultipleSupports( self.supported_claims .clone() diff --git a/src/models/blockchain/type_scripts/mod.rs b/src/models/blockchain/type_scripts/mod.rs index 2c5020b8..884e5962 100644 --- a/src/models/blockchain/type_scripts/mod.rs +++ b/src/models/blockchain/type_scripts/mod.rs @@ -1,19 +1,27 @@ use crate::{ - models::consensus::{SecretWitness, ValidationLogic}, + models::consensus::{mast_hash::MastHash, SecretWitness, ValidationLogic}, Hash, }; use get_size::GetSize; use serde::{Deserialize, Serialize}; use std::hash::{Hash as StdHash, Hasher as StdHasher}; use tasm_lib::{ - triton_vm::{instruction::LabelledInstruction, program::Program}, - twenty_first::shared_math::bfield_codec::BFieldCodec, + triton_vm::{ + instruction::LabelledInstruction, + program::{Program, PublicInput}, + }, + twenty_first::{ + shared_math::bfield_codec::BFieldCodec, util_types::algebraic_hasher::AlgebraicHasher, + }, Digest, }; use native_currency::native_currency_program; -use super::transaction::primitive_witness::PrimitiveWitness; +use super::transaction::{ + primitive_witness::{PrimitiveWitness, SaltedUtxos}, + transaction_kernel::TransactionKernel, +}; pub mod native_currency; pub mod neptune_coins; @@ -70,3 +78,21 @@ impl TypeScript { } } } + +pub trait TypeScriptWitness { + fn transaction_kernel(&self) -> TransactionKernel; + fn salted_input_utxos(&self) -> SaltedUtxos; + fn salted_output_utxos(&self) -> SaltedUtxos; + + fn type_script_standard_input(&self) -> PublicInput { + PublicInput::new( + [ + self.transaction_kernel().mast_hash().reversed().values(), + Hash::hash(&self.salted_input_utxos()).reversed().values(), + Hash::hash(&self.salted_output_utxos()).reversed().values(), + ] + .concat() + .to_vec(), + ) + } +} diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index f8e4dec8..8b099877 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -1,16 +1,15 @@ +use std::collections::HashMap; + use crate::models::blockchain::transaction::primitive_witness::arbitrary_primitive_witness_with; use crate::models::blockchain::transaction::primitive_witness::PrimitiveWitness; use crate::models::blockchain::transaction::primitive_witness::SaltedUtxos; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernelField; use crate::models::blockchain::transaction::utxo::Coin; -use crate::models::blockchain::transaction::utxo::Utxo; use crate::models::blockchain::transaction::PublicAnnouncement; use crate::models::consensus::mast_hash::MastHash; use crate::models::consensus::SecretWitness; use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; -use crate::util_types::mutator_set::mutator_set_kernel::get_swbf_indices; -use crate::util_types::mutator_set::shared::NUM_TRIALS; use crate::Hash; use get_size::GetSize; use itertools::Itertools; @@ -21,7 +20,11 @@ use proptest::strategy::BoxedStrategy; use proptest::strategy::Strategy; use proptest_arbitrary_interop::arb; use serde::{Deserialize, Serialize}; +use tasm_lib::memory::encode_to_memory; +use tasm_lib::memory::FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS; +use tasm_lib::triton_vm::program::PublicInput; use tasm_lib::twenty_first::prelude::AlgebraicHasher; +use tasm_lib::twenty_first::shared_math::tip5::Tip5; use tasm_lib::{ triton_vm::{ instruction::LabelledInstruction, @@ -36,6 +39,7 @@ use crate::models::consensus::tasm::builtins as tasm; use crate::models::consensus::tasm::program::ConsensusProgram; use super::neptune_coins::NeptuneCoins; +use super::TypeScriptWitness; #[derive(Debug, Clone, Deserialize, Serialize, BFieldCodec, GetSize, PartialEq, Eq)] pub struct TimeLock {} @@ -58,8 +62,14 @@ impl ConsensusProgram for TimeLock { // get in the current program's hash digest let self_digest: Digest = tasm::own_program_digest(); - // read standard input: the transaction kernel mast hash + // read standard input: + // - transaction kernel mast hash + // - input salted utxos digest + // - output salted utxos digest + // (All type scripts take this triple as input.) let tx_kernel_digest: Digest = tasm::tasm_io_read_stdin___digest(); + let input_utxos_digest: Digest = tasm::tasm_io_read_stdin___digest(); + let _output_utxos_digest: Digest = tasm::tasm_io_read_stdin___digest(); // divine the timestamp and authenticate it against the kernel mast hash let leaf_index: u32 = 5; @@ -69,50 +79,22 @@ impl ConsensusProgram for TimeLock { tasm::tasm_hashing_merkle_verify(tx_kernel_digest, leaf_index, leaf, tree_height); // get pointers to objects living in nondeterministic memory: - // - list of input UTXOs - // - list of input UTXOs' membership proofs in the mutator set - // - transaction kernel + // - input Salted UTXOs let input_utxos_pointer: u64 = tasm::tasm_io_read_secin___bfe().value(); - let input_utxos: Vec = - tasm::decode_from_memory(BFieldElement::new(input_utxos_pointer)); - let input_mps_pointer: BFieldElement = tasm::tasm_io_read_secin___bfe(); - let input_mps: Vec = tasm::decode_from_memory(input_mps_pointer); - let transaction_kernel_pointer: BFieldElement = tasm::tasm_io_read_secin___bfe(); - let transaction_kernel: TransactionKernel = - tasm::decode_from_memory(transaction_kernel_pointer); - - // authenticate kernel - let transaction_kernel_hash = Hash::hash(&transaction_kernel); - assert_eq!(transaction_kernel_hash, tx_kernel_digest); - - // compute the inputs (removal records' absolute index sets) - let mut inputs_derived: Vec = Vec::with_capacity(input_utxos.len()); - let mut i: usize = 0; - while i < input_utxos.len() { - let aocl_leaf_index: u64 = input_mps[i].auth_path_aocl.leaf_index; - let receiver_preimage: Digest = input_mps[i].receiver_preimage; - let sender_randomness: Digest = input_mps[i].sender_randomness; - let item: Digest = Hash::hash(&input_utxos[i]); - let index_set: [u128; NUM_TRIALS as usize] = - get_swbf_indices(item, sender_randomness, receiver_preimage, aocl_leaf_index); - inputs_derived.push(Hash::hash(&index_set)); - i += 1; - } - // read inputs (absolute index sets) from kernel - let mut inputs_kernel: Vec = Vec::with_capacity(transaction_kernel.inputs.len()); - i = 0; - while i < transaction_kernel.inputs.len() { - let index_set = transaction_kernel.inputs[i].absolute_indices.to_vec(); - inputs_kernel.push(Hash::hash(&index_set)); - i += 1; - } + // it's important to read the outputs digest too, but we actually don't care about + // the output UTXOs (in this type script) + let _output_utxos_pointer: u64 = tasm::tasm_io_read_secin___bfe().value(); - // authenticate inputs - tasm::tasm_list_unsafeimplu32_multiset_equality(inputs_derived, inputs_kernel); + // authenticate salted input UTXOs against the digest that was read from stdin + let input_salted_utxos: SaltedUtxos = + tasm::decode_from_memory(BFieldElement::new(input_utxos_pointer)); + let input_salted_utxos_digest: Digest = Tip5::hash(&input_salted_utxos); + assert_eq!(input_salted_utxos_digest, input_utxos_digest); // iterate over inputs - i = 0; + let input_utxos = input_salted_utxos.utxos; + let mut i = 0; while i < input_utxos.len() { // get coins let coins: &Vec = &input_utxos[i].coins; @@ -448,21 +430,51 @@ impl TimeLockWitness { impl SecretWitness for TimeLockWitness { fn nondeterminism(&self) -> NonDeterminism { - let individual_tokens = [ - vec![self.transaction_kernel.timestamp], - self.release_dates.encode(), - ] - .concat(); - NonDeterminism::new(individual_tokens).with_digests( - self.transaction_kernel - .mast_path(TransactionKernelField::Timestamp) - .clone(), - ) + let mut memory: HashMap = HashMap::new(); + let input_salted_utxos_address = FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS; + let output_salted_utxos_address = encode_to_memory( + &mut memory, + input_salted_utxos_address, + self.input_utxos.clone(), + ); + encode_to_memory( + &mut memory, + output_salted_utxos_address, + SaltedUtxos::empty(), + ); + let individual_tokens = vec![ + self.transaction_kernel.timestamp, + input_salted_utxos_address, + output_salted_utxos_address, + ]; + let mast_path = self + .transaction_kernel + .mast_path(TransactionKernelField::Timestamp) + .clone(); + NonDeterminism::new(individual_tokens).with_digests(mast_path) } fn subprogram(&self) -> Program { Program::new(&TimeLock::code()) } + + fn standard_input(&self) -> PublicInput { + self.type_script_standard_input() + } +} + +impl TypeScriptWitness for TimeLockWitness { + fn transaction_kernel(&self) -> TransactionKernel { + self.transaction_kernel.clone() + } + + fn salted_input_utxos(&self) -> SaltedUtxos { + self.input_utxos.clone() + } + + fn salted_output_utxos(&self) -> SaltedUtxos { + SaltedUtxos::empty() + } } impl Arbitrary for TimeLockWitness { @@ -555,12 +567,12 @@ mod test { use crate::models::{ blockchain::type_scripts::time_lock::TimeLock, - consensus::{mast_hash::MastHash, tasm::program::ConsensusProgram, SecretWitness}, + consensus::{tasm::program::ConsensusProgram, SecretWitness}, }; use super::TimeLockWitness; - #[proptest] + #[proptest(cases = 1)] fn test_unlocked( #[strategy(1usize..=3)] _num_inputs: usize, #[strategy(1usize..=3)] _num_outputs: usize, @@ -569,10 +581,12 @@ mod test { #[strategy(TimeLockWitness::arbitrary_with((#_release_dates, #_num_outputs, #_num_public_announcements)))] time_lock_witness: TimeLockWitness, ) { - let transaction_kernel = &time_lock_witness.transaction_kernel; - TimeLock::run( - transaction_kernel.mast_hash().reversed().values().as_ref(), - time_lock_witness.nondeterminism(), + assert!( + TimeLock::run( + &time_lock_witness.standard_input().individual_tokens, + time_lock_witness.nondeterminism(), + ).is_ok(), + "time lock program did not halt gracefully" ); } } diff --git a/src/models/consensus/mod.rs b/src/models/consensus/mod.rs index a21c6369..f2694974 100644 --- a/src/models/consensus/mod.rs +++ b/src/models/consensus/mod.rs @@ -63,6 +63,9 @@ impl GetSize for SingleProof { pub trait SecretWitness: Clone + Serialize + PartialEq + Eq + GetSize + BFieldCodec + Sized { + /// The program's (public/standard) input + fn standard_input(&self) -> PublicInput; + /// The non-determinism for the VM that this witness corresponds to fn nondeterminism(&self) -> NonDeterminism; diff --git a/src/models/consensus/tasm/program.rs b/src/models/consensus/tasm/program.rs index ca50afba..317a7aea 100644 --- a/src/models/consensus/tasm/program.rs +++ b/src/models/consensus/tasm/program.rs @@ -1,7 +1,9 @@ +use std::panic::catch_unwind; + +use itertools::Itertools; use tasm_lib::{ triton_vm::{ - instruction::LabelledInstruction, - program::{NonDeterminism, Program}, + instruction::LabelledInstruction, program::{NonDeterminism, Program} }, twenty_first::shared_math::b_field_element::BFieldElement, Digest, @@ -11,6 +13,10 @@ use crate::models::blockchain::shared::Hash; use super::environment; +pub enum ConsensusError { + RustShadowPanic(String) +} + pub trait ConsensusProgram { /// The canonical reference source code for the consensus program, written in the /// subset of rust that the tasm-lang compiler understands. To run this program, call @@ -32,9 +38,18 @@ pub trait ConsensusProgram { fn run( input: &[BFieldElement], nondeterminism: NonDeterminism, - ) -> Vec { - environment::init(input, nondeterminism); - Self::source(); - environment::PUB_OUTPUT.take() + ) -> Result, ConsensusError> { + println!("Running consensus program with input: {}", input.iter().map(|b|b.value()).join(",")); + match catch_unwind(|| + { + environment::init(input, nondeterminism); + Self::source(); + environment::PUB_OUTPUT.take() + }) { + Ok(result) => Result::Ok(result), + Err(e) => { + Result::Err(ConsensusError::RustShadowPanic(format!("{:?}", e))) + }, + } } } From e5734d4443a099ca789dfe4817dbf24aa483d5f5 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Fri, 23 Feb 2024 16:25:24 +0100 Subject: [PATCH 33/33] fix: Time lock tests --- src/models/blockchain/block/validity.rs | 1 - .../block/validity/coinbase_is_valid.rs | 1 - .../correct_control_parameter_update.rs | 1 - .../block/validity/correct_mmr_update.rs | 1 - .../validity/correct_mutator_set_update.rs | 1 - .../block/validity/mmr_membership.rs | 1 - .../block/validity/predecessor_is_valid.rs | 1 - .../block/validity/transaction_is_valid.rs | 1 - .../blockchain/type_scripts/neptune_coins.rs | 10 +++-- .../blockchain/type_scripts/time_lock.rs | 39 +++++++++++++++++-- src/models/consensus/tasm/environment.rs | 9 ++++- src/models/consensus/tasm/program.rs | 30 +++++++------- 12 files changed, 67 insertions(+), 29 deletions(-) diff --git a/src/models/blockchain/block/validity.rs b/src/models/blockchain/block/validity.rs index 0e569d58..26238806 100644 --- a/src/models/blockchain/block/validity.rs +++ b/src/models/blockchain/block/validity.rs @@ -75,5 +75,4 @@ impl SecretWitness for PrincipalBlockValidationWitness { fn standard_input(&self) -> PublicInput { todo!() } - } diff --git a/src/models/blockchain/block/validity/coinbase_is_valid.rs b/src/models/blockchain/block/validity/coinbase_is_valid.rs index 5566363e..2bb6f246 100644 --- a/src/models/blockchain/block/validity/coinbase_is_valid.rs +++ b/src/models/blockchain/block/validity/coinbase_is_valid.rs @@ -28,7 +28,6 @@ impl SecretWitness for CoinbaseIsValidWitness { fn standard_input(&self) -> PublicInput { todo!() } - } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/block/validity/correct_control_parameter_update.rs b/src/models/blockchain/block/validity/correct_control_parameter_update.rs index f3af0fc4..ea3a0cb4 100644 --- a/src/models/blockchain/block/validity/correct_control_parameter_update.rs +++ b/src/models/blockchain/block/validity/correct_control_parameter_update.rs @@ -27,7 +27,6 @@ impl SecretWitness for CorrectControlParameterUpdateWitness { fn standard_input(&self) -> PublicInput { todo!() } - } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/block/validity/correct_mmr_update.rs b/src/models/blockchain/block/validity/correct_mmr_update.rs index 18801b2c..e7392365 100644 --- a/src/models/blockchain/block/validity/correct_mmr_update.rs +++ b/src/models/blockchain/block/validity/correct_mmr_update.rs @@ -28,7 +28,6 @@ impl SecretWitness for CorrectMmrUpdateWitness { fn standard_input(&self) -> PublicInput { todo!() } - } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/block/validity/correct_mutator_set_update.rs b/src/models/blockchain/block/validity/correct_mutator_set_update.rs index 00a4023f..a828f30f 100644 --- a/src/models/blockchain/block/validity/correct_mutator_set_update.rs +++ b/src/models/blockchain/block/validity/correct_mutator_set_update.rs @@ -27,7 +27,6 @@ impl SecretWitness for CorrectMutatorSetUpdateWitness { fn standard_input(&self) -> PublicInput { todo!() } - } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/block/validity/mmr_membership.rs b/src/models/blockchain/block/validity/mmr_membership.rs index c48046c0..612bdd0c 100644 --- a/src/models/blockchain/block/validity/mmr_membership.rs +++ b/src/models/blockchain/block/validity/mmr_membership.rs @@ -28,7 +28,6 @@ impl SecretWitness for MmrMembershipWitness { fn standard_input(&self) -> PublicInput { todo!() } - } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/block/validity/predecessor_is_valid.rs b/src/models/blockchain/block/validity/predecessor_is_valid.rs index 0fcb940f..a0b71a5f 100644 --- a/src/models/blockchain/block/validity/predecessor_is_valid.rs +++ b/src/models/blockchain/block/validity/predecessor_is_valid.rs @@ -27,7 +27,6 @@ impl SecretWitness for PredecessorIsValidWitness { fn standard_input(&self) -> PublicInput { todo!() } - } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/block/validity/transaction_is_valid.rs b/src/models/blockchain/block/validity/transaction_is_valid.rs index c087450f..a3ae086a 100644 --- a/src/models/blockchain/block/validity/transaction_is_valid.rs +++ b/src/models/blockchain/block/validity/transaction_is_valid.rs @@ -27,7 +27,6 @@ impl SecretWitness for TransactionIsValidWitness { fn standard_input(&self) -> PublicInput { todo!() } - } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/models/blockchain/type_scripts/neptune_coins.rs b/src/models/blockchain/type_scripts/neptune_coins.rs index c90384a9..514fe13a 100644 --- a/src/models/blockchain/type_scripts/neptune_coins.rs +++ b/src/models/blockchain/type_scripts/neptune_coins.rs @@ -314,14 +314,18 @@ impl Display for NeptuneCoins { pub fn pseudorandom_amount(seed: [u8; 32]) -> NeptuneCoins { let mut rng: StdRng = SeedableRng::from_seed(seed); let number: [u32; 4] = rng.gen(); - NeptuneCoins(U32s::new(number)) + let mut nau = U32s::new(number); + for _ in 0..10 { + nau.div_two(); + } + NeptuneCoins(nau) } impl<'a> Arbitrary<'a> for NeptuneCoins { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let mut nau: U32s = U32s::new(u.arbitrary()?); - while nau > NeptuneCoins::conversion_factor() * 42000000.into() { - nau = U32s::new(u.arbitrary()?); + for _ in 0..10 { + nau.div_two(); } Ok(NeptuneCoins(nau)) } diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index 8b099877..23b41180 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -451,7 +451,9 @@ impl SecretWitness for TimeLockWitness { .transaction_kernel .mast_path(TransactionKernelField::Timestamp) .clone(); - NonDeterminism::new(individual_tokens).with_digests(mast_path) + NonDeterminism::new(individual_tokens) + .with_digests(mast_path) + .with_ram(memory) } fn subprogram(&self) -> Program { @@ -562,6 +564,8 @@ impl Arbitrary for TimeLockWitness { #[cfg(test)] mod test { + use std::time::{SystemTime, UNIX_EPOCH}; + use proptest::{collection::vec, strategy::Just}; use test_strategy::proptest; @@ -572,7 +576,7 @@ mod test { use super::TimeLockWitness; - #[proptest(cases = 1)] + #[proptest(cases = 5)] fn test_unlocked( #[strategy(1usize..=3)] _num_inputs: usize, #[strategy(1usize..=3)] _num_outputs: usize, @@ -585,8 +589,37 @@ mod test { TimeLock::run( &time_lock_witness.standard_input().individual_tokens, time_lock_witness.nondeterminism(), - ).is_ok(), + ) + .is_ok(), "time lock program did not halt gracefully" ); } + + fn now() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64 + } + + #[proptest(cases = 5)] + fn test_locked( + #[strategy(1usize..=3)] _num_inputs: usize, + #[strategy(1usize..=3)] _num_outputs: usize, + #[strategy(1usize..=3)] _num_public_announcements: usize, + #[strategy(vec(now()+1000*60*60*24..now()+1000*60*60*24*7, #_num_inputs))] + _release_dates: Vec, + #[strategy(TimeLockWitness::arbitrary_with((#_release_dates, #_num_outputs, #_num_public_announcements)))] + time_lock_witness: TimeLockWitness, + ) { + println!("now: {}", now()); + assert!( + TimeLock::run( + &time_lock_witness.standard_input().individual_tokens, + time_lock_witness.nondeterminism(), + ) + .is_err(), + "time lock program failed to panic" + ); + } } diff --git a/src/models/consensus/tasm/environment.rs b/src/models/consensus/tasm/environment.rs index cc26d380..a3bd52a9 100644 --- a/src/models/consensus/tasm/environment.rs +++ b/src/models/consensus/tasm/environment.rs @@ -24,7 +24,11 @@ thread_local! { pub(super) static PROGRAM_DIGEST: RefCell = RefCell::new(Digest::default()); } -pub(crate) fn init(input: &[BFieldElement], nondeterminism: NonDeterminism) { +pub(crate) fn init( + program_digest: Digest, + input: &[BFieldElement], + nondeterminism: NonDeterminism, +) { let mut pub_input_reversed = input.to_vec(); pub_input_reversed.reverse(); let mut inidividual_tokens_reversed = nondeterminism.individual_tokens; @@ -48,4 +52,7 @@ pub(crate) fn init(input: &[BFieldElement], nondeterminism: NonDeterminism, ) -> Result, ConsensusError> { - println!("Running consensus program with input: {}", input.iter().map(|b|b.value()).join(",")); - match catch_unwind(|| - { - environment::init(input, nondeterminism); - Self::source(); - environment::PUB_OUTPUT.take() - }) { - Ok(result) => Result::Ok(result), - Err(e) => { - Result::Err(ConsensusError::RustShadowPanic(format!("{:?}", e))) - }, - } + println!( + "Running consensus program with input: {}", + input.iter().map(|b| b.value()).join(",") + ); + let emulation_result = catch_unwind(|| { + environment::init(Self::hash(), input, nondeterminism); + Self::source(); + environment::PUB_OUTPUT.take() + }); + match emulation_result { + Ok(result) => Result::Ok(result), + Err(e) => Result::Err(ConsensusError::RustShadowPanic(format!("{:?}", e))), + } } }