# BringPass protocol — draft 0.3

**Design proposal, 14 September 2026. Not a standard, deployed service, production
verifier or universal NFC implementation.** This revision replaces the earlier
HTTP/OpenAPI proposal. The scope is registration, use, revocation and retraction.
Names below express local operations or logical messages, not URLs, HTTP verbs,
assigned NFC instruction bytes, applet identifiers or a mandatory server layout.

## 1. What changes, and where?

There are two separate questions: **can this particular reader/backend accept the
credential?** and **does it understand the BringPass native protocol?** Do not call
an entirely working barcode “incompatible” because it has no native awareness.

| Class | Provider's BringPass awareness | Work on token side | Provider software/hardware change |
|---|---|---|---|
| C0 · existing credential | None | Store/select an authorised existing code, or reuse an already compatible physical medium | None, if the existing rules already permit it |
| C1 · token-side profile | Partial capability, implemented locally; the provider still has no native peer | Match the approved identifier format or implement its existing application on compatible hardware | None; ordinary enrolment, policy checks and testing still happen |
| C2 · native | Full cooperative protocol | Verify provider, consent, sign and manage per-relationship keys | A native verifier and lifecycle integration; an existing capable reader may be reusable |
| X · blocked | Irrelevant | Cannot supply an accepted medium/application/credential lawfully | Requires provider cooperation, different media or integration; not silently C1 |

C0/C1 are **not security assurance levels**. C0 may reuse an already cryptographically
protected, legitimately provisioned credential; a plain C1 barcode may still be
copyable. Report compatibility per operation: registration/use/revocation/retraction
can have different capabilities. “No software change” does not mean no staff work,
no identity check, no new permitted policy, or no key provisioning.

A provider bridge is an optional integration project, not an obligatory stage and
not a zero-change route. Bringing your own credential does not change the provider's
internal account primary key, membership data or authority to grant access.

## 2. Three communication boundaries

```
Owner / management application
        | local authenticated profile operations
        v
Wallet / card / future token  -- existing barcode/QR/NFC behaviour --> legacy reader
        |
        +-- native CHALLENGE / PROOF / RESULT ---------------------> native peer
```

**Boundary A — local control:** a common model for selecting and managing credentials.
It may live within one application or span an authenticated management app and card.
Its logical command names are not exposed to arbitrary readers.

**Boundary B — legacy presentation:** only the reader's existing behaviour. A barcode
scanner receives its expected value, not a BringPass envelope. An NFC reader sees
its existing radio and application exchange, including its existing authentication
where required. No `REGISTER`, `REVOKE` or other BringPass message is injected.

**Boundary C — native exchange:** both peers implement the message semantics below.
Transport bindings, chip applets and a secure deployment profile are separate work.
No central ID broker, REST service or public directory is required by this design.

## 3. Local profile model

Illustrative owner-side data, **not a reader request**:

```json
{
  "version": "0.3",
  "profile": "locally-random-reference",
  "providerLabel": "City library",
  "mode": "legacy",
  "selection": "explicit",
  "provenance": "provider-approved-user-value",
  "presentation": {
    "driver": "optical.code128",
    "value": "00481720",
    "constraints": {"alphabet": "digits", "length": 8}
  },
  "remoteState": "not-confirmed"
}
```

Local profile names and labels are not proof of a provider's identity. Native
profiles separately store an authenticated provider binding/pin and a random
relationship reference, and point to non-exportable key handles where available.
Do not include private keys or a globally identifying root certificate in messages.

| Local command | Effect | Required boundary |
|---|---|---|
| `PROFILE.IMPORT` | Validate a descriptor, value provenance and constraints; stage a profile | Authenticated owner management, approved drivers only |
| `PROFILE.SELECT` | Choose the relationship/medium for the next interaction | Explicit user choice, or a genuinely authenticated provider context |
| `PROFILE.PRESENT` | Ask the renderer/driver to expose the already approved representation | Selected profile and local policy; not permission to generate arbitrary APDUs |
| `PROFILE.SUSPEND` | Stop local use while resolving a problem | Owner control; does not disable copies or a lost device |
| `PROFILE.REMOVE` | Forget the local association/key handle | Owner control; never claim provider-side revocation merely because this succeeded |

