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
18 changes: 13 additions & 5 deletions docs/integration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,14 +386,22 @@ const archiveProof = await buildArchiveProof(node, blockHash);
#### 2. Sign

```typescript
// Private notes
// Private notes only
const sig = await wallet.signMigrationModeB(
signer, recipient, oldVersion, newVersion, newApp, [fullProof],
signer, recipient, oldVersion, newVersion, newApp,
{ notes },
);

// Public state (owned)
const sig = await wallet.signPublicStateMigrationModeB(
signer, recipient, oldVersion, newVersion, newApp, data, abiType,
// Public state only (owned)
const sig = await wallet.signMigrationModeB(
signer, recipient, oldVersion, newVersion, newApp,
{ publicData: [{ data, abiType }] },
);

// Mixed (public state + private notes in one signature)
const sig = await wallet.signMigrationModeB(
signer, recipient, oldVersion, newVersion, newApp,
{ publicData: [{ data, abiType }], notes },
);
```

Expand Down
6 changes: 3 additions & 3 deletions docs/spec/mode-b-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ Only the true owner of the nullifier hiding key can migrate their notes.
Both private notes and owned public state use a single domain separator (`DOM_SEP__CLAIM_B`). The builder accumulates all data into a single running hash (note hashes and packed public state fields), then signs once:

```
final_hash = poseidon2_hash([...note_hashes, ...packed_public_state_fields])
final_hash = poseidon2_hash([...packed_public_state_hashes, ...note_hashes])
msg = poseidon2_hash([DOM_SEP__CLAIM_B, old_rollup, current_rollup, final_hash, recipient, new_app])
```

