-
Notifications
You must be signed in to change notification settings - Fork 613
feat(e2e): interactive handshake e2e #24590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
nchamo
merged 4 commits into
merge-train/fairies-v5
from
nchamo/f-787-feate2e-interactive-handshake-end-to-end-flow-wallet-helpers
Jul 8, 2026
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
baba31a
feat(e2e): interactive handshake e2e with in-process responder
nchamo a6352cd
Merge remote-tracking branch 'origin/merge-train/fairies-v5' into nch…
nchamo 5222bca
refactor(e2e): import PXE hook types instead of deriving them
nchamo 5336e8e
docs(standard-contracts): TODO to source request kind from artifact
nchamo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
125 changes: 125 additions & 0 deletions
125
yarn-project/end-to-end/src/automine/delivery/interactive_handshake_responder.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| import { AztecAddress, type CompleteAddress } from '@aztec/aztec.js/addresses'; | ||
| import { DomainSeparator } from '@aztec/constants'; | ||
| import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto/poseidon'; | ||
| import { Schnorr, type SchnorrSignature } from '@aztec/foundation/crypto/schnorr'; | ||
| import { Fq, Fr } from '@aztec/foundation/curves/bn254'; | ||
| import type { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; | ||
| import type { CustomRequest } from '@aztec/pxe/config'; | ||
| import { | ||
| INTERACTIVE_HANDSHAKE_REQUEST_KIND, | ||
| STANDARD_HANDSHAKE_REGISTRY_ADDRESS, | ||
| } from '@aztec/standard-contracts/handshake-registry/constants'; | ||
| import { type PublicKeys, derivePublicKeyFromSecretKey } from '@aztec/stdlib/keys'; | ||
|
|
||
| /** | ||
| * The decoded payload of the registry's interactive-handshake signature request. Note it never carries the sender, | ||
| * so the recipient authorizes the handshake without learning who initiated it. | ||
| */ | ||
| export type InteractiveHandshakeRequest = { | ||
| /** The account whose authorization is being requested. */ | ||
| recipient: AztecAddress; | ||
| chainId: Fr; | ||
| version: Fr; | ||
| /** The x-coordinate of the handshake's ephemeral public key (its y-coordinate is positive by construction). */ | ||
| ephPkX: Fr; | ||
| }; | ||
|
|
||
| /** | ||
| * The recipient's signed authorization for an interactive handshake. Mirrors the registry contract's | ||
| * `RecipientSignature` struct field for field. | ||
| */ | ||
| export type RecipientSignature = { | ||
| /** The recipient's master public keys, bound in-circuit to the recipient's address via `partialAddress`. */ | ||
| publicKeys: PublicKeys; | ||
| partialAddress: Fr; | ||
| /** The x-coordinate of the recipient's master message-signing public key. */ | ||
| mspkX: Fr; | ||
| /** Whether that key's y-coordinate is positive, so the circuit can reconstruct the point from `mspkX`. */ | ||
| mspkYIsPositive: boolean; | ||
| /** The schnorr signature over the handshake message. */ | ||
| signature: SchnorrSignature; | ||
| }; | ||
|
|
||
| /** | ||
| * Parses and validates the registry's interactive-handshake signature request. | ||
| * | ||
| * @throws If the request kind is not {@link INTERACTIVE_HANDSHAKE_REQUEST_KIND}, the issuing contract is not the | ||
| * standard HandshakeRegistry, or the payload does not have the expected shape. | ||
| */ | ||
| export function parseInteractiveHandshakeRequest(request: CustomRequest): InteractiveHandshakeRequest { | ||
| if (!request.kind.equals(INTERACTIVE_HANDSHAKE_REQUEST_KIND)) { | ||
| throw new Error(`Not an interactive-handshake signature request: unexpected kind ${request.kind}`); | ||
| } | ||
| if (!request.contractAddress.equals(STANDARD_HANDSHAKE_REGISTRY_ADDRESS)) { | ||
| throw new Error( | ||
| `Interactive-handshake signature request issued by ${request.contractAddress}, expected the standard HandshakeRegistry at ${STANDARD_HANDSHAKE_REGISTRY_ADDRESS}`, | ||
| ); | ||
| } | ||
| if (request.payload.length !== 4) { | ||
| throw new Error(`Interactive-handshake signature request payload has ${request.payload.length} fields, expected 4`); | ||
| } | ||
|
|
||
| const [recipient, chainId, version, ephPkX] = request.payload; | ||
| return { recipient: new AztecAddress(recipient), chainId, version, ephPkX }; | ||
| } | ||
|
|
||
| /** | ||
| * Produces the recipient's signed authorization for an interactive handshake, signing with the master | ||
| * message-signing secret key. | ||
| */ | ||
| export async function signInteractiveHandshake( | ||
| request: InteractiveHandshakeRequest, | ||
| completeAddress: CompleteAddress, | ||
| masterMessageSigningSecretKey: GrumpkinScalar, | ||
| ): Promise<RecipientSignature> { | ||
| const mspk = await derivePublicKeyFromSecretKey(masterMessageSigningSecretKey); | ||
| const [mspkX, mspkYIsPositive] = mspk.toXAndSign(); | ||
|
|
||
| const message = await computeInteractiveHandshakeSignatureMessage({ | ||
| chainId: request.chainId, | ||
| version: request.version, | ||
| registry: STANDARD_HANDSHAKE_REGISTRY_ADDRESS, | ||
| ephPkX: request.ephPkX, | ||
| }); | ||
| const signature = await new Schnorr().constructSignature(message, masterMessageSigningSecretKey); | ||
|
|
||
| return { | ||
| publicKeys: completeAddress.publicKeys, | ||
| partialAddress: completeAddress.partialAddress, | ||
| mspkX, | ||
| mspkYIsPositive, | ||
| signature, | ||
| }; | ||
| } | ||
|
|
||
| /** Serializes a {@link RecipientSignature} to the field layout the registry's in-circuit deserialization expects. */ | ||
| export function recipientSignatureToFields(recipientSignature: RecipientSignature): Fr[] { | ||
| const s = Fq.fromBuffer(recipientSignature.signature.s); | ||
| const e = Fq.fromBuffer(recipientSignature.signature.e); | ||
| return [ | ||
| ...recipientSignature.publicKeys.toFields(), | ||
| recipientSignature.partialAddress, | ||
| recipientSignature.mspkX, | ||
| new Fr(recipientSignature.mspkYIsPositive), | ||
| s.lo, | ||
| s.hi, | ||
| e.lo, | ||
| e.hi, | ||
| ]; | ||
| } | ||
|
|
||
| /** | ||
| * The message an interactive-handshake authorization signs: the handshake's ephemeral key and chain context under | ||
| * `DomainSeparator.INTERACTIVE_HANDSHAKE_SIGNATURE`, exactly as the registry recomputes it in-circuit. | ||
| */ | ||
| function computeInteractiveHandshakeSignatureMessage(args: { | ||
| chainId: Fr; | ||
| version: Fr; | ||
| registry: AztecAddress; | ||
| ephPkX: Fr; | ||
| }): Promise<Fr> { | ||
| return poseidon2HashWithSeparator( | ||
| [args.chainId, args.version, args.registry, args.ephPkX], | ||
| DomainSeparator.INTERACTIVE_HANDSHAKE_SIGNATURE, | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
yarn-project/standard-contracts/src/handshake-registry/constants.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,23 @@ | ||
| // Lightweight metadata leaf export for browser bundles: importing from | ||
| // `@aztec/standard-contracts/handshake-registry/constants` avoids dragging in the | ||
| // `HandshakeRegistry.json` static import. | ||
| import { sha256ToField } from '@aztec/foundation/crypto/sha256'; | ||
| import type { Fr } from '@aztec/foundation/curves/bn254'; | ||
| import type { AztecAddress } from '@aztec/stdlib/aztec-address'; | ||
|
|
||
| import { StandardContractAddress, StandardContractClassId, StandardContractSalt } from '../standard_contract_data.js'; | ||
|
|
||
| export const STANDARD_HANDSHAKE_REGISTRY_ADDRESS: AztecAddress = StandardContractAddress.HandshakeRegistry; | ||
| export const STANDARD_HANDSHAKE_REGISTRY_CLASS_ID = StandardContractClassId.HandshakeRegistry; | ||
| export const STANDARD_HANDSHAKE_REGISTRY_SALT = StandardContractSalt.HandshakeRegistry; | ||
|
|
||
| /** | ||
| * Request kind under which the HandshakeRegistry asks for a recipient's interactive-handshake signature through the | ||
| * `resolveCustomRequest` hook. Mirrors `INTERACTIVE_HANDSHAKE_REQUEST_KIND` in the registry contract. | ||
| */ | ||
| // TODO: remove this mirrored constant and read the value from the HandshakeRegistry artifact once the contract | ||
| // global can be `#[abi]`-exported. Fixed upstream but not yet released: | ||
| // https://github.com/noir-lang/noir/pull/12714 and https://github.com/noir-lang/noir/issues/12620. | ||
| export const INTERACTIVE_HANDSHAKE_REQUEST_KIND: Fr = sha256ToField([ | ||
| Buffer.from('HANDSHAKE_REGISTRY::INTERACTIVE_HANDSHAKE_REQUEST'), | ||
| ]); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be able to use the
#[abi]attribute in the contract on this global and then we can access the constant through the contract artifact. We then do not risk any constant drift.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claude is telling me that
#[abi]only supports literal constants. So we wouldn't be able to usesha256_to_field("HANDSHAKE_REGISTRY::INTERACTIVE_HANDSHAKE_REQUEST".as_bytes())Do you know if it's correct?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Claude is correct, then I would try to keep the current version instead of using a "magic" literal value. This test would actually catch any drifts, so I think we would be safe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claude is right and it is actually worse. Confirmed with a minimal repro against the nargo we pin (beta.22). A computed initializer like sha256_to_field(...) on an #[abi] global (even 40 + 2) crashes the compiler with an internal panic during ABI emission. noir-lang/noir#12714 fixes the panic in beta.23.
Separately, exported globals are unnamed in the artifact until beta.23 (noir-lang/noir#12620).
Let's just leave a TODO marker here that we can remove this mirrored constant at the next Noir release.