@atlasauth/pca-bbs
v0.4.3
Published
BBS signatures (CFRG draft) for PCA: issue a capability as a multi-message BBS credential, then present it with selective disclosure + unlinkable zero-knowledge proof-of-possession, so an agent reveals only the attributes a tool needs and uses can't be co
Maintainers
Readme
@atlasauth/pca-bbs
BBS signatures (BLS12-381-SHA-256, following the IRTF CFRG BBS draft) for Proof-Carrying Authority (PCA). A PCA capability is issued as a multi-message BBS credential, then presented with selective disclosure and an unlinkable zero-knowledge proof of possession. An agent can reveal only the attributes a tool needs (for example the scope caveat) while hiding the rest (holder, budget, expiry), and two presentations of the same credential cannot be correlated.
There are two layers:
- the BBS core:
keyGen,createGenerators,messagesToScalars,sign,verify,proofGen,proofVerify; - the PCA layer: map a capability to an ordered attribute vector (
iss,sub, thencav:<i>:<type>per caveat), issue it, present a subset, verify the presentation.
Install
npm i @atlasauth/pca-bbsDepends on @atlasauth/pca, @noble/curves and @noble/hashes (installed automatically).
Usage
import {
generateKeyPair, issueCapabilityCredential, presentCredential, verifyCredential, verifyPresentation,
} from '@atlasauth/pca-bbs';
const bytes = (s: string) => new TextEncoder().encode(s);
const capability = {
issuer: 'issuer-public-key',
holder: 'agent-public-key',
caveats: [
{ type: 'scope', actions: ['search:read'] },
{ type: 'budget_alloc', limit: 100 },
],
};
const { sk, pk } = generateKeyPair();
const cred = issueCapabilityCredential(capability, { sk, pk });
verifyCredential(cred); // true: every attribute is signed by the issuer
// Reveal only the scope caveat, bound to one action so the proof cannot be replayed elsewhere.
const pres = presentCredential(cred, {
disclose: ['cav:0:scope'],
presentationHeader: bytes('action:search?q=atlas'),
});
verifyPresentation(pres); // true
verifyPresentation(pres, { pk, presentationHeader: bytes('action:search?q=atlas') }); // pin issuer + contextpresentCredential throws if a requested attribute name does not exist. Pass a pinned pk and your own presentationHeader to verifyPresentation so the verifier asserts its own expectations rather than trusting the values carried in the presentation.
Status
Part of Proof-Carrying Authority. Experimental and unaudited. The CFRG draft is not a final standard, and BBS on BLS12-381 is not post-quantum.
What is validated (ciphersuite BLS12-381-SHA-256 only):
- Official fixture set of draft-irtf-cfrg-bbs-signatures, vendored in
test-vectors/with repository commit and per-file SHA-256: key generation,hash_to_scalar, message-to-scalar mapping, generators, all 10 signature cases (valid and invalid) and all 15 proof cases (valid and invalid). Signing reproduces the published signatures byte-for-byte, and proof generation with the published randomness reproduces the published proofs byte-for-byte. The fixtures are byte-identical across draft revisions -06 to -12. - Independent implementation: the Rust crate
zkryptium0.7.1 (draft -12, onbls12_381_plus). Keys and signatures it produces are reproduced exactly, its randomized proofs verify here, and proofs and signatures produced here (including no-disclosure and full-disclosure selections) verify there. Tampered proofs and signatures are rejected on both sides. The runner source is intest-vectors/xcheck-zkryptium. - Rejection reasons are asserted:
verifyDetailedandproofVerifyDetailedreport why something failed (malformed signature, public key or proof, pairing mismatch, challenge mismatch, bad disclosed indexes).
What is not validated:
- The SHAKE-256 ciphersuite, pseudonyms and blind signatures are not implemented.
- The credential layer (
issueCapabilityCredential,presentCredential) is PCA-specific and has no external standard to test against; it is covered by this package's own tests only. - Secret-scalar point multiplications (signing key,
1/(SK+e), signed messages, the prover's blinding scalars) use the library's fixed-window multiply with a fake point (Point.multiply), and secret inversions use Fermat exponentiation;multiplyUnsaferemains only for public verifier scalars. A dudect-style test (ct.test.ts: Welch t on interleaved weight-1 versus weight-253 scalars, fail at|t| >= 10with a median shift over 5%) passes, and a positive control with the variable-time multiply is detected. This is not a constant-time guarantee: JavaScript BigInt arithmetic and the JIT/GC have data-dependent timing, and the library's window method indexes a precomputed table by secret digits (a cache-timing channel). Use a native or WebAssembly constant-time implementation, or an HSM, for long-lived issuer keys facing a co-resident attacker. No independent audit.
Source and issues: https://github.com/Atlas-Authorization/pca
License
MIT - see LICENSE