A driver can prepare bytes, display a symbol, or answer the existing applet's state
machine. It must obey its capability declaration. In particular, a profile cannot
rewrite an ordinary immutable hardware UID, manufacture an issuer signature, or
make an NFC-A card speak an unrelated radio technology. Protected applications
require legitimate provisioning and the appropriate secrets, not key extraction.
Driver names above are local selectors, not registered standards identifiers.

Keep imported descriptors as bounded data, not executable plug-ins. Reject
unsupported driver versions, ambiguous encodings, hidden truncation, arbitrary
script/URL execution, excessive sizes and unapproved key requests. Use an allowlist
of reviewed drivers; isolate driver state and key use by profile.

### Selection is the important legacy limitation

A plain scanner supplies no authenticated provider identity. A card UID read often
happens before a card application can choose a relationship. An AID identifies an
application, not necessarily a unique organisation; multiple providers can use it.
Do not derive a private relationship from an unauthenticated reader-supplied string.

For optical profiles the user normally selects a card in the wallet. For a UID-only
system, enrol the actual compatible physical card; one fixed UID may be shared and
therefore linkable. A passive card cannot display changing service-specific codes
without a suitable display, and a phone cannot be presumed to emulate stable UIDs.

## 4. Lifecycle terms

**REGISTER:** authorise a particular credential for an existing provider account.
For a new native relationship, create a provider-scoped random relationship ID.
For an additional device, retain that relationship and add a distinct credential ID
and key. Public-key possession is not sufficient authority to choose an account.

**USE:** present the existing representation, or prove the enrolled key in a native
exchange. The provider applies its *current* membership and permission policy.
Proof of possession is neither identity proofing nor authorisation by itself.

**REVOKE:** make one credential unusable at this provider, commonly because it was
lost, stolen, compromised or replaced. Preserve the relationship and other distinct
credentials. Copies sharing a legacy value/key cannot be separately revoked.

**RETRACT:** in this draft, the holder requests withdrawal of the entire BringPass
relationship enrolment at one provider. All its BringPass credentials are disabled;
the provider's underlying account and any unrelated provider-issued credentials
remain. This is deliberately different from revoking one device. It is not account
closure, cancellation of a contract, a GDPR erasure request or an instruction to
remove legally necessary logs. This terminology is a proposed product convention.

**FORGET locally:** remove a profile from one wallet. No remote effect is implied.
Record “provider action pending” until a verified native receipt, or independent
legacy desk/portal confirmation, establishes the remote change. A lost carrier
cannot be reliably wiped by changing the wallet on another device.

## 5. Legacy ceremonies — existing messages only

| Operation | Service desk | Self-service | What the reader receives |
|---|---|---|---|
| Register | Staff locate/verify account; select add-card; read approved value; enforce uniqueness; test | Only if an existing authenticated portal already supports adding that value/medium; otherwise desk | Its ordinary scan/tap/application exchange |
| Use | Normal transaction; staff handle exceptions under existing policy | Select approved profile; scan/tap; provider performs its existing lookup/validation | The same value or protected exchange as before |
| Revoke one | Staff identify and disable the exposed binding; sync existing reader caches | Existing authenticated lost-card/remove-card function, if present | Usually nothing; revocation is backend administration |
| Retract relationship | Staff remove all enrolled BringPass bindings, keep the account as required | Existing credential-management controls only; otherwise request desk handling | Nothing new; token cannot command an unaware backend to delete bindings |

An issuer's existing barcode may be re-displayed with permission: C0 consolidation,
not a newly user-chosen ID. C1 can generate a new allowed alias *before enrolment*
when the provider accepts user values. Preserve leading zeros, check digits and
field boundaries. Collisions are resolved by the provider's existing uniqueness
check; retain the accepted alias, not merely a derivation recipe. For immutable
UID collisions, choose another accepted carrier; software cannot re-roll that UID.

Updating an alias or rotating a key does not update a legacy provider by magic.
Enrol the replacement using the ordinary process and retire the old association.
Never replace a static code with an expiring signed token unless the existing
validation system already supports exactly that format and issuance policy.

## 6. Native model and trust bootstrap

A native relationship has a random 128-bit or larger local reference independent
of its authenticators. Each physical/software carrier normally has a distinct key
and credential ID per provider. The reference examples use 128-bit random IDs and
32-byte random challenges. These are draft choices, not universal hardware claims.

Before accepting native messages, the token must know an authenticated provider
identity and verification key, established through a trusted enrolment channel or
an independently verified management process. A self-signed key and a provider name
read from a QR are not enough. Key rotation needs an authenticated continuity or
re-verification procedure; never silently replace the pin from an incoming message.

The provider supplies a **trusted authority context** from its existing desk,
account session or verified recovery workflow. It binds the account, operation,
target, interaction and applicable step-up requirement. A client cannot grant this
permission by filling in `account`, `approved` or `context` in its message.

The reference suite signs challenges, proofs and receipts using Ed25519 as defined
by RFC 8032. It demonstrates interoperability; it is not a claim that all passive
smartcards support Ed25519. A production P-256, WebAuthn or other binding needs its
own reviewed suite mapping. No downgrade to a copyable legacy value is automatic.

## 7. Native message sequence

All four ordinary token-present operations use the same sequence:

```
Provider                     Token
CHALLENGE(op, tx, context) -> verify provider + policy; approve the actual action
                            select/create the scoped credential
                         <- PROOF(op, tx, relationship, credential, challenge hash)
verify outstanding transaction + proof + current authority
atomically apply operation, consume challenge and record result
RESULT(op, tx, request hash, outcome, revision) -> verify receipt; update local state
```

Challenge fields bind version, suite, operation, transaction, authenticated
provider, expected relationship, target, fresh nonce, expiry and interaction
context. A USE challenge may omit the relationship so the token can disclose its
provider-specific identifier only after authenticating the provider. Other
operations bind the already selected relationship.

A proof binds the **whole signed challenge**, not only a random nonce. It carries
the acting credential and relationship. REGISTER includes the new public key and
its proof of possession. Other operations must verify the already enrolled key;
they must not accept a caller-provided replacement key. REVOKE includes the exact
target credential; RETRACT includes the explicitly confirmed whole-relationship
scope. Destructive actions need stronger local/provider approval than a casual tap.

Results are provider-signed and bound to the exact proof, operation and target. An
accepted proof may still be denied by membership policy. The example implementation
raises a rejection without a success receipt; a production error binding should
avoid account enumeration and expose a coarse failure to untrusted peers.

### Mutations and replay

Validate, check current state/authority, apply the change, consume the challenge
and persist the receipt in **one durable transaction**. Retry of the exact same
proof/context returns the prior receipt with `fresh=false`, never a second action.
A different proof reusing a transaction is rejected. Physical actuation requires
its own durable, idempotent delivery/outbox boundary; the protocol cannot guarantee
that a door or external process executed exactly once.

The reference keeps a 120-second challenge lifetime and an incrementing relationship
revision. A token need not have a trusted real-time clock for the provider to enforce
freshness. It rejects stale signed mutation receipts using remembered revision.
Timeout is an unknown outcome, not success: keep the transaction and obtain/retry
the *same* receipt through the chosen binding. Do not silently re-enrol or wipe.

### Loss and verified recovery

An alternative enrolled credential can sign REVOKE for the lost target **after**
the provider has independently approved management of that account. It cannot
self-authorise management just by being valid for USE.

When no working credential remains, the provider's verified recovery procedure can
perform an administrative REVOKE without a lost-device proof. A signed outcome is
returned to that trusted management channel. This is a local provider action, not
an unauthenticated `REVOKE` message sent by any claimant. Holder-requested retraction
can also use a provider's verified recovery/desk process; the executable sample
implements lost-device revocation and leaves full account recovery policy to ports.

## 8. Reference encoding, not a transport mandate