Expand Down Expand Up @@ -230,8 +230,8 @@ The `MigrationArchiveRegistry` on the new rollup stores this address (set at dep
A migration webapp will orchestrate the end-to-end Mode B flow. The wallet's role is to expose key management, signing, and key registration primitives. Mode B extends the Mode A wallet requirements (see [Mode A -- Wallet Integration](mode-a-spec.md#wallet-integration)) with the following additional responsibilities:

- **Key registration.** Before the snapshot height H, the wallet must support calling `keyRegistry.register(mpk)` on the old rollup's `MigrationKeyRegistry`. This is a one-time, write-once operation. If a user misses this window, Mode B migration is permanently unavailable for that account. Wallets should prompt registration early and confirm inclusion in a block before H.
- **Nullifier hiding key access.** Mode B requires the wallet to expose the nullifier hiding key (NHK) for address verification. The `MigrationAccount` interface provides `getNhk()` for this purpose. Note: the NHK currently leaves the wallet in raw form. A future improvement could mask the NHK (e.g. `nhk + mask`) so it never leaves the wallet unprotected.
- **Public state signing.** In addition to `signMigrationModeB()` (private notes), the wallet must support `signPublicStateMigrationModeB()` for owned public state migration, which produces a hash of encoded migration data.
- **Nullifier hiding key access.** Mode B requires the wallet to expose the nullifier hiding key (NHK) for the non-nullification check. The `MigrationAccount` interface provides `getNhk()` for this purpose. Note: the NHK currently leaves the wallet in raw form. A future improvement could mask the NHK (e.g. `nhk + mask`) so it never leaves the wallet unprotected.
- **Signing.** `signMigrationModeB()` produces a single signature covering private notes, public state data, or both. The hash input order matches the Noir builder: packed public data fields first, then note hashes.

For key derivation, Browser vs Node environments, and key persistence, see [General Specification -- Wallet Integration](migration-spec.md#wallet-integration-shared).

Expand Down
4 changes: 2 additions & 2 deletions e2e-tests/migration-mode-b.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,9 @@ async function main() {
oldMigrationSigner,
blockHeader.global_variables.version,
new Fr(env.newRollupVersion),
[balanceNote],
newUserManager.address,
newApp.address,
{ notes: [balanceNote] },
);

console.log(` Migration args prepared.\n`);
Expand Down Expand Up @@ -295,9 +295,9 @@ async function main() {
oldMigrationSigner,
blockHeader.global_variables.version,
new Fr(env.newRollupVersion),
[nullifiedNote],
newUserManager.address,
newApp.address,
{ notes: [nullifiedNote] },
);

await expectRevert(
Expand Down
40 changes: 20 additions & 20 deletions e2e-tests/migration-public-mode-b.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,16 +297,14 @@ async function main() {
const oldMigrationSigner = await oldUserWallet.getMigrationSignerFromAddress(
OWNED_STRUCT_MAP_OWNER,
);
const ownedStructMapSignature =
await newUserWallet.signPublicStateMigrationModeB(
oldMigrationSigner,
newUserManager.address,
new Fr(env.oldRollupVersion),
new Fr(env.newRollupVersion),
newApp,
OWNED_STRUCT_MAP,
someStructAbiType,
);
const ownedStructMapSignature = await newUserWallet.signMigrationModeB(
oldMigrationSigner,
newUserManager.address,
new Fr(env.oldRollupVersion),
new Fr(env.newRollupVersion),
newApp,
{ publicData: [{ data: OWNED_STRUCT_MAP, abiType: someStructAbiType }] },
);
await newAppUser.methods
.migrate_to_public_owned_struct_map_mode_b(
ownedStructMapProof,
Expand All @@ -332,16 +330,18 @@ async function main() {
const oldMigrationSigner2 = await oldUserWallet.getMigrationSignerFromAddress(
OWNED_STRUCT_NESTED_MAP_OWNER,
);
const ownedStructNestedMapSignature =
await newUserWallet.signPublicStateMigrationModeB(
oldMigrationSigner2,
newUser2Manager.address,
new Fr(env.oldRollupVersion),
new Fr(env.newRollupVersion),
newApp,
OWNED_STRUCT_NESTED_MAP,
someStructAbiType,
);
const ownedStructNestedMapSignature = await newUserWallet.signMigrationModeB(
oldMigrationSigner2,
newUser2Manager.address,
new Fr(env.oldRollupVersion),
new Fr(env.newRollupVersion),
newApp,
{
publicData: [
{ data: OWNED_STRUCT_NESTED_MAP, abiType: someStructAbiType },
],
},
);
await newAppUser.methods
.migrate_to_public_owned_struct_nested_map_mode_b(
ownedStructNestedMapProof,
Expand Down
4 changes: 2 additions & 2 deletions e2e-tests/nft-migration-mode-b.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,9 @@ async function main() {
oldMigrationSigner,
blockHeader.global_variables.version,
new Fr(env.newRollupVersion),
[activeNote],
newUserManager.address,
newApp.address,
{ notes: [activeNote] },
);

console.log(" Migration args prepared.\n");
Expand Down Expand Up @@ -323,9 +323,9 @@ async function main() {
oldMigrationSigner,
blockHeader.global_variables.version,
new Fr(env.newRollupVersion),
[nullifiedNote],
newUserManager.address,
newApp.address,
{ notes: [nullifiedNote] },
);

const nullifiedTokenId = nullifiedNoteProof.note_proof_data.data.token_id;
Expand Down
5 changes: 2 additions & 3 deletions e2e-tests/nft-migration-public-mode-b.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,13 @@ async function main() {
oldUserManager.address,
);
// For NFT, the signed data is the owner AztecAddress (not a balance amount)
const signature = await newUserWallet.signPublicStateMigrationModeB(
const signature = await newUserWallet.signMigrationModeB(
oldMigrationSigner,
newUserManager.address,
new Fr(env.oldRollupVersion),
new Fr(env.newRollupVersion),
newApp.address,
oldUserManager.address,
ownerAbiType,
{ publicData: [{ data: oldUserManager.address, abiType: ownerAbiType }] },
);

// ============================================================
Expand Down
23 changes: 10 additions & 13 deletions e2e-tests/token-migration-mode-a.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TokenMigrationAppV1Contract } from "./artifacts/TokenMigrationAppV1.js";
import { TokenMigrationAppV2Contract } from "./artifacts/TokenMigrationAppV2.js";
import { Fr } from "@aztec/foundation/curves/bn254";
import { signMigrationModeA } from "../ts/aztec-state-migration/index.js";
import { signMigrationModeA } from "aztec-state-migration/mode-a";
import { deploy } from "./deploy.js";
import {
deployTokenAppPair,
Expand Down Expand Up @@ -132,12 +132,11 @@ async function main() {
// Step 5: Bridge archive root
// ============================================================
console.log("Step 5. Bridging archive root...");
const { l1Result, provenBlockNumber, blockHeader } = await bridgeBlock(
const { provenBlockNumber, blockHeader } = await bridgeBlock(
env,
newArchiveRegistry,
);
console.log(` Proven block: ${l1Result.provenBlockNumber}`);
console.log(` Archive root: ${l1Result.provenArchiveRoot}\n`);
console.log(` Proven block: ${provenBlockNumber}`);

// ============================================================
// Step 6: Prepare migration args
Expand All @@ -155,9 +154,9 @@ async function main() {
);
}

const [migrationNoteProof] = await oldUserWallet.buildMigrationNoteProofs(
const migrationNoteProof = await oldUserWallet.buildMigrationNoteProof(
provenBlockNumber,
lockNotesAndData,
lockNotesAndData[0],
);

const oldMigrationSigner = await oldUserWallet.getMigrationSignerFromAddress(
Expand Down Expand Up @@ -290,11 +289,10 @@ async function main() {
console.log("Step 11. Bridging archive root for public lock note...");

const {
l1Result: l1ResultPublic,
provenBlockNumber: publicProvenBlockNumber,
blockHeader: publicBlockHeader,
} = await bridgeBlock(env, newArchiveRegistry);
console.log(` Proven block: ${l1ResultPublic.provenBlockNumber}\n`);
console.log(` Proven block: ${publicProvenBlockNumber}\n`);

// ============================================================
// Step 12: Get public lock note, filter, build proof
Expand Down Expand Up @@ -325,11 +323,10 @@ async function main() {
);
}

const [publicMigrationNoteProof] =
await oldUserWallet.buildMigrationNoteProofs(
publicProvenBlockNumber,
filteredNotes,
);
const publicMigrationNoteProof = await oldUserWallet.buildMigrationNoteProof(
publicProvenBlockNumber,
filteredNotes[0],
);

const publicSignature = await signMigrationModeA(
oldMigrationSigner,
Expand Down
23 changes: 11 additions & 12 deletions e2e-tests/token-migration-mode-b.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Fr } from "@aztec/foundation/curves/bn254";
import { signMigrationModeB } from "../ts/aztec-state-migration/index.js";
import { signMigrationModeB } from "aztec-state-migration/mode-b";
import { deploy } from "./deploy.js";
import {
deployTokenAppPair,
Expand Down Expand Up @@ -217,11 +217,11 @@ async function main() {
` Active notes: ${balanceNotesActive.length}, Nullified notes: ${balanceNotesNullified.length}`,
);

const balanceNotes = balanceNotesActive.slice(0, 1);
const balanceNote = balanceNotesActive[0];

const fullProofs = await oldUserWallet.buildFullNoteProofs(
const fullProof = await oldUserWallet.buildFullNoteProof(
provenBlockNumber,
balanceNotes,
balanceNote,
(note) => UintNote.fromNote(note),
);

Expand All @@ -238,9 +238,9 @@ async function main() {
oldMigrationSigner,
blockHeader.global_variables.version,
new Fr(env.newRollupVersion),
balanceNotes,
newUserManager.address,
newApp.address,
{ notes: [balanceNote] },
);

console.log(" Migration args prepared.\n");
Expand All @@ -250,8 +250,7 @@ async function main() {
// ============================================================
console.log("Step 9. Calling migrate_mode_b on NEW rollup...");

const noteProof = fullProofs[0];
const migrateAmount = noteProof.note_proof_data.data.value;
const migrateAmount = fullProof.note_proof_data.data.value;
console.log(` Migrating amount: ${migrateAmount}`);

const newBalanceBefore = await newAppUser.methods
Expand All @@ -263,7 +262,7 @@ async function main() {
.migrate_mode_b(
migrateAmount,
signature,
noteProof,
fullProof,
blockHeader,
oldUserManager.address,
publicKeys,
Expand Down Expand Up @@ -297,7 +296,7 @@ async function main() {
.migrate_mode_b(
migrateAmount,
signature,
noteProof,
fullProof,
blockHeader,
oldUserManager.address,
publicKeys,
Expand All @@ -320,19 +319,19 @@ async function main() {

const nullifiedNote = balanceNotesNullified[0];

const [nullifiedNoteProof] = await oldUserWallet.buildFullNoteProofs(
const nullifiedNoteProof = await oldUserWallet.buildFullNoteProof(
provenBlockNumber,
[nullifiedNote],
nullifiedNote,
(note) => UintNote.fromNote(note),
);

const nullifiedNoteSig = await signMigrationModeB(
oldMigrationSigner,
blockHeader.global_variables.version,
new Fr(env.newRollupVersion),
[nullifiedNote],
newUserManager.address,
newApp.address,
{ notes: [nullifiedNote] },
);

const nullifiedAmount = nullifiedNoteProof.note_proof_data.data.value;
Expand Down
5 changes: 2 additions & 3 deletions e2e-tests/token-migration-public-mode-b.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,14 +184,13 @@ async function main() {
const oldMigrationSigner = await oldUserWallet.getMigrationSignerFromAddress(
oldUserManager.address,
);
const signature = await newUserWallet.signPublicStateMigrationModeB(
const signature = await newUserWallet.signMigrationModeB(
oldMigrationSigner,
newUserManager.address,
new Fr(env.oldRollupVersion),
new Fr(env.newRollupVersion),
newApp.address,
MINT_AMOUNT,
balanceAbiType,
{ publicData: [{ data: MINT_AMOUNT, abiType: balanceAbiType }] },
);

// ============================================================
Expand Down
5 changes: 1 addition & 4 deletions ts/aztec-state-migration/mode-b/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,4 @@ export {
buildPublicMapDataProof,
} from "./proofs.js";

export {
signMigrationModeB,
signPublicStateMigrationModeB,
} from "./signature.js";
export { signMigrationModeB } from "./signature.js";
Loading