@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.
@@ -0,0 +1,181 @@
1
+ /**
2
+ * generate_golden_files.mjs
3
+ *
4
+ * Generates golden-file signed VCs using the @mattrglobal library.
5
+ * These fixtures are saved as static JSON for backward compatibility testing
6
+ * against the new NAPI-based implementation.
7
+ *
8
+ * Outputs:
9
+ * test-data/golden/mattrglobal-signed-prc.json (BbsBlsSignature2020 signed VC)
10
+ * test-data/golden/mattrglobal-derived-prc.json (BbsBlsSignatureProof2020 derived proof)
11
+ *
12
+ * Usage:
13
+ * node generate_golden_files.mjs
14
+ */
15
+
16
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
17
+ import { resolve, dirname } from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { createRequire } from 'node:module';
20
+
21
+ // @mattrglobal libraries (CJS, loaded via createRequire)
22
+ const require = createRequire(import.meta.url);
23
+ const { Bls12381G2KeyPair } = require('@mattrglobal/bls12381-key-pair');
24
+ const {
25
+ BbsBlsSignature2020,
26
+ BbsBlsSignatureProof2020,
27
+ deriveProof,
28
+ } = require('@mattrglobal/jsonld-signatures-bbs');
29
+ const { sign, purposes, extendContextLoader } = require('jsonld-signatures');
30
+
31
+ const __dirname = dirname(fileURLToPath(import.meta.url));
32
+ const dataDir = resolve(__dirname, 'test-data');
33
+ const goldenDir = resolve(dataDir, 'golden');
34
+ const loadJson = (name) => JSON.parse(readFileSync(resolve(dataDir, name), 'utf8'));
35
+
36
+ // Load test data
37
+ const inputDocument = loadJson('inputDocument.json');
38
+ const keyPairJson = loadJson('keyPair.json');
39
+ const controllerDocument = loadJson('controllerDocument.json');
40
+ const citizenVocab = loadJson('citizenVocab.json');
41
+ const bbsContext = loadJson('bbs.json');
42
+ const credentialsContext = loadJson('credentialsContext.json');
43
+ const suiteContext = loadJson('suiteContext.json');
44
+ const revealDocument = loadJson('deriveProofFrame.json');
45
+
46
+ // Build document loader (same pattern as test_jsonld_crossverify.mjs)
47
+ const documents = {
48
+ "did:example:489398593#test": keyPairJson,
49
+ "did:example:489398593": controllerDocument,
50
+ "https://w3id.org/citizenship/v1": citizenVocab,
51
+ "https://w3id.org/security/bbs/v1": bbsContext,
52
+ "https://www.w3.org/2018/credentials/v1": credentialsContext,
53
+ "https://w3id.org/security/suites/jws-2020/v1": suiteContext,
54
+ };
55
+
56
+ const customDocLoader = (url) => {
57
+ const context = documents[url];
58
+ if (context) {
59
+ return { contextUrl: null, document: context, documentUrl: url };
60
+ }
61
+ throw new Error(`Attempted to remote load context: '${url}', please cache instead`);
62
+ };
63
+ const documentLoader = extendContextLoader(customDocLoader);
64
+
65
+ async function main() {
66
+ // Ensure golden directory exists
67
+ mkdirSync(goldenDir, { recursive: true });
68
+
69
+ // ---------- Step 1: Sign the PRC document ----------
70
+ console.log('Creating key pair from test data...');
71
+ const keyPair = await Bls12381G2KeyPair.from({
72
+ id: keyPairJson.id,
73
+ controller: keyPairJson.controller,
74
+ publicKeyBase58: keyPairJson.publicKeyBase58,
75
+ privateKeyBase58: keyPairJson.privateKeyBase58,
76
+ });
77
+
78
+ const suite = new BbsBlsSignature2020({ key: keyPair });
79
+
80
+ console.log('Signing inputDocument with @mattrglobal BbsBlsSignature2020...');
81
+ const signedDocument = await sign(inputDocument, {
82
+ suite,
83
+ purpose: new purposes.AssertionProofPurpose(),
84
+ documentLoader,
85
+ });
86
+
87
+ const signedPath = resolve(goldenDir, 'mattrglobal-signed-prc.json');
88
+ writeFileSync(signedPath, JSON.stringify(signedDocument, null, 2) + '\n');
89
+ console.log(` Saved signed document to: ${signedPath}`);
90
+ console.log(` Proof type: ${signedDocument.proof.type}`);
91
+ console.log(` Verification method: ${signedDocument.proof.verificationMethod}`);
92
+
93
+ // ---------- Step 2: Derive a proof ----------
94
+ console.log('Deriving proof with @mattrglobal BbsBlsSignatureProof2020...');
95
+ const derivedDocument = await deriveProof(signedDocument, revealDocument, {
96
+ suite: new BbsBlsSignatureProof2020(),
97
+ documentLoader,
98
+ });
99
+
100
+ const derivedPath = resolve(goldenDir, 'mattrglobal-derived-prc.json');
101
+ writeFileSync(derivedPath, JSON.stringify(derivedDocument, null, 2) + '\n');
102
+ console.log(` Saved derived document to: ${derivedPath}`);
103
+ console.log(` Proof type: ${derivedDocument.proof.type}`);
104
+ console.log(` Nonce present: ${!!derivedDocument.proof.nonce}`);
105
+
106
+ // ---------- Step 3: Verify both documents ----------
107
+ console.log('Verifying signed document...');
108
+ const { verify } = require('jsonld-signatures');
109
+ const verifySignedResult = await verify(signedDocument, {
110
+ suite: new BbsBlsSignature2020(),
111
+ purpose: new purposes.AssertionProofPurpose(),
112
+ documentLoader,
113
+ });
114
+ console.log(` Signed document verified: ${verifySignedResult.verified}`);
115
+ if (!verifySignedResult.verified) {
116
+ console.error(' ERROR:', verifySignedResult.error);
117
+ process.exit(1);
118
+ }
119
+
120
+ console.log('Verifying derived document...');
121
+ const verifyDerivedResult = await verify(derivedDocument, {
122
+ suite: new BbsBlsSignatureProof2020(),
123
+ purpose: new purposes.AssertionProofPurpose(),
124
+ documentLoader,
125
+ });
126
+ console.log(` Derived document verified: ${verifyDerivedResult.verified}`);
127
+ if (!verifyDerivedResult.verified) {
128
+ console.error(' ERROR:', verifyDerivedResult.error);
129
+ process.exit(1);
130
+ }
131
+
132
+ // ---------- Step 4: Sign additional VC variants ----------
133
+ // These test different field counts (BBS+ signatures depend on statement count)
134
+
135
+ const loadGolden = (name) => JSON.parse(readFileSync(resolve(goldenDir, name), 'utf8'));
136
+
137
+ // Minimal VC (few fields)
138
+ const minimalDoc = loadGolden('inputDocument-minimal.json');
139
+ console.log('Signing minimal VC (few fields)...');
140
+ const signedMinimal = await sign(minimalDoc, {
141
+ suite: new BbsBlsSignature2020({ key: keyPair }),
142
+ purpose: new purposes.AssertionProofPurpose(),
143
+ documentLoader,
144
+ });
145
+ const minimalPath = resolve(goldenDir, 'mattrglobal-signed-minimal.json');
146
+ writeFileSync(minimalPath, JSON.stringify(signedMinimal, null, 2) + '\n');
147
+ console.log(` Saved: ${minimalPath}`);
148
+
149
+ // Rich VC (many fields, similar to Nuggets identity credentials)
150
+ const richDoc = loadGolden('inputDocument-rich.json');
151
+ console.log('Signing rich VC (many fields)...');
152
+ const signedRich = await sign(richDoc, {
153
+ suite: new BbsBlsSignature2020({ key: keyPair }),
154
+ purpose: new purposes.AssertionProofPurpose(),
155
+ documentLoader,
156
+ });
157
+ const richPath = resolve(goldenDir, 'mattrglobal-signed-rich.json');
158
+ writeFileSync(richPath, JSON.stringify(signedRich, null, 2) + '\n');
159
+ console.log(` Saved: ${richPath}`);
160
+
161
+ // Verify additional variants
162
+ for (const [name, doc] of [['minimal', signedMinimal], ['rich', signedRich]]) {
163
+ const result = await verify(doc, {
164
+ suite: new BbsBlsSignature2020(),
165
+ purpose: new purposes.AssertionProofPurpose(),
166
+ documentLoader,
167
+ });
168
+ console.log(` ${name} VC verified: ${result.verified}`);
169
+ if (!result.verified) {
170
+ console.error(' ERROR:', result.error);
171
+ process.exit(1);
172
+ }
173
+ }
174
+
175
+ console.log('\nAll golden files generated and verified successfully.');
176
+ }
177
+
178
+ main().catch((err) => {
179
+ console.error('Fatal error:', err);
180
+ process.exit(1);
181
+ });
package/index.d.ts CHANGED
@@ -3,6 +3,26 @@
3
3
 
