← Book map

Chapter 07 · Tabs

Two details, in order

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.

Two details, in order The app encryption request carries an authentication detail first, constrained to the chain, followed by the app encryption detail. DETAILS[0] MUST PRECEDE DETAILS[1] auth detail REQUIRED_SYSTEM app encryption detail derivation · z-address · return ESK QR verified first The wallet enforces recipient constraints in the auth detail before it shows the encryption request.

FIGURE 7.1 · Ordering is semantic, not cosmetic.

The only tab that composes two details

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.

What the encryption detail carries

FieldRule in this app
encryptResponseToAddressMust start with zs1; parsed with SaplingPaymentAddress.fromAddressString
derivationNumberNon-negative integer, default 0
derivationIDOptional, i-address or FQN
requestIDOptional; defaults to the signing identity
returnEskBoolean; asks the wallet to return the extended spending key
redirectsRequired 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.

Round-trip verification

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.

Reading the code

src/routes/requests/encryption.ts
  parseOptionalZAddress / parseDerivationNumber / parseOptionalIAddress
  buildAppEncryptionRequest()   # auth detail + encryption detail
  generateAppEncryptionQr()     # sign → round-trip → verify → QR