Repository navigation
ARC-0031: Exchange addresses #145
snarkworld
started this conversation in
ARCs
Replies: 1 comment
|
LGTM! |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
arc: 0031
title: Exchange Addresses
authors: Chris Beck chris@aleolabs.io, Stark Riedesel stark@aleolabs.io, Alex Kim alex@aleolabs.io, John Reynolds john@aleolabs.io
discussion: #145
topic: Application
status: Draft
created: 10-19-26
Abstract
This ARC defines an exchange address: a v1 address with the letter
xinserted afteraleo, giving a 64-character string that starts withaleox1. The marker tells a paying wallet that the recipient is an exchange and accepts public transfers only.An exchange address contains the v1 address unchanged, including its checksum. You produce one by inserting a character, and you read one by removing that character. The marker is advisory: the paying wallet reads it and removes it before the address reaches the virtual machine. This ARC requires no change to the protocol, snarkOS, snarkVM consensus, or Leo, and it can't fork the network. v1 addresses are unaffected.
Motivation
An Aleo address carries a public key and nothing else. It can't say how its owner wants to be paid.
That matters most for exchanges. An exchange credits a deposit by reading a mapping, so a public transfer is straightforward to attribute to a customer. A private transfer arrives as a record that the exchange must find by view-key scanning and can't readily attribute. Many exchanges therefore accept public transfers only, and they have no way to say so in the one string they hand out.
This proposal addresses several key objectives:
Specification
Terminology
This ARC uses the requirement keywords MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY as defined in RFC 2119.
aleo1address format that Aleo already uses.aleox1format that this ARC defines.xthat distinguishes an exchange address from a v1 address.Address Format
A v1 address is bech32m with the human-readable part
aleoand a 52-character payload. The payload encodes a point on the Edwards BLS12 curve, serialized as its 32-byte little-endian affine x-coordinate.An exchange address is a v1 address with the marker
xinserted betweenaleoand the1separator. Everything after the separator, including the 6-character checksum, is copied from the v1 address without change:The following grammar uses ABNF, including the case-sensitive
%sliteral from RFC 7405:The checksum in an exchange address is the v1 checksum, which bech32m computes over the human-readable part
aleo. An implementation MUST NOT recompute the checksum overaleox. A string checksummed that way is invalid under this ARC.Keeping the checksum is deliberate. People compare addresses by their leading and trailing characters, so
aleox1ml2xr6f…u8hw5uandaleo1ml2xr6f…u8hw5uare recognizably the same account. A recomputed checksum would replace all 6 trailing characters, and the two forms of one account would look like two accounts. It would also make every implementation depend on a bech32 encoder rather than a string insertion. The cost is that the checksum doesn't cover the marker (see Security & Compliance).Both forms are lowercase only. BIP-173 and BIP-350 also permit an all-uppercase string, which encodes more compactly in a QR code's alphanumeric mode. This ARC doesn't, because snarkVM already rejects an uppercase v1 address. QR codes must therefore use byte mode.
This ARC defines one marker. A later ARC can define another marker by the same mechanism.
Encoding
xat index 4, betweenaleoand1.Decoding
aleox1, remove thexat index 4 and decode the remaining 63 characters as a v1 address. The recipient is an exchange that accepts public transfers only.A decoded exchange address and its v1 form denote the same account.
Validation
A conforming wallet MUST reject an address when any of the following is true:
aleo1.aleox1._.Rule 5 departs from snarkVM's program parser, which accepts
_separators inside an address literal. If a wallet accepted them, two different strings would denote the same payee.A wallet MUST report a rejection to the payer.
Paying an Exchange Address
After a wallet decodes an exchange address, it MUST either send the funds through a function that it knows lands them in the recipient's public balance, or refuse. It MUST NOT remove the marker to proceed.
For
credits.aleo, the payer's own balance determines the function:transfer_publictransfer_private_to_publictransfer_privateandtransfer_public_to_privateproduce a record for the recipient, so a wallet MUST NOT use them. Source visibility is the payer's own choice, and the marker doesn't constrain it.A wallet can't tell from an arbitrary function's signature who ends up owning a record. For a token program or any other program, a wallet MUST refuse unless it knows the function's behavior.
BIP-21 takes the same position with its
req-convention: a payment that ignores a required preference isn't the payment the recipient asked for. A wallet that can't send a public transfer SHOULD say so rather than report a generic failure.Test Cases
In the following tables,
ADDRis the valid v1 addressaleo1ml2xr6fawppd6uaf8gn95uy2fpqqg8gk74k0lu8na7uvayk64v8qu8hw5u. Every vector belongs to that same account.Accepted:
ADDRaleox1ml2xr6fawppd6uaf8gn95uy2fpqqg8gk74k0lu8na7uvayk64v8qu8hw5uRemoving the marker from the second produces the first, character for character.
Rejected:
aleox1ml2xr6fawppd6uaf8gn95uy2fpqqg8gk74k0lu8na7uvayk64v8q7y7vj3aleox.aleox1ml2xr6fawppd6uaf8gn95uy2fpqqg8gk74k0lu8na7uvayk64v8qu8hw5qaleox1ml2xr6fawppd6uaf8gn95uy2fpqqg8gk74k0lu8na7uvayk64v8qu8hw5aleo1.ALEOX1ML2XR6FAWPPD6UAF8GN95UY2FPQQG8GK74K0LU8NA7UVAYK64V8QU8HW5Ualeo1ml2xr6fawppd6uaf8gn95uy2fpqqg8gk74k0lu8na7uvayk64v8qu8hw_5_.The first rejected vector is the one implementers get wrong. It's a well-formed bech32m string with the human-readable part
aleox, and this ARC rejects it.FAQs
The following are commonly asked questions that are intended to provide clarity on the design choices in this ARC.
Why not recompute the checksum over an
aleoxprefix?Treating
aleoxas a genuine human-readable part would make the checksum cover the marker, so deleting thexwould produce an invalid string. This ARC keeps the v1 checksum instead, so the two forms of an account stay visibly identical and implementations need only a string insertion (see Address Format).Why a single marker instead of a general-purpose flag field?
A character after the separator could encode several preference bits. That costs a larger specification, validity rules for combinations, and agreement on preferences nobody has asked for. A later ARC can add markers if the need arises.
Why not enforce the preference on-chain?
The virtual machine could reject a private transfer to an exchange account by reading a recipient-controlled mapping, which a payer couldn't defeat by stripping the marker.
transfer_privatehas no finalize block, though, so the recipient exists only inside the circuit. Rejecting it would need an in-circuit membership proof against registry state. A later ARC can revisit enforcement without changing this encoding.Reference Implementations
An exploratory snarkVM branch validates the encoding: https://github.com/cbeck88/snarkVM/tree/explore/colored-addresses
It adds
AddressMarkerandAddress::{to_marked_string, from_marked_str}inconsole/types/address, and its tests use the vectors in Test Cases verbatim. It leavesFromStr, the program parser,Display, and serde unchanged, and tests that they still reject an exchange address.The branch surfaced two constraints:
FromStr, so aFromStrthat acceptedaleox1would make node and REST JSON accept it too. Decoding a marked address needs a separate entry point.Wallet integration, which selects the transfer function and applies the send-public-or-refuse rule, isn't implemented yet.
Dependencies
The address encoding lives in snarkVM (
console/types/address), and the transfer functions a payer selects between are incredits.aleo. This ARC affects wallets, SDKs, block explorers, and any application that displays or accepts an address.Backwards Compatibility
v1 addresses don't change. No consensus rule, circuit, transition, record format, or serialization changes, so there's no fork risk and no network upgrade is required.
Every deployed snarkVM decoder rejects an exchange address.
Address::from_strrejects it on length:The program parser, which handles program literals and CLI inputs, rejects it because the string doesn't start with
aleo1. Tooling that matches addresses with a pattern such asaleo1[a-z0-9]{58}doesn't match one either.This fail-closed behavior is intended. A wallet that accepted the address and ignored the marker would send the exchange a private transfer that it asked not to receive, and report no error.
The cost is that adoption depends on wallets. An exchange that publishes an exchange address before its depositors' wallets support it gets deposits refused. Exchanges SHOULD NOT publish exchange addresses until the wallets they expect to be paid from support them.
Security & Compliance
The Marker Isn't Enforced
A payer that doesn't conform can disregard the marker, and the exchange still receives the transfer. An exchange MUST NOT rely on the marker for correctness, accounting, or compliance, and MUST still handle an incoming private receipt.
The chain stores a record's owner as an
Address<N>, which is a curve point. The marker never reaches the virtual machine, so no circuit, transition, or validator observes it.The Checksum Doesn't Cover the Marker
Deleting the
xproduces a valid v1 address for the same account, and nothing in the encoding detects that. Any system that trims, extracts, or normalizes an address can drop the marker silently. Stripping it never sends funds to a different account; it only loses the preference.Exchanges that need a preference no intermediate system can remove should treat this format as a convenience rather than a guarantee.
The Marker Identifies the Recipient as an Exchange
An exchange address is a public label. It appears on deposit pages, in QR codes, and on explorer pages, and it tells anyone who sees it that the account belongs to an exchange. An operator who doesn't want that should publish a v1 address.
One Account, Two Address Strings
An exchange address and its v1 counterpart denote the same account, but the two strings aren't equal. A system that keys on the address string, such as an address book, an allowlist, or an explorer index, treats one account as two unless it removes the marker first. Store and compare the v1 form. A block explorer SHOULD accept an exchange address and display the v1 form.
This is the most likely integration defect, because it fails silently and produces a missed credit rather than a rejected payment.
References
req-convention): https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki%sliteral): https://www.rfc-editor.org/rfc/rfc5234, https://www.rfc-editor.org/rfc/rfc7405All reactions