Skip to content

Commit aad1faf

Browse files
author
sklppy88
committed
init
1 parent d6bb3d3 commit aad1faf

File tree

13 files changed

+176
-30
lines changed

13 files changed

+176
-30
lines changed

yarn-project/aztec.js/src/rpc_clients/pxe_client.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Note,
99
NullifierMembershipWitness,
1010
type PXE,
11+
SiblingPath,
1112
SimulatedTx,
1213
Tx,
1314
TxEffect,
@@ -58,6 +59,7 @@ export const createPXEClient = (url: string, fetch = makeFetch([1, 2, 3], false)
5859
TxExecutionRequest,
5960
TxHash,
6061
Buffer32,
62+
SiblingPath,
6163
},
6264
{
6365
EncryptedNoteL2BlockL2Logs,

yarn-project/aztec.js/src/wallet/base_wallet.ts

+9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
type OutgoingNotesFilter,
1111
type PXE,
1212
type PXEInfo,
13+
type SiblingPath,
1314
type SimulatedTx,
1415
type SyncStatus,
1516
type Tx,
@@ -25,6 +26,7 @@ import {
2526
type CompleteAddress,
2627
type Fq,
2728
type Fr,
29+
type L1_TO_L2_MSG_TREE_HEIGHT,
2830
type PartialAddress,
2931
type Point,
3032
} from '@aztec/circuits.js';
@@ -206,4 +208,11 @@ export abstract class BaseWallet implements Wallet {
206208
) {
207209
return this.pxe.getEvents(type, eventMetadata, from, limit, vpks);
208210
}
211+
public getL1ToL2MembershipWitness(
212+
contractAddress: AztecAddress,
213+
messageHash: Fr,
214+
secret: Fr,
215+
): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>]> {
216+
return this.pxe.getL1ToL2MembershipWitness(contractAddress, messageHash, secret);
217+
}
209218
}

yarn-project/circuit-types/src/interfaces/pxe.ts

