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
2 changes: 1 addition & 1 deletion Cargo.lock

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

22 changes: 11 additions & 11 deletions frame/evm/precompile/shielded-pool/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ fn encode_unshield(
// ─── Convenience: shield then return the current Merkle root ─────────────────

fn do_shield(commitment: [u8; 32], value: u128) {
let input = encode_shield(0, commitment, &[0xAB; 176]);
let input = encode_shield(0, commitment, &[0xAB; 180]);
let mut h = MockHandle::with_value(input, value);
assert_success(ShieldedPoolPrecompile::<Test>::execute(&mut h));
}
Expand Down Expand Up @@ -379,7 +379,7 @@ fn shield_rejects_truncated_input() {
fn shield_rejects_below_min_amount() {
// MinShieldAmount = 100; sending value = 1 should be rejected by the pallet.
new_test_ext().execute_with(|| {
let input = encode_shield(0, [0x11; 32], &[0xAB; 168]);
let input = encode_shield(0, [0x11; 32], &[0xAB; 180]);
let mut h = MockHandle::with_value(input, 1);
expect_error(ShieldedPoolPrecompile::<Test>::execute(&mut h));
});
Expand All @@ -389,7 +389,7 @@ fn shield_rejects_below_min_amount() {
fn shield_stores_commitment_and_updates_balance() {
new_test_ext().execute_with(|| {
let commitment = [0x11; 32];
let input = encode_shield(0, commitment, &[0xAB; 176]);
let input = encode_shield(0, commitment, &[0xAB; 180]);
let mut h = MockHandle::with_value(input, 1_000);
assert_success(ShieldedPoolPrecompile::<Test>::execute(&mut h));

Expand Down Expand Up @@ -433,7 +433,7 @@ fn shield_updates_merkle_root_after_each_insertion() {
#[test]
fn shield_with_zero_value_rejected() {
new_test_ext().execute_with(|| {
let input = encode_shield(0, [0xAA; 32], &[0x00; 168]);
let input = encode_shield(0, [0xAA; 32], &[0x00; 180]);
let mut h = MockHandle::with_value(input, 0);
expect_error(ShieldedPoolPrecompile::<Test>::execute(&mut h));
});
Expand Down Expand Up @@ -462,7 +462,7 @@ fn private_transfer_rejects_empty_proof() {
root,
&[[0x11; 32], [0x22; 32]],
&[[0x33; 32], [0x44; 32]],
&[vec![0xAA; 176], vec![0xBB; 176]],
&[vec![0xAA; 180], vec![0xBB; 180]],
0,
0,
1,
Expand Down Expand Up @@ -505,7 +505,7 @@ fn private_transfer_rejects_mismatched_nullifier_commitment_count() {
root,
&[[0x11; 32], [0x22; 32]], // 2 nullifiers
&[[0x33; 32]], // 1 commitment
&[vec![0xAA; 176]], // 1 memo
&[vec![0xAA; 180]], // 1 memo
0,
0,
1,
Expand All @@ -526,7 +526,7 @@ fn private_transfer_rejects_mismatched_commitment_memo_count() {
root,
&[[0x11; 32], [0x22; 32]], // 2 nullifiers
&[[0x33; 32], [0x44; 32]], // 2 commitments
&[vec![0xAA; 176]], // 1 memo — mismatch
&[vec![0xAA; 180]], // 1 memo — mismatch
0,
0,
1,
Expand All @@ -551,7 +551,7 @@ fn private_transfer_happy_path() {
root,
&[nullifier_1, nullifier_2],
&[commitment_1, commitment_2],
&[vec![0xAA; 176], vec![0xBB; 176]],
&[vec![0xAA; 180], vec![0xBB; 180]],
0,
0,
1,
Expand Down Expand Up @@ -594,7 +594,7 @@ fn private_transfer_rejects_double_spend() {
root,
&[nullifier, [0x02; 32]],
&[[0x03; 32], [0x04; 32]],
&[vec![0xAA; 176], vec![0xBB; 176]],
&[vec![0xAA; 180], vec![0xBB; 180]],
0,
0,
1,
Expand All @@ -619,7 +619,7 @@ fn private_transfer_root_updates_after_outputs() {
root_before,
&[[0x11; 32], [0x22; 32]],
&[[0x33; 32], [0x44; 32]],
&[vec![0xAA; 176], vec![0xBB; 176]],
&[vec![0xAA; 180], vec![0xBB; 180]],
0,
0,
1,
Expand Down Expand Up @@ -825,7 +825,7 @@ fn full_lifecycle_shield_transfer_unshield() {
root_1,
&[nullifier_in, [0x00; 32]],
&[commitment_out_1, commitment_out_2],
&[vec![0xAA; 176], vec![0xBB; 176]],
&[vec![0xAA; 180], vec![0xBB; 180]],
0,
0,
1,
Expand Down
9 changes: 9 additions & 0 deletions frame/shielded-pool/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

All notable changes to `pallet-shielded-pool` will be documented in this file.

## [0.10.0] - 2026-07-10

### Changed
- **`MAX_ENCRYPTED_MEMO_SIZE` 176 → 180** to accommodate the new `circuit_version` field carried in the memo plaintext (see `orbinum-encrypted-memo` 0.7.0). `EncryptedMemo` wire offsets updated: `ciphertext [12..132]`, `tag [132..148]`, `ephPk [148..180]`. The pallet still treats the memo as opaque bytes — only the length bound and slice offsets change.
- **Weights regenerated** (`src/weights.rs`) — the larger memo raises `CommitmentMemos`' `MaxEncodedLen` (`max_size` 226 → 230), changing the `proof_size` of every extrinsic that reads/writes it (`shield`, `shield_batch`, `private_transfer`, `unshield`, `claim_shielded_fees`). Benchmarked on the reference host (`ubuntu-32gb-fsn1-1`, AMD EPYC-Genoa) with `--steps=50 --repeat=20 --chain=dev`. Runtime `spec_version` 5 → 6.

### Breaking
- `EncryptedMemo` now requires exactly 180 bytes; 176-byte memos are rejected. Consensus-affecting (covered by the `spec_version` bump). `transaction_version` is intentionally **not** bumped: the extrinsic SCALE codec is unchanged (the memo arg is still `BoundedVec<u8>` — only its max bound rose), so offline-signed extrinsics still decode identically.

## [0.9.0] - 2026-07-09

### Changed
Expand Down
2 changes: 1 addition & 1 deletion frame/shielded-pool/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pallet-shielded-pool"
version = "0.9.0"
version = "0.10.0"
description = "Shielded pool pallet for private transactions using ZK proofs"
authors = ["Orbinum Team"]
license = "GPL-3.0-or-later"
Expand Down
2 changes: 1 addition & 1 deletion frame/shielded-pool/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ mod benchmarks {
let (caller, asset_id) = setup_benchmark_env::<T>();
let amount: BalanceOf<T> = T::MinShieldAmount::get() * 10u32.into();
let commitment = Commitment([1u8; 32]);
// Memo must be exactly 168 bytes (MAX_ENCRYPTED_MEMO_SIZE): nonce(12) + data(108) + MAC(16) + ephPk(32)
// Memo must be exactly 180 bytes (MAX_ENCRYPTED_MEMO_SIZE): nonce(12) + data(120) + MAC(16) + ephPk(32)
let memo_bytes = vec![0u8; MAX_ENCRYPTED_MEMO_SIZE as usize];
let encrypted_memo = FrameEncryptedMemo(memo_bytes.try_into().unwrap());

Expand Down
6 changes: 2 additions & 4 deletions frame/shielded-pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ pub mod pallet {
/// * `AmountTooSmall` - Amount is below minimum
/// * `MerkleTreeFull` - No more space in the tree
/// * `CommitmentAlreadyExists` - Duplicate commitment
/// * `InvalidMemoSize` - Encrypted memo is not exactly 168 bytes
/// * `InvalidMemoSize` - Encrypted memo is not exactly 180 bytes
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::shield())]
pub fn shield(
Expand Down Expand Up @@ -585,7 +585,7 @@ pub mod pallet {
/// * `NullifierAlreadyUsed` - Double-spend attempt
/// * `InvalidProof` - ZK proof verification failed
/// * `FeeTooLow` - Fee is below `T::Relayer::min_relay_fee()`
/// * `InvalidMemoSize` - Any encrypted memo is not exactly 168 bytes
/// * `InvalidMemoSize` - Any encrypted memo is not exactly 180 bytes
/// * `MemoCommitmentMismatch` - Number of memos does not match number of commitments
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::private_transfer(commitments.len() as u32))]
Expand All @@ -600,8 +600,6 @@ pub mod pallet {
asset_id: u32,
fee: BalanceOf<T>,
relayer: Option<sp_core::H160>,
// Circuit version the spent notes were created under; the proof is
// verified against this version's VK (not merely the active one).
circuit_version: u32,
) -> DispatchResult {
ensure_none(origin)?;
Expand Down
2 changes: 1 addition & 1 deletion frame/shielded-pool/src/operations/private_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ mod tests {
}

fn short_memo() -> EncryptedMemo {
// 32 bytes — too short (not 168)
// 32 bytes — too short (not 180)
EncryptedMemo::new(vec![0x01u8; 32]).unwrap()
}

Expand Down
6 changes: 3 additions & 3 deletions frame/shielded-pool/src/operations/unshield.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl UnshieldOperation {
!CommitmentRepository::exists::<T>(&change_comm),
Error::<T>::CommitmentAlreadyExists
);
// For partial unshield, memo must be valid size (176 bytes).
// For partial unshield, memo must be valid size (180 bytes).
if !change_encrypted_memo.is_empty() {
ensure!(
change_encrypted_memo.is_valid_size(),
Expand Down Expand Up @@ -899,7 +899,7 @@ mod tests {
asset_id,
1000u128,
Commitment::new([0x31u8; 32]),
FrameEncryptedMemo::from_bytes(&[0u8; 176]).unwrap(),
FrameEncryptedMemo::from_bytes(&[0u8; 180]).unwrap(),
));
assert_eq!(tracked(asset_id), pool_physical());
assert_eq!(tracked(asset_id), 1000);
Expand Down Expand Up @@ -933,7 +933,7 @@ mod tests {
fee_note,
50u128,
asset_id,
crate::types::EncryptedMemo::from_bytes(&[0u8; 176]).unwrap(),
crate::types::EncryptedMemo::from_bytes(&[0u8; 180]).unwrap(),
vec![0x01u8; 128],
signals,
1,
Expand Down
24 changes: 12 additions & 12 deletions frame/shielded-pool/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,8 @@ pub type DefaultMerklePath = MerklePath<DEFAULT_TREE_DEPTH>;
// EncryptedMemo (concrete, FRAME-compatible — used in storage & extrinsics)
// ════════════════════════════════════════════════════════════════════════════

/// Max encrypted memo size: `nonce(12) + ciphertext(116) + MAC(16) + ephPk(32) = 176`.
pub const MAX_ENCRYPTED_MEMO_SIZE: u32 = 176;
/// Max encrypted memo size: `nonce(12) + ciphertext(120) + MAC(16) + ephPk(32) = 180`.
pub const MAX_ENCRYPTED_MEMO_SIZE: u32 = 180;

/// Encrypted memo attached to a commitment (ChaCha20-Poly1305).
#[derive(
Expand Down Expand Up @@ -348,46 +348,46 @@ impl EncryptedMemo {
}
}
pub fn ciphertext(&self) -> &[u8] {
// Invariant: see nonce(). ciphertext occupies bytes 12..128.
// Invariant: see nonce(). ciphertext occupies bytes 12..132.
debug_assert_eq!(
self.0.len(),
MAX_ENCRYPTED_MEMO_SIZE as usize,
"EncryptedMemo invariant violated: expected {} bytes, got {}",
MAX_ENCRYPTED_MEMO_SIZE,
self.0.len()
);
if self.0.len() >= 128 {
&self.0[12..128]
if self.0.len() >= 132 {
&self.0[12..132]
} else {
&[]
}
}
pub fn tag(&self) -> &[u8] {
// Layout: nonce(0..12) | ciphertext(12..128) | tag/MAC(128..144) | ephPk(144..176)
// Layout: nonce(0..12) | ciphertext(12..132) | tag/MAC(132..148) | ephPk(148..180)
debug_assert_eq!(
self.0.len(),
MAX_ENCRYPTED_MEMO_SIZE as usize,
"EncryptedMemo invariant violated: expected {} bytes, got {}",
MAX_ENCRYPTED_MEMO_SIZE,
self.0.len()
);
if self.0.len() >= 144 {
&self.0[128..144]
if self.0.len() >= 148 {
&self.0[132..148]
} else {
&[]
}
}
pub fn eph_pk(&self) -> &[u8] {
// Ephemeral BabyJubJub public key (packed, LE) occupies bytes 144..176.
// Ephemeral BabyJubJub public key (packed, LE) occupies bytes 148..180.
debug_assert_eq!(
self.0.len(),
MAX_ENCRYPTED_MEMO_SIZE as usize,
"EncryptedMemo invariant violated: expected {} bytes, got {}",
MAX_ENCRYPTED_MEMO_SIZE,
self.0.len()
);
if self.0.len() >= 176 {
&self.0[144..176]
if self.0.len() >= 180 {
&self.0[148..180]
} else {
&[]
}
Expand Down Expand Up @@ -663,7 +663,7 @@ mod tests {
let bytes = [0x01u8; MAX_ENCRYPTED_MEMO_SIZE as usize];
let memo = EncryptedMemo::from_bytes(&bytes).unwrap();
assert_eq!(memo.nonce().len(), 12);
assert_eq!(memo.ciphertext().len(), 116);
assert_eq!(memo.ciphertext().len(), 120);
assert_eq!(memo.tag().len(), 16);
assert_eq!(memo.eph_pk().len(), 32);
}
Expand Down
Loading
Loading