Skip to content

fix(standards): rework SWAP to support public payback notes#2949

Open
JereSalo wants to merge 14 commits into
nextfrom
jere/swap-v2
Open

fix(standards): rework SWAP to support public payback notes#2949
JereSalo wants to merge 14 commits into
nextfrom
jere/swap-v2

Conversation

@JereSalo

@JereSalo JereSalo commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Public payback wasn't possible on SWAP notes: the on-chain script lacked the data needed to construct the payback recipient. This PR embeds the creator account ID in SWAP storage so the consumer can derive the payback P2ID recipient at consume time.

Related Issues:
0xMiden/miden-client#1739 - after this PR we'll be able to tackle this on the client side.
#2950 - Partially solves this issue just for SWAP and for the target_account_id scenario, which we don't send if the payback is private to preserve privacy of the target account (that in the future may be different from the creator account, that's why it makes sense not to reveal that info). We don't change the serial_num logic here nor we solve it for the PSWAP.

JereSalo added 2 commits May 18, 2026 10:45
Public payback notes were unrecoverable from on-chain data: the
previous SWAP precomputed the payback recipient off-line and embedded
only the resulting hash, so the consuming script had no preimage to
register with the advice provider.

Build the payback P2ID recipient at consume time from data available
in SWAP storage so the on-chain script can call p2id::new (which also
registers the recipient preimage in the advice map):

- Embed the creator account ID in storage (hybrid embed, mirroring
  PSWAP) so the consumer reads it directly instead of going through
  active_note::get_sender.
- Derive the payback serial as swap_serial[0] + 1.
- Derive the payback tag from the creator account ID prefix via
  note_tag::create_account_target.

Storage shrinks from 14 to 11 items: the precomputed recipient and tag
are no longer stored. The Rust constructor sets creator_id = sender by
convention.
@JereSalo JereSalo added no changelog This PR does not require an entry in the `CHANGELOG.md` file pr-from-maintainers PRs that come from internal contributors or integration partners. They should be given priority labels May 18, 2026
JereSalo added 4 commits May 18, 2026 15:15
# Conflicts:
#	crates/miden-standards/src/note/swap.rs
Branch SWAP MASM on payback note type so private paybacks store an opaque
precomputed recipient digest (and tag) instead of the creator id. Public
paybacks keep the creator id in plaintext since the consumer needs it to
reconstruct the recipient via p2id::new. The unified 16-felt storage asserts
zero on the slots unused by each branch, making the privacy guarantee
structural rather than convention-based.
Comment thread crates/miden-standards/asm/standards/notes/swap.masm Outdated
@JereSalo JereSalo marked this pull request as ready for review May 20, 2026 21:37

@partylikeits1983 partylikeits1983 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good! Thank you!

The only comment I have is removing the unnecessary masm stack comment

Comment thread crates/miden-standards/asm/standards/notes/swap.masm Outdated
Co-authored-by: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com>

@mmagician mmagician left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, though I'm not sure about the phrasing around the convention of who the payback target is. AFAIU it doesn't need to be the creator

# store note storage into memory starting at address 0
push.0 exec.active_note::get_storage
# store note storage into memory starting at STORAGE_PTR
push.STORAGE_PTR exec.active_note::get_storage

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nice thx

Comment on lines +203 to +204
/// Account ID of the payback receiver. Today this is the SWAP creator by convention, but
/// the script does not enforce it: it can be any account in a future iteration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

by which convention? the API explicitly allows setting an arbitrary AccountId:

pub fn new_public(
    requested_asset: Asset,
    payback_target_id: AccountId, # <------ not necessarily sender
    payback_tag: NoteTag,
) -> Self {

Comment on lines +316 to +320
/// Deserializes [`SwapNoteStorage`] from a slice of exactly 16 [`Felt`]s.
impl TryFrom<&[Felt]> for SwapNoteStorage {
type Error = NoteError;

fn try_from(note_storage: &[Felt]) -> Result<Self, Self::Error> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is this ever used outside of tests?

#
# The payback target id could be derived from `active_note::get_sender` since today the target
# equals the SWAP sender, but it's stored explicitly because this slot is meant to represent an
# arbitrary payback target (not necessarily the creator) in a future iteration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

same as below, I think this is already supported in this iteration unless I'm missing sth

Comment on lines +174 to +179
/// | `[0..7]` | Requested asset (key + value) |
/// | `[8..11]` | Payback recipient digest (private mode; zero in public mode) |
/// | `[12]` | Payback note type |
/// | `[13]` | Payback note tag |
/// | `[14]` | Payback target account ID prefix (public mode; zero in private mode) |
/// | `[15]` | Payback target account ID suffix (public mode; zero in private mode) |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Question: instead of putting account ID into elements 14 and 15, would it make sense to put them into elements 8 and 9? This would reduce the "waste" a bit. Or does this create too many complications?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no changelog This PR does not require an entry in the `CHANGELOG.md` file pr-from-maintainers PRs that come from internal contributors or integration partners. They should be given priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants