Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fd450e8
wip: u256 to felt scaling procedure
partylikeits1983 Jan 23, 2026
0a32d83
refactor: pass quotient via stack
partylikeits1983 Jan 23, 2026
db571d5
refactor: procedure comments
partylikeits1983 Jan 23, 2026
f9ab944
refactor: modify tests & use assertz
partylikeits1983 Jan 23, 2026
79393a9
refactor: cleanup test helper inputs
partylikeits1983 Jan 23, 2026
24bb3b1
refactor: cleanup tests
partylikeits1983 Jan 23, 2026
bb9f83b
feat: add inline sanity check test
partylikeits1983 Jan 23, 2026
546f7cb
refactor: cleanup test
partylikeits1983 Jan 23, 2026
a0b2542
refator: simplify and rename to verify_u256_to_native_amount_conversion
partylikeits1983 Jan 25, 2026
64e8e8d
refactor: import FUNGIBLE_ASSET_MAX_AMOUNT & use word::eqz
partylikeits1983 Jan 27, 2026
cfc8404
revert: rename to try_to_u64
partylikeits1983 Jan 27, 2026
d96bb85
refactor: use Felt::MODULUS
partylikeits1983 Jan 27, 2026
39ae54f
refactor: simplify EthAmount
partylikeits1983 Jan 27, 2026
d760564
refactor: rename EthAmountError variants
partylikeits1983 Jan 27, 2026
2fe0022
refactor: renamed functions and refactored errors & conversion
partylikeits1983 Jan 29, 2026
9e3fc38
fix: taplo fmt
partylikeits1983 Jan 29, 2026
a3e6b19
Update crates/miden-testing/tests/agglayer/asset_conversion.rs
partylikeits1983 Jan 30, 2026
b43b561
Update crates/miden-testing/tests/agglayer/asset_conversion.rs
partylikeits1983 Jan 30, 2026
78dd28e
Update crates/miden-testing/tests/agglayer/asset_conversion.rs
partylikeits1983 Jan 30, 2026
27d5829
Update crates/miden-testing/tests/agglayer/asset_conversion.rs
partylikeits1983 Feb 2, 2026
4e8f2cb
Update crates/miden-testing/tests/agglayer/asset_conversion.rs
partylikeits1983 Feb 2, 2026
3ee237d
chore: clean up EthAmountError enum and improve docs
partylikeits1983 Feb 2, 2026
993706e
refactor: cleanup doc comments and test case
partylikeits1983 Feb 4, 2026
e5974a4
refactor: use existing error messages
partylikeits1983 Feb 5, 2026
449f42b
fix: update global_index to use little-endian format
partylikeits1983 Feb 12, 2026
3ea2737
fix: clean up test_utils and crypto_utils files with agglayer-new cha…
partylikeits1983 Feb 12, 2026
cee0624
fix: rm unused function
partylikeits1983 Feb 13, 2026
dc54e31
chore: merge agglayer-new
partylikeits1983 Feb 16, 2026
30f2416
feat: encorporate endianness reveral procedure
mmagician Feb 11, 2026
092e398
chore: merge latest agglayer-new
partylikeits1983 Feb 16, 2026
407699d
feat: clean up tests & add fuzzing test
partylikeits1983 Feb 16, 2026
bce7641
refactor: add byte ordering comment & add testing attribute for from_…
partylikeits1983 Feb 16, 2026
1ed1414
Update crates/miden-testing/tests/agglayer/asset_conversion.rs
partylikeits1983 Feb 17, 2026
3bedfd3
Update crates/miden-testing/tests/agglayer/asset_conversion.rs
partylikeits1983 Feb 17, 2026
63f621d
refactor: cleanup fuzzing test & address comments
partylikeits1983 Feb 17, 2026
53825b5
chore: use EthAmount directly in tests (#2455)
mmagician Feb 18, 2026
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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/miden-agglayer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ miden-utils-sync = { workspace = true }

# Third-party dependencies
primitive-types = { workspace = true }
thiserror = { workspace = true }

[dev-dependencies]
miden-agglayer = { features = ["testing"], path = "." }
Expand Down
2 changes: 1 addition & 1 deletion crates/miden-agglayer/asm/bridge/agglayer_faucet.masm
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ proc build_p2id_output_note
# TODO: implement scale down logic; stubbed out for now
padw loc_loadw_be.BUILD_P2ID_AMOUNT_MEM_LOC_1 padw loc_loadw_be.BUILD_P2ID_AMOUNT_MEM_LOC_0
# => [AMOUNT[0], AMOUNT[1], tag, note_type, RECIPIENT]
exec.asset_conversion::scale_u256_to_native_amount
exec.asset_conversion::verify_u256_to_native_amount_conversion_stubbed
# => [amount, tag, note_type, RECIPIENT]

exec.faucets::distribute
Expand Down
253 changes: 246 additions & 7 deletions crates/miden-agglayer/asm/bridge/asset_conversion.masm
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
use miden::core::math::u64
use miden::core::word
use miden::agglayer::utils
use ::miden::protocol::asset::FUNGIBLE_ASSET_MAX_AMOUNT

# CONSTANTS
# =================================================================================================

const MAX_SCALING_FACTOR=18

# ERRORS
# ERRORS
# =================================================================================================

const ERR_SCALE_AMOUNT_EXCEEDED_LIMIT="maximum scaling factor is 18"
const ERR_X_TOO_LARGE="x must fit into 128 bits (x4..x7 must be 0)"
const ERR_UNDERFLOW="x < y*10^s (underflow detected)"
const ERR_REMAINDER_TOO_LARGE="remainder z must be < 10^s"

const ERR_Y_TOO_LARGE="y exceeds max fungible token amount"

#! Calculate 10^scale where scale is a u8 exponent.
#!
Expand Down Expand Up @@ -105,10 +113,241 @@ pub proc scale_native_amount_to_u256
# => [RESULT_U256[0], RESULT_U256[1]]
end

#! TODO: implement scaling down
#!
#! Inputs: [U256[0], U256[1]]
#! Outputs: [amount]
pub proc scale_u256_to_native_amount
repeat.7 drop end
#! Reverse the limbs and change the byte endianness of the result.
pub proc reverse_limbs_and_change_byte_endianness
# reverse the felts within each word
# [a, b, c, d, e, f, g, h] -> [h, g, f, e, d, c, b, a]
exec.word::reverse
swapw
exec.word::reverse

# change the byte endianness of each felt
repeat.8
exec.utils::swap_u32_bytes
movdn.7
end

# => [RESULT_U256[0], RESULT_U256[1]]
end

#! Subtract two 128-bit integers (little-endian u32 limbs) and assert no underflow.
#!
#! Computes:
#! z = x - y
#! with the constraint:
#! y <= x
#!
#! Inputs: [y0, y1, y2, y3, x0, x1, x2, x3]
#! Outputs: [z0, z1, z2, z3]
#!
#! Panics if:
#! - y > x (ERR_UNDERFLOW)
proc u128_sub_no_underflow
# Put x-word on top for easier access.
swapw
# => [x0, x1, x2, x3, y0, y1, y2, y3]

# ---------------------------------------------------------------------------------------------
# Low 64 bits: (x1,x0) - (y1,y0)
# Arrange args for u64::overflowing_sub as:
# [y1, y0, x1, x0]
# ---------------------------------------------------------------------------------------------
swap
# => [x1, x0, x2, x3, y0, y1, y2, y3]

movup.5
movup.5
swap
# => [y1, y0, x1, x0, x2, x3, y2, y3]

exec.u64::overflowing_sub
# => [borrow_low, z1, z0, x2, x3, y2, y3]

# ---------------------------------------------------------------------------------------------
# High 64 bits (raw): (x3,x2) - (y3,y2)
# Arrange args as:
# [y3, y2, x3, x2]
# ---------------------------------------------------------------------------------------------
movup.3
movup.4
# => [x3, x2, borrow_low, z1, z0, y2, y3]

movup.6
movup.6
swap
# => [y3, y2, x3, x2, borrow_low, z1, z0]

exec.u64::overflowing_sub
# => [underflow_high_raw, t_hi, t_lo, borrow_low, z1, z0]

# ---------------------------------------------------------------------------------------------
# Apply propagated borrow from low 64-bit subtraction:
# (t_hi,t_lo) - borrow_low
# ---------------------------------------------------------------------------------------------
swap.3
push.0
exec.u64::overflowing_sub
# => [underflow_high_borrow, z3, z2, underflow_high_raw, z1, z0]

# Underflow iff either high-half step underflowed.
movup.3 or
assertz.err=ERR_UNDERFLOW
# => [z3, z2, z1, z0]

# Return little-endian limbs.
exec.word::reverse
# => [z0, z1, z2, z3]
end

#! Verify conversion from an AggLayer U256 amount to a Miden native amount (Felt)
#!
#! Specification:
#! Verify that a provided y is the quotient of dividing x by 10^scale_exp:
#! y = floor(x / 10^scale_exp)
#!
#! This procedure does NOT perform division. It proves the quotient is correct by checking:
#! 1) y is within the allowed fungible token amount range
#! 2) y_scaled = y * 10^scale_exp (computed via scale_native_amount_to_u256)
#! 3) z = x - y_scaled (must not underflow, i.e. y_scaled <= x)
#! 4) z fits in 64 bits (upper 192 bits are zero)
#! 5) (z1, z0) < 10^scale_exp (remainder bound)
#!
#! These conditions prove:
#! x = y_scaled + z, with 0 <= z < 10^scale_exp
#! which uniquely implies:
#! y = floor(x / 10^scale_exp)
#!
#! Example (ETH -> Miden base 1e8):
#! - EVM amount: 100 ETH = 100 * 10^18
#! - Miden amount: 100 ETH = 100 * 10^8
#! - Therefore the scale-down factor is:
#! scale = 10^(18 - 8) = 10^10
#! scale_exp = 10
#! - Inputs/expected values:
#! x = 100 * 10^18
#! y = floor(x / 10^10) = 100 * 10^8
#! y_scaled = y * 10^10 = 100 * 10^18
#! z = x - y_scaled = 0
#!
#! NOTE: For efficiency, this verifier enforces x < 2^128 by requiring x4..x7 == 0.
#!
#! Inputs: [x7, x6, x5, x4, x3, x2, x1, x0, scale_exp, y]
#! Where x is encoded as 8 u32 limbs in big-endian order.
#! (x7 is most significant limb and is at the top of the stack)
#! Each limb is expected to contain little-endian bytes.
#! Outputs: [y]
#!
#! Where:
#! - x: The original AggLayer amount as an unsigned 256-bit integer (U256).
#! It is provided on the operand stack as 8 big-endian u32 limbs:
#! x = x0 + x1·2^32 + x2·2^64 + x3·2^96 + x4·2^128 + x5·2^160 + x6·2^192 + x7·2^224
#! - x0..x7: 32-bit limbs of x in big-endian order (x0 is least significant).
#! - scale_exp: The base-10 exponent used for scaling down (an integer in [0, 18]).
#! - y: The provided quotient (Miden native amount) as a Felt interpreted as an unsigned u64.
#! - y_scaled: The 256-bit value y * 10^scale_exp represented as 8 u32 limbs (big-endian).
#! - z: The remainder-like difference z = x - y_scaled (essentially dust that is lost in the
#! conversion due to precision differences). This verifier requires z < 10^scale_exp.
#!
#! Panics if:
#! - scale_exp > 18 (asserted in pow10 via scale_native_amount_to_u256)
#! - y exceeds the max fungible token amount
#! - x does not fit into 128 bits (x4..x7 are not all zero)
#! - x < y * 10^scale_exp (underflow)
#! - z does not fit in 64 bits
#! - (z1, z0) >= 10^scale_exp (remainder too large)
pub proc verify_u256_to_native_amount_conversion

# reverse limbs and byte endianness
exec.reverse_limbs_and_change_byte_endianness
# => [x0, x1, x2, x3, x4, x5, x6, x7, scale_exp, y]

Comment on lines +259 to +263
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently the changes in 30f2416 include both the procedure itself, which was cherry-picked, but also other changes that incorporate that procedure into the rest of the code.
I'm not sure if the rebase will work smoothly afterwards because of this. Ideally, these would have been two separate commits

# =============================================================================================
# Step 0: Enforce x < 2^128
# Constraint: x4 == x5 == x6 == x7 == 0
# =============================================================================================
swapw
exec.word::eqz
assert.err=ERR_X_TOO_LARGE
# => [x0, x1, x2, x3, scale_exp, y]

# =============================================================================================
# Step 1: Enforce y <= MAX_FUNGIBLE_TOKEN_AMOUNT
# Constraint: y <= MAX_FUNGIBLE_TOKEN_AMOUNT
# =============================================================================================
dup.5
push.FUNGIBLE_ASSET_MAX_AMOUNT
lte
# => [is_lte, x0, x1, x2, x3, scale_exp, y]

assert.err=ERR_Y_TOO_LARGE
# => [x0, x1, x2, x3, scale_exp, y]

# =============================================================================================
# Step 2: Compute y_scaled = y * 10^scale_exp
#
# Call:
# scale_native_amount_to_u256(amount=y, target_scale=scale_exp)
# =============================================================================================
movup.4
movup.5
# => [y, scale_exp, x0, x1, x2, x3]

dup.1 dup.1
# => [y, scale_exp, y, scale_exp, x0, x1, x2, x3]

exec.scale_native_amount_to_u256
# => [y_scaled0..y_scaled7, y, scale_exp, x0, x1, x2, x3]

# Drop the upper word as it's guaranteed to be zero since y_scaled will fit in 123 bits
# (amount: 63 bits, 10^target_scale: 60 bits).
swapw dropw
# => [y_scaled0, y_scaled1, y_scaled2, y_scaled3, y, scale_exp, x0, x1, x2, x3]

# =============================================================================================
# Step 3: Compute z = x - y_scaled and prove no underflow
# z := x - y_scaled
# Constraint: y_scaled <= x
# =============================================================================================
movup.5 movup.5
# => [y, scale_exp, y_scaled0, y_scaled1, y_scaled2, y_scaled3, x0, x1, x2, x3]

movdn.9 movdn.9
# => [y_scaled0, y_scaled1, y_scaled2, y_scaled3, x0, x1, x2, x3, y, scale_exp]

exec.u128_sub_no_underflow
# => [z0, z1, z2, z3, y, scale_exp]

# =============================================================================================
# Step 4: Enforce z < 10^scale_exp (remainder bound)
#
# We compare z against 10^scale_exp using a u64 comparison on (z1, z0).
# To make that comparison complete, we must first prove z fits into 64 bits, i.e. z2 == z3 == 0.
#
# This is justified because scale_exp <= 18, so 10^scale_exp <= 10^18 < 2^60.
# Therefore any valid remainder z < 10^scale_exp must be < 2^60 and thus must have z2 == z3 == 0.
# =============================================================================================
exec.word::reverse
# => [z3, z2, z1, z0, y, scale_exp]

assertz.err=ERR_REMAINDER_TOO_LARGE # z3 == 0
assertz.err=ERR_REMAINDER_TOO_LARGE # z2 == 0
# => [z1, z0, y, scale_exp]

movup.3
exec.pow10
# => [scale, z1, z0, y]

u32split
# => [scale_hi, scale_lo, z1, z0, y]

exec.u64::lt
# => [is_lt, y]

assert.err=ERR_REMAINDER_TOO_LARGE
# => [y]
end

# TODO: Rm & use verify_u256_to_native_amount_conversion
pub proc verify_u256_to_native_amount_conversion_stubbed
repeat.7 drop end
end
12 changes: 12 additions & 0 deletions crates/miden-agglayer/src/errors/agglayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ pub const ERR_MSB_NONZERO: MasmError = MasmError::from_static_str("most-signific
/// Error Message: "address limb is not u32"
pub const ERR_NOT_U32: MasmError = MasmError::from_static_str("address limb is not u32");

/// Error Message: "remainder z must be < 10^s"
pub const ERR_REMAINDER_TOO_LARGE: MasmError = MasmError::from_static_str("remainder z must be < 10^s");

/// Error Message: "rollup index must be zero for a mainnet deposit"
pub const ERR_ROLLUP_INDEX_NON_ZERO: MasmError = MasmError::from_static_str("rollup index must be zero for a mainnet deposit");

Expand All @@ -49,7 +52,16 @@ pub const ERR_SCALE_AMOUNT_EXCEEDED_LIMIT: MasmError = MasmError::from_static_st
/// Error Message: "merkle proof verification failed: provided SMT root does not match the computed root"
pub const ERR_SMT_ROOT_VERIFICATION_FAILED: MasmError = MasmError::from_static_str("merkle proof verification failed: provided SMT root does not match the computed root");

/// Error Message: "x < y*10^s (underflow detected)"
pub const ERR_UNDERFLOW: MasmError = MasmError::from_static_str("x < y*10^s (underflow detected)");

/// Error Message: "UPDATE_GER note attachment target account does not match consuming account"
pub const ERR_UPDATE_GER_TARGET_ACCOUNT_MISMATCH: MasmError = MasmError::from_static_str("UPDATE_GER note attachment target account does not match consuming account");
/// Error Message: "UPDATE_GER script expects exactly 8 note storage items"
pub const ERR_UPDATE_GER_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS: MasmError = MasmError::from_static_str("UPDATE_GER script expects exactly 8 note storage items");

/// Error Message: "x must fit into 128 bits (x4..x7 must be 0)"
pub const ERR_X_TOO_LARGE: MasmError = MasmError::from_static_str("x must fit into 128 bits (x4..x7 must be 0)");

/// Error Message: "y exceeds max fungible token amount"
pub const ERR_Y_TOO_LARGE: MasmError = MasmError::from_static_str("y exceeds max fungible token amount");
Loading