`examples/python/protocol.py` and `examples/javascript/protocol.mjs` implement a
small experimental byte encoding for signed statements. It is not assigned NFC
framing and not a claim of COSE, CBOR, WebAuthn or FIDO conformance.

```
UTF-8("BringPass/reference/0.3") || 0x00
|| uint16_be(number_of_fields)
|| for each field: uint32_be(utf8_byte_length) || UTF-8(field)
```

Only strings; no numbers, floats, JSON object key order or Unicode normalisation.
Reject malformed UTF-8/unpaired surrogates, more than 32 fields, a field over 4096
bytes, or total signed data over 16384 bytes. `version` and `suite` are explicit.
IDs, keys and signatures use lowercase hex in this diagnostic profile.

Ordered field arrays:

```
CHALLENGE: kind, version, suite, op, tx, provider, relationship, target,
           nonce, expiry_decimal_seconds, trusted_context
PROOF:     kind, version, suite, op, tx, provider, relationship, credential,
           target, hash_of_signed_challenge, new_public_key_or_empty
RESULT:    kind, version, suite, op, tx, provider, relationship, acting_credential,
           target, hash_of_signed_proof, outcome, state, revision_decimal, effect_scope
```

`hash_of_signed_* = SHA-256(canonical_fields || raw_signature_bytes)`.
The outer JSON envelope `{fields: [...], signature: "hex"}` is a debugging/file
representation, not a provider API. The signature covers the length-prefixed bytes,
not arbitrary JSON serialization. The JSON Schema and fixed test vectors are
included in `examples/shared/`; test seeds are public fixtures, never real keys.

Native transport selection must specify framing, fragmentation, AIDs if applicable,
maximum payload and latency, channel/transaction binding, reader authority and
failure behavior. This draft intentionally allocates no NFC command bytes.
A generic NFC link does not provide all of those properties by itself.

## 9. Offline readers and security limits

The executable reference acknowledges **online-store** effects only. A provider's
successful revocation may not yet have reached offline readers. Do not promise
instant global revocation or implement offline access just by caching `accepted`.
A separate offline profile needs expiring entitlements, trusted verifier keys,
clock/rollback policy, entitlement/reader binding and cache synchronisation.

Challenge-response alone does not stop a live relay, guarantee a trusted display,
prove the biological person's identity, or make a compromised phone safe. Specify
proximity/user-verification policy for the threat model. Avoid presenting one
public root key to all providers. Fixed UIDs, attestation, transport metadata and
correlated transaction timing can still link otherwise pairwise identifiers.

Revocation history/tombstones prevent reuse or resurrection. Minimise retained
personal data while respecting the provider's legal obligations. Do not advertise
retraction as erasure of every log or deletion of an unrelated account.

## 10. What is implemented here?

Executable Python and JavaScript references implement both native peers, four
ordinary operations, signed receipts, idempotent retry, scoped authority fixtures,
one provider-local lost-device recovery operation and a legacy payload example.
They use software keys and single-threaded memory stores. No real user sessions,
attestation, secure element, NFC driver, display renderer, durable storage, recovery
identity checks or device-pairing protocol is implemented.

The other eleven languages provide paired scaffolds with explicit interfaces exposing the same four
operations and explicit fail-closed trust/verification/transaction ports. They are
not eleven new crypto libraries. Both sides and legacy boundaries are visible;
transport code, HTTP endpoints and imaginary SDK calls are intentionally absent.

## References

1. Android HCE: existing readers require their existing application protocol;
   ISO-DEP/APDU support and UID caveats.
   https://developer.android.com/develop/connectivity/nfc/hce
2. NXP MIFARE DESFire EV3: supported radio/application capabilities and UID options.
   https://www.nxp.com/products/MF3DHx3
3. RFC 8032: EdDSA / Ed25519.
   https://www.rfc-editor.org/rfc/rfc8032.html
4. W3C WebAuthn: a possible *separate* web binding, with origin/RP/account ceremony
   requirements; not identical to this reference encoding.
   https://www.w3.org/TR/webauthn-3/

The architecture, operation names, local profiles and reference encoding above are
BringPass design proposals. The references do not imply endorsement or certification.
