Chapter 07 · Tabs
The only tab that composes an auth detail with its request, and the only one that verifies its own QR by round-tripping the deeplink.
FIGURE 7.1 · Ordering is semantic, not cosmetic.
App encryption asks an identity to encrypt data back to a viewing key. The primitive requires an authentication detail alongside it, and the code comments say exactly why: the wallet processes the auth first, so it can enforce a recipient constraint before presenting the encryption request.
details: [
new AuthenticationRequestOrdinalVDXFObject({ data: authDetails }), // REQUIRED_SYSTEM
new AppEncryptionRequestOrdinalVDXFObject({ data: appEncryptDetails })
]
The auth detail's requestID defaults to the signing identity if no request id was given,
and its constraint is REQUIRED_SYSTEM pinned to VRSC/VRSCTEST. Reordering the array
produces a request that parses but loses the guard.
| Field | Rule in this app |
|---|---|
encryptResponseToAddress | Must start with zs1; parsed with SaplingPaymentAddress.fromAddressString |
derivationNumber | Non-negative integer, default 0 |
derivationID | Optional, i-address or FQN |
requestID | Optional; defaults to the signing identity |
returnEsk | Boolean; asks the wallet to return the extended spending key |
redirects | Required and non-empty for this tab |
The redirect requirement is deliberate: an encryption response with nowhere to go is a dead request. The route rejects an empty array rather than emitting one.
const deeplink = reqToSign.toWalletDeeplinkUri();
const back = primitives.GenericRequest.fromWalletDeeplinkUri(deeplink);
const ok = await verusId.verifyGenericRequest(back);
if (!ok) throw new Error("Failed to verify the generated GenericRequest.");
This is the only tab that verifies its own output before returning it — parse the deeplink back, check the signature against the chain, and fail loudly. It is a pattern worth copying: the QR is only useful if the round trip preserves the signed bytes.
returnEsk is a capability upgrade, not a checkbox. A returned extended spending key can
derive every message in that derivation chain; decrypting one response cannot.
src/routes/requests/encryption.ts parseOptionalZAddress / parseDerivationNumber / parseOptionalIAddress buildAppEncryptionRequest() # auth detail + encryption detail generateAppEncryptionQr() # sign → round-trip → verify → QR