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
98 changes: 98 additions & 0 deletions src/content/docs/developer/integrations/addresses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
title: Address format
description: Learn how wallet addresses for Phoenix (shielded) and Moonlight (public) are encoded in Dusk.
---


In Dusk, an address is nothing more than the account’s public-key, which is compressed and serialized as raw bytes, and then encoded with `Base58`. As the compressed-point representation is injected into `Base58`, every address maps uniquely to its key(s). The encoding is human-readable while remaining compact and safe.

## Quick reference

| Address type | Underlying key(s) | Alphabet |
|--------------|------------------|----------|
| **Moonlight** (public) | 1 × `BLS12-381 G2` <br>compressed (96 B) | `Base58` |
| **Phoenix** (shielded) | 2 × `Jubjub` points (`A`,`B`) <br>compressed (2 × 32 B) | `Base58` |

---

## Encoding steps

1. **Start from the public key(s)**
* Moonlight: a single point in the `BLS12-381` `G2` group.
* Phoenix: two points (`A` and `B`) on the `Jubjub` curve.
2. **Compress each point** using the curve’s compressed representation (`x`-coordinate plus sign-bit).
3. **Concatenate and serialize** → raw byte string.
4. **`Base58`-encode** the byte string → wallet‐friendly address.

Because the y-coordinate is not stored, it is recomputed on the fly when decoding, guaranteeing that any address that passes `Address::from_str()` is a valid point on its curve.

---

## Decoding & validation

The following program can be taken as a reference:
```rust

use rusk_wallet::{Address, Error};
use std::str::FromStr;

fn main() -> Result<(), Error> {
// These are valid example address strings as they would be shown in our wallet
// They are generated by serializing the respective public keys and encode them
// With bs58 to make the strings small(ish)
let public_address_str =
"qe1FbZxf6YaCAeFNSvL1G82cBhG4Q4gBf4vKYo527Vws3b23jdbBuzKSFsdUHnZeBgsTnyNJLkApEpRyJw87sdzR9g9iESJrG5ZgpCs9jq88m6d4qMY5txGpaXskRQmkzE3";
let shielded_address_str =
"ivmscertKgRyX8wNMJJsQcSVEyPsfSMUQXSAgeAPQXsndqFq9Pmknzhm61QvcEEdxPaGgxDS4RHpb6KKccrnSKN";

// Create a public address from an address string
let public_address = Address::from_str(public_address_str)?;
println!("public address:\n{:?}", public_address);
println!("public key:\n{:?}", public_address.public_key()?);

println!("");

// Create a shielded address from an address string
let shielded_address = Address::from_str(shielded_address_str)?;
println!("shielded address:\n{:?}", shielded_address);
println!(
"shielded key A:\n{:?}",
shielded_address.shielded_key()?.A()
);
println!(
"shielded key B:\n{:?}",
shielded_address.shielded_key()?.B()
);

Ok(())
}
```

This would give the following result:

```bash
public address:
qe1FbZxf6YaCAeFNSvL1G82cBhG4Q4gBf4vKYo527Vws3b23jdbBuzKSFsdUHnZeBgsTnyNJLkApEpRyJw87sdzR9g9iESJrG5ZgpCs9jq88m6d4qMY5txGpaXskRQmkzE3
public key:
PublicKey(G2Affine { x: 0x1376dca3359fcf084a904343591c9c6d420dea1e576a957120af2ea4c46ef9d3bb1394ad3cbc92413c59b1ce518c0850 + 0x0d31d41cb190cb54fab965d3edba820a8e8fc5cdf5df2ef0b17ed7b318c290808564ec96651502cb086f4e2317de8844*u, y: 0x05be43f1835f4e56e3678af726e4e92bfab8fb1fbb2eb8f862027915d33a0aeca65fd805087209bbc2b830cefba14527 + 0x056d8a47bdddda58ceea71ebd6804f45f9a16de9fa1f6c9ff8de510a1d3b6e024c56ee0bdd6113f12686bfd9c92f8689*u, infinity: Choice(0) })

shielded address:
ivmscertKgRyX8wNMJJsQcSVEyPsfSMUQXSAgeAPQXsndqFq9Pmknzhm61QvcEEdxPaGgxDS4RHpb6KKccrnSKN
shielded key A:
ExtendedPoint { u: 0x10c2c9b0d8139578fedc0fb7483050375829a91cdc1f0639b9ce335a6bef1640, v: 0x330343b6cde96d4ba72416ba53f7228c940c929bc675ea86107bafc2af072824, z: 0x0000000000000000000000000000000000000000000000000000000000000001, t1: 0x10c2c9b0d8139578fedc0fb7483050375829a91cdc1f0639b9ce335a6bef1640, t2: 0x330343b6cde96d4ba72416ba53f7228c940c929bc675ea86107bafc2af072824 }
shielded key B:
ExtendedPoint { u: 0x522c0be0e1314820427fd17742e027ec7977e8c8e92e5e917ff97e954f447888, v: 0x65065117420942dc7f73de113a5308d4134b52e84191efdf47b28b6aefee5159, z: 0x0000000000000000000000000000000000000000000000000000000000000001, t1: 0x522c0be0e1314820427fd17742e027ec7977e8c8e92e5e917ff97e954f447888, t2: 0x65065117420942dc7f73de113a5308d4134b52e84191efdf47b28b6aefee5159 }
```

More specifically, the `Address::from_str()` performs:

- `Base58` decoding
- Curve-point decompression (reconstructing `y`)
- Structural checks (expected byte length)
- Error return if any step fails.


## Resources
For further reference, you can look at:
- The [example program](https://github.com/dusk-network/phoenix/blob/master/core/src/keys/public.rs).
- [`rusk_wallet::address`](https://github.com/dusk-network/rusk/blob/master/rusk-wallet/src/wallet/address.rs) for the full parsing routine.
2 changes: 1 addition & 1 deletion src/sidebars/developerSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default function sidebar(currentPath) {
{ label: "RUES", href: '/developer/integrations/rues' },
{ label: "Integrate with Exchanges", href: '/developer/integrations/exchanges' },
{ label: "Hashing Algorithms", href: '/developer/integrations/hashing-algorithms' },
{ label: "Historical Events", href: '/developer/integrations/archive_endpoints' },
{ label: "Address Format", href: '/developer/integrations/addresses' },
],
false),
createGroup("Use Digital Identity", currentPath, [
Expand Down