Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 `fee_note_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, fee_note_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, fee_note_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, fee_note_block, scheme_id, pad(12)]

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

Expand Down
70 changes: 42 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 || [fee_note_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 || [fee_note_block, initial_nonce,
#! account_id_suffix, account_id_prefix]).
#!
#! Inputs: [ASSET_ID, ASSET_VALUE]
#! Inputs: [fee_note_block, ASSET_ID, ASSET_VALUE]
#! Outputs: []
#!
#! Where:
#! - fee_note_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:
#! - fee_note_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(fee_note_block: BlockNumber, asset: Asset)
Comment thread
partylikeits1983 marked this conversation as resolved.
Outdated
u32assert
Comment thread
partylikeits1983 marked this conversation as resolved.
# => [fee_note_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, fee_note_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, fee_note_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
# => [fee_note_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, [fee_note_block, initial_nonce, account_id_suffix, account_id_prefix],
# ASSET_ID, ASSET_VALUE]

exec.poseidon2::merge
Expand Down Expand Up @@ -328,6 +333,10 @@ end
#! accounts obtain it via native_conversion_info, which needs no commitment since it is read
#! from the reference block.
#!
#! Multisigs pass the block bound by the signed summary as fee_note_block. Other standard auth
#! components pass the execution reference block. This affects only the fee note's serial number;
#! fee computation and foreign account reads still use the execution reference block.
#!
Comment thread
partylikeits1983 marked this conversation as resolved.
Outdated
#! The fee amount is computed via the compute_fee kernel procedure. num_extra_cycles only needs
#! to cover the number of cycles the authentication procedure spends after this procedure
#! returns (e.g. transaction summary creation, hashing and signature verification, see
Expand All @@ -346,14 +355,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, fee_note_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).
#! - fee_note_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 +375,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 fee_note_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, fee_note_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, fee_note_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, fee_note_block]

movdn.5
# => [num_extra_cycles, CONVERSION_INFO, total_sponsored_fee_amount]
movdn.6
# => [num_extra_cycles, CONVERSION_INFO, fee_note_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, fee_note_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]
# fee_note_block, total_sponsored_fee_amount]

exec.tx::compute_fee
# => [fee_amount, CONVERSION_INFO, total_sponsored_fee_amount]
# => [fee_amount, CONVERSION_INFO, fee_note_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, fee_note_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, fee_note_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, fee_note_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]
# fee_note_block, total_sponsored_fee_amount]

movup.2 swap
# => [fee_amount, rate_num, rate_den, faucet_id_suffix, faucet_id_prefix,
# total_sponsored_fee_amount]
# fee_note_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, fee_note_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, fee_note_block, total_sponsored_fee_amount]

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

movup.8
# => [fee_note_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 || [fee_note_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, `fee_note_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,
fee_note_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(fee_note_block.as_u32()),
initial_nonce,
sender.suffix(),
sender.prefix().as_felt(),
Expand Down
Loading
Loading