Skip to content

Bridge program upgrade: hook data, multiple attesters, ceiling burn limit, burn_public_as_signer method#3

Open
Roee-87 wants to merge 17 commits into
masterfrom
feat/bridge-upgrades
Open

Bridge program upgrade: hook data, multiple attesters, ceiling burn limit, burn_public_as_signer method#3
Roee-87 wants to merge 17 commits into
masterfrom
feat/bridge-upgrades

Conversation

@Roee-87

@Roee-87 Roee-87 commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator

The modifications to the bridge program are intended to enable users to mint private tokens via a wrapper contract. In order to support this feature, we expand the deposit payload array size from 240 byte to 305 bytes in order to accommodate a 65 byte hookData field. The first byte will be used as a flag to indicate whether mint_public_v2 or mint_private_v2 should be used. The next 32 bytes will contain an encoding that will permit a user to atomically mint privately via a wrapper program. The remaining 32 bytes will remain unused.

Furthermore, we add support for minting from multiple source chains by matching the localToken field with its correct attester key. This enables users to mint using deposit attestations generated from both ARC chain and Ethereum mainnet by matching the localToken field of the deposit payload to an attester key via a new mapping called circle_attesters. We also include a configurable ceiling limit for individual burn transactions. Finally, we add a burn_public_as_signer method that explicitly burn funds from the signer.

This PR contains modifications which require deprecating some existing functions and replacing them with nearly identical implementation that include some additional minor changes. The features enabled by this PR are:

  1. Replacing the check_mint_invariants method with check_mint_invariants_v2. The main changes to this new method are:
  • deposit_input: [u8; 305] now encompasses a byte array with 305 elements.
  • assertions that check that the hookDataLen portion of the deposit payload is equal to 65:
assert_eq(deposit_input[236], 0u8);
assert_eq(deposit_input[237], 0u8);
assert_eq(deposit_input[238], 0u8);
assert_eq(deposit_input[239], 65u8);
  • Ensuring that if private mint method from the wrapper contract is invoked (by depositing public funds into the designated wrapper program), that the hookData field is not all 0u8's.
// Ensure that if the recipient address is the wrapper program for private mint,
// then the hookData field cannot be zero.
let hookData: [u8; 32] = [0u8; 32];
for i in 0u32..32u32{
    hookData[i] = deposit_input[241u32 + i];
}
if (recipient_address == PRIVATE_MINT_WRAPPER_ADDRESS) {
    assert_eq(self.caller, PRIVATE_MINT_WRAPPER_ADDRESS);
    assert(hookData != [0u8; 32]);
}
  • The return signature now includes the local_token_contract_address variable, which will be used to select the correct attester key for ECDSA signature verification.
  1. Replacing mint_private with mint_private_v2. NOTE that this preserves the existing private minting feature which exposes the user's Aleo address in the deposit payload. This is unrelated to the private mint via wrapper program implementation.
  • Function signature includes the updated deposit_input: [u8; 305] type.
  • Uses the new check_mint_invariants_v2(deposit_input, digest) method.
  • Checks that the first byte of the hookData uses the correct flag corresponding with the mint_private_v2 method:
let flag_byte: u8 = deposit_input[240];
assert_eq(flag_byte, 1u8);
  • matches the local_token to the correct attester key:
let circle_attester_key: [u8; 20] = circle_attesters.get(local_token);
  1. Replacing mint_public with mint_public_v2.
  • Function signature includes the updated deposit_input: [u8; 305] type.
  • Uses the new check_mint_invariants_v2(deposit_input, digest) method.
  • Checks that the first byte of the hookData uses the correct flag corresponding with the mint_public_v2 method:
let flag_byte: u8 = deposit_input[240];
assert_eq(flag_byte, 0u8);
  • matches the local_token to the correct attester key:
let circle_attester_key: [u8; 20] = circle_attesters.get(local_token);

4.  The existing `burn_public` is modified to include a ceiling amount check in the finalize block:
```Leo
let max_burn_amount: u128 = maximum_burn_amount.get_or_use(true, MAXIMUM_BURN_AMOUNT);
assert(amount <= max_burn_amount);
  1. Adding a burn_public_as_signer method which is a copy of the burn_public method with the exception of the sender field being replaced with `self.signer.

  2. We also add a check for unused bits in the address (indices 253, 254, 255) and make sure they are all set to zero. This is referenced in a comment in the PR.

  3. Private mint and burn wrapper program. This wrapper programs leverages the hookData field of the deposit payload to to mint private USDCx without leaking the final recipient's Aleo address. This is accomplished by first minting public USDCx to the wrapper program and then transferring the funds to the final recipient via transfer_public_to_private in one transaction. In order to ensure that the caller is authorized to call this method, the caller must prove that they are able to generate the 32 byte encoding from the hookData field.
    The second method allows a user to burn private USDCx by first sending private USDCx to the wrapper program via transfer_private_to_public and then calling the bridge's burn_public method in one transaction.

@Roee-87 Roee-87 self-assigned this Apr 16, 2026
@Roee-87 Roee-87 added the enhancement New feature or request label Apr 16, 2026
@Roee-87 Roee-87 marked this pull request as ready for review April 17, 2026 21:44
Comment thread bridge_program/src/main.leo Outdated

const USDCX_PAUSE_KEY: address = aleo16s9af9darj0j5k7fpaxjq0u9fepd6sc4svrkr9vs4d3wlmp5lqyq4h3fpl;
const USDCX_MANAGER_KEY: address = aleo13zt4uq0u09sffnf4ctgu47k5n30txjx2w9cwcqgneapjeqsywsqqu6hspt;
const PRIVATE_MINT_WRAPPER_ADDRESS: address = usdcx_private_mint.aleo;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We can choose whatever program name we want.

@eranrund eranrund left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Mostly looks good, I think there is one important assertion that is missing and another optional one that wouldn't hurt to add.
Thank you!

Comment thread bridge_program/src/main.leo Outdated
Comment thread bridge_program/src/main.leo Outdated
Comment thread bridge_program/src/main.leo Outdated
Comment thread bridge_program/src/main.leo
Comment thread bridge_program/src/main.leo
for i in 0u8..253u8 {
sliced_bits[i] = address_bits[i];
}
for i in 0u8..3u8 {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This check ensures that an invalid address cannot "mask" a valid address by leveraging the unused bits.

Comment thread private_token_wrapper/src/main.leo Outdated
@Roee-87 Roee-87 changed the title added upgrade logic for hookData and multiple attesters USDCx program upgrade Apr 21, 2026
@Roee-87 Roee-87 changed the title USDCx program upgrade Bridge program upgrade: hook data, multiple attesters, ceiling burn limit, burn_public_as_signer method Apr 22, 2026
Comment thread bridge_program/src/main.leo Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants