@agenticprimitives/privacy-credentials
v0.0.0-alpha.1
Published
Privacy presentation layer (spec 305 / G-6): selectively-disclosed SD-JWT VC presentations, pairwise relationship pseudonyms, spec-304 issuer-key facet verification. One suite per request, never a fallback ladder.
Maintainers
Readme
@agenticprimitives/privacy-credentials
The privacy presentation layer — turns vault-held credentials into selectively-disclosed, pairwise-pseudonymous presentations a verifier can check without learning more than the transaction requires.
Spec: specs/305-privacy-credentials.md · Issuer keys:
specs/304-issuer-key-facets.md · Doctrine: the ADR-0010
amendment ("the Smart Agent address is the accountability root, not the default presentation
identifier") and the alignment audit
§6.2 (eIDAS 2.0 Art 5a makes selective disclosure + unlinkability EU market-access requirements).
What it does
import {
issueSdJwt, presentSdJwt, verifyPresentation,
generateHolderSecret, pairwiseHandle,
createIssuerKeyResolver,
} from '@agenticprimitives/privacy-credentials';
// ISSUER (an org SA with a spec-304 facet key; sign = injected KMS ES256):
const credential = await issueSdJwt({
issuer: orgCaip10, keyId: 'issuer-key:sd-jwt:2026-07', vct: 'ap:procurement-delegate',
sdClaims: { authorizedRole: 'procurement.delegate', counterpartyClass: 'approved.vendor' },
holderPublicKeyJwk, sign: kmsEs256Signer,
});
// → into the holder's vault.
// HOLDER (presents to ONE verifier, revealing ONE claim):
const handle = await pairwiseHandle(holderSecret, 'vendor.example'); // stable HERE, unlinkable elsewhere
const payload = await presentSdJwt({
sdJwt: credential, discloseClaims: ['authorizedRole'],
audience: request.audience, nonce: request.nonce, holderSign,
});
// VERIFIER (no AP runtime needed — chain checks injected):
const resolveIssuerKey = createIssuerKeyResolver(lookupSignedFacet, {
verifySaSignature, // UniversalSignatureValidator read
checkAttestation, // AttestationRegistry liveness read
});
const res = await verifyPresentation(
{ type: 'PrivatePresentationV1', suite: 'sd-jwt', relationshipHandle: handle, payload },
request,
{ resolveIssuerKey },
);
// res.claims === { authorizedRole: 'procurement.delegate' } — and nothing else.The verifier learns: the claim it asked for, that a custody-governed Smart Agent issued it (via the spec-304 facet binding), and that the presenter holds the credential (key binding bound to this audience + nonce). It does not learn the holder's SA, the other claims, or anything that links this presentation to the same holder's presentations elsewhere.
Design rules
- One suite per proof request (ADR-0013):
sd-jwttoday;bbs-bls12381-g2,anoncreds-v1,zk-predicateare reserved enum slots that reject, never a fallback ladder. - Predicates reject on sd-jwt — the suite can't prove
amount <= limit; refusing beats silently revealing the field. Predicate proofs arrive with the W2+ suites. - Issuer facet first: a revoked or unresolvable spec-304 facet fails the whole presentation.
- Dependency-free (WebCrypto only — Node ≥ 20 and workerd); chain reads and KMS signing are injected ports.
Status
w1-sdjwt-pseudonyms — port types, pairwise pseudonyms, the self-contained SD-JWT VC suite,
spec-304 facet verification, 11-test suite (unlinkability, forgery, replay, revoked-facet,
claim-exactness). W2: demo wiring + BBS decision + decoy digests + credential status lists.
