"""Aliro-flavored mdoc DeviceResponse for the step-up phase. Per Aliro §8.4.2, the User Device returns an ISO 18013-5 DeviceResponse with two changes: readerAuth / documentErrors / errors / deviceSigned MUST NOT be present, and map keys are replaced by the integer aliases in Table 8-22 (still encoded as text strings). For v1 (no Access Data Elements; Access Document carries only the credential long-term public key inside IssuerAuth), the DeviceResponse is effectively static once the AD is generated: { "1": "1.0", # version "2": [ # documents { "1": { # issuerSigned "1": {}, # nameSpaces (empty — no ADEs) "2": # IssuerAuth — raw COSE_Sign1 bytes }, "5": "aliro-a" # docType } ], "3": 0 # status (0 = OK) } The Access Document is already a serialized COSE_Sign1, which is itself valid CBOR (a 4-element array). Splicing its bytes into the DeviceResponse at the IssuerAuth position yields valid CBOR — no decode/re-encode needed. """ import cbor2 def build_device_response(access_document_bytes: bytes) -> bytes: """Returns a deterministic-CBOR DeviceResponse wrapping the given Access Document as IssuerAuth. ``access_document_bytes`` must be the serialized COSE_Sign1 produced by :func:`build_access_document` — it is embedded verbatim. """ # cbor2.dumps with canonical=True produces deterministic CBOR. The AD is # loaded first so it lives in the structure as real Python objects that # cbor2 re-encodes — this gives us canonical ordering guarantees even if # the source AD was encoded differently. ad = cbor2.loads(access_document_bytes) return cbor2.dumps( { "1": "1.0", "2": [ { "1": { "1": {}, "2": ad, }, "5": "aliro-a", } ], "3": 0, }, canonical=True, )