What are you trying to do?
I'm trying to build a battleships game in aztec.nr. You can see the current status here
The aztec.nr tests worked, but the UI didn't. I then built a typescript test to try to figure out the issue faster. It passed when I only had one wallet but when I refactored the test to use two wallets and I started getting the same error as the UI:
Error: No public key registered for address 0x2cd29c5d4b53dbd2a1bbd09dc110fb82946e51bbdf21ef9f485ed1b567796ad1.
Register it by calling pxe.addAccount(...).
See docs for context: https://docs.aztec.network/developers/resources/debugging/aztecnr-errors#simulation-error-no-public-key-registered-for-address-0x0-register-it-by-calling-pxeregisterrecipient-or-pxeregisteraccount
Caused by: Error: No public key registered for address 0x2cd29c5d4b53dbd2a1bbd09dc110fb82946e51bbdf21ef9f485ed1b567796ad1.
Register it by calling pxe.addAccount(...).
See docs for context: https://docs.aztec.network/developers/resources/debugging/aztecnr-errors#simulation-error-no-public-key-registered-for-address-0x0-register-it-by-calling-pxeregisterrecipient-or-pxeregisteraccount
❯ UtilityExecutionOracle.getCompleteAddress node_modules/@aztec/pxe/dest/contract_function_simulator/oracle/utility_execution_oracle.js:145:19
❯ node_modules/@aztec/simulator/dest/private/acvm/acvm.js:33:23
❯ acvm node_modules/@aztec/simulator/dest/private/acvm/acvm.js:13:36
❯ SimulatorRecorderWrapper.#simulate node_modules/@aztec/simulator/dest/private/circuit_recording/simulator_recorder_wrapper.js:25:22
❯ ContractFunctionSimulator.runUtility node_modules/@aztec/pxe/dest/contract_function_simulator/contract_function_simulator.js:144:41
❯ node_modules/@aztec/pxe/dest/pxe.js:677:17
❯ node_modules/@aztec/pxe/dest/pxe.js:149:32
❯ node_modules/@aztec/foundation/dest/queue/serial_queue.js:58:33
❯ FifoMemoryQueue.process node_modules/@aztec/foundation/dest/queue/base_memory_queue.js:110:17
Code Reference
I managed to reduce the workflow to the following steps so that it failed
- Host creates game
- Guest joins
- Host calls utility
get_status and then I guest the same error as before
It was weird to get that error on a utility function, but then I started digging in. Looking at the code and what the code path looked like, I realized that before the utility function is processed, private notes are synced. Then I realized that if I removed this code from the contract when the guest joined, it stopped failing:
// Store my turn and send it to my opponent
self.storage.private_turns
.at(game_id)
.at(turn)
.at(player)
.initialize(PlayedTurnNote { shot, timestamp })
.deliver_to(opponent, MessageDelivery.ONCHAIN_CONSTRAINED);
This code basically creates a note for the guest that specifies where the shot was and sends it to the host. I continued to investigate and this is what I found:
Basically when a new note is processed, it calls attempt_note_discovery which calls attempt_note_nonce_discovery which calls compute_note_hash_and_nullifier which calls note.compute_nullifier_unconstrained
And for "simple" notes, this is the default implementation:
unconstrained fn compute_nullifier_unconstrained(
self,
owner: aztec::protocol_types::address::AztecAddress,
note_hash_for_nullification: Field,
) -> Field {
let owner_npk_m = aztec::keys::getters::get_public_keys(owner).npk_m;
// We invoke hash as a static trait function rather than calling owner_npk_m.hash() directly
// in the quote to avoid "trait not in scope" compiler warnings.
let owner_npk_m_hash = aztec::protocol_types::traits::Hash::hash(owner_npk_m);
let secret = aztec::keys::getters::get_nsk_app(owner_npk_m_hash);
aztec::protocol_types::hash::poseidon2_hash_with_separator(
[note_hash_for_nullification, secret],
aztec::protocol_types::constants::DOM_SEP__NOTE_NULLIFIER as Field,
)
}
The problem was that the host didn't have the guests (owner) keys, and that when it failed (on the first line).
Claude suggested using [custom_note] instead of [note] and implementing it like this:
impl NoteHash for PlayedTurnNote {
unconstrained fn compute_nullifier_unconstrained(
self,
_owner: AztecAddress,
note_hash_for_nullification: Field,
) -> Field {
// For unconstrained nullifier computation (used during nonce discovery by recipients
// who may not have the owner's keys), we use a deterministic computation based on
// the note hash. This allows the note to be processed without requiring owner's keys.
// Note: This means the actual nullifier will differ from this when the real owner
// nullifies the note, but that's fine because nonce discovery just needs a consistent
// value to identify the note.
poseidon2_hash_with_separator(
[note_hash_for_nullification, 0],
DOM_SEP__NOTE_NULLIFIER as Field,
)
}
}
When I did that, the test started passing. You can see the change here
While the test passed, I believe that the correct behavior would be to be able to share a note, even if it's owned my me
Aztec Version
4.0.0-nightly.20260122
OS
MacOs
Browser (if relevant)
No response
Node Version
24.13.0
Additional Context
No response
What are you trying to do?
I'm trying to build a battleships game in aztec.nr. You can see the current status here
The aztec.nr tests worked, but the UI didn't. I then built a typescript test to try to figure out the issue faster. It passed when I only had one wallet but when I refactored the test to use two wallets and I started getting the same error as the UI:
Code Reference
I managed to reduce the workflow to the following steps so that it failed
get_statusand then I guest the same error as beforeIt was weird to get that error on a utility function, but then I started digging in. Looking at the code and what the code path looked like, I realized that before the utility function is processed, private notes are synced. Then I realized that if I removed this code from the contract when the guest joined, it stopped failing:
This code basically creates a note for the guest that specifies where the shot was and sends it to the host. I continued to investigate and this is what I found:
Basically when a new note is processed, it calls
attempt_note_discoverywhichcalls attempt_note_nonce_discoverywhich callscompute_note_hash_and_nullifierwhichcalls note.compute_nullifier_unconstrainedAnd for "simple" notes, this is the default implementation:
The problem was that the host didn't have the guests (owner) keys, and that when it failed (on the first line).
Claude suggested using
[custom_note]instead of[note]and implementing it like this:When I did that, the test started passing. You can see the change here
While the test passed, I believe that the correct behavior would be to be able to share a note, even if it's owned my me
Aztec Version
4.0.0-nightly.20260122
OS
MacOs
Browser (if relevant)
No response
Node Version
24.13.0
Additional Context
No response