Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

### Changes

- [BREAKING] Added a `serial_number_block` argument to `fee::pay_fee` and `fee::create_and_fund_fee_note`. Multisigs use the signed proposal block to keep fee-note serial numbers stable across execution reference blocks; other standard auth components use the execution reference block ([#3836](https://github.com/0xMiden/protocol/issues/3836)).
- Added a check that the guardian public key is not one of the approver public keys ([#3764](https://github.com/0xMiden/protocol/pull/3764)).
- [BREAKING] Updated the Miden VM and crypto crate family to v0.31.0 and `midenc-hir-type` to v0.13.0. Execution proofs now include a format version and compatible VM and PVM verifier roots, and protocol deserialization rejects unversioned proof bytes from earlier releases. Verifier outcomes now report separate VM and precompile security parameters ([#3806](https://github.com/0xMiden/protocol/pull/3806)).
- [BREAKING] Updated the Miden VM and crypto crate family to v0.30.0 and `midenc-hir-type` to v0.12.0. `LocalTransactionProver::new` now takes `miden_prover::Prover`, `CoreLibrary` exposes one merged package, and `TransactionVerifier::verify` now returns `VerificationOutcome` so callers can handle outstanding precompile work ([#3782](https://github.com/0xMiden/protocol/pull/3782)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub use {is_signer} from miden::standards::auth::multisig
#! a zero verification base fee no note is created. Because the fee is paid before the transaction
#! summary is created, the fee note and the vault withdrawal funding it are covered by the approver
#! signatures.
#! The fee note's serial number uses the block bound by the summary, so it stays unchanged at a
#! newer execution reference block when the account nonce is unchanged.
#!
#! Inputs:
#! Operand stack: [AUTH_ARGS]
Expand Down Expand Up @@ -48,6 +50,10 @@ pub proc auth_tx_multisig(auth_args: word)
exec.signature::estimate_multisig_authentication_cycles
# => [num_extra_cycles, CONVERSION_INFO, block_number, SALT]

# Use the signed block for the fee note and keep it for summary creation.
dup.5 movdn.5
# => [num_extra_cycles, CONVERSION_INFO, block_number, block_number, SALT]

exec.fee::pay_fee drop
# => [block_number, SALT]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ pub proc auth_network_transaction(auth_args: word)
# => [num_extra_cycles, CONVERSION_INFO, collected_fee_amount, is_fee_asset_id_match,
# sponsor_unlimited, pad(16)]

exec.tx::get_reference_block_number movdn.5
# => [num_extra_cycles, CONVERSION_INFO, serial_number_block, collected_fee_amount,
# is_fee_asset_id_match, sponsor_unlimited, pad(16)]

exec.fee::pay_fee
# => [sponsored_fee_amount, collected_fee_amount, is_fee_asset_id_match, sponsor_unlimited, pad(16)]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use miden::protocol::active_account
use miden::protocol::native_account
use miden::protocol::tx
use miden::core::word
use miden::standards::fee

Expand Down Expand Up @@ -41,6 +42,9 @@ pub proc auth_no_auth
push.NO_AUTH_POST_FEE_CYCLES
# => [num_extra_cycles, CONVERSION_INFO, pad(16)]

exec.tx::get_reference_block_number movdn.5
# => [num_extra_cycles, CONVERSION_INFO, serial_number_block, pad(16)]

exec.fee::pay_fee drop
# => [pad(16)]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use miden::standards::auth::signature
use miden::standards::fee
use miden::protocol::native_account
use miden::protocol::tx

# CONSTANTS
# =================================================================================================
Expand Down Expand Up @@ -71,6 +72,9 @@ pub proc auth_tx(auth_args: word)
swap movdn.5
# => [num_extra_cycles, CONVERSION_INFO, scheme_id, pad(12)]

exec.tx::get_reference_block_number movdn.5
# => [num_extra_cycles, CONVERSION_INFO, serial_number_block, scheme_id, pad(12)]

exec.fee::pay_fee drop
# => [scheme_id, pad(12)]

Expand Down
66 changes: 38 additions & 28 deletions crates/miden-standards/asm/standards/fee/mod.masm
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use miden::protocol::asset
use miden::protocol::native_account
use miden::protocol::output_note
use miden::protocol::tx
use {Asset} from miden::protocol::types
use {Asset, BlockNumber} from miden::protocol::types
use miden::standards::assets::fungible_asset
use miden::standards::fees
use miden::standards::notes::tx_fee
Expand Down Expand Up @@ -52,7 +52,7 @@ const MAX_CONVERTED_AMOUNT_HI_LIMB = 0x80000000
const U32_LIMB_MULTIPLIER = 0x0000000100000000

# Domain-separation tag for the fee note's serial number ("fee" in hex): the serial number is
# derived as hash(FEE_DOMAIN || [ref_block_num, initial_nonce, account_id_suffix,
# derived as hash(FEE_DOMAIN || [serial_number_block, initial_nonce, account_id_suffix,
# account_id_prefix]) with FEE_DOMAIN = [FEE_DOMAIN_TAG, 0, 0, 0], so a fee note's serial number
# cannot collide with serial numbers derived from similar tuples in other contexts.
const FEE_DOMAIN_TAG = 0xFEE
Expand Down Expand Up @@ -263,35 +263,40 @@ end
#! Creates a new TX_FEE output note and funds it with the provided asset from the
#! account's vault.
#!
#! The note's serial number is derived as hash(FEE_DOMAIN || [ref_block_num, initial_nonce,
#! The note's serial number is derived as hash(FEE_DOMAIN || [serial_number_block, initial_nonce,
#! account_id_suffix, account_id_prefix]).
#!
#! Inputs: [ASSET_ID, ASSET_VALUE]
#! Inputs: [serial_number_block, ASSET_ID, ASSET_VALUE]
#! Outputs: []
#!
#! Where:
#! - serial_number_block is the block number used to derive the note's serial number.
#! - ASSET_ID is the asset ID of the fee payment asset.
#! - ASSET_VALUE is the value of the fee payment asset.
#!
#! Panics if:
#! - serial_number_block is not a u32.
#! - the account vault holds less of the payment asset than the amount to be paid.
#! - the maximum number of output notes is exceeded.
#!
#! Invocation: exec
pub proc create_and_fund_fee_note(asset: Asset)
pub proc create_and_fund_fee_note(serial_number_block: BlockNumber, asset: Asset)
u32assert
Comment thread
partylikeits1983 marked this conversation as resolved.
# => [serial_number_block, ASSET_ID, ASSET_VALUE]

# derive the note's serial number
exec.native_account::get_id
# => [account_id_suffix, account_id_prefix, ASSET_ID, ASSET_VALUE]
# => [account_id_suffix, account_id_prefix, serial_number_block, ASSET_ID, ASSET_VALUE]

exec.active_account::get_nonce
# => [initial_nonce, account_id_suffix, account_id_prefix, ASSET_ID, ASSET_VALUE]
# => [initial_nonce, account_id_suffix, account_id_prefix, serial_number_block, ASSET_ID, ASSET_VALUE]

exec.tx::get_reference_block_number
# => [ref_block_num, initial_nonce, account_id_suffix, account_id_prefix, ASSET_ID, ASSET_VALUE]
movup.3
# => [serial_number_block, initial_nonce, account_id_suffix, account_id_prefix, ASSET_ID, ASSET_VALUE]

# domain-separate the serial number by hashing the tuple with the FEE domain tag
push.0.0.0 push.FEE_DOMAIN_TAG
# => [FEE_DOMAIN, [ref_block_num, initial_nonce, account_id_suffix, account_id_prefix],
# => [FEE_DOMAIN, [serial_number_block, initial_nonce, account_id_suffix, account_id_prefix],
# ASSET_ID, ASSET_VALUE]

exec.poseidon2::merge
Expand Down Expand Up @@ -346,14 +351,15 @@ end
#! in the cycle estimate and the fee. This prices each network note through its target account's
#! fee policy via an FPI call, which requires that target to be provisioned as a foreign account.
#!
#! Inputs: [num_extra_cycles, CONVERSION_INFO]
#! Inputs: [num_extra_cycles, CONVERSION_INFO, serial_number_block]
#! Outputs: [total_sponsored_fee_amount]
#!
#! Where:
#! - num_extra_cycles is the estimated number of cycles the caller spends after this call until
#! the end of the authentication procedure.
#! - CONVERSION_INFO is [faucet_id_suffix, faucet_id_prefix, rate_num, rate_den], or the empty
#! word if no conversion info was committed (only accepted when the computed fee is zero).
#! - serial_number_block is the block number used to derive the fee note's serial number.
#! - total_sponsored_fee_amount is the total amount the sponsorship step moved into sponsorship
#! notes.
#!
Expand All @@ -365,68 +371,72 @@ end
#! maximum cycle count.
#! - the computed fee is non-zero and CONVERSION_INFO is the empty word.
#! - the computed fee is non-zero and CONVERSION_INFO is not the native conversion info.
#! - the computed fee is non-zero and serial_number_block is not a u32.
#! - the account vault holds less of the fee asset than the amount to be paid.
#! - the maximum number of output notes is exceeded.
#!
#! Invocation: exec
pub proc pay_fee(num_extra_cycles: felt, conversion_info: ConversionInfo) -> felt
pub proc pay_fee(num_extra_cycles: felt, conversion_info: ConversionInfo, serial_number_block: BlockNumber) -> felt
# sponsor any network output notes before the fee is computed, so the sponsorship notes
# this creates are counted by both the cycle estimate and compute_fee.
exec.tx::get_fee_asset_id
# => [FEE_ASSET_ID, num_extra_cycles, CONVERSION_INFO]
# => [FEE_ASSET_ID, num_extra_cycles, CONVERSION_INFO, serial_number_block]

exec.fees::create_network_note_sponsorships
# => [total_sponsored_fee_amount, num_extra_cycles, CONVERSION_INFO]
# => [total_sponsored_fee_amount, num_extra_cycles, CONVERSION_INFO, serial_number_block]

movdn.5
# => [num_extra_cycles, CONVERSION_INFO, total_sponsored_fee_amount]
movdn.6
# => [num_extra_cycles, CONVERSION_INFO, serial_number_block, total_sponsored_fee_amount]

exec.apply_cycle_margins
# => [num_estimated_extra_cycles, CONVERSION_INFO, total_sponsored_fee_amount]
# => [num_estimated_extra_cycles, CONVERSION_INFO, serial_number_block, total_sponsored_fee_amount]

# no output notes are excluded from the fee computation
padw movup.4
# => [num_estimated_extra_cycles, EXCLUDE_NOTES_COMMITMENT, CONVERSION_INFO,
# total_sponsored_fee_amount]
# serial_number_block, total_sponsored_fee_amount]

exec.tx::compute_fee
# => [fee_amount, CONVERSION_INFO, total_sponsored_fee_amount]
# => [fee_amount, CONVERSION_INFO, serial_number_block, total_sponsored_fee_amount]

dup eq.0
if.true
# a zero fee requires no fee note
drop dropw
drop dropw drop
# => [total_sponsored_fee_amount]
else
movdn.4
# => [CONVERSION_INFO, fee_amount, total_sponsored_fee_amount]
# => [CONVERSION_INFO, fee_amount, serial_number_block, total_sponsored_fee_amount]

# a non-zero fee requires committed conversion info
exec.word::testz assertz.err=ERR_FEE_CONVERSION_INFO_MISSING
# => [CONVERSION_INFO, fee_amount, total_sponsored_fee_amount]
# => [CONVERSION_INFO, fee_amount, serial_number_block, total_sponsored_fee_amount]

# pin the payment to the native fee asset at 1-to-1 rate
dupw exec.native_conversion_info assert_eqw.err=ERR_FEE_CONVERSION_INFO_NOT_NATIVE
# => [CONVERSION_INFO, fee_amount, total_sponsored_fee_amount]
# => [CONVERSION_INFO, fee_amount, serial_number_block, total_sponsored_fee_amount]

# convert the fee amount into the payment asset's amount
# (CONVERSION_INFO reads [faucet_id_suffix, faucet_id_prefix, rate_num, rate_den])
movup.2 movup.3 movup.4
# => [fee_amount, rate_den, rate_num, faucet_id_suffix, faucet_id_prefix,
# total_sponsored_fee_amount]
# serial_number_block, total_sponsored_fee_amount]

movup.2 swap
# => [fee_amount, rate_num, rate_den, faucet_id_suffix, faucet_id_prefix,
# total_sponsored_fee_amount]
# serial_number_block, total_sponsored_fee_amount]

exec.convert_amount
# => [payment_amount, faucet_id_suffix, faucet_id_prefix, total_sponsored_fee_amount]
# => [payment_amount, faucet_id_suffix, faucet_id_prefix, serial_number_block, total_sponsored_fee_amount]

movdn.2
# => [faucet_id_suffix, faucet_id_prefix, payment_amount, total_sponsored_fee_amount]
# => [faucet_id_suffix, faucet_id_prefix, payment_amount, serial_number_block, total_sponsored_fee_amount]

exec.fungible_asset::create
# => [ASSET_ID, ASSET_VALUE, total_sponsored_fee_amount]
# => [ASSET_ID, ASSET_VALUE, serial_number_block, total_sponsored_fee_amount]

movup.8
# => [serial_number_block, ASSET_ID, ASSET_VALUE, total_sponsored_fee_amount]

exec.create_and_fund_fee_note
# => [total_sponsored_fee_amount]
Expand Down
9 changes: 6 additions & 3 deletions crates/miden-standards/src/note/tx_fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,23 +139,26 @@ impl TxFeeNote {
/// Derives the serial number that `miden::standards::fee::pay_fee` uses for
/// the TX_FEE note it creates during a transaction.
///
/// The serial number is `hash(FEE_DOMAIN || [ref_block_num, initial_nonce,
/// The serial number is `hash(FEE_DOMAIN || [serial_number_block, initial_nonce,
/// account_id_suffix, account_id_prefix])` with the FEE domain tag `[0xFEE, 0, 0, 0]`. It is
/// unique per (account, nonce) pair and lets clients precompute the note's recipient before
/// executing the transaction, while the domain tag separates it from serial numbers derived
/// from similar tuples in other contexts.
///
/// For multisigs, `serial_number_block` is the block bound by the signed summary. Other
/// standard auth components use the execution reference block.
///
/// This derivation must be kept in sync with `create_and_fund_fee_note` in the
/// `miden::standards::fee` MASM module.
pub fn derive_serial_number(
sender: AccountId,
initial_nonce: Felt,
ref_block_num: BlockNumber,
serial_number_block: BlockNumber,
) -> Word {
// Domain-separation tag for the fee note's serial number ("fee" in hex).
let fee_domain = Word::from([Felt::from(Self::TAG_ID), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
let tuple = Word::from([
Felt::from(ref_block_num.as_u32()),
Felt::from(serial_number_block.as_u32()),
initial_nonce,
sender.suffix(),
sender.prefix().as_felt(),
Expand Down
Loading
Loading