+16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
type CompleteAddress,
44
type Fq,
55
type Fr,
6+
type L1_TO_L2_MSG_TREE_HEIGHT,
67
type PartialAddress,
78
type Point,
89
} from '@aztec/circuits.js';
@@ -19,6 +20,7 @@ import { type L2Block } from '../l2_block.js';
1920
import { type GetUnencryptedLogsResponse, type L1EventPayload, type LogFilter } from '../logs/index.js';
2021
import { type IncomingNotesFilter } from '../notes/incoming_notes_filter.js';
2122
import { type ExtendedNote, type OutgoingNotesFilter, type UniqueNote } from '../notes/index.js';
23+
import { type SiblingPath } from '../sibling_path/sibling_path.js';
2224
import { type NoteProcessorStats } from '../stats/stats.js';
2325
import { type SimulatedTx, type Tx, type TxHash, type TxReceipt } from '../tx/index.js';
2426
import { type TxEffect } from '../tx_effect.js';
@@ -239,6 +241,20 @@ export interface PXE {
239241
*/
240242
getIncomingNotes(filter: IncomingNotesFilter): Promise<UniqueNote[]>;
241243

244+
/**
245+
* Fetches an L1 to L2 message from the node.
246+
* @param contractAddress - Address of a contract by which the message was emitted.
247+
* @param messageHash - Hash of the message.
248+
* @param secret - Secret used to compute a nullifier.
249+
* @dev Contract address and secret are only used to compute the nullifier to get non-nullified messages
250+
* @returns The l1 to l2 membership witness (index of message in the tree and sibling path).
251+
*/
252+
getL1ToL2MembershipWitness(
253+
contractAddress: AztecAddress,
254+
messageHash: Fr,
255+
secret: Fr,
256+
): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>]>;
257+
242258
/**
243259
* Gets outgoing notes of accounts registered in this PXE based on the provided filter.
244260
* @param filter - The filter to apply to the notes.

yarn-project/circuit-types/src/messaging/l1_to_l2_message.ts

+37
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
import { type L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/circuits.js';
2+
import { computeL1ToL2MessageNullifier } from '@aztec/circuits.js/hash';
3+
import { type AztecAddress } from '@aztec/foundation/aztec-address';
14
import { sha256ToField } from '@aztec/foundation/crypto';
25
import { Fr } from '@aztec/foundation/fields';
36
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
47

8+
import { type AztecNode } from '../interfaces/aztec-node.js';
9+
import { MerkleTreeId } from '../merkle_tree_id.js';
10+
import { type SiblingPath } from '../sibling_path/index.js';
511
import { L1Actor } from './l1_actor.js';
612
import { L2Actor } from './l2_actor.js';
713

@@ -70,3 +76,34 @@ export class L1ToL2Message {
7076
return new L1ToL2Message(L1Actor.random(), L2Actor.random(), Fr.random(), Fr.random());
7177
}
7278
}
79+
80+
// This functionality is not on the node because we do not want to pass the node the secret, and give the node the ability to derive a valid nullifer for an L1 to L2 message.
81+
export async function getNonNullifiedL1ToL2MessageWitness(
82+
node: AztecNode,
83+
contractAddress: AztecAddress,
84+
messageHash: Fr,
85+
secret: Fr,
86+
): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>]> {
87+
let nullifierIndex: bigint | undefined;
88+
let messageIndex = 0n;
89+
let startIndex = 0n;
90+
let siblingPath: SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>;
91+
92+
// We iterate over messages until we find one whose nullifier is not in the nullifier tree --> we need to check
93+
// for nullifiers because messages can have duplicates.
94+
do {
95+
const response = await node.getL1ToL2MessageMembershipWitness('latest', messageHash, startIndex);
96+
if (!response) {
97+
throw new Error(`No non-nullified L1 to L2 message found for message hash ${messageHash.toString()}`);
98+
}
99+
[messageIndex, siblingPath] = response;
100+
101+
const messageNullifier = computeL1ToL2MessageNullifier(contractAddress, messageHash, secret, messageIndex);
102+
103+
nullifierIndex = await node.findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, messageNullifier);
104+
105+
startIndex = messageIndex + 1n;
106+
} while (nullifierIndex !== undefined);
107+
108+
return [messageIndex, siblingPath];
109+
}

yarn-project/cli-wallet/src/cmds/bridge_fee_juice.ts

+20-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { createCompatibleClient } from '@aztec/aztec.js';
2-
import { type AztecAddress } from '@aztec/circuits.js';
32
import { FeeJuicePortalManager, prettyPrintJSON } from '@aztec/cli/utils';
43
import { createEthereumChain, createL1Clients } from '@aztec/ethereum';
4+
import { type AztecAddress } from '@aztec/foundation/aztec-address';
5+
import { Fr } from '@aztec/foundation/fields';
56
import { type DebugLogger, type LogFn } from '@aztec/foundation/log';
67

78
export async function bridgeL1FeeJuice(
@@ -24,9 +25,13 @@ export async function bridgeL1FeeJuice(
2425
// Prepare L2 client
2526
const client = await createCompatibleClient(rpcUrl, debugLogger);
2627

28+
const {
29+
protocolContractAddresses: { feeJuice: feeJuiceAddress },
30+
} = await client.getPXEInfo();
31+
2732
// Setup portal manager
2833
const portal = await FeeJuicePortalManager.new(client, publicClient, walletClient, debugLogger);
29-
const { claimAmount, claimSecret } = await portal.bridgeTokensPublic(recipient, amount, mint);
34+
const { claimAmount, claimSecret, messageHash } = await portal.bridgeTokensPublic(recipient, amount, mint);
3035

3136
if (json) {
3237
const out = {
@@ -40,8 +45,20 @@ export async function bridgeL1FeeJuice(
4045
} else {
4146
log(`Bridged ${claimAmount} fee juice to L2 portal`);
4247
}
43-
log(`claimAmount=${claimAmount},claimSecret=${claimSecret}\n`);
48+
log(`claimAmount=${claimAmount},claimSecret=${claimSecret},messageHash=${messageHash}\n`);
4449
log(`Note: You need to wait for two L2 blocks before pulling them from the L2 side`);
50+
log(`This command will now continually poll every minute for the inclusion of the newly created L1 to L2 message`);
4551
}
52+
53+
const interval = setInterval(async () => {
54+
const witness = await client.getL1ToL2MembershipWitness(feeJuiceAddress, Fr.fromString(messageHash), claimSecret);
55+
if (witness) {
56+
log(`Your bridged fee juice is now available. You can claim it like this:
57+
aztec send claim_public --args ${recipient} ${amount} ${claimSecret} ${witness[0]} -ca ${feeJuiceAddress} -c FeeJuice -sk $SECRET_KEY
58+
`);
59+
clearInterval(interval);
60+
}
61+
}, 60_000);
62+
4663
return claimSecret;
4764
}

yarn-project/cli-wallet/src/cmds/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -379,15 +379,15 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL
379379
address,
380380
secretKey,
381381
rpcUrl,
382-
fields,
382+
body: noteFields,
383383
hash,
384384
} = options;
385385
const artifactPath = await artifactPathFromPromiseOrAlias(artifactPathPromise, contractAddress, db);
386386
const client = await createCompatibleClient(rpcUrl, debugLogger);
387387
const account = await createOrRetrieveAccount(client, address, db, undefined, secretKey);
388388
const wallet = await account.getWallet();
389389

390-
await addNote(wallet, address, contractAddress, noteName, storageFieldName, artifactPath, hash, fields, log);
390+
await addNote(wallet, address, contractAddress, noteName, storageFieldName, artifactPath, hash, noteFields, log);
391391
});
392392

393393
return program;

yarn-project/cli/src/cmds/l1/bridge_erc20.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ export async function bridgeERC20(
2727
// Setup portal manager
2828
const manager = new L1PortalManager(portalAddress, tokenAddress, publicClient, walletClient, debugLogger);
2929
let claimSecret: Fr;
30+
let messageHash: `0x${string}`;
3031
if (privateTransfer) {
31-
({ claimSecret } = await manager.bridgeTokensPrivate(recipient, amount, mint));
32+
({ claimSecret, messageHash } = await manager.bridgeTokensPrivate(recipient, amount, mint));
3233
} else {
33-
({ claimSecret } = await manager.bridgeTokensPublic(recipient, amount, mint));
34+
({ claimSecret, messageHash } = await manager.bridgeTokensPublic(recipient, amount, mint));
3435
}
3536

3637
if (json) {
@@ -46,7 +47,7 @@ export async function bridgeERC20(
4647
} else {
4748
log(`Bridged ${amount} tokens to L2 portal`);
4849
}
49-
log(`claimAmount=${amount},claimSecret=${claimSecret}\n`);
50+
log(`claimAmount=${amount},claimSecret=${claimSecret}\n,messageHash=${messageHash}`);
5051
log(`Note: You need to wait for two L2 blocks before pulling them from the L2 side`);
5152
}
5253
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { type AztecAddress, type Fr, createCompatibleClient } from '@aztec/aztec.js';
2+
import { type DebugLogger, type LogFn } from '@aztec/foundation/log';
3+
4+
export async function getL1ToL2MessageWitness(
5+
rpcUrl: string,
6+
contractAddress: AztecAddress,
7+
messageHash: Fr,
8+
secret: Fr,
9+
debugLogger: DebugLogger,
10+
log: LogFn,
11+
) {
12+
const client = await createCompatibleClient(rpcUrl, debugLogger);
13+
const messageWitness = await client.getL1ToL2MembershipWitness(contractAddress, messageHash, secret);
14+
15+
log(
16+
messageWitness === undefined
17+
? `
18+
L1 to L2 Message not found.
19+
`
20+
: `
21+
L1 to L2 message index: ${messageWitness[0]}
22+
L1 to L2 message sibling path: ${messageWitness[1]}
23+
`,
24+
);
25+
}

yarn-project/cli/src/cmds/pxe/index.ts

+13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
logJson,
88
parseAztecAddress,
99
parseEthereumAddress,
10+
parseField,
1011
parseFieldFromHexString,
1112
parseOptionalAztecAddress,
1213
parseOptionalInteger,
@@ -165,6 +166,18 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL
165166
await blockNumber(options.rpcUrl, debugLogger, log);
166167
});
167168

169+
program
170+
.command('get-l1-to-l2-message-witness')
171+
.description('Gets a L1 to L2 message witness.')
172+
.requiredOption('-ca, --contract-address <address>', 'Aztec address of the contract.', parseAztecAddress)
173+
.requiredOption('--message-hash <messageHash>', 'The L1 to L2 message hash.', parseField)
174+
.requiredOption('--secret <secret>', 'The secret used to claim the L1 to L2 message', parseField)
175+
.addOption(pxeOption)
176+
.action(async ({ contractAddress, messageHash, secret, rpcUrl }) => {
177+
const { getL1ToL2MessageWitness } = await import('./get_l1_to_l2_message_witness.js');
178+
await getL1ToL2MessageWitness(rpcUrl, contractAddress, messageHash, secret, debugLogger, log);
179+
});
180+
168181
program
169182
.command('get-node-info')
170183
.description('Gets the information of an aztec node at a URL.')

yarn-project/cli/src/utils/portal_manager.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
export interface L2Claim {
1818
claimSecret: Fr;
1919
claimAmount: Fr;
20+
messageHash: `0x${string}`;
2021
}
2122

2223
function stringifyEthAddress(address: EthAddress | Hex, name?: string) {
@@ -94,13 +95,17 @@ export class FeeJuicePortalManager {
9495

9596
this.logger.info('Sending L1 Fee Juice to L2 to be claimed publicly');
9697
const args = [to.toString(), amount, claimSecretHash.toString()] as const;
98+
99+
const { result: messageHash } = await this.contract.simulate.depositToAztecPublic(args);
100+
97101
await this.publicClient.waitForTransactionReceipt({
98102
hash: await this.contract.write.depositToAztecPublic(args),
99103
});
100104

101105
return {
102106
claimAmount: new Fr(amount),
103107
claimSecret,
108+
messageHash,
104109
};
105110
}
106111

@@ -163,13 +168,34 @@ export class L1PortalManager {
163168

164169
await this.tokenManager.approve(amount, this.contract.address, 'TokenPortal');
165170

171+
let messageHash: `0x${string}`;
172+
166173
if (privateTransfer) {
174+
const secret = Fr.random();
175+
const secretHash = computeSecretHash(secret);
167176
this.logger.info('Sending L1 tokens to L2 to be claimed privately');
177+
({ result: messageHash } = await this.contract.simulate.depositToAztecPrivate([
178+
secretHash.toString(),
179+
amount,
180+
claimSecretHash.toString(),
181+
]));
182+
168183
await this.publicClient.waitForTransactionReceipt({
169-
hash: await this.contract.write.depositToAztecPrivate([Fr.ZERO.toString(), amount, claimSecretHash.toString()]),
184+
hash: await this.contract.write.depositToAztecPrivate([
185+
secretHash.toString(),
186+
amount,
187+
claimSecretHash.toString(),
188+
]),
170189
});
190+
this.logger.info(`Redeem shield secret: ${secret.toString()}, secret hash: ${secretHash.toString()}`);
171191
} else {
172192
this.logger.info('Sending L1 tokens to L2 to be claimed publicly');
193+
({ result: messageHash } = await this.contract.simulate.depositToAztecPublic([
194+
to.toString(),
195+
amount,
196+
claimSecretHash.toString(),
197+
]));
198+
173199
await this.publicClient.waitForTransactionReceipt({
174200
hash: await this.contract.write.depositToAztecPublic([to.toString(), amount, claimSecretHash.toString()]),
175201
});
@@ -178,6 +204,7 @@ export class L1PortalManager {
178204
return {
179205
claimAmount: new Fr(amount),
180206
claimSecret,
207+
messageHash,
181208
};
182209
}
183210
}

yarn-project/pxe/src/pxe_http/pxe_http_server.ts

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
Note,
1010
NullifierMembershipWitness,
1111
type PXE,
12+
SiblingPath,
1213
SimulatedTx,
1314
Tx,
1415
TxEffect,
@@ -50,6 +51,7 @@ export function createPXERpcServer(pxeService: PXE): JsonRpcServer {
5051
Note,
5152
ExtendedNote,
5253
UniqueNote,
54+
SiblingPath,
5355
AuthWitness,
5456
L2Block,
5557
TxEffect,

yarn-project/pxe/src/pxe_service/pxe_service.ts

+11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
type PXE,
1818
type PXEInfo,
1919
type PrivateKernelProver,
20+
type SiblingPath,
2021
SimulatedTx,
2122
SimulationError,
2223
TaggedLog,
@@ -27,11 +28,13 @@ import {
2728
type TxReceipt,
2829
UnencryptedTxL2Logs,
2930
UniqueNote,
31+
getNonNullifiedL1ToL2MessageWitness,
3032
isNoirCallStackUnresolved,
3133
} from '@aztec/circuit-types';
3234
import {
3335
AztecAddress,
3436
type CompleteAddress,
37+
type L1_TO_L2_MSG_TREE_HEIGHT,
3538
type PartialAddress,
3639
computeContractClassId,
3740
getContractClassFromArtifact,
@@ -355,6 +358,14 @@ export class PXEService implements PXE {
355358
return Promise.all(extendedNotes);
356359
}
357360

361+
public async getL1ToL2MembershipWitness(
362+
contractAddress: AztecAddress,
363+
messageHash: Fr,
364+
secret: Fr,
365+
): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>]> {
366+
return await getNonNullifiedL1ToL2MessageWitness(this.node, contractAddress, messageHash, secret);
367+
}
368+
358369
public async addNote(note: ExtendedNote, scope?: AztecAddress) {
359370
const owner = await this.db.getCompleteAddress(note.owner);
360371
if (!owner) {

0 commit comments

Comments
 (0)