@nuggetslife/vc 0.0.23 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/demo_bbs_2023.mjs +203 -0
- package/demo_bbs_ietf.mjs +193 -0
- package/demo_sd_jwt.mjs +148 -0
- package/generate_golden_files.mjs +181 -0
- package/index.d.ts +42 -0
- package/index.js +14 -1
- package/package.json +7 -7
- package/src/bbs_2023.rs +110 -0
- package/src/bbs_ietf.rs +255 -0
- package/src/lib.rs +3 -0
- package/src/sd_jwt.rs +133 -0
- package/test-data/golden/inputDocument-minimal.json +17 -0
- package/test-data/golden/inputDocument-rich.json +29 -0
- package/test-data/golden/mattrglobal-derived-prc.json +36 -0
- package/test-data/golden/mattrglobal-signed-minimal.json +30 -0
- package/test-data/golden/mattrglobal-signed-prc.json +42 -0
- package/test-data/golden/mattrglobal-signed-rich.json +42 -0
- package/test_backward_compat.mjs +287 -0
- package/test_bbs_2023.mjs +427 -0
- package/test_bbs_ietf.mjs +168 -0
- package/test_sd_jwt.mjs +288 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BBS-2023 Data Integrity Demo — W3C Verifiable Credentials with Selective Disclosure
|
|
3
|
+
*
|
|
4
|
+
* This demo walks through the full BBS-2023 cryptosuite lifecycle:
|
|
5
|
+
* 1. Issuer signs a JSON-LD Verifiable Credential with a DataIntegrityProof
|
|
6
|
+
* 2. Holder derives a selective disclosure proof (reveals only chosen fields)
|
|
7
|
+
* 3. Verifier checks the derived proof
|
|
8
|
+
* 4. Bonus: Blind signing with holder commitment (holder binding)
|
|
9
|
+
*
|
|
10
|
+
* BBS-2023 replaces BbsBlsSignature2020 for W3C Data Integrity proofs.
|
|
11
|
+
*
|
|
12
|
+
* Key differences from BbsBlsSignature2020:
|
|
13
|
+
* OLD: JSON-LD framing with @explicit reveal documents, base58 key pairs,
|
|
14
|
+
* BbsBlsSignature2020/BbsBlsSignatureProof2020 proof types,
|
|
15
|
+
* class-based API (new BbsBlsSignature2020({key})), async operations
|
|
16
|
+
* NEW: JSON Pointers for field selection, hex-encoded BBS keys,
|
|
17
|
+
* single "DataIntegrityProof" type with cryptosuite "bbs-2023",
|
|
18
|
+
* function-based API, mandatory vs selective pointer split
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
bbsIetfKeygen,
|
|
23
|
+
bbs2023Sign,
|
|
24
|
+
bbs2023Derive,
|
|
25
|
+
bbs2023Verify,
|
|
26
|
+
bbs2023HolderCommit,
|
|
27
|
+
bbs2023BlindSign,
|
|
28
|
+
} from './index.js';
|
|
29
|
+
|
|
30
|
+
function separator(title) {
|
|
31
|
+
console.log('\n' + '='.repeat(70));
|
|
32
|
+
console.log(` ${title}`);
|
|
33
|
+
console.log('='.repeat(70) + '\n');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function printProof(doc) {
|
|
37
|
+
const { proof, ...rest } = doc;
|
|
38
|
+
console.log('Document (without proof):', JSON.stringify(rest, null, 2));
|
|
39
|
+
console.log('Proof:');
|
|
40
|
+
console.log(' type:', proof.type);
|
|
41
|
+
console.log(' cryptosuite:', proof.cryptosuite);
|
|
42
|
+
console.log(' proofValue:', proof.proofValue.substring(0, 60) + '...');
|
|
43
|
+
if (proof.verificationMethod) console.log(' verificationMethod:', proof.verificationMethod);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Key Setup ────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
separator('Step 0: Generate BBS Key Pair');
|
|
49
|
+
|
|
50
|
+
const kp = bbsIetfKeygen();
|
|
51
|
+
console.log('BBS key pair (IETF draft-irtf-cfrg-bbs-signatures):');
|
|
52
|
+
console.log(' Secret key (hex, 32 bytes):', kp.secretKey.substring(0, 20) + '...');
|
|
53
|
+
console.log(' Public key (hex, 96 bytes):', kp.publicKey.substring(0, 20) + '...');
|
|
54
|
+
|
|
55
|
+
// ── Sample Credential ────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
const credential = {
|
|
58
|
+
'@context': [
|
|
59
|
+
'https://www.w3.org/2018/credentials/v1',
|
|
60
|
+
{
|
|
61
|
+
// Inline context defining terms (in production, use published contexts)
|
|
62
|
+
givenName: 'http://schema.org/givenName',
|
|
63
|
+
familyName: 'http://schema.org/familyName',
|
|
64
|
+
email: 'http://schema.org/email',
|
|
65
|
+
birthDate: 'http://schema.org/birthDate',
|
|
66
|
+
alumniOf: 'http://schema.org/alumniOf',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
type: ['VerifiableCredential'],
|
|
70
|
+
issuer: 'did:example:issuer',
|
|
71
|
+
issuanceDate: '2025-01-01T00:00:00Z',
|
|
72
|
+
credentialSubject: {
|
|
73
|
+
id: 'did:example:alice',
|
|
74
|
+
givenName: 'Alice',
|
|
75
|
+
familyName: 'Smith',
|
|
76
|
+
email: 'alice@example.com',
|
|
77
|
+
birthDate: '1990-01-15',
|
|
78
|
+
alumniOf: 'Example University',
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// ── 1. Sign ──────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
separator('Step 1: Issuer signs the credential (base proof)');
|
|
85
|
+
|
|
86
|
+
console.log('Mandatory pointers (always revealed): /issuer, /issuanceDate');
|
|
87
|
+
console.log('Everything else can be selectively disclosed by the holder.\n');
|
|
88
|
+
|
|
89
|
+
const signed = bbs2023Sign({
|
|
90
|
+
document: credential,
|
|
91
|
+
secretKey: kp.secretKey,
|
|
92
|
+
publicKey: kp.publicKey,
|
|
93
|
+
mandatoryPointers: ['/issuer', '/issuanceDate'],
|
|
94
|
+
verificationMethod: 'did:example:issuer#bbs-key-1',
|
|
95
|
+
proofPurpose: 'assertionMethod',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
printProof(signed);
|
|
99
|
+
|
|
100
|
+
console.log('\nCompare with old BbsBlsSignature2020:');
|
|
101
|
+
console.log(' OLD: proof.type = "BbsBlsSignature2020"');
|
|
102
|
+
console.log(' NEW: proof.type = "DataIntegrityProof", proof.cryptosuite = "bbs-2023"');
|
|
103
|
+
|
|
104
|
+
// Verify the base proof
|
|
105
|
+
const baseResult = bbs2023Verify(signed, kp.publicKey);
|
|
106
|
+
console.log('\nBase proof verified:', baseResult.verified);
|
|
107
|
+
|
|
108
|
+
// ── 2. Derive ────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
separator('Step 2: Holder derives selective disclosure proof');
|
|
111
|
+
|
|
112
|
+
console.log('Selective pointers (holder chooses to reveal): /credentialSubject/givenName');
|
|
113
|
+
console.log('Hidden: familyName, email, birthDate, alumniOf\n');
|
|
114
|
+
|
|
115
|
+
console.log('Compare with old BbsBlsSignature2020:');
|
|
116
|
+
console.log(' OLD: revealDocument = { credentialSubject: { "@explicit": true, givenName: {} } }');
|
|
117
|
+
console.log(' NEW: selectivePointers = ["/credentialSubject/givenName"]\n');
|
|
118
|
+
|
|
119
|
+
const derived = bbs2023Derive({
|
|
120
|
+
document: signed,
|
|
121
|
+
selectivePointers: ['/credentialSubject/givenName'],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
printProof(derived);
|
|
125
|
+
|
|
126
|
+
console.log('\nNote: the derived proof only covers mandatory + selected fields.');
|
|
127
|
+
console.log('The proof will not verify claims outside those sets.');
|
|
128
|
+
console.log('In practice, the holder strips undisclosed fields before sharing.');
|
|
129
|
+
|
|
130
|
+
// ── 3. Verify ────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
separator('Step 3: Verifier checks the derived proof');
|
|
133
|
+
|
|
134
|
+
const derivedResult = bbs2023Verify(derived, kp.publicKey);
|
|
135
|
+
console.log('Derived proof verified:', derivedResult.verified);
|
|
136
|
+
console.log('Error:', derivedResult.error ?? 'none');
|
|
137
|
+
|
|
138
|
+
// Show what's cryptographically attested
|
|
139
|
+
console.log('\nCryptographically attested fields (mandatory + selective pointers):');
|
|
140
|
+
console.log(' issuer:', derived.issuer, '(mandatory)');
|
|
141
|
+
console.log(' issuanceDate:', derived.issuanceDate, '(mandatory)');
|
|
142
|
+
console.log(' credentialSubject.givenName:', derived.credentialSubject?.givenName, '(selective)');
|
|
143
|
+
console.log(' credentialSubject.familyName: present but NOT attested by derived proof');
|
|
144
|
+
console.log(' credentialSubject.email: present but NOT attested by derived proof');
|
|
145
|
+
|
|
146
|
+
// Wrong key should fail
|
|
147
|
+
const wrongKp = bbsIetfKeygen();
|
|
148
|
+
const wrongResult = bbs2023Verify(derived, wrongKp.publicKey);
|
|
149
|
+
console.log('\nVerify with wrong key:', wrongResult.verified, '(expected false)');
|
|
150
|
+
|
|
151
|
+
// ── 4. Blind Signing (Holder Commitment) ─────────────────────────────
|
|
152
|
+
|
|
153
|
+
separator('Step 4: Blind Signing — Holder Binding');
|
|
154
|
+
|
|
155
|
+
console.log('Blind signing lets the holder commit to secret fields before the issuer signs.');
|
|
156
|
+
console.log('The issuer signs without seeing the committed values.');
|
|
157
|
+
console.log('This has no equivalent in BbsBlsSignature2020.\n');
|
|
158
|
+
|
|
159
|
+
// 4a. Holder creates a commitment
|
|
160
|
+
const commitment = bbs2023HolderCommit({
|
|
161
|
+
document: credential,
|
|
162
|
+
publicKey: kp.publicKey,
|
|
163
|
+
mandatoryPointers: ['/issuer', '/issuanceDate'],
|
|
164
|
+
committedPointers: ['/credentialSubject/email'],
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
console.log('Holder commitment:');
|
|
168
|
+
console.log(' commitment (hex):', commitment.commitment.substring(0, 40) + '...');
|
|
169
|
+
console.log(' blindFactor (hex):', commitment.blindFactor.substring(0, 40) + '...');
|
|
170
|
+
console.log(' committedIndices:', commitment.committedIndices);
|
|
171
|
+
|
|
172
|
+
// 4b. Issuer blind-signs with the commitment
|
|
173
|
+
const blindSigned = bbs2023BlindSign({
|
|
174
|
+
document: credential,
|
|
175
|
+
secretKey: kp.secretKey,
|
|
176
|
+
publicKey: kp.publicKey,
|
|
177
|
+
commitment: commitment.commitment,
|
|
178
|
+
hmacKey: commitment.hmacKey,
|
|
179
|
+
committedIndices: commitment.committedIndices,
|
|
180
|
+
mandatoryPointers: ['/issuer', '/issuanceDate'],
|
|
181
|
+
verificationMethod: 'did:example:issuer#bbs-key-1',
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
console.log('\nBlind-signed proof created:', blindSigned.proof.type, blindSigned.proof.cryptosuite);
|
|
185
|
+
|
|
186
|
+
// 4c. Holder derives (must include blindFactor)
|
|
187
|
+
const blindDerived = bbs2023Derive({
|
|
188
|
+
document: blindSigned,
|
|
189
|
+
selectivePointers: ['/credentialSubject/givenName', '/credentialSubject/email'],
|
|
190
|
+
blindFactor: commitment.blindFactor,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// 4d. Verify
|
|
194
|
+
const blindResult = bbs2023Verify(blindDerived, kp.publicKey);
|
|
195
|
+
console.log('Blind-signed derived proof verified:', blindResult.verified);
|
|
196
|
+
|
|
197
|
+
separator('Demo Complete');
|
|
198
|
+
console.log('BBS-2023 provides W3C-standard selective disclosure with:');
|
|
199
|
+
console.log(' - JSON Pointers instead of JSON-LD framing');
|
|
200
|
+
console.log(' - DataIntegrityProof type (not BbsBlsSignature2020)');
|
|
201
|
+
console.log(' - Mandatory vs selective pointer split');
|
|
202
|
+
console.log(' - Blind signing for holder binding (new capability)');
|
|
203
|
+
console.log('');
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BBS IETF Demo — Low-Level BBS Signatures (draft-irtf-cfrg-bbs-signatures)
|
|
3
|
+
*
|
|
4
|
+
* This demo shows the raw BBS cryptographic primitives:
|
|
5
|
+
* 1. Key generation
|
|
6
|
+
* 2. Sign a set of messages
|
|
7
|
+
* 3. Verify the signature
|
|
8
|
+
* 4. Generate a selective disclosure proof (reveal only some messages)
|
|
9
|
+
* 5. Verify the proof
|
|
10
|
+
*
|
|
11
|
+
* This is the low-level layer that BBS-2023 is built on top of.
|
|
12
|
+
* Most developers will use BBS-2023 (for VCs) or SD-JWT (for plain claims)
|
|
13
|
+
* instead of calling these directly. But understanding the primitives helps.
|
|
14
|
+
*
|
|
15
|
+
* Key concepts:
|
|
16
|
+
* - BBS signs a list of messages as a single signature
|
|
17
|
+
* - A holder can create a zero-knowledge proof revealing only some messages
|
|
18
|
+
* - The verifier learns nothing about hidden messages
|
|
19
|
+
* - Two ciphersuites: SHA-256 (default) and SHAKE-256
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
bbsIetfKeygen,
|
|
24
|
+
bbsIetfSign,
|
|
25
|
+
bbsIetfVerify,
|
|
26
|
+
bbsIetfProofGen,
|
|
27
|
+
bbsIetfProofVerify,
|
|
28
|
+
} from './index.js';
|
|
29
|
+
|
|
30
|
+
function separator(title) {
|
|
31
|
+
console.log('\n' + '='.repeat(70));
|
|
32
|
+
console.log(` ${title}`);
|
|
33
|
+
console.log('='.repeat(70) + '\n');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── 1. Key Generation ────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
separator('Step 1: Key Generation');
|
|
39
|
+
|
|
40
|
+
// Generate a random BBS key pair
|
|
41
|
+
const kp = bbsIetfKeygen();
|
|
42
|
+
console.log('Random BBS key pair:');
|
|
43
|
+
console.log(' Secret key (32 bytes hex):', kp.secretKey);
|
|
44
|
+
console.log(' Public key (96 bytes hex):', kp.publicKey.substring(0, 40) + '...');
|
|
45
|
+
|
|
46
|
+
// Deterministic keygen with IKM (Input Key Material)
|
|
47
|
+
const ikm = Buffer.alloc(32);
|
|
48
|
+
for (let i = 0; i < 32; i++) ikm[i] = i + 1;
|
|
49
|
+
const kpDet = bbsIetfKeygen(ikm);
|
|
50
|
+
const kpDet2 = bbsIetfKeygen(ikm);
|
|
51
|
+
console.log('\nDeterministic keygen (same IKM = same keys):');
|
|
52
|
+
console.log(' Keys match:', kpDet.secretKey === kpDet2.secretKey);
|
|
53
|
+
|
|
54
|
+
// ── 2. Sign Messages ────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
separator('Step 2: Sign a Set of Messages');
|
|
57
|
+
|
|
58
|
+
// BBS signs an ordered list of messages in a single operation.
|
|
59
|
+
// Each message is a string; the library handles hashing to curve scalars.
|
|
60
|
+
const messages = [
|
|
61
|
+
'given_name: Alice',
|
|
62
|
+
'family_name: Smith',
|
|
63
|
+
'date_of_birth: 1990-01-15',
|
|
64
|
+
'email: alice@example.com',
|
|
65
|
+
'employee_id: EMP-42',
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
console.log('Messages to sign:');
|
|
69
|
+
messages.forEach((m, i) => console.log(` [${i}] ${m}`));
|
|
70
|
+
|
|
71
|
+
const sk = Buffer.from(kp.secretKey, 'hex');
|
|
72
|
+
const pk = Buffer.from(kp.publicKey, 'hex');
|
|
73
|
+
const header = Buffer.from('credential-context-v1');
|
|
74
|
+
|
|
75
|
+
const signature = bbsIetfSign(sk, pk, header, messages, 'SHA-256');
|
|
76
|
+
console.log('\nSignature (80 bytes):', signature.toString('hex').substring(0, 40) + '...');
|
|
77
|
+
|
|
78
|
+
// ── 3. Verify Signature ─────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
separator('Step 3: Verify the Signature');
|
|
81
|
+
|
|
82
|
+
const isValid = bbsIetfVerify(pk, signature, header, messages, 'SHA-256');
|
|
83
|
+
console.log('Signature valid:', isValid);
|
|
84
|
+
|
|
85
|
+
// Wrong key
|
|
86
|
+
const wrongKp = bbsIetfKeygen();
|
|
87
|
+
const wrongPk = Buffer.from(wrongKp.publicKey, 'hex');
|
|
88
|
+
const isValidWrong = bbsIetfVerify(wrongPk, signature, header, messages, 'SHA-256');
|
|
89
|
+
console.log('Wrong key valid:', isValidWrong, '(expected false)');
|
|
90
|
+
|
|
91
|
+
// Tampered message
|
|
92
|
+
const tampered = [...messages];
|
|
93
|
+
tampered[2] = 'date_of_birth: 2000-01-01';
|
|
94
|
+
const isValidTampered = bbsIetfVerify(pk, signature, header, tampered, 'SHA-256');
|
|
95
|
+
console.log('Tampered message valid:', isValidTampered, '(expected false)');
|
|
96
|
+
|
|
97
|
+
// ── 4. Selective Disclosure Proof ────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
separator('Step 4: Generate Selective Disclosure Proof');
|
|
100
|
+
|
|
101
|
+
// The holder wants to prove they have a valid credential but only
|
|
102
|
+
// reveal their name and employee ID (indices 0, 1, 4).
|
|
103
|
+
// date_of_birth (2) and email (3) stay hidden.
|
|
104
|
+
|
|
105
|
+
const disclosedIndices = [0, 1, 4];
|
|
106
|
+
const presentationHeader = Buffer.from('verifier-challenge-xyz');
|
|
107
|
+
|
|
108
|
+
console.log('Disclosed messages (what the verifier will see):');
|
|
109
|
+
for (const idx of disclosedIndices) {
|
|
110
|
+
console.log(` [${idx}] ${messages[idx]}`);
|
|
111
|
+
}
|
|
112
|
+
console.log('Hidden messages (verifier learns nothing about these):');
|
|
113
|
+
for (let i = 0; i < messages.length; i++) {
|
|
114
|
+
if (!disclosedIndices.includes(i)) {
|
|
115
|
+
console.log(` [${i}] ${messages[i]}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const proof = bbsIetfProofGen(
|
|
120
|
+
pk,
|
|
121
|
+
signature,
|
|
122
|
+
header,
|
|
123
|
+
presentationHeader,
|
|
124
|
+
messages,
|
|
125
|
+
disclosedIndices,
|
|
126
|
+
'SHA-256',
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
console.log(`\nProof generated (${proof.length} bytes)`);
|
|
130
|
+
|
|
131
|
+
// ── 5. Verify Proof ─────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
separator('Step 5: Verify the Selective Disclosure Proof');
|
|
134
|
+
|
|
135
|
+
// The verifier only has: public key, proof, disclosed messages, total count
|
|
136
|
+
// They do NOT have the original signature or hidden messages.
|
|
137
|
+
const disclosedMessages = {};
|
|
138
|
+
for (const idx of disclosedIndices) {
|
|
139
|
+
disclosedMessages[idx] = messages[idx];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log('Verifier inputs:');
|
|
143
|
+
console.log(' Public key: (96 bytes)');
|
|
144
|
+
console.log(' Proof: (' + proof.length + ' bytes)');
|
|
145
|
+
console.log(' Disclosed messages:', JSON.stringify(disclosedMessages, null, 4));
|
|
146
|
+
console.log(' Total message count:', messages.length);
|
|
147
|
+
|
|
148
|
+
const proofValid = bbsIetfProofVerify(
|
|
149
|
+
pk,
|
|
150
|
+
proof,
|
|
151
|
+
header,
|
|
152
|
+
presentationHeader,
|
|
153
|
+
disclosedMessages,
|
|
154
|
+
messages.length,
|
|
155
|
+
'SHA-256',
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
console.log('\nProof valid:', proofValid);
|
|
159
|
+
|
|
160
|
+
// Try with wrong disclosed message
|
|
161
|
+
const wrongDisclosed = { ...disclosedMessages, 0: 'given_name: Bob' };
|
|
162
|
+
const proofValidWrong = bbsIetfProofVerify(
|
|
163
|
+
pk, proof, header, presentationHeader,
|
|
164
|
+
wrongDisclosed, messages.length, 'SHA-256',
|
|
165
|
+
);
|
|
166
|
+
console.log('Wrong disclosed message valid:', proofValidWrong, '(expected false)');
|
|
167
|
+
|
|
168
|
+
// ── 6. Ciphersuites ─────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
separator('Step 6: Ciphersuite Comparison');
|
|
171
|
+
|
|
172
|
+
console.log('Two ciphersuites available:');
|
|
173
|
+
console.log(' SHA-256 — default, widely compatible');
|
|
174
|
+
console.log(' SHAKE-256 — alternative hash, same security level\n');
|
|
175
|
+
|
|
176
|
+
const shakekp = bbsIetfKeygen(ikm, null, 'SHAKE-256');
|
|
177
|
+
const shakeSk = Buffer.from(shakekp.secretKey, 'hex');
|
|
178
|
+
const shakePk = Buffer.from(shakekp.publicKey, 'hex');
|
|
179
|
+
|
|
180
|
+
const shakeSig = bbsIetfSign(shakeSk, shakePk, null, ['test'], 'SHAKE-256');
|
|
181
|
+
const shakeValid = bbsIetfVerify(shakePk, shakeSig, null, ['test'], 'SHAKE-256');
|
|
182
|
+
console.log('SHAKE-256 sign + verify:', shakeValid);
|
|
183
|
+
|
|
184
|
+
// Cross-ciphersuite: should NOT work
|
|
185
|
+
const crossValid = bbsIetfVerify(shakePk, shakeSig, null, ['test'], 'SHA-256');
|
|
186
|
+
console.log('SHA-256 verify of SHAKE-256 sig:', crossValid, '(expected false)');
|
|
187
|
+
|
|
188
|
+
separator('Demo Complete');
|
|
189
|
+
console.log('BBS IETF provides the cryptographic foundation for:');
|
|
190
|
+
console.log(' - BBS-2023 Data Integrity proofs (high-level VC API)');
|
|
191
|
+
console.log(' - Any application needing multi-message signatures with selective disclosure');
|
|
192
|
+
console.log(' - Zero-knowledge proofs: verifier learns nothing about hidden messages');
|
|
193
|
+
console.log('');
|
package/demo_sd_jwt.mjs
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SD-JWT Demo — Selective Disclosure using JSON Web Tokens
|
|
3
|
+
*
|
|
4
|
+
* This demo walks through the full SD-JWT lifecycle:
|
|
5
|
+
* 1. Issuer creates a credential with selectively-disclosable claims
|
|
6
|
+
* 2. Holder creates a presentation revealing only chosen claims
|
|
7
|
+
* 3. Verifier checks the presentation
|
|
8
|
+
*
|
|
9
|
+
* SD-JWT (RFC 9901) is a simpler alternative to BBS+ for selective disclosure.
|
|
10
|
+
* It works with standard JWTs (ES256, EdDSA, etc.) — no pairing-based crypto needed.
|
|
11
|
+
*
|
|
12
|
+
* Compare with BbsBlsSignature2020:
|
|
13
|
+
* OLD: Requires JSON-LD framing, BLS key pairs, reveal documents, linked data suites
|
|
14
|
+
* NEW: Plain JSON claims, standard JWK keys, just list which fields are disclosable
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
JoseNamedCurve,
|
|
19
|
+
generateJwk,
|
|
20
|
+
sdJwtIssue,
|
|
21
|
+
sdJwtPresent,
|
|
22
|
+
sdJwtVerify,
|
|
23
|
+
} from './index.js';
|
|
24
|
+
|
|
25
|
+
function separator(title) {
|
|
26
|
+
console.log('\n' + '='.repeat(70));
|
|
27
|
+
console.log(` ${title}`);
|
|
28
|
+
console.log('='.repeat(70) + '\n');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── Key Setup ────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
separator('Step 0: Generate Keys');
|
|
34
|
+
|
|
35
|
+
const issuerJwk = generateJwk(JoseNamedCurve.P256);
|
|
36
|
+
const { d: _issD, ...issuerPublicJwk } = issuerJwk;
|
|
37
|
+
console.log('Issuer key (ES256/P-256):');
|
|
38
|
+
console.log(' Public:', JSON.stringify(issuerPublicJwk));
|
|
39
|
+
|
|
40
|
+
const holderJwk = generateJwk(JoseNamedCurve.P256);
|
|
41
|
+
const { d: _holD, ...holderPublicJwk } = holderJwk;
|
|
42
|
+
console.log('Holder key (ES256/P-256):');
|
|
43
|
+
console.log(' Public:', JSON.stringify(holderPublicJwk));
|
|
44
|
+
|
|
45
|
+
// ── 1. Issue ─────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
separator('Step 1: Issuer creates SD-JWT credential');
|
|
48
|
+
|
|
49
|
+
const claims = {
|
|
50
|
+
iss: 'https://issuer.example.com',
|
|
51
|
+
sub: 'did:example:user123',
|
|
52
|
+
given_name: 'Alice',
|
|
53
|
+
family_name: 'Smith',
|
|
54
|
+
email: 'alice@example.com',
|
|
55
|
+
birthdate: '1990-01-15',
|
|
56
|
+
nationalities: ['US', 'DE'],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Mark which claims the holder can choose to reveal or hide.
|
|
60
|
+
// Non-listed claims (iss, sub) are always visible.
|
|
61
|
+
const disclosable = [
|
|
62
|
+
'given_name',
|
|
63
|
+
'family_name',
|
|
64
|
+
'email',
|
|
65
|
+
'birthdate',
|
|
66
|
+
'nationalities[0]', // Individual array elements can be disclosable too
|
|
67
|
+
'nationalities[1]',
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
console.log('Claims:', JSON.stringify(claims, null, 2));
|
|
71
|
+
console.log('Disclosable fields:', disclosable);
|
|
72
|
+
|
|
73
|
+
const issued = sdJwtIssue(
|
|
74
|
+
claims,
|
|
75
|
+
disclosable,
|
|
76
|
+
issuerJwk, // Issuer's private key (signs the JWT)
|
|
77
|
+
'ES256', // Standard JWT algorithm
|
|
78
|
+
holderPublicJwk, // Optional: bind to holder's key (Key Binding)
|
|
79
|
+
2, // Optional: add 2 decoy digests (privacy)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
console.log('\nSD-JWT token (truncated):');
|
|
83
|
+
console.log(' ' + issued.sdJwt.substring(0, 80) + '...');
|
|
84
|
+
console.log(`\n${issued.disclosures.length} disclosures created:`);
|
|
85
|
+
for (const d of issued.disclosures) {
|
|
86
|
+
const label = d.claimName || '(array element)';
|
|
87
|
+
console.log(` - ${label}: ${JSON.stringify(d.claimValue)}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── 2. Present (selective disclosure) ────────────────────────────────
|
|
91
|
+
|
|
92
|
+
separator('Step 2: Holder creates presentation (selective disclosure)');
|
|
93
|
+
|
|
94
|
+
// The holder chooses which disclosures to reveal.
|
|
95
|
+
// Here: reveal name but hide email, birthdate, and one nationality.
|
|
96
|
+
const nameDisclosures = issued.disclosures.filter(
|
|
97
|
+
d => d.claimName === 'given_name' || d.claimName === 'family_name'
|
|
98
|
+
);
|
|
99
|
+
const usDisclosure = issued.disclosures.find(
|
|
100
|
+
d => !d.claimName && d.claimValue === 'US'
|
|
101
|
+
);
|
|
102
|
+
const toReveal = [...nameDisclosures, usDisclosure].filter(Boolean).map(d => d.encoded);
|
|
103
|
+
|
|
104
|
+
console.log('Revealing:', nameDisclosures.map(d => d.claimName).join(', '), '+ US nationality');
|
|
105
|
+
console.log('Hiding: email, birthdate, DE nationality');
|
|
106
|
+
|
|
107
|
+
const presentation = sdJwtPresent(
|
|
108
|
+
issued.sdJwt,
|
|
109
|
+
toReveal,
|
|
110
|
+
holderJwk, // Holder's private key (for Key Binding JWT)
|
|
111
|
+
'ES256',
|
|
112
|
+
'https://verifier.example.com', // Audience
|
|
113
|
+
'nonce-abc-123', // Nonce from verifier
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
console.log('\nPresentation token (truncated):');
|
|
117
|
+
console.log(' ' + presentation.presentation.substring(0, 80) + '...');
|
|
118
|
+
|
|
119
|
+
// ── 3. Verify ────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
separator('Step 3: Verifier checks the presentation');
|
|
122
|
+
|
|
123
|
+
const result = sdJwtVerify(
|
|
124
|
+
presentation.presentation,
|
|
125
|
+
issuerPublicJwk, // Issuer's public key
|
|
126
|
+
holderPublicJwk, // Holder's public key (verify Key Binding)
|
|
127
|
+
'https://verifier.example.com', // Expected audience
|
|
128
|
+
'nonce-abc-123', // Expected nonce
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
console.log('Verified:', result.verified);
|
|
132
|
+
console.log('Disclosed claims:', JSON.stringify(result.claims, null, 2));
|
|
133
|
+
console.log('');
|
|
134
|
+
|
|
135
|
+
// Show what the verifier can and cannot see
|
|
136
|
+
console.log('What the verifier sees:');
|
|
137
|
+
console.log(' iss:', result.claims.iss, '(always visible)');
|
|
138
|
+
console.log(' sub:', result.claims.sub, '(always visible)');
|
|
139
|
+
console.log(' given_name:', result.claims.given_name ?? '(hidden)');
|
|
140
|
+
console.log(' family_name:', result.claims.family_name ?? '(hidden)');
|
|
141
|
+
console.log(' email:', result.claims.email ?? '(hidden)');
|
|
142
|
+
console.log(' birthdate:', result.claims.birthdate ?? '(hidden)');
|
|
143
|
+
console.log(' nationalities:', JSON.stringify(result.claims.nationalities));
|
|
144
|
+
|
|
145
|
+
separator('Demo Complete');
|
|
146
|
+
console.log('SD-JWT provides selective disclosure with standard JWT infrastructure.');
|
|
147
|
+
console.log('No JSON-LD, no BLS keys, no linked data suites required.');
|
|
148
|
+
console.log('');
|