4
4
  /* auto-generated by NAPI-RS */
5
5
 
6
+ export interface Bbs2023VerifyOutput {
7
+ verified: boolean
8
+ error?: string
9
+ }
10
+ export declare function bbs2023Sign(options: any): any
11
+ export declare function bbs2023Derive(options: any): any
12
+ export declare function bbs2023HolderCommit(options: any): any
13
+ export declare function bbs2023BlindSign(options: any): any
14
+ export declare function bbs2023Verify(document: any, publicKey?: string | undefined | null): Bbs2023VerifyOutput
15
+ export interface BbsIetfKeyPair {
16
+ /** Hex-encoded secret key (32 bytes) */
17
+ secretKey: string
18
+ /** Hex-encoded public key (96 bytes, compressed G2) */
19
+ publicKey: string
20
+ }
21
+ export declare function bbsIetfKeygen(ikm?: Buffer | undefined | null, keyInfo?: Buffer | undefined | null, ciphersuite?: string | undefined | null): BbsIetfKeyPair
22
+ export declare function bbsIetfSign(secretKey: Buffer, publicKey: Buffer, header: Buffer | undefined | null, messages: Array<string>, ciphersuite?: string | undefined | null): Buffer
23
+ export declare function bbsIetfVerify(publicKey: Buffer, signature: Buffer, header: Buffer | undefined | null, messages: Array<string>, ciphersuite?: string | undefined | null): boolean
24
+ export declare function bbsIetfProofGen(publicKey: Buffer, signature: Buffer, header: Buffer | undefined | null, presentationHeader: Buffer | undefined | null, messages: Array<string>, disclosedIndices: Array<number>, ciphersuite?: string | undefined | null): Buffer
25
+ export declare function bbsIetfProofVerify(publicKey: Buffer, proof: Buffer, header: Buffer | undefined | null, presentationHeader: Buffer | undefined | null, disclosedMessages: any, totalMessageCount: number, ciphersuite?: string | undefined | null): boolean
6
26
  export interface BoundSignatureSuiteOptions {
7
27
  key?: KeyPairOptions
8
28
  verificationMethod?: string
@@ -336,6 +356,28 @@ export declare function unblindSignature(blindSignature: Uint8Array, blindingFac
336
356
  * Output: derived document JSON with embedded proof
337
357
  */
338
358
  export declare function deriveProofHolderBound(proofDocument: any, revealDocument: any, options: any): Promise<any>
359
+ export interface SdJwtDisclosure {
360
+ salt: string
361
+ claimName?: string
362
+ claimValue: any
363
+ encoded: string
364
+ digest: string
365
+ }
366
+ export interface SdJwtIssueOutput {
367
+ sdJwt: string
368
+ disclosures: Array<SdJwtDisclosure>
369
+ }
370
+ export interface SdJwtPresentOutput {
371
+ presentation: string
372
+ }
373
+ export interface SdJwtVerifyOutput {
374
+ verified: boolean
375
+ claims: any
376
+ error?: string
377
+ }
378
+ export declare function sdJwtIssue(claims: any, disclosable: Array<string>, jwk: any, alg: string, holderJwk?: any | undefined | null, decoyCount?: number | undefined | null): SdJwtIssueOutput
379
+ export declare function sdJwtPresent(sdJwt: string, disclosuresToReveal: Array<string>, kbJwk?: any | undefined | null, kbAlg?: string | undefined | null, audience?: string | undefined | null, nonce?: string | undefined | null): SdJwtPresentOutput
380
+ export declare function sdJwtVerify(presentation: string, issuerJwk: any, holderJwk?: any | undefined | null, audience?: string | undefined | null, nonce?: string | undefined | null): SdJwtVerifyOutput
339
381
  export declare class BbsBlsHolderBoundSignature2022 {
340
382
  type: string
341
383
  constructor(options?: BoundSignatureSuiteOptions | undefined | null)
package/index.js CHANGED
@@ -310,8 +310,18 @@ if (!nativeBinding) {
310
310
  throw new Error(`Failed to load native binding`)
311
311
  }
312
312
 
313
- const { BbsBlsHolderBoundSignature2022, BbsBlsHolderBoundSignatureProof2022, BbsBlsSignature2020, BbsBlsSignatureProof2020, Bls12381G2KeyPair, KeyPairSigner, KeyPairVerifier, BoundBls12381G2KeyPair, JoseNamedCurve, JoseContentEncryption, JoseKeyEncryption, JoseSigningAlgorithm, generateJwk, generateKeyPair, joseEncrypt, joseDecrypt, generalEncryptJson, decryptJson, compactSignJson, compactJsonVerify, flattenedSignJson, jsonVerify, generalSignJson, JsonLd, ldSign, ldVerify, ldDeriveProof, deriveProof, createCommitment, verifyCommitment, unblindSignature, deriveProofHolderBound } = nativeBinding
313
+ const { bbs2023Sign, bbs2023Derive, bbs2023HolderCommit, bbs2023BlindSign, bbs2023Verify, bbsIetfKeygen, bbsIetfSign, bbsIetfVerify, bbsIetfProofGen, bbsIetfProofVerify, BbsBlsHolderBoundSignature2022, BbsBlsHolderBoundSignatureProof2022, BbsBlsSignature2020, BbsBlsSignatureProof2020, Bls12381G2KeyPair, KeyPairSigner, KeyPairVerifier, BoundBls12381G2KeyPair, JoseNamedCurve, JoseContentEncryption, JoseKeyEncryption, JoseSigningAlgorithm, generateJwk, generateKeyPair, joseEncrypt, joseDecrypt, generalEncryptJson, decryptJson, compactSignJson, compactJsonVerify, flattenedSignJson, jsonVerify, generalSignJson, JsonLd, ldSign, ldVerify, ldDeriveProof, deriveProof, createCommitment, verifyCommitment, unblindSignature, deriveProofHolderBound, sdJwtIssue, sdJwtPresent, sdJwtVerify } = nativeBinding
314
314
 
315
+ module.exports.bbs2023Sign = bbs2023Sign
316
+ module.exports.bbs2023Derive = bbs2023Derive
317
+ module.exports.bbs2023HolderCommit = bbs2023HolderCommit
318
+ module.exports.bbs2023BlindSign = bbs2023BlindSign
319
+ module.exports.bbs2023Verify = bbs2023Verify
320
+ module.exports.bbsIetfKeygen = bbsIetfKeygen
321
+ module.exports.bbsIetfSign = bbsIetfSign
322
+ module.exports.bbsIetfVerify = bbsIetfVerify
323
+ module.exports.bbsIetfProofGen = bbsIetfProofGen
324
+ module.exports.bbsIetfProofVerify = bbsIetfProofVerify
315
325
  module.exports.BbsBlsHolderBoundSignature2022 = BbsBlsHolderBoundSignature2022
316
326
  module.exports.BbsBlsHolderBoundSignatureProof2022 = BbsBlsHolderBoundSignatureProof2022
317
327
  module.exports.BbsBlsSignature2020 = BbsBlsSignature2020
@@ -344,3 +354,6 @@ module.exports.createCommitment = createCommitment
344
354
  module.exports.verifyCommitment = verifyCommitment
345
355
  module.exports.unblindSignature = unblindSignature
346
356
  module.exports.deriveProofHolderBound = deriveProofHolderBound
357
+ module.exports.sdJwtIssue = sdJwtIssue
358
+ module.exports.sdJwtPresent = sdJwtPresent
359
+ module.exports.sdJwtVerify = sdJwtVerify
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuggetslife/vc",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "main": "index.js",
5
5
  "types": "index.d.ts",
6
6
  "napi": {
@@ -32,17 +32,17 @@
32
32
  "build": "napi build --platform --release",
33
33
  "build:debug": "napi build --platform",
34
34
  "prepublishOnly": "napi prepublish -t npm",
35
- "test": "node test.mjs && node test_jose.mjs && node test_jsonld_crossverify.mjs",
35
+ "test": "node test.mjs && node test_jose.mjs && node test_sd_jwt.mjs && node test_bbs_ietf.mjs && node test_bbs_2023.mjs && node test_jsonld_crossverify.mjs && node test_backward_compat.mjs",
36
36
  "universal": "napi universal",
37
37
  "version": "napi version"
38
38
  },
39
39
  "packageManager": "yarn@4.3.1",
40
40
  "optionalDependencies": {
41
- "@nuggetslife/vc-darwin-arm64": "0.0.23",
42
- "@nuggetslife/vc-linux-arm64-gnu": "0.0.23",
43
- "@nuggetslife/vc-linux-arm64-musl": "0.0.23",
44
- "@nuggetslife/vc-linux-x64-gnu": "0.0.23",
45
- "@nuggetslife/vc-linux-x64-musl": "0.0.23"
41
+ "@nuggetslife/vc-darwin-arm64": "0.0.25",
42
+ "@nuggetslife/vc-linux-arm64-gnu": "0.0.25",
43
+ "@nuggetslife/vc-linux-arm64-musl": "0.0.25",
44
+ "@nuggetslife/vc-linux-x64-gnu": "0.0.25",
45
+ "@nuggetslife/vc-linux-x64-musl": "0.0.25"
46
46
  },
47
47
  "dependencies": {}
48
48
  }
@@ -0,0 +1,110 @@
1
+ use serde_json::Value;
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Result types
5
+ // ---------------------------------------------------------------------------
6
+
7
+ #[napi(object)]
8
+ pub struct Bbs2023VerifyOutput {
9
+ pub verified: bool,
10
+ pub error: Option<String>,
11
+ }
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // bbs2023Sign — create a base proof (issuer)
15
+ // ---------------------------------------------------------------------------
16
+
17
+ #[napi(js_name = "bbs2023Sign")]
18
+ pub fn bbs2023_sign(options: Value) -> napi::Result<Value> {
19
+ let sign_opts: vc::bbs_2023::SignOptions = serde_json::from_value(options)
20
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023Sign parse options: {e}")))?;
21
+
22
+ let rt = tokio::runtime::Runtime::new()
23
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023Sign runtime: {e}")))?;
24
+
25
+ let result = rt
26
+ .block_on(vc::bbs_2023::create_base_proof(sign_opts))
27
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023Sign failed: {e}")))?;
28
+
29
+ Ok(result)
30
+ }
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // bbs2023Derive — create a derived proof (holder)
34
+ // ---------------------------------------------------------------------------
35
+
36
+ #[napi(js_name = "bbs2023Derive")]
37
+ pub fn bbs2023_derive(options: Value) -> napi::Result<Value> {
38
+ let derive_opts: vc::bbs_2023::DeriveOptions = serde_json::from_value(options)
39
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023Derive parse options: {e}")))?;
40
+
41
+ let rt = tokio::runtime::Runtime::new()
42
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023Derive runtime: {e}")))?;
43
+
44
+ let result = rt
45
+ .block_on(vc::bbs_2023::create_derived_proof(derive_opts))
46
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023Derive failed: {e}")))?;
47
+
48
+ Ok(result)
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // bbs2023HolderCommit — holder creates Pedersen commitment for blind signing
53
+ // ---------------------------------------------------------------------------
54
+
55
+ #[napi(js_name = "bbs2023HolderCommit")]
56
+ pub fn bbs2023_holder_commit(options: Value) -> napi::Result<Value> {
57
+ let commit_opts: vc::bbs_2023::HolderCommitOptions = serde_json::from_value(options)
58
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023HolderCommit parse options: {e}")))?;
59
+
60
+ let rt = tokio::runtime::Runtime::new()
61
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023HolderCommit runtime: {e}")))?;
62
+
63
+ let result = rt
64
+ .block_on(vc::bbs_2023::create_holder_commitment(commit_opts))
65
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023HolderCommit failed: {e}")))?;
66
+
67
+ serde_json::to_value(&result)
68
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023HolderCommit serialize: {e}")))
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // bbs2023BlindSign — issuer creates blind base proof with holder's commitment
73
+ // ---------------------------------------------------------------------------
74
+
75
+ #[napi(js_name = "bbs2023BlindSign")]
76
+ pub fn bbs2023_blind_sign(options: Value) -> napi::Result<Value> {
77
+ let blind_opts: vc::bbs_2023::BlindSignOptions = serde_json::from_value(options)
78
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023BlindSign parse options: {e}")))?;
79
+
80
+ let rt = tokio::runtime::Runtime::new()
81
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023BlindSign runtime: {e}")))?;
82
+
83
+ let result = rt
84
+ .block_on(vc::bbs_2023::create_blind_base_proof(blind_opts))
85
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023BlindSign failed: {e}")))?;
86
+
87
+ Ok(result)
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // bbs2023Verify — verify a base or derived proof
92
+ // ---------------------------------------------------------------------------
93
+
94
+ #[napi(js_name = "bbs2023Verify")]
95
+ pub fn bbs2023_verify(
96
+ document: Value,
97
+ public_key: Option<String>,
98
+ ) -> napi::Result<Bbs2023VerifyOutput> {
99
+ let rt = tokio::runtime::Runtime::new()
100
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023Verify runtime: {e}")))?;
101
+
102
+ let result = rt
103
+ .block_on(vc::bbs_2023::verify_proof(document, public_key))
104
+ .map_err(|e| napi::Error::from_reason(format!("bbs2023Verify failed: {e}")))?;
105
+
106
+ Ok(Bbs2023VerifyOutput {
107
+ verified: result.verified,
108
+ error: result.error,
109
+ })
110
+ }
@@ -0,0 +1,255 @@
1
+ use napi::bindgen_prelude::Buffer;
2
+ use serde_json::Value;
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Result types
6
+ // ---------------------------------------------------------------------------
7
+
8
+ #[napi(object)]
9
+ pub struct BbsIetfKeyPair {
10
+ /// Hex-encoded secret key (32 bytes)
11
+ pub secret_key: String,
12
+ /// Hex-encoded public key (96 bytes, compressed G2)
13
+ pub public_key: String,
14
+ }
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Key Generation
18
+ // ---------------------------------------------------------------------------
19
+
20
+ #[napi(js_name = "bbsIetfKeygen")]
21
+ pub fn bbs_ietf_keygen(
22
+ ikm: Option<Buffer>,
23
+ key_info: Option<Buffer>,
24
+ ciphersuite: Option<String>,
25
+ ) -> napi::Result<BbsIetfKeyPair> {
26
+ let ikm_bytes = ikm.as_deref();
27
+ let key_info_bytes = key_info.as_deref().unwrap_or(&[]);
28
+ let cs = ciphersuite.as_deref().unwrap_or("SHA-256");
29
+
30
+ let kp = match cs {
31
+ "SHAKE-256" => vc::bbs_ietf::keygen::<vc::bbs_ietf::Bls12381Shake256>(
32
+ ikm_bytes,
33
+ key_info_bytes,
34
+ ),
35
+ _ => vc::bbs_ietf::keygen::<vc::bbs_ietf::Bls12381Sha256>(
36
+ ikm_bytes,
37
+ key_info_bytes,
38
+ ),
39
+ }
40
+ .map_err(|e| napi::Error::from_reason(format!("bbsIetfKeygen: {e}")))?;
41
+
42
+ Ok(BbsIetfKeyPair {
43
+ secret_key: hex::encode(vc::bbs_ietf::serialize::scalar_to_bytes(&kp.secret_key.0)),
44
+ public_key: hex::encode(vc::bbs_ietf::serialize::point_to_octets_g2(&kp.public_key.0)),
45
+ })
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Sign
50
+ // ---------------------------------------------------------------------------
51
+
52
+ #[napi(js_name = "bbsIetfSign")]
53
+ pub fn bbs_ietf_sign(
54
+ secret_key: Buffer,
55
+ public_key: Buffer,
56
+ header: Option<Buffer>,
57
+ messages: Vec<String>,
58
+ ciphersuite: Option<String>,
59
+ ) -> napi::Result<Buffer> {
60
+ let sk = vc::bbs_ietf::serialize::bytes_to_scalar(&secret_key)
61
+ .map_err(|e| napi::Error::from_reason(format!("bbsIetfSign invalid SK: {e}")))?;
62
+ let pk_point = vc::bbs_ietf::serialize::octets_to_point_g2(&public_key)
63
+ .map_err(|e| napi::Error::from_reason(format!("bbsIetfSign invalid PK: {e}")))?;
64
+
65
+ let header = header.as_deref().unwrap_or(&[]);
66
+ let cs = ciphersuite.as_deref().unwrap_or("SHA-256");
67
+
68
+ let sk = vc::bbs_ietf::SecretKey(sk);
69
+ let pk = vc::bbs_ietf::PublicKey(pk_point);
70
+
71
+ let sig = match cs {
72
+ "SHAKE-256" => {
73
+ let scalars: Vec<_> = messages
74
+ .iter()
75
+ .map(|m| vc::bbs_ietf::map_message_to_scalar::<vc::bbs_ietf::Bls12381Shake256>(m.as_bytes()))
76
+ .collect();
77
+ vc::bbs_ietf::sign::<vc::bbs_ietf::Bls12381Shake256>(&sk, &pk, header, &scalars)
78
+ }
79
+ _ => {
80
+ let scalars: Vec<_> = messages
81
+ .iter()
82
+ .map(|m| vc::bbs_ietf::map_message_to_scalar::<vc::bbs_ietf::Bls12381Sha256>(m.as_bytes()))
83
+ .collect();
84
+ vc::bbs_ietf::sign::<vc::bbs_ietf::Bls12381Sha256>(&sk, &pk, header, &scalars)
85
+ }
86
+ }
87
+ .map_err(|e| napi::Error::from_reason(format!("bbsIetfSign: {e}")))?;
88
+
89
+ let bytes = vc::bbs_ietf::serialize_signature(&sig);
90
+ Ok(Buffer::from(bytes.to_vec()))
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Verify
95
+ // ---------------------------------------------------------------------------
96
+
97
+ #[napi(js_name = "bbsIetfVerify")]
98
+ pub fn bbs_ietf_verify(
99
+ public_key: Buffer,
100
+ signature: Buffer,
101
+ header: Option<Buffer>,
102
+ messages: Vec<String>,
103
+ ciphersuite: Option<String>,
104
+ ) -> napi::Result<bool> {
105
+ let pk_point = vc::bbs_ietf::serialize::octets_to_point_g2(&public_key)
106
+ .map_err(|e| napi::Error::from_reason(format!("bbsIetfVerify invalid PK: {e}")))?;
107
+ let sig = vc::bbs_ietf::deserialize_signature(&signature)
108
+ .map_err(|e| napi::Error::from_reason(format!("bbsIetfVerify invalid sig: {e}")))?;
109
+
110
+ let header = header.as_deref().unwrap_or(&[]);
111
+ let cs = ciphersuite.as_deref().unwrap_or("SHA-256");
112
+ let pk = vc::bbs_ietf::PublicKey(pk_point);
113
+
114
+ match cs {
115
+ "SHAKE-256" => {
116
+ let scalars: Vec<_> = messages
117
+ .iter()
118
+ .map(|m| vc::bbs_ietf::map_message_to_scalar::<vc::bbs_ietf::Bls12381Shake256>(m.as_bytes()))
119
+ .collect();
120
+ vc::bbs_ietf::verify::<vc::bbs_ietf::Bls12381Shake256>(&pk, &sig, header, &scalars)
121
+ }
122
+ _ => {
123
+ let scalars: Vec<_> = messages
124
+ .iter()
125
+ .map(|m| vc::bbs_ietf::map_message_to_scalar::<vc::bbs_ietf::Bls12381Sha256>(m.as_bytes()))
126
+ .collect();
127
+ vc::bbs_ietf::verify::<vc::bbs_ietf::Bls12381Sha256>(&pk, &sig, header, &scalars)
128
+ }
129
+ }
130
+ .map_err(|e| napi::Error::from_reason(format!("bbsIetfVerify: {e}")))
131
+ }
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // ProofGen
135
+ // ---------------------------------------------------------------------------
136
+
137
+ #[napi(js_name = "bbsIetfProofGen")]
138
+ pub fn bbs_ietf_proof_gen(
139
+ public_key: Buffer,
140
+ signature: Buffer,
141
+ header: Option<Buffer>,
142
+ presentation_header: Option<Buffer>,
143
+ messages: Vec<String>,
144
+ disclosed_indices: Vec<u32>,
145
+ ciphersuite: Option<String>,
146
+ ) -> napi::Result<Buffer> {
147
+ let pk_point = vc::bbs_ietf::serialize::octets_to_point_g2(&public_key)
148
+ .map_err(|e| napi::Error::from_reason(format!("bbsIetfProofGen invalid PK: {e}")))?;
149
+ let sig = vc::bbs_ietf::deserialize_signature(&signature)
150
+ .map_err(|e| napi::Error::from_reason(format!("bbsIetfProofGen invalid sig: {e}")))?;
151
+
152
+ let header = header.as_deref().unwrap_or(&[]);
153
+ let ph = presentation_header.as_deref().unwrap_or(&[]);
154
+ let cs = ciphersuite.as_deref().unwrap_or("SHA-256");
155
+ let indices: Vec<usize> = disclosed_indices.iter().map(|&i| i as usize).collect();
156
+ let pk = vc::bbs_ietf::PublicKey(pk_point);
157
+
158
+ let proof = match cs {
159
+ "SHAKE-256" => {
160
+ let scalars: Vec<_> = messages
161
+ .iter()
162
+ .map(|m| vc::bbs_ietf::map_message_to_scalar::<vc::bbs_ietf::Bls12381Shake256>(m.as_bytes()))
163
+ .collect();
164
+ vc::bbs_ietf::proof_gen::<vc::bbs_ietf::Bls12381Shake256>(
165
+ &pk, &sig, header, ph, &scalars, &indices,
166
+ )
167
+ }
168
+ _ => {
169
+ let scalars: Vec<_> = messages
170
+ .iter()
171
+ .map(|m| vc::bbs_ietf::map_message_to_scalar::<vc::bbs_ietf::Bls12381Sha256>(m.as_bytes()))
172
+ .collect();
173
+ vc::bbs_ietf::proof_gen::<vc::bbs_ietf::Bls12381Sha256>(
174
+ &pk, &sig, header, ph, &scalars, &indices,
175
+ )
176
+ }
177
+ }
178
+ .map_err(|e| napi::Error::from_reason(format!("bbsIetfProofGen: {e}")))?;
179
+
180
+ Ok(Buffer::from(proof))
181
+ }
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // ProofVerify
185
+ // ---------------------------------------------------------------------------
186
+
187
+ #[napi(js_name = "bbsIetfProofVerify")]
188
+ pub fn bbs_ietf_proof_verify(
189
+ public_key: Buffer,
190
+ proof: Buffer,
191
+ header: Option<Buffer>,
192
+ presentation_header: Option<Buffer>,
193
+ disclosed_messages: Value, // { "0": "msg0", "2": "msg2", ... }
194
+ total_message_count: u32,
195
+ ciphersuite: Option<String>,
196
+ ) -> napi::Result<bool> {
197
+ let pk_point = vc::bbs_ietf::serialize::octets_to_point_g2(&public_key)
198
+ .map_err(|e| napi::Error::from_reason(format!("bbsIetfProofVerify invalid PK: {e}")))?;
199
+
200
+ let header = header.as_deref().unwrap_or(&[]);
201
+ let ph = presentation_header.as_deref().unwrap_or(&[]);
202
+ let cs = ciphersuite.as_deref().unwrap_or("SHA-256");
203
+ let pk = vc::bbs_ietf::PublicKey(pk_point);
204
+
205
+ let obj = disclosed_messages
206
+ .as_object()
207
+ .ok_or_else(|| napi::Error::from_reason("disclosedMessages must be an object"))?;
208
+
209
+ match cs {
210
+ "SHAKE-256" => {
211
+ let disclosed: Vec<(usize, vc::bbs_ietf::Scalar)> = obj
212
+ .iter()
213
+ .map(|(k, v)| {
214
+ let idx: usize = k.parse().map_err(|_| {
215
+ napi::Error::from_reason(format!("invalid index: {k}"))
216
+ })?;
217
+ let msg_str = v.as_str().unwrap_or("");
218
+ let scalar = vc::bbs_ietf::map_message_to_scalar::<vc::bbs_ietf::Bls12381Shake256>(msg_str.as_bytes());
219
+ Ok((idx, scalar))
220
+ })
221
+ .collect::<napi::Result<_>>()?;
222
+ vc::bbs_ietf::proof_verify::<vc::bbs_ietf::Bls12381Shake256>(
223
+ &pk,
224
+ &proof,
225
+ header,
226
+ ph,
227
+ &disclosed,
228
+ total_message_count as usize,
229
+ )
230
+ .map_err(|e| napi::Error::from_reason(format!("bbsIetfProofVerify: {e}")))
231
+ }
232
+ _ => {
233
+ let disclosed: Vec<(usize, vc::bbs_ietf::Scalar)> = obj
234
+ .iter()
235
+ .map(|(k, v)| {
236
+ let idx: usize = k.parse().map_err(|_| {
237
+ napi::Error::from_reason(format!("invalid index: {k}"))
238
+ })?;
239
+ let msg_str = v.as_str().unwrap_or("");
240
+ let scalar = vc::bbs_ietf::map_message_to_scalar::<vc::bbs_ietf::Bls12381Sha256>(msg_str.as_bytes());
241
+ Ok((idx, scalar))
242
+ })
243
+ .collect::<napi::Result<_>>()?;
244
+ vc::bbs_ietf::proof_verify::<vc::bbs_ietf::Bls12381Sha256>(
245
+ &pk,
246
+ &proof,
247
+ header,
248
+ ph,
249
+ &disclosed,
250
+ total_message_count as usize,
251
+ )
252
+ .map_err(|e| napi::Error::from_reason(format!("bbsIetfProofVerify: {e}")))
253
+ }
254
+ }
255
+ }
package/src/lib.rs CHANGED
@@ -3,7 +3,10 @@
3
3
  #[macro_use]
4
4
  extern crate napi_derive;
5
5
 
6
+ pub mod bbs_2023;
7
+ pub mod bbs_ietf;
6
8
  pub mod bls_signatures;
7
9
  pub mod jose;
8
10
  pub mod jsonld;
9
11
  pub mod ld_signatures;
12
+ pub mod sd_jwt;