← Book map

Chapter 06 · Transport

Eight steps, two parties

Build, wrap, serialise, hash, sign, encode, parse, verify. Every request you will read follows this sequence, and most bugs live in the boundary between step six and seven.

The signing lifecycle The signer builds details, wraps them in an envelope, serialises, hashes, signs with the identity key, and emits the request. The verifier parses, recomputes the hash, and checks the signature against the signing identity at a block height. SIGNER VERIFIER REQUEST BYTES CHALLENGE? SIGNED ENVELOPE RESPONSE VERDICT SIGNER — LOCAL STEPS 1. build OrdinalVDXFObject[] → new GenericRequest({ details }) 2. set createdAt / salt / responseURIs so the message is single-use 3. sign hash of toBuffer() with the signing identity's key The verifier never trusts the sender's framing — it re-derives every flag and hash from the bytes.

FIGURE 6.1 · Two parties, one buffer.

The lifecycle in eight steps

  1. The signer decides what is being asked: one or more detail objects (auth, identity update, invoice…).
  2. Each detail is wrapped in its ordinal class so it knows its type byte.
  3. The details array goes into a GenericRequest, which sets flags from the fields present.
  4. The signer adds uniqueness: createdAt, salt, or a Challenge.
  5. The request is serialised with toBuffer() and hashed.
  6. The hash is signed by the signing identity — this is what VerifiableSignatureData carries.
  7. The signed envelope is serialised again (the signature is now part of the bytes) and encoded as a deeplink or QR string.
  8. The verifier parses, recomputes the same hash, resolves the identity at a block height, and checks the signature.

Why step 7 re-serialises

The signature is a field of the envelope, so it changes the bytes. That is fine as long as the verifier knows which bytes were signed: with GenericEnvelope the signature is computed over the envelope hash without the signature field, and the flag tells the parser whether to expect it. Getting that boundary wrong is the most common way to produce a request that a wallet accepts structurally but refuses cryptographically.

Structural validity and cryptographic validity are two different checks. A request can parse perfectly and still be an invalid signature — the library will not tell you which one failed.

Where identity resolution fits

The library does not look up identities. The verifier must ask a node (or its own index) what the signing identity's primary address and revocation status were at a given height, then check the signature against that. system_id and the block height in the challenge hash are what make that check replay-resistant.

Reading the code

src/vdxf/classes/Request.ts:42          # getChallengeHash — height participates
src/vdxf/classes/VerifiableSignatureData.ts
src/vdxf/classes/Challenge.ts           # toSha256
src/vdxf/classes/CompactAddressObject.ts # compact identity references