Derselbe Austausch. In deiner Sprache.
Beispiele für Token und Anbieter mit allen vier Lebenszyklusvorgängen. Ohne vorgeschriebene Endpunkte, Netzwerkbibliothek oder Serverstruktur.
Den Austausch modellieren. Den Transport später wählen.
Lokale Verwaltungsbefehle bleiben in Wallet oder Token. Bestandstreiber geben nur die Darstellung aus, die der vorhandene Leser bereits versteht. Den nativen Austausch implementieren beide Seiten.
Python und JavaScript enthalten ausführbare Softwarereferenzen. Die anderen elf Sprachen enthalten explizite Prüfungs- und Transaktionsschnittstellen für beide Seiten, keine erfundenen SDK-Importe.
// Native example: local message objects, no transport.
challenge = provider.challenge(operation, relationship, target, authority)
proof = token.respond(challenge, local_approval)
receipt, newly_committed = provider.finish(proof, trusted_context)
token.accept(receipt)
// Legacy: only the agreed value goes to the reader.
value = selected_profile.present(owner_verified, selected)
// No BringPass REGISTER / REVOKE / RETRACT command is sent.
Die Aufgaben beider Seiten sehen.
Codekommentare und gemeinsame Entwicklungsdokumente sind auf Englisch. Das Referenzprofil ist experimentell; Produktivadapter müssen geprüft werden.
JavaScript Ausführbare Referenz
Token ↔ Anbieter · REGISTER / USE / REVOKE / RETRACT
/** BringPass 0.3 experimental reference — Node.js, no HTTP or device transport.
* Trust, authority, storage and human approval are LOCAL ports, not caller claims.
* Single-threaded/in-memory demo. See docs/protocol.md before adapting it.
*/
import {generateKeyPairSync,createPublicKey,createPrivateKey,sign as edSign,
verify as edVerify,createHash,randomBytes} from 'node:crypto';
export const VERSION='0.3', SUITE='Ed25519';
export const OPS=Object.freeze(['REGISTER','USE','REVOKE','RETRACT']);
const DOMAIN=Buffer.from('BringPass/reference/0.3\0','utf8');
export function check(ok,why) {if(!ok) throw new Error(why);}
const hex=(s,n)=>typeof s==='string'&&s.length===n*2&&/^[0-9a-f]+$/.test(s);
export function encode(fields) {
check(Array.isArray(fields)&&fields.length>0&&fields.length<=32,'field count');
const count=Buffer.alloc(2);count.writeUInt16BE(fields.length);
const chunks=[DOMAIN,count];
for(const value of fields){
check(typeof value==='string'&&!/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(value),'string encoding');
const b=Buffer.from(value,'utf8');check(b.length<=4096,'field too large');
const size=Buffer.alloc(4);size.writeUInt32BE(b.length);chunks.push(size,b);
}
const out=Buffer.concat(chunks);check(out.length<=16384,'message too large');return out;
}
export function rawPublic(key){return createPublicKey(key).export({type:'spki',format:'der'}).subarray(-32).toString('hex');}
export function keyFromSeed(seed){
check(hex(seed,32),'test seed');
return createPrivateKey({key:Buffer.from('302e020100300506032b657004220420'+seed,'hex'),format:'der',type:'pkcs8'});
}
function pub(hexKey){check(hex(hexKey,32),'public key');return createPublicKey({key:Buffer.from('302a300506032b6570032100'+hexKey,'hex'),format:'der',type:'spki'});}
const freshKey=()=>generateKeyPairSync('ed25519').privateKey;
export function signed(key,fields){
return Object.freeze({fields:Object.freeze([...fields]),signature:edSign(null,encode(fields),key).toString('hex')});
}
export function parseMessage(obj){
check(obj&&Object.keys(obj).sort().join(',')==='fields,signature'&&hex(obj.signature,64),'envelope');encode(obj.fields);
return Object.freeze({fields:Object.freeze([...obj.fields]),signature:obj.signature});
}
export function verify(key,message){check(hex(message.signature,64)&&edVerify(null,encode(message.fields),pub(key),Buffer.from(message.signature,'hex')),'signature');}
export function digest(message){return createHash('sha256').update(encode(message.fields)).update(Buffer.from(message.signature,'hex')).digest('hex');}
function header(message,kind,n){const f=message.fields;check(f.length===n&&f[0]===kind&&f[1]===VERSION&&f[2]===SUITE,'version/suite/kind');check(OPS.includes(f[3])&&hex(f[4],16),'operation/transaction');return f;}
const equal=(a,b)=>a.length===b.length&&a.every((x,i)=>x===b[i]);
const targetOK=(op,target)=>['REGISTER','USE'].includes(op)?target==='':op==='REVOKE'?hex(target,16):target==='*';
/** Authority = {account, operations, context} from a trusted provider adapter.
* Do not deserialize it from claimant data. It is NOT an authentication protocol.
*/
export class Provider {
constructor(provider,key=freshKey(),clock=()=>Math.floor(Date.now()/1000)){
Object.assign(this,{provider,key,clock});this.relations=new Map();this.pending=new Map();this.completed=new Map();
}
challenge(op,rid,target,authority){
check(OPS.includes(op)&&authority.operations.includes(op),'management authority required');
check(authority.context&&(authority.account||op==='USE'),'trusted context / management account');
check(hex(rid,16)||(op==='USE'&&rid===''),'relationship');check(targetOK(op,target),'target');
const tx=randomBytes(16).toString('hex');
const q=signed(this.key,['CHALLENGE',VERSION,SUITE,op,tx,this.provider,rid,target,randomBytes(32).toString('hex'),String(this.clock()+120),authority.context]);
this.pending.set(tx,{q,authority});return q;
}
finish(proof,trustedContext){
const f=header(proof,'PROOF',11);
const [,,,op,tx,provider,rid,cid,target,qhash,publicKey]=f;
check(provider===this.provider&&hex(rid,16)&&hex(cid,16),'scope');const phash=digest(proof);
if(this.completed.has(tx)){
const old=this.completed.get(tx);check(old.hash===phash&&old.context===trustedContext,'retry differs');
return {receipt:old.receipt,newlyCommitted:false};
}
check(this.pending.has(tx),'unknown transaction');const {q,authority}=this.pending.get(tx),c=q.fields;
check(c[3]===op&&c[5]===provider&&c[7]===target&&digest(q)===qhash,'challenge binding');
check(c[6]===rid||(op==='USE'&&c[6]===''),'relationship binding');
check(c[10]===trustedContext&&authority.context===trustedContext,'session/desk binding');
check(this.clock()<=Number(c[9])&&authority.operations.includes(op),'expired/unauthorised');
let relation=this.relations.get(rid),state;
if(relation)check(relation.state==='active'&&((op==='USE'&&!authority.account)||relation.account===authority.account),'relationship disabled/wrong account');
if(op==='REGISTER'){
check(hex(publicKey,32),'new key required');check(!relation||!relation.keys.has(cid),'credential ID already used');verify(publicKey,proof);
if(!relation){relation={account:authority.account,state:'active',revision:0,entitled:true,keys:new Map()};this.relations.set(rid,relation);}
relation.keys.set(cid,{publicKey,state:'active'});relation.revision++;state='registered';
}else{
check(publicKey===''&&relation,'do not supply a replacement verification key');const actor=relation.keys.get(cid);
check(actor?.state==='active','actor revoked/unknown');verify(actor.publicKey,proof);
if(op==='USE'){check(relation.entitled,'not currently entitled');state='accepted';}
else if(op==='REVOKE'){
check(relation.keys.has(target),'unknown revocation target');relation.keys.get(target).state='revoked';relation.revision++;state='revoked';
}else{
relation.state='retracted';for(const k of relation.keys.values())k.state='revoked';relation.revision++;state='retracted';
}
}
// Production must make ALL the checks + state change + consumption + receipt durable and atomic.
const receipt=signed(this.key,['RESULT',VERSION,SUITE,op,tx,provider,rid,cid,target,phash,'OK',state,String(relation.revision),'online-store']);
this.completed.set(tx,{hash:phash,context:trustedContext,receipt});this.pending.delete(tx);
return {receipt,newlyCommitted:true}; // Gate physical actuation on this flag; integrate a durable outbox.
}
revokeViaRecovery(rid,target,authority){
check(authority.operations.includes('REVOKE')&&authority.context,'recovery authority');
const r=this.relations.get(rid);check(r&&r.account===authority.account&&r.state==='active','account');
check(r.keys.has(target),'target');r.keys.get(target).state='revoked';r.revision++;
return signed(this.key,['RESULT',VERSION,SUITE,'REVOKE',randomBytes(16).toString('hex'),this.provider,rid,'',target,'','OK','revoked',String(r.revision),'online-store']);
}
}
export class Token {
constructor(provider,pin,rid=randomBytes(16).toString('hex'),key=freshKey()){
Object.assign(this,{provider,pin,rid,key});this.cid=randomBytes(16).toString('hex');this.state='pending';this.revision=0;this.awaiting=new Map();
}
respond(challenge,approved){
const c=header(challenge,'CHALLENGE',11);verify(this.pin,challenge);
check(c[5]===this.provider&&(c[6]===this.rid||(c[3]==='USE'&&c[6]==='')),'provider/relationship');
check(hex(c[8],32)&&/^\d+$/.test(c[9])&&c[10],'challenge fields');
check(approved&&!['revoked','retracted','forgotten'].includes(this.state),'local consent/state');
check(targetOK(c[3],c[7]),'target');check(c[3]==='REGISTER'||this.state==='active','not enrolled');
const p=signed(this.key,['PROOF',VERSION,SUITE,c[3],c[4],this.provider,this.rid,this.cid,c[7],digest(challenge),c[3]==='REGISTER'?rawPublic(this.key):'']);
this.awaiting.set(c[4],p);return p;
}
accept(receipt){
const f=header(receipt,'RESULT',14);verify(this.pin,receipt);const p=this.awaiting.get(f[4]);
check(p&&equal(f.slice(3,9),p.fields.slice(3,9))&&f[9]===digest(p)&&f[10]==='OK','receipt binding');
check(f[13]==='online-store'&&/^\d+$/.test(f[12])&&Number(f[12])>=this.revision,'receipt scope/revision');
check(f[11]==={REGISTER:'registered',USE:'accepted',REVOKE:'revoked',RETRACT:'retracted'}[f[3]],'receipt state');
this.revision=Number(f[12]);
if(f[3]==='REGISTER')this.state='active';
else if(f[3]==='RETRACT')this.state='retracted';
else if(f[3]==='REVOKE'&&f[8]===this.cid)this.state='revoked';
}
forgetLocally(){this.state='forgotten';this.awaiting.clear();} // No remote revocation or secure erasure is implied.
}
export function presentLegacy(profile,ownerUnlocked,selected){
check(ownerUnlocked&&selected&&profile.selection==='explicit','local selection/unlock');
check(['barcode','qr','nfc-uid','nfc-application'].includes(profile.medium),'medium');
check(profile.authority==='provider-approved'&&profile.value,'approval/value');
return profile.value; // Renderer/driver receives ONLY the agreed value. NFC UID is not writable via HCE.
}
TypeScript Beidseitiges Grundgerüst · Adapter nötig
Token ↔ Anbieter · REGISTER / USE / REVOKE / RETRACT
/** Transport-independent paired scaffold. No network SDK.
* Implement these ports from the normative draft. Reference crypto/codec and
* executable Token/Provider classes are in ../javascript/protocol.mjs.
* beginVerified MUST verify signature, scoped enrolled key, fresh challenge,
* actual interaction binding and current authority INSIDE a durable transaction.
* Cached retries return the prior receipt; close() rolls back unless committed.
*/
export type Operation = "REGISTER" | "USE" | "REVOKE" | "RETRACT";
export type Envelope = Readonly<{fields: readonly string[]; signature: string}>;
export interface Verified {
readonly op: Operation; readonly target: string;
// Full verified challenge/proof and account context are retained by Transaction.
}
export interface Transaction {
readonly intent: Verified;
cached(): Envelope | null;
registerNewKey(): void; // New-key proof AND independent account authority.
requireCurrentEntitlement(): void;
revokeTarget(): void;
retractRelationship(): void;
signResult(): Envelope;
commitOnce(result: Envelope): void;
close(): void;
}
export interface Ports {
challenge(op: Operation, relationship: string, target: string, trustedContext: string): Envelope;
verifyProviderAndApprove(challenge: Envelope): void;
signProof(challenge: Envelope): Envelope;
verifyResultAndApply(result: Envelope): void;
beginVerified(proof: Envelope, trustedContext: string): Transaction;
// Returns a signed recovery receipt, without demanding the lost key.
recoverRevoke(relationship: string, target: string, trustedContext: string): Envelope;
}
export class TokenSide {
constructor(private readonly ports: Ports) {}
respond(challenge: Envelope): Envelope {
this.ports.verifyProviderAndApprove(challenge);
return this.ports.signProof(challenge);
}
accept(receipt: Envelope): void {this.ports.verifyResultAndApply(receipt);}
}
export class ProviderSide {
constructor(private readonly ports: Ports) {}
start(op: Operation, rid: string, target: string, context: string): Envelope {
return this.ports.challenge(op, rid, target, context);
}
finish(proof: Envelope, trustedContext: string): {receipt: Envelope; fresh: boolean} {
const tx = this.ports.beginVerified(proof, trustedContext);
try {
const cached = tx.cached();
if (cached) return {receipt: cached, fresh: false};
switch (tx.intent.op) {
case "REGISTER": tx.registerNewKey(); break;
case "USE": tx.requireCurrentEntitlement(); break;
case "REVOKE": tx.revokeTarget(); break;
case "RETRACT": tx.retractRelationship(); break;
default: throw new Error("Unsupported operation");
}
const receipt = tx.signResult(); tx.commitOnce(receipt);
return {receipt, fresh: true}; // Only a fresh USE may schedule one actuation.
} finally {tx.close();}
}
lost(rid: string, target: string, trustedContext: string): Envelope {
return this.ports.recoverRevoke(rid, target, trustedContext);
}
}
export function legacyValue(value: string, approved: boolean, selected: boolean): string {
if (!approved || !selected || !value) throw new Error("Local profile not approved/selected");
return value; // Renderer/driver input only. No BringPass command reaches a legacy reader.
}
Python Ausführbare Referenz
Token ↔ Anbieter · REGISTER / USE / REVOKE / RETRACT
"""BringPass 0.3 experimental message reference. NOT a production identity system.
Transport-free. Requires cryptography. All authority/pinning ports are local trusted
inputs, never fields accepted from a claimant. Memory stores are for testing only.
"""
from __future__ import annotations
from dataclasses import dataclass, field
import hashlib
import secrets
import struct
import time
from typing import Callable
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
VERSION = "0.3"
SUITE = "Ed25519"
OPS = frozenset({"REGISTER", "USE", "REVOKE", "RETRACT"})
DOMAIN = b"BringPass/reference/0.3\x00"
class Rejected(ValueError):
pass
def require(ok: bool, why: str) -> None:
if not ok:
raise Rejected(why)
def encode(fields: list[str]) -> bytes:
"""Canonical signed bytes, NOT an NFC/APDU or network framing specification."""
require(isinstance(fields, list) and 1 <= len(fields) <= 32, "field count")
out = bytearray(DOMAIN + struct.pack(">H", len(fields)))
for value in fields:
require(isinstance(value, str), "all fields must be strings")
b = value.encode("utf-8", errors="strict")
require(len(b) <= 4096, "field too large")
out += struct.pack(">I", len(b)) + b
require(len(out) <= 16384, "message too large")
return bytes(out)
def raw_public(key: Ed25519PrivateKey) -> str:
return key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw).hex()
def hex_value(value: str, size: int) -> bool:
return isinstance(value, str) and len(value) == 2*size and all(c in "0123456789abcdef" for c in value)
@dataclass(frozen=True)
class Message:
fields: tuple[str, ...]
signature: str
def as_dict(self) -> dict:
return {"fields": list(self.fields), "signature": self.signature}
@classmethod
def parse(cls, obj: dict) -> "Message":
require(isinstance(obj, dict) and set(obj) == {"fields", "signature"}, "envelope")
encode(obj["fields"])
require(hex_value(obj["signature"], 64), "signature encoding")
return cls(tuple(obj["fields"]), obj["signature"])
def sign(key: Ed25519PrivateKey, fields: list[str]) -> Message:
return Message(tuple(fields), key.sign(encode(fields)).hex())
def verify(public: str, message: Message) -> None:
require(hex_value(public, 32) and hex_value(message.signature, 64), "key/signature encoding")
try:
Ed25519PublicKey.from_public_bytes(bytes.fromhex(public)).verify(
bytes.fromhex(message.signature), encode(list(message.fields)))
except (InvalidSignature, ValueError) as exc:
raise Rejected("signature") from exc
def digest(message: Message) -> str:
return hashlib.sha256(encode(list(message.fields)) + bytes.fromhex(message.signature)).hexdigest()
def header(message: Message, kind: str, length: int) -> tuple[str, ...]:
f = message.fields
require(len(f) == length and f[:3] == (kind, VERSION, SUITE), "version, suite or kind")
require(f[3] in OPS and hex_value(f[4], 16), "operation/transaction")
return f
@dataclass(frozen=True)
class Authority:
"""Supplied by the provider's existing trusted desk/session/recovery adapter.
DO NOT construct from client JSON. A real adapter must also enforce freshness,
account ownership, step-up, CSRF/origin and binding to the actual interaction.
USE may be public; management operations require independent authority.
"""
account: str
operations: frozenset[str]
context: str
@dataclass
class Relation:
account: str
state: str = "active"
revision: int = 0
entitled: bool = True
keys: dict[str, tuple[str, str]] = field(default_factory=dict)
class Provider:
def __init__(self, provider: str, key: Ed25519PrivateKey | None = None,
clock: Callable[[], int] = lambda: int(time.time())):
self.provider, self.key, self.clock = provider, key or Ed25519PrivateKey.generate(), clock
self.relations: dict[str, Relation] = {}
self.pending: dict[str, tuple[Message, Authority]] = {}
self.completed: dict[str, tuple[str, str, Message]] = {}
def challenge(self, op: str, rid: str, target: str, authority: Authority) -> Message:
require(op in OPS and op in authority.operations, "management authority required")
require(bool(authority.context) and (bool(authority.account) or op == "USE"), "trusted context / management account required")
require(hex_value(rid, 16) or (op == "USE" and rid == ""), "relationship")
require((op in {"REGISTER", "USE"} and target == "") or
(op == "REVOKE" and hex_value(target, 16)) or
(op == "RETRACT" and target == "*"), "target")
tx = secrets.token_hex(16)
q = sign(self.key, ["CHALLENGE", VERSION, SUITE, op, tx, self.provider, rid,
target, secrets.token_hex(32), str(self.clock()+120), authority.context])
self.pending[tx] = (q, authority)
return q
def finish(self, proof: Message, trusted_context: str) -> tuple[Message, bool]:
"""Return (signed receipt, newly_committed). Never actuate twice on retry.
Production: lock/recheck membership + consume challenge + commit state +
persist receipt in ONE durable transaction. This sample is single-threaded.
"""
f = header(proof, "PROOF", 11)
_, _, _, op, tx, provider, rid, cid, target, qhash, public = f
require(provider == self.provider and hex_value(rid, 16) and hex_value(cid, 16), "scope")
phash = digest(proof)
if tx in self.completed:
oldhash, context, receipt = self.completed[tx]
require(oldhash == phash and context == trusted_context, "retry differs")
return receipt, False
require(tx in self.pending, "unknown transaction")
q, authority = self.pending[tx]
c = q.fields
require(c[3] == op and c[5] == provider and c[7] == target and digest(q) == qhash, "challenge binding")
require((c[6] == rid or (op == "USE" and c[6] == "")), "relationship binding")
require(c[10] == trusted_context == authority.context, "session/desk binding")
require(self.clock() <= int(c[9]) and op in authority.operations, "expired/unauthorised")
relation = self.relations.get(rid)
if relation is not None:
require(relation.state == "active" and ((op == "USE" and not authority.account) or relation.account == authority.account), "relationship disabled or wrong account")
if op == "REGISTER":
require(hex_value(public, 32), "new key required")
require(relation is None or cid not in relation.keys, "credential ID already used")
verify(public, proof) # proof of the new key, NOT proof of account ownership
if relation is None:
relation = Relation(authority.account)
self.relations[rid] = relation
relation.keys[cid] = (public, "active")
relation.revision += 1
state = "registered"
else:
require(public == "" and relation is not None, "do not supply a replacement verification key")
require(cid in relation.keys and relation.keys[cid][1] == "active", "actor revoked/unknown")
verify(relation.keys[cid][0], proof)
if op == "USE":
require(relation.entitled, "not currently entitled")
state = "accepted"
elif op == "REVOKE":
require(target in relation.keys, "unknown revocation target")
relation.keys[target] = (relation.keys[target][0], "revoked")
relation.revision += 1
state = "revoked"
else:
relation.state = "retracted"
relation.keys = {k: (v[0], "revoked") for k,v in relation.keys.items()}
relation.revision += 1
state = "retracted"
receipt = sign(self.key, ["RESULT", VERSION, SUITE, op, tx, provider, rid, cid,
target, phash, "OK", state, str(relation.revision), "online-store"])
self.completed[tx] = (phash, trusted_context, receipt)
del self.pending[tx]
return receipt, True
def revoke_via_recovery(self, rid: str, target: str, authority: Authority) -> Message:
"""No lost-device signature. Trusted recovery/session re-verifies the account.
This is a provider-local administrative action, NOT an unauthenticated wire command.
The receipt is returned to that trusted management channel, not to Token.accept().
"""
require("REVOKE" in authority.operations and bool(authority.context), "recovery authority")
r = self.relations.get(rid)
require(r is not None and r.account == authority.account and r.state == "active", "account")
require(target in r.keys, "target")
r.keys[target] = (r.keys[target][0], "revoked")
r.revision += 1
return sign(self.key, ["RESULT", VERSION, SUITE, "REVOKE", secrets.token_hex(16),
self.provider, rid, "", target, "", "OK", "revoked", str(r.revision), "online-store"])
class Token:
def __init__(self, provider: str, pinned_provider_key: str, rid: str | None = None,
key: Ed25519PrivateKey | None = None):
self.provider, self.pin = provider, pinned_provider_key
self.rid, self.cid = rid or secrets.token_hex(16), secrets.token_hex(16)
self.key = key or Ed25519PrivateKey.generate()
self.state, self.revision = "pending", 0
self.awaiting: dict[str, Message] = {}
def respond(self, challenge: Message, approved: bool) -> Message:
"""approved comes from a trusted local user-verification/consent path;
never from a reader-controlled bit. Simple presence is not strong user verification.
"""
c = header(challenge, "CHALLENGE", 11)
verify(self.pin, challenge)
require(c[5] == self.provider and (c[6] == self.rid or (c[3] == "USE" and c[6] == "")), "provider/relationship")
require(hex_value(c[8], 32) and c[9].isdigit() and bool(c[10]), "challenge fields")
require(approved and self.state not in {"revoked", "retracted", "forgotten"}, "local consent/state")
op, target = c[3], c[7]
require((op in {"REGISTER", "USE"} and target == "") or
(op == "REVOKE" and hex_value(target, 16)) or
(op == "RETRACT" and target == "*"), "target")
require(op == "REGISTER" or self.state == "active", "not enrolled")
proof = sign(self.key, ["PROOF", VERSION, SUITE, op, c[4], self.provider, self.rid,
self.cid, target, digest(challenge), raw_public(self.key) if op == "REGISTER" else ""])
self.awaiting[c[4]] = proof
return proof
def accept(self, receipt: Message) -> None:
f = header(receipt, "RESULT", 14)
verify(self.pin, receipt)
require(f[4] in self.awaiting, "no matching local transaction")
p = self.awaiting[f[4]]
require(f[3:9] == p.fields[3:9] and f[9] == digest(p) and f[10] == "OK", "receipt binding")
require(f[13] == "online-store" and f[12].isdigit() and int(f[12]) >= self.revision, "receipt scope/revision")
expected = {"REGISTER":"registered", "USE":"accepted", "REVOKE":"revoked", "RETRACT":"retracted"}[f[3]]
require(f[11] == expected, "receipt state")
self.revision = int(f[12])
if f[3] == "REGISTER":
self.state = "active"
elif f[3] == "RETRACT":
self.state = "retracted"
elif f[3] == "REVOKE" and f[8] == self.cid:
self.state = "revoked"
# Keep awaiting proof for idempotent receipt acceptance. Production uses bounded durable storage.
def forget_locally(self) -> None:
self.state = "forgotten" # No remote revocation occurred. Memory is not secure erasure.
self.awaiting.clear() # An old receipt must not resurrect a locally removed profile.
@dataclass(frozen=True)
class LegacyProfile:
"""Local owner-approved profile. This object is NEVER transmitted to the reader."""
name: str
medium: str
value: str
selection: str = "explicit"
authority: str = "provider-approval-required"
def present(self, owner_unlocked: bool, selected: bool) -> str:
require(owner_unlocked and selected and self.selection == "explicit", "select/unlock locally")
require(self.medium in {"barcode", "qr", "nfc-uid", "nfc-application"}, "unsupported medium")
require(self.authority == "provider-approved", "not approved for enrolment/use")
require(bool(self.value), "empty value")
# Only the pre-agreed value goes to a renderer/driver. No BringPass command.
# An NFC driver must implement the real existing application; UID is a read-only constraint.
return self.value
Java Beidseitiges Grundgerüst · Adapter nötig
Token ↔ Anbieter · REGISTER / USE / REVOKE / RETRACT
// JDK 17+. Paired protocol scaffold, NOT a cryptographic verifier.
// Ports must implement the verification/transaction contract in examples/README.md.
// No request method, URL, reader SDK or fictitious published library is used.
public final class Protocol {
public enum Op { REGISTER, USE, REVOKE, RETRACT }
public record Envelope(String[] fields, String signature) {}
public record Result(Envelope receipt, boolean fresh) {}
public interface Tx extends AutoCloseable {
Op operation(); Envelope cached();
void registerNewKey(); void requireCurrentEntitlement();
void revokeTarget(); void retractRelationship();
Envelope signResult(); void commitOnce(Envelope result);
void close(); // Rollback unless committed; release transaction lock.
}
public interface Ports {
Envelope challenge(Op op, String rid, String target, String trustedContext);
void verifyProviderAndApprove(Envelope challenge);
Envelope signProof(Envelope challenge);
void verifyResultAndApply(Envelope result);
Tx beginVerified(Envelope proof, String trustedContext);
Envelope recoverRevoke(String rid, String target, String trustedContext);
}
public static final class TokenSide {
private final Ports ports;
public TokenSide(Ports ports) { this.ports = ports; }
public Envelope respond(Envelope challenge) {
ports.verifyProviderAndApprove(challenge); return ports.signProof(challenge);
}
public void accept(Envelope result) { ports.verifyResultAndApply(result); }
}
public static final class ProviderSide {
private final Ports ports;
public ProviderSide(Ports ports) { this.ports = ports; }
public Envelope start(Op op, String rid, String target, String context) {
return ports.challenge(op, rid, target, context);
}
public Result finish(Envelope proof, String trustedContext) {
try (Tx tx = ports.beginVerified(proof, trustedContext)) {
Envelope cached = tx.cached();
if (cached != null) return new Result(cached, false);
switch (tx.operation()) {
case REGISTER -> tx.registerNewKey();
case USE -> tx.requireCurrentEntitlement();
case REVOKE -> tx.revokeTarget();
case RETRACT -> tx.retractRelationship();
}
Envelope result = tx.signResult(); tx.commitOnce(result);
return new Result(result, true);
}
}
public Envelope lost(String rid, String target, String context) {
return ports.recoverRevoke(rid, target, context);
}
}
public static String legacyValue(String value, boolean approved, boolean selected) {
if (!approved || !selected || value == null || value.isEmpty())
throw new IllegalArgumentException("Local approval/selection required");
return value; // Only an agreed barcode/QR value or input to an existing NFC driver.
}
}
C# Beidseitiges Grundgerüst · Adapter nötig
Token ↔ Anbieter · REGISTER / USE / REVOKE / RETRACT
// .NET 8+. Paired scaffold; no networking or fabricated SDK.
// beginVerified owns an atomic transaction including verification, authority,
// state, challenge consumption and signed receipt. Dispose rolls back otherwise.
using System;
namespace BringPass.Draft;
public enum Op { REGISTER, USE, REVOKE, RETRACT }
public sealed record Envelope(string[] Fields, string Signature);
public sealed record Result(Envelope Receipt, bool Fresh);
public interface ITransaction : IDisposable {
Op Operation { get; }
Envelope? Cached();
void RegisterNewKey(); void RequireCurrentEntitlement();
void RevokeTarget(); void RetractRelationship();
Envelope SignResult(); void CommitOnce(Envelope result);
}
public interface IPorts {
Envelope Challenge(Op op, string rid, string target, string trustedContext);
void VerifyProviderAndApprove(Envelope challenge);
Envelope SignProof(Envelope challenge);
void VerifyResultAndApply(Envelope result);
ITransaction BeginVerified(Envelope proof, string trustedContext);
Envelope RecoverRevoke(string rid, string target, string trustedContext);
}
public sealed class TokenSide(IPorts ports) {
public Envelope Respond(Envelope challenge) {
ports.VerifyProviderAndApprove(challenge); return ports.SignProof(challenge);
}
public void Accept(Envelope result) => ports.VerifyResultAndApply(result);
}
public sealed class ProviderSide(IPorts ports) {
public Envelope Start(Op op, string rid, string target, string context) =>
ports.Challenge(op, rid, target, context);
public Result Finish(Envelope proof, string trustedContext) {
using var tx = ports.BeginVerified(proof, trustedContext);
var cached = tx.Cached();
if (cached is not null) return new(cached, false);
switch (tx.Operation) {
case Op.REGISTER: tx.RegisterNewKey(); break;
case Op.USE: tx.RequireCurrentEntitlement(); break;
case Op.REVOKE: tx.RevokeTarget(); break;
case Op.RETRACT: tx.RetractRelationship(); break;
default: throw new InvalidOperationException("Unsupported operation");
}
var result = tx.SignResult(); tx.CommitOnce(result); return new(result, true);
}
public Envelope Lost(string rid, string target, string context) =>
ports.RecoverRevoke(rid, target, context);
}
public static class Legacy {
public static string Present(string value, bool approved, bool selected) {
if (!approved || !selected || String.IsNullOrEmpty(value))
throw new InvalidOperationException("Local approval/selection required");
return value; // Only input to the approved renderer or existing NFC driver.
}
}
Go Beidseitiges Grundgerüst · Adapter nötig
Token ↔ Anbieter · REGISTER / USE / REVOKE / RETRACT
// Package draft illustrates BOTH peers. It is not a device driver or security library.
// BeginVerified must authenticate/authorise/bind the proof and acquire an atomic
// transaction. Close rolls back unless committed; cached USE must not actuate again.
package draft
import "errors"
type Op string
const (Register Op = "REGISTER"; Use Op = "USE"; Revoke Op = "REVOKE"; Retract Op = "RETRACT")
type Envelope struct { Fields []string; Signature string }
type Result struct { Receipt Envelope; Fresh bool }
type Tx interface {
Operation() Op
Cached() *Envelope
RegisterNewKey() error
RequireCurrentEntitlement() error
RevokeTarget() error
RetractRelationship() error
SignResult() (Envelope, error)
CommitOnce(Envelope) error
Close()
}
type Ports interface {
Challenge(Op, string, string, string) (Envelope,error)
VerifyProviderAndApprove(Envelope) error
SignProof(Envelope) (Envelope,error)
VerifyResultAndApply(Envelope) error
BeginVerified(Envelope,string) (Tx,error)
RecoverRevoke(string,string,string) (Envelope,error)
}
type TokenSide struct { Ports Ports }
func(t TokenSide) Respond(q Envelope)(Envelope,error){
if err:=t.Ports.VerifyProviderAndApprove(q);err!=nil{return Envelope{},err}
return t.Ports.SignProof(q)
}
func(t TokenSide) Accept(r Envelope)error{return t.Ports.VerifyResultAndApply(r)}
type ProviderSide struct { Ports Ports }
func(p ProviderSide) Start(op Op,rid,target,context string)(Envelope,error){
return p.Ports.Challenge(op,rid,target,context)
}
func(p ProviderSide) Finish(proof Envelope,context string)(Result,error){
tx,err:=p.Ports.BeginVerified(proof,context);if err!=nil{return Result{},err};defer tx.Close()
if old:=tx.Cached();old!=nil{return Result{*old,false},nil}
switch tx.Operation(){
case Register: err=tx.RegisterNewKey()
case Use: err=tx.RequireCurrentEntitlement()
case Revoke: err=tx.RevokeTarget()
case Retract: err=tx.RetractRelationship()
default: err=errors.New("unsupported operation")
}
if err!=nil{return Result{},err}
receipt,err:=tx.SignResult();if err!=nil{return Result{},err}
if err=tx.CommitOnce(receipt);err!=nil{return Result{},err}
return Result{receipt,true},nil
}
func(p ProviderSide) Lost(rid,target,context string)(Envelope,error){return p.Ports.RecoverRevoke(rid,target,context)}
func LegacyValue(value string,approved,selected bool)(string,error){
if !approved||!selected||value==""{return "",errors.New("local approval/selection required")}
return value,nil // No BringPass command crosses the legacy boundary.
}
PHP Beidseitiges Grundgerüst · Adapter nötig
Token ↔ Anbieter · REGISTER / USE / REVOKE / RETRACT
<?php
declare(strict_types=1);
// PHP 8.1+. Transport-free paired scaffold. No provider API or SDK is presumed.
// Ports implement verification, current authority, atomicity and trust per README.
enum Op: string { case REGISTER='REGISTER'; case USE='USE'; case REVOKE='REVOKE'; case RETRACT='RETRACT'; }
final class Envelope { public function __construct(public readonly array $fields, public readonly string $signature) {} }
final class Outcome { public function __construct(public readonly Envelope $receipt, public readonly bool $fresh) {} }
interface Transaction {
public function operation(): Op;
public function cached(): ?Envelope;
public function registerNewKey(): void;
public function requireCurrentEntitlement(): void;
public function revokeTarget(): void;
public function retractRelationship(): void;
public function signResult(): Envelope;
public function commitOnce(Envelope $result): void;
public function close(): void; // Roll back unless committed.
}
interface Ports {
public function challenge(Op $op,string $rid,string $target,string $context): Envelope;
public function verifyProviderAndApprove(Envelope $challenge): void;
public function signProof(Envelope $challenge): Envelope;
public function verifyResultAndApply(Envelope $result): void;
public function beginVerified(Envelope $proof,string $trustedContext): Transaction;
public function recoverRevoke(string $rid,string $target,string $trustedContext): Envelope;
}
final class TokenSide {
public function __construct(private Ports $ports) {}
public function respond(Envelope $q): Envelope {
$this->ports->verifyProviderAndApprove($q); return $this->ports->signProof($q);
}
public function accept(Envelope $result): void { $this->ports->verifyResultAndApply($result); }
}
final class ProviderSide {
public function __construct(private Ports $ports) {}
public function start(Op $op,string $rid,string $target,string $context): Envelope {
return $this->ports->challenge($op,$rid,$target,$context);
}
public function finish(Envelope $proof,string $trustedContext): Outcome {
$tx=$this->ports->beginVerified($proof,$trustedContext);
try {
$old=$tx->cached(); if ($old !== null) return new Outcome($old,false);
switch($tx->operation()) {
case Op::REGISTER: $tx->registerNewKey(); break;
case Op::USE: $tx->requireCurrentEntitlement(); break;
case Op::REVOKE: $tx->revokeTarget(); break;
case Op::RETRACT: $tx->retractRelationship(); break;
}
$result=$tx->signResult(); $tx->commitOnce($result); return new Outcome($result,true);
} finally { $tx->close(); }
}
public function lost(string $rid,string $target,string $context): Envelope {
return $this->ports->recoverRevoke($rid,$target,$context);
}
}
function legacyValue(string $value,bool $approved,bool $selected): string {
if (!$approved || !$selected || $value==='') throw new RuntimeException('Local approval/selection required');
return $value; // No BringPass command sent to the reader.
}
Ruby Beidseitiges Grundgerüst · Adapter nötig
Token ↔ Anbieter · REGISTER / USE / REVOKE / RETRACT
# Ruby 3+. BOTH sides, transport-independent scaffold, not a verifier.
# Ports implement the contract in examples/README.md; no permissive default.
module BringPassDraft
OPS = %w[REGISTER USE REVOKE RETRACT].freeze
Envelope = Struct.new(:fields, :signature, keyword_init: true)
Result = Struct.new(:receipt, :fresh, keyword_init: true)
class Ports
def challenge(op, rid, target, context) = raise(NotImplementedError)
def verify_provider_and_approve(challenge) = raise(NotImplementedError)
def sign_proof(challenge) = raise(NotImplementedError)
def verify_result_and_apply(result) = raise(NotImplementedError)
def begin_verified(proof, trusted_context) = raise(NotImplementedError)
def recover_revoke(rid, target, trusted_context) = raise(NotImplementedError)
end
class Transaction
# Created only after cryptographic verification inside a locked transaction.
def operation = raise(NotImplementedError)
def cached = raise(NotImplementedError)
def register_new_key = raise(NotImplementedError)
def require_current_entitlement = raise(NotImplementedError)
def revoke_target = raise(NotImplementedError)
def retract_relationship = raise(NotImplementedError)
def sign_result = raise(NotImplementedError)
def commit_once(result) = raise(NotImplementedError)
def close = raise(NotImplementedError) # Roll back unless committed.
end
class TokenSide
def initialize(ports) = @ports = ports
def respond(challenge)
@ports.verify_provider_and_approve(challenge)
@ports.sign_proof(challenge)
end
def accept(result) = @ports.verify_result_and_apply(result)
end
class ProviderSide
def initialize(ports) = @ports = ports
def start(op, rid, target, context) = @ports.challenge(op, rid, target, context)
def finish(proof, trusted_context)
tx = @ports.begin_verified(proof, trusted_context)
begin
old = tx.cached
return Result.new(receipt: old, fresh: false) if old
case tx.operation
when 'REGISTER' then tx.register_new_key
when 'USE' then tx.require_current_entitlement
when 'REVOKE' then tx.revoke_target
when 'RETRACT' then tx.retract_relationship
else raise ArgumentError, 'Unsupported operation'
end
result = tx.sign_result
tx.commit_once(result)
Result.new(receipt: result, fresh: true)
ensure
tx.close
end
end
def lost(rid, target, context) = @ports.recover_revoke(rid, target, context)
end
def self.legacy_value(value, approved:, selected:)
raise ArgumentError, 'Local approval/selection required' unless approved && selected && !value.empty?
value # The only payload sent to the approved renderer/driver.
end
end
Kotlin Beidseitiges Grundgerüst · Adapter nötig
Token ↔ Anbieter · REGISTER / USE / REVOKE / RETRACT
// Kotlin/JVM. Paired transport-free scaffold; production ports are intentionally abstract.
// beginVerified performs ALL verification inside a transaction. close rolls back
// unless committed. See examples/README.md for crypto, freshness and authority rules.
package bringpass.draft
import java.io.Closeable
enum class Op { REGISTER, USE, REVOKE, RETRACT }
data class Envelope(val fields: List<String>, val signature: String)
data class Result(val receipt: Envelope, val fresh: Boolean)
interface Tx : Closeable {
val operation: Op
fun cached(): Envelope?
fun registerNewKey()
fun requireCurrentEntitlement()
fun revokeTarget()
fun retractRelationship()
fun signResult(): Envelope
fun commitOnce(result: Envelope)
}
interface Ports {
fun challenge(op: Op, rid: String, target: String, trustedContext: String): Envelope
fun verifyProviderAndApprove(challenge: Envelope)
fun signProof(challenge: Envelope): Envelope
fun verifyResultAndApply(result: Envelope)
fun beginVerified(proof: Envelope, trustedContext: String): Tx
fun recoverRevoke(rid: String, target: String, trustedContext: String): Envelope
}
class TokenSide(private val ports: Ports) {
fun respond(challenge: Envelope): Envelope {
ports.verifyProviderAndApprove(challenge)
return ports.signProof(challenge)
}
fun accept(result: Envelope) = ports.verifyResultAndApply(result)
}
class ProviderSide(private val ports: Ports) {
fun start(op: Op, rid: String, target: String, context: String) =
ports.challenge(op, rid, target, context)
fun finish(proof: Envelope, trustedContext: String): Result =
ports.beginVerified(proof, trustedContext).use { tx ->
val cached = tx.cached()
if (cached != null) Result(cached, false)
else {
when (tx.operation) {
Op.REGISTER -> tx.registerNewKey()
Op.USE -> tx.requireCurrentEntitlement()
Op.REVOKE -> tx.revokeTarget()
Op.RETRACT -> tx.retractRelationship()
}
val result = tx.signResult()
tx.commitOnce(result)
Result(result, true)
}
}
fun lost(rid: String, target: String, context: String) =
ports.recoverRevoke(rid, target, context)
}
fun legacyValue(value: String, approved: Boolean, selected: Boolean): String {
require(approved && selected && value.isNotEmpty())
return value // No new message reaches the legacy reader.
}
Swift Beidseitiges Grundgerüst · Adapter nötig
Token ↔ Anbieter · REGISTER / USE / REVOKE / RETRACT
// Swift 5.9+. BOTH peers, no transport or presumed smartcard capabilities.
// Ports implement the verified-transaction contract in examples/README.md.
// close() rolls back unless committed; cached retries must not repeat an actuation.
import Foundation
enum Op { case register, use, revoke, retract }
struct Envelope { let fields: [String]; let signature: String }
struct Outcome { let receipt: Envelope; let fresh: Bool }
enum DraftError: Error { case localApprovalRequired }
protocol Transaction {
var operation: Op { get }
func cached() -> Envelope?
func registerNewKey() throws
func requireCurrentEntitlement() throws
func revokeTarget() throws
func retractRelationship() throws
func signResult() throws -> Envelope
func commitOnce(_ result: Envelope) throws
func close()
}
protocol Ports {
func challenge(_ op: Op, _ rid: String, _ target: String, _ context: String) throws -> Envelope
func verifyProviderAndApprove(_ challenge: Envelope) throws
func signProof(_ challenge: Envelope) throws -> Envelope
func verifyResultAndApply(_ result: Envelope) throws
func beginVerified(_ proof: Envelope, _ context: String) throws -> any Transaction
func recoverRevoke(_ rid: String, _ target: String, _ context: String) throws -> Envelope
}
struct TokenSide {
let ports: any Ports
func respond(_ q: Envelope) throws -> Envelope {
try ports.verifyProviderAndApprove(q); return try ports.signProof(q)
}
func accept(_ result: Envelope) throws { try ports.verifyResultAndApply(result) }
}
struct ProviderSide {
let ports: any Ports
func start(_ op: Op, _ rid: String, _ target: String, _ context: String) throws -> Envelope {
try ports.challenge(op,rid,target,context)
}
func finish(_ proof: Envelope, _ context: String) throws -> Outcome {
let tx = try ports.beginVerified(proof,context); defer { tx.close() }
if let old = tx.cached() { return Outcome(receipt: old,fresh: false) }
switch tx.operation {
case .register: try tx.registerNewKey()
case .use: try tx.requireCurrentEntitlement()
case .revoke: try tx.revokeTarget()
case .retract: try tx.retractRelationship()
}
let result = try tx.signResult(); try tx.commitOnce(result)
return Outcome(receipt: result,fresh: true)
}
func lost(_ rid: String, _ target: String, _ context: String) throws -> Envelope {
try ports.recoverRevoke(rid,target,context)
}
}
func legacyValue(_ value: String, approved: Bool, selected: Bool) throws -> String {
guard approved && selected && !value.isEmpty else { throw DraftError.localApprovalRequired }
return value // Renderer/standard driver input only.
}
Rust Beidseitiges Grundgerüst · Adapter nötig
Token ↔ Anbieter · REGISTER / USE / REVOKE / RETRACT
// Rust 2021. BOTH peers, typed ports scaffold. No networking/crates required.
// Begin_verified locks and verifies per examples/README.md. Close rolls back unless
// committed. A production Tx implementation should also use RAII/Drop on panic.
#[derive(Clone, Copy)] pub enum Op { Register, Use, Revoke, Retract }
#[derive(Clone)] pub struct Envelope { pub fields: Vec<String>, pub signature: String }
pub struct Outcome { pub receipt: Envelope, pub fresh: bool }
pub trait Transaction {
fn operation(&self) -> Op;
fn cached(&self) -> Option<Envelope>;
fn register_new_key(&mut self) -> Result<(),String>;
fn require_current_entitlement(&mut self) -> Result<(),String>;
fn revoke_target(&mut self) -> Result<(),String>;
fn retract_relationship(&mut self) -> Result<(),String>;
fn sign_result(&mut self) -> Result<Envelope,String>;
fn commit_once(&mut self, result: &Envelope) -> Result<(),String>;
fn close(&mut self);
}
pub trait Ports {
fn challenge(&self, op: Op, rid: &str, target: &str, context: &str) -> Result<Envelope,String>;
fn verify_provider_and_approve(&self, q: &Envelope) -> Result<(),String>;
fn sign_proof(&self, q: &Envelope) -> Result<Envelope,String>;
fn verify_result_and_apply(&self, r: &Envelope) -> Result<(),String>;
fn begin_verified(&self, p: &Envelope, context: &str) -> Result<Box<dyn Transaction>,String>;
fn recover_revoke(&self, rid: &str, target: &str, context: &str) -> Result<Envelope,String>;
}
pub struct TokenSide<'a> { pub ports: &'a dyn Ports }
impl TokenSide<'_> {
pub fn respond(&self, q: &Envelope) -> Result<Envelope,String> {
self.ports.verify_provider_and_approve(q)?; self.ports.sign_proof(q)
}
pub fn accept(&self, r: &Envelope) -> Result<(),String> { self.ports.verify_result_and_apply(r) }
}
pub struct ProviderSide<'a> { pub ports: &'a dyn Ports }
impl ProviderSide<'_> {
pub fn start(&self, op: Op, rid: &str, target: &str, context: &str) -> Result<Envelope,String> {
self.ports.challenge(op,rid,target,context)
}
pub fn finish(&self, proof: &Envelope, context: &str) -> Result<Outcome,String> {
let mut tx = self.ports.begin_verified(proof,context)?;
let result = (|| {
if let Some(old) = tx.cached() { return Ok(Outcome { receipt: old, fresh: false }); }
match tx.operation() {
Op::Register => tx.register_new_key()?,
Op::Use => tx.require_current_entitlement()?,
Op::Revoke => tx.revoke_target()?,
Op::Retract => tx.retract_relationship()?,
}
let receipt = tx.sign_result()?; tx.commit_once(&receipt)?;
Ok(Outcome { receipt, fresh: true })
})();
tx.close(); result
}
pub fn lost(&self, rid: &str, target: &str, context: &str) -> Result<Envelope,String> {
self.ports.recover_revoke(rid,target,context)
}
}
pub fn legacy_value(value: &str, approved: bool, selected: bool) -> Result<&str,String> {
if !approved || !selected || value.is_empty() { return Err("Local approval/selection required".into()); }
Ok(value) // Standard renderer/driver only, no new BringPass reader command.
}
C Beidseitiges Grundgerüst · Adapter nötig
Token ↔ Anbieter · REGISTER / USE / REVOKE / RETRACT
/* C11. Both sides as a fail-closed, transport-free callback scaffold.
* All callbacks below must be implemented; begin_verified verifies signatures,
* context, current authority, freshness and state under one transaction lock.
* close rolls back unless commit_once succeeded. Callback messages are caller-
* allocated; their storage/ownership remains with the adapter. See README.md.
*/
#include <stddef.h>
#include <stdbool.h>
typedef enum { REGISTER, USE, REVOKE, RETRACT } bp_op;
typedef struct { const char *const *fields; size_t count; const char *signature; } bp_message;
typedef struct bp_tx bp_tx;
struct bp_tx {
void *state;
bp_op op;
const bp_message *(*cached)(bp_tx *);
int (*register_key)(bp_tx *);
int (*require_entitlement)(bp_tx *);
int (*revoke_target)(bp_tx *);
int (*retract_relation)(bp_tx *);
int (*sign_result)(bp_tx *, bp_message *);
int (*commit_once)(bp_tx *,const bp_message *);
void (*close)(bp_tx *);
};
typedef struct {
void *state;
int (*challenge)(void *,bp_op,const char *,const char *,const char *,bp_message *);
int (*verify_provider_and_approve)(void *,const bp_message *);
int (*sign_proof)(void *,const bp_message *,bp_message *);
int (*verify_result_and_apply)(void *,const bp_message *);
int (*begin_verified)(void *,const bp_message *,const char *,bp_tx **);
int (*recover_revoke)(void *,const char *,const char *,const char *,bp_message *);
} bp_ports;
int bp_token_respond(bp_ports *p,const bp_message *q,bp_message *proof) {
if (!p||!q||!proof||!p->verify_provider_and_approve||!p->sign_proof) return -1;
if (p->verify_provider_and_approve(p->state,q)) return -1;
return p->sign_proof(p->state,q,proof);
}
int bp_token_accept(bp_ports *p,const bp_message *result) {
if (!p||!result||!p->verify_result_and_apply) return -1;
return p->verify_result_and_apply(p->state,result);
}
int bp_provider_start(bp_ports *p,bp_op op,const char *rid,const char *target,
const char *context,bp_message *out) {
if (!p||!p->challenge||!rid||!target||!context||!out) return -1;
return p->challenge(p->state,op,rid,target,context,out);
}
int bp_provider_finish(bp_ports *p,const bp_message *proof,const char *context,
bp_message *result,bool *fresh) {
bp_tx *tx=NULL; const bp_message *old; int rc=-1;
if (!p||!proof||!context||!result||!fresh||!p->begin_verified) return -1;
*fresh=false;
if (p->begin_verified(p->state,proof,context,&tx)||!tx) return -1;
if (!tx->close) return -1; /* Invalid adapter: callbacks must be installed together. */
if (!tx->cached||!tx->sign_result||!tx->commit_once) goto done;
old=tx->cached(tx); if(old){*result=*old; rc=0; goto done;}
switch(tx->op){
case REGISTER: if(tx->register_key) rc=tx->register_key(tx); break;
case USE: if(tx->require_entitlement) rc=tx->require_entitlement(tx); break;
case REVOKE: if(tx->revoke_target) rc=tx->revoke_target(tx); break;
case RETRACT: if(tx->retract_relation) rc=tx->retract_relation(tx); break;
default: rc=-1;
}
if(rc||tx->sign_result(tx,result)){rc=-1;goto done;}
rc=tx->commit_once(tx,result); if(!rc)*fresh=true;
done:
tx->close(tx); return rc;
}
int bp_provider_lost(bp_ports *p,const char *rid,const char *target,
const char *context,bp_message *out) {
if(!p||!p->recover_revoke||!rid||!target||!context||!out)return -1;
return p->recover_revoke(p->state,rid,target,context,out);
}
const char *bp_legacy_value(const char *value,bool approved,bool selected) {
return approved&&selected&&value&&value[0]?value:NULL;
/* Returned bytes are for the agreed renderer/driver, NOT a new reader command. */
}
C++ Beidseitiges Grundgerüst · Adapter nötig
Token ↔ Anbieter · REGISTER / USE / REVOKE / RETRACT
// C++17. Paired transport-free scaffold, not an authentication implementation.
// VerifiedTransaction is produced ONLY after full verification under a lock.
// Its destructor rolls back unless commitOnce succeeds. See examples/README.md.
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
namespace bringpass {
enum class Op { REGISTER, USE, REVOKE, RETRACT };
struct Envelope { std::vector<std::string> fields; std::string signature; };
struct Result { Envelope receipt; bool fresh; };
struct Tx {
virtual ~Tx() = default;
virtual Op operation() const = 0;
virtual std::optional<Envelope> cached() const = 0;
virtual void registerNewKey() = 0;
virtual void requireCurrentEntitlement() = 0;
virtual void revokeTarget() = 0;
virtual void retractRelationship() = 0;
virtual Envelope signResult() = 0;
virtual void commitOnce(const Envelope&) = 0;
};
struct Ports {
virtual ~Ports() = default;
virtual Envelope challenge(Op,const std::string&,const std::string&,const std::string&) = 0;
virtual void verifyProviderAndApprove(const Envelope&) = 0;
virtual Envelope signProof(const Envelope&) = 0;
virtual void verifyResultAndApply(const Envelope&) = 0;
virtual std::unique_ptr<Tx> beginVerified(const Envelope&,const std::string&) = 0;
virtual Envelope recoverRevoke(const std::string&,const std::string&,const std::string&) = 0;
};
struct TokenSide {
Ports& ports;
Envelope respond(const Envelope& q) { ports.verifyProviderAndApprove(q); return ports.signProof(q); }
void accept(const Envelope& r) { ports.verifyResultAndApply(r); }
};
struct ProviderSide {
Ports& ports;
Envelope start(Op op,const std::string& rid,const std::string& target,const std::string& context) {
return ports.challenge(op,rid,target,context);
}
Result finish(const Envelope& proof,const std::string& trustedContext) {
auto tx = ports.beginVerified(proof,trustedContext);
if (!tx) throw std::runtime_error("Verified transaction required");
if (auto old = tx->cached()) return {*old,false};
switch (tx->operation()) {
case Op::REGISTER: tx->registerNewKey(); break;
case Op::USE: tx->requireCurrentEntitlement(); break;
case Op::REVOKE: tx->revokeTarget(); break;
case Op::RETRACT: tx->retractRelationship(); break;
default: throw std::runtime_error("Unsupported operation");
}
auto result = tx->signResult(); tx->commitOnce(result); return {result,true};
}
Envelope lost(const std::string& rid,const std::string& target,const std::string& context) {
return ports.recoverRevoke(rid,target,context);
}
};
std::string legacyValue(const std::string& value,bool approved,bool selected) {
if (!approved || !selected || value.empty()) throw std::runtime_error("Local approval/selection required");
return value; // Only the agreed renderer/driver payload.
}
}
Eine abstrakte Schnittstelle darf keine Sicherheitsabkürzung werden.
Bevor ein Anbieter-Handler eine Transaktion erhält, muss dessen Adapter den tatsächlichen Nachweis, die gebundene Zuständigkeit, Sitzungs- oder Schalterkontext, Frische, Kontoberechtigung und aktuellen Schlüsselzustand prüfen. Änderung, Challenge-Verbrauch und Bestätigung atomar speichern.
Ein für USE akzeptierter Schlüssel besitzt nicht automatisch Rechte zum Registrieren, Sperren oder Zurückziehen. Wiederherstellung ist ein separat geprüftes Kontoverfahren, keine Forderung nach einer Signatur der verlorenen Karte.
Erforderliche Schnittstellenvereinbarung lesen →Lokal ausführen. Signierte Bytes vergleichen.
python3 -m pip install -r examples/python/requirements.txt
python3 examples/python/demo.py
node examples/javascript/demo.mjs
python3 tests/check_protocol.py
node tests/check_protocol.mjs
Die Referenzen tauschen lokale Objekte aus, nutzen Test-Berechtigungskontexte und halten Zustand im Arbeitsspeicher. Sie melden sich nicht an echten Konten an, sprechen keine NFC-Leser an und bieten keinen dauerhaften Sperrspeicher.