@nuggetslife/vc 0.0.10 → 0.0.15

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.
Files changed (31) hide show
  1. package/Cargo.toml +5 -2
  2. package/index.d.ts +303 -3
  3. package/index.js +15 -1
  4. package/package.json +11 -11
  5. package/src/bls_signatures/bbs_bls_holder_bound_signature_2022/mod.rs +268 -0
  6. package/src/bls_signatures/bbs_bls_holder_bound_signature_2022/types.rs +26 -0
  7. package/src/bls_signatures/bbs_bls_holder_bound_signature_proof_2022/mod.rs +100 -0
  8. package/src/bls_signatures/bbs_bls_holder_bound_signature_proof_2022/types.rs +17 -0
  9. package/src/bls_signatures/bbs_bls_signature_2020/mod.rs +329 -0
  10. package/src/bls_signatures/bbs_bls_signature_2020/types.rs +37 -0
  11. package/src/bls_signatures/bbs_bls_signature_proof_2020/mod.rs +92 -0
  12. package/src/bls_signatures/bbs_bls_signature_proof_2020/types.rs +13 -0
  13. package/src/bls_signatures/bls_12381_g2_keypair/mod.rs +470 -0
  14. package/src/{types.rs → bls_signatures/bls_12381_g2_keypair/types.rs} +0 -11
  15. package/src/{validators.rs → bls_signatures/bls_12381_g2_keypair/validators.rs} +1 -1
  16. package/src/bls_signatures/bound_bls_12381_g2_keypair/mod.rs +70 -0
  17. package/src/bls_signatures/bound_bls_12381_g2_keypair/types.rs +11 -0
  18. package/src/bls_signatures/mod.rs +6 -0
  19. package/src/jsonld.rs +200 -0
  20. package/src/ld_signatures.rs +311 -0
  21. package/src/lib.rs +3 -463
  22. package/test-data/bbs.json +92 -0
  23. package/test-data/citizenVocab.json +57 -0
  24. package/test-data/controllerDocument.json +5 -0
  25. package/test-data/credentialsContext.json +315 -0
  26. package/test-data/deriveProofFrame.json +15 -0
  27. package/test-data/inputDocument.json +29 -0
  28. package/test-data/keyPair.json +6 -0
  29. package/test-data/suiteContext.json +82 -0
  30. package/test.mjs +1088 -22
  31. package/test_jsonld_crossverify.mjs +256 -0
package/Cargo.toml CHANGED
@@ -10,12 +10,15 @@ crate-type = ["cdylib"]
10
10
  # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
11
11
  base64 = "0.22.1"
12
12
  bs58 = "0.5.1"
13
+ chrono = "0.4.38"
13
14
  hex = "0.4.3"
15
+ lru = "0.14.0"
14
16
  # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
15
- napi = { version = "2.12.2", default-features = false, features = ["napi4", "tokio_rt"] }
16
- napi-derive = "2.12.2"
17
+ napi = { version = "2.16.10", default-features = false, features = ["full"] }
18
+ napi-derive = "2.16.12"
17
19
  serde = { version = "1.0.203", features = ["derive"] }
18
20
  serde_json = "1.0.117"
21
+ tokio = { version = "1.40.0", features = ["sync"] }
19
22
  vc = {path = "../rs"}
20
23
 
21
24
  [build-dependencies]
package/index.d.ts CHANGED
@@ -3,6 +3,68 @@
3
3
 
4
4
  /* auto-generated by NAPI-RS */
5
5
 
6
+ export interface BoundSignatureSuiteOptions {
7
+ key?: KeyPairOptions
8
+ verificationMethod?: string
9
+ date?: string
10
+ commitment?: Uint8Array
11
+ blinded?: Array<number>
12
+ }
13
+ export interface BoundCreateProofOptions {
14
+ document: any
15
+ contexts?: any
16
+ }
17
+ export interface BoundVerifyProofOptions {
18
+ document: any
19
+ blindingFactor: Uint8Array
20
+ blindedMessages: Array<Uint8Array>
21
+ contexts?: any
22
+ }
23
+ export interface BoundDeriveProofOptions {
24
+ document: any
25
+ revealDocument: any
26
+ blindingFactor: Uint8Array
27
+ blindedMessages: Array<Uint8Array>
28
+ contexts?: any
29
+ nonce?: string
30
+ }
31
+ export interface BoundVerifyDerivedProofOptions {
32
+ document: any
33
+ contexts?: any
34
+ }
35
+ export interface SignatureSuiteOptions {
36
+ key?: KeyPairOptions
37
+ verificationMethod?: string
38
+ date?: string
39
+ }
40
+ export interface CreateProofOptions {
41
+ document: any
42
+ contexts?: any
43
+ }
44
+ export interface VerifyProofOptions {
45
+ document: any
46
+ contexts?: any
47
+ }
48
+ export interface SuiteSignOptions {
49
+ document: any
50
+ proof: any
51
+ verifyData: Array<Uint8Array>
52
+ }
53
+ export interface VerifySignatureOptions {
54
+ document: any
55
+ proof: any
56
+ verifyData: Array<Uint8Array>
57
+ }
58
+ export interface DeriveProofOptions {
59
+ document: any
60
+ revealDocument: any
61
+ contexts?: any
62
+ nonce?: string
63
+ }
64
+ export interface VerifyDerivedProofOptions {
65
+ document: any
66
+ contexts?: any
67
+ }
6
68
  export interface KeyPairOptions {
7
69
  id?: string
8
70
  controller?: string
@@ -94,7 +156,150 @@ export interface KeyPairVerifierOptions {
94
156
  data: Array<Uint8Array>
95
157
  signature: Uint8Array
96
158
  }
97
- export class Bls12381G2KeyPair {
159
+ export interface BoundKeyPairOptions {
160
+ id?: string
161
+ controller?: string
162
+ publicKeyBase58?: string
163
+ privateKeyBase58?: string
164
+ commitment: Uint8Array
165
+ blinded: Array<number>
166
+ }
167
+ /**
168
+ * Sign a document with BbsBlsSignature2020 and embed the proof.
169
+ *
170
+ * Input: `{ document, keyPair: {id, controller, publicKeyBase58, privateKeyBase58}, contexts? }`
171
+ * Output: signed document JSON with embedded proof
172
+ */
173
+ export declare function ldSign(options: any): Promise<any>
174
+ /**
175
+ * Verify a document with an embedded proof (auto-detects proof type).
176
+ *
177
+ * Input: `{ document, contexts? }`
178
+ * Output: `{ verified: boolean, error?: string }`
179
+ */
180
+ export declare function ldVerify(options: any): Promise<any>
181
+ /**
182
+ * Derive a selective disclosure proof from a signed document.
183
+ *
184
+ * Input: `{ document, revealDocument, nonce?, contexts? }`
185
+ * Output: derived document JSON with embedded proof
186
+ */
187
+ export declare function ldDeriveProof(options: any): Promise<any>
188
+ /**
189
+ * Standalone deriveProof function matching the JS reference's
190
+ * `deriveProof(proofDocument, revealDocument, { suite, documentLoader, nonce })`.
191
+ *
192
+ * Input: proofDocument (signed doc), revealDocument (frame), options `{ contexts?, nonce? }`
193
+ * Output: derived document JSON with embedded proof
194
+ */
195
+ export declare function deriveProof(proofDocument: any, revealDocument: any, options: any): Promise<any>
196
+ /**
197
+ * Create a blind signature commitment (holder side).
198
+ *
199
+ * Input: issuerDID (for resolving public key), messages (blinded messages as Uint8Array[]),
200
+ * blinded (indices), nonce, knownMessageCount, options `{ contexts? }`
201
+ * Output: `{ commitment, challengeHash, blindingFactor, proofOfHiddenMessages }` (all Uint8Array)
202
+ */
203
+ export declare function createCommitment(publicKey: Uint8Array, messages: Array<Uint8Array>, blinded: Array<number>, nonce: Uint8Array, knownMessageCount: number): Promise<any>
204
+ /**
205
+ * Verify a blind signature commitment (issuer side).
206
+ *
207
+ * Returns true if the commitment proof is valid.
208
+ */
209
+ export declare function verifyCommitment(publicKey: Uint8Array, commitment: Uint8Array, proofOfHiddenMessages: Uint8Array, challengeHash: Uint8Array, blinded: Array<number>, nonce: Uint8Array, knownMessageCount: number): Promise<boolean>
210
+ /**
211
+ * Unblind a blind signature (holder side).
212
+ *
213
+ * Takes the blind signature from the issuer and the holder's blinding factor,
214
+ * returns the unblinded standard signature.
215
+ */
216
+ export declare function unblindSignature(blindSignature: Uint8Array, blindingFactor: Uint8Array): Promise<Uint8Array>
217
+ /**
218
+ * Derive a selective disclosure proof from a holder-bound signed document.
219
+ *
220
+ * Input: proofDocument (signed doc), revealDocument (frame),
221
+ * options `{ blindingFactor: Uint8Array, blindedMessages: Uint8Array[], contexts?, nonce? }`
222
+ * Output: derived document JSON with embedded proof
223
+ */
224
+ export declare function deriveProofHolderBound(proofDocument: any, revealDocument: any, options: any): Promise<any>
225
+ export declare class BbsBlsHolderBoundSignature2022 {
226
+ type: string
227
+ constructor(options?: BoundSignatureSuiteOptions | undefined | null)
228
+ /**
229
+ * Create a blind sign commitment context.
230
+ *
231
+ * Returns `{ knownMessageCount, nonce }` — the holder uses these to create a blind commitment.
232
+ * Matches JS `BbsBlsHolderBoundSignature2022.createBlindSignCommitmentContext()`.
233
+ */
234
+ createBlindSignCommitmentContext(options: BoundCreateProofOptions): Promise<any>
235
+ /** Create a proof for a document using BbsBlsHolderBoundSignature2022 (blind signing). */
236
+ createProof(options: BoundCreateProofOptions): Promise<any>
237
+ /**
238
+ * Verify a holder-bound signed document.
239
+ *
240
+ * Returns `{ verified: boolean, error?: string }`.
241
+ */
242
+ verifyProof(options: BoundVerifyProofOptions): Promise<any>
243
+ /** Ensure both BBS and holder-bound suite contexts are present. */
244
+ ensureSuiteContext(document: any): any
245
+ /** Returns the proof types this suite matches. */
246
+ get proofType(): Array<string>
247
+ }
248
+ export declare class BbsBlsHolderBoundSignatureProof2022 {
249
+ type: string
250
+ constructor()
251
+ /** Derive a selective disclosure proof from a holder-bound signed document. */
252
+ deriveProof(options: BoundDeriveProofOptions): Promise<any>
253
+ /**
254
+ * Verify a derived (selective disclosure) holder-bound proof.
255
+ *
256
+ * Returns `{ verified: boolean, error?: string }`.
257
+ */
258
+ verifyProof(options: BoundVerifyDerivedProofOptions): Promise<any>
259
+ /** Returns the proof types this suite produces. */
260
+ get proofType(): Array<string>
261
+ /** Returns the proof types from which this suite can derive. */
262
+ get supportedDerivedProofType(): Array<string>
263
+ }
264
+ export declare class BbsBlsSignature2020 {
265
+ type: string
266
+ constructor(options?: SignatureSuiteOptions | undefined | null)
267
+ /** Generate a BbsBlsSignature2020 suite from a seed (async key generation). */
268
+ static generate(options: SignatureSuiteOptions): Promise<BbsBlsSignature2020>
269
+ /**
270
+ * Create a proof for a document using BbsBlsSignature2020.
271
+ *
272
+ * Returns just the proof object (matching JS reference behavior).
273
+ */
274
+ createProof(options: CreateProofOptions): Promise<any>
275
+ /**
276
+ * Verify a document with an embedded proof.
277
+ *
278
+ * Returns `{ verified: boolean, error?: string }`.
279
+ */
280
+ verifyProof(options: VerifyProofOptions): Promise<any>
281
+ /** Ensure the BBS suite context is present in the document's @context. */
282
+ ensureSuiteContext(document: any): any
283
+ sign(options: SuiteSignOptions): Promise<any>
284
+ verifySignature(options: VerifySignatureOptions): Promise<boolean>
285
+ }
286
+ export declare class BbsBlsSignatureProof2020 {
287
+ type: string
288
+ constructor()
289
+ /** Derive a selective disclosure proof from a signed document. */
290
+ deriveProof(options: DeriveProofOptions): Promise<any>
291
+ /**
292
+ * Verify a derived (selective disclosure) proof.
293
+ *
294
+ * Returns `{ verified: boolean, error?: string }`.
295
+ */
296
+ verifyProof(options: VerifyDerivedProofOptions): Promise<any>
297
+ /** Returns the proof types this suite produces. */
298
+ get proofType(): Array<string>
299
+ /** Returns the proof types from which this suite can derive. */
300
+ get supportedDerivedProofType(): Array<string>
301
+ }
302
+ export declare class Bls12381G2KeyPair {
98
303
  id?: string
99
304
  controller?: string
100
305
  privateKeyInner?: Array<number>
@@ -117,9 +322,104 @@ export class Bls12381G2KeyPair {
117
322
  static fingerprintFromPublicKey(options: FingerPrintFromPublicKeyOptions): string
118
323
  verifyFingerprint(fingerprint: string): void
119
324
  }
120
- export class KeyPairSigner {
325
+ export declare class KeyPairSigner {
121
326
  sign(options: KeyPairSignerOptions): Promise<Uint8Array>
122
327
  }
123
- export class KeyPairVerifier {
328
+ export declare class KeyPairVerifier {
124
329
  verify(options: KeyPairVerifierOptions): Promise<boolean>
125
330
  }
331
+ export declare class BoundBls12381G2KeyPair {
332
+ constructor(options: BoundKeyPairOptions)
333
+ static from(options: BoundKeyPairOptions): BoundBls12381G2KeyPair
334
+ get commitment(): Uint8Array
335
+ get blinded(): Array<number>
336
+ }
337
+ /**
338
+ * JSON-LD processor — drop-in replacement for the jsonld.js API.
339
+ *
340
+ * Holds a `DocumentLoader` and `ContextResolver` internally so they are
341
+ * created once and reused across all method calls.
342
+ *
343
+ * ```js
344
+ * const jsonld = new JsonLd({ contexts: { "https://example.org/ctx": {...} } });
345
+ * const expanded = await jsonld.expand(doc);
346
+ * const compacted = await jsonld.compact(expanded, ctx);
347
+ * ```
348
+ */
349
+ export declare class JsonLd {
350
+ /**
351
+ * Create a new JSON-LD processor.
352
+ *
353
+ * Accepts optional `{ contexts?: Record<string, any> }` to register
354
+ * additional URL → document mappings in the document loader (on top
355
+ * of the built-in nuggets contexts).
356
+ */
357
+ constructor(options?: any | undefined | null)
358
+ /**
359
+ * Expand a JSON-LD document.
360
+ *
361
+ * `options` can include: `base`, `expandContext`, `keepFreeFloatingNodes`,
362
+ * `processingMode`.
363
+ *
364
+ * Returns an array of expanded JSON-LD objects.
365
+ */
366
+ expand(input: any, options?: any | undefined | null): Promise<any>
367
+ /**
368
+ * Compact a JSON-LD document using a context.
369
+ *
370
+ * `ctx` — the compaction context (object, array, or string URL).
371
+ * `options` can include: `base`, `compactArrays`, `graph`,
372
+ * `skipExpansion`, `processingMode`.
373
+ *
374
+ * Returns the compacted JSON-LD object.
375
+ */
376
+ compact(input: any, ctx: any, options?: any | undefined | null): Promise<any>
377
+ /**
378
+ * Flatten a JSON-LD document.
379
+ *
380
+ * `ctx` — optional context for compacting the flattened result.
381
+ * Pass `null` to get the flattened expanded form.
382
+ * `options` can include: `base`, `expandContext`, `processingMode`.
383
+ *
384
+ * Returns flattened array (no context) or compacted object (with context).
385
+ */
386
+ flatten(input: any, ctx?: any | undefined | null, options?: any | undefined | null): Promise<any>
387
+ /**
388
+ * Frame a JSON-LD document.
389
+ *
390
+ * `frame` — the framing template.
391
+ * `options` can include: `base`, `embed`, `explicit`, `requireAll`,
392
+ * `omitDefault`, `omitGraph`, `processingMode`.
393
+ *
394
+ * Returns the framed JSON-LD object.
395
+ */
396
+ frame(input: any, frame: any, options?: any | undefined | null): Promise<any>
397
+ /**
398
+ * Convert a JSON-LD document to an RDF dataset.
399
+ *
400
+ * `options` can include: `base`, `expandContext`, `skipExpansion`,
401
+ * `format` (`"application/n-quads"` for string output), `processingMode`.
402
+ *
403
+ * Returns an array of quads or an N-Quads string (depending on `format`).
404
+ */
405
+ toRDF(input: any, options?: any | undefined | null): Promise<any>
406
+ /**
407
+ * Convert an RDF dataset to a JSON-LD document.
408
+ *
409
+ * `dataset` — N-Quads string or array of quads.
410
+ * `options` can include: `useRdfType`, `useNativeTypes`, `rdfDirection`.
411
+ *
412
+ * Returns an array of JSON-LD objects.
413
+ */
414
+ fromRDF(dataset: any, options?: any | undefined | null): any
415
+ /**
416
+ * Canonize (normalize) a JSON-LD document.
417
+ *
418
+ * `options` can include: `base`, `expandContext`, `skipExpansion`,
419
+ * `inputFormat` (`"application/n-quads"`), `format`, `algorithm`,
420
+ * `processingMode`.
421
+ *
422
+ * Returns the canonical N-Quads string.
423
+ */
424
+ canonize(input: any, options?: any | undefined | null): Promise<any>
425
+ }
package/index.js CHANGED
@@ -310,8 +310,22 @@ if (!nativeBinding) {
310
310
  throw new Error(`Failed to load native binding`)
311
311
  }
312
312
 
313
- const { Bls12381G2KeyPair, KeyPairSigner, KeyPairVerifier } = nativeBinding
313
+ const { BbsBlsHolderBoundSignature2022, BbsBlsHolderBoundSignatureProof2022, BbsBlsSignature2020, BbsBlsSignatureProof2020, Bls12381G2KeyPair, KeyPairSigner, KeyPairVerifier, BoundBls12381G2KeyPair, JsonLd, ldSign, ldVerify, ldDeriveProof, deriveProof, createCommitment, verifyCommitment, unblindSignature, deriveProofHolderBound } = nativeBinding
314
314
 
315
+ module.exports.BbsBlsHolderBoundSignature2022 = BbsBlsHolderBoundSignature2022
316
+ module.exports.BbsBlsHolderBoundSignatureProof2022 = BbsBlsHolderBoundSignatureProof2022
317
+ module.exports.BbsBlsSignature2020 = BbsBlsSignature2020
318
+ module.exports.BbsBlsSignatureProof2020 = BbsBlsSignatureProof2020
315
319
  module.exports.Bls12381G2KeyPair = Bls12381G2KeyPair
316
320
  module.exports.KeyPairSigner = KeyPairSigner
317
321
  module.exports.KeyPairVerifier = KeyPairVerifier
322
+ module.exports.BoundBls12381G2KeyPair = BoundBls12381G2KeyPair
323
+ module.exports.JsonLd = JsonLd
324
+ module.exports.ldSign = ldSign
325
+ module.exports.ldVerify = ldVerify
326
+ module.exports.ldDeriveProof = ldDeriveProof
327
+ module.exports.deriveProof = deriveProof
328
+ module.exports.createCommitment = createCommitment
329
+ module.exports.verifyCommitment = verifyCommitment
330
+ module.exports.unblindSignature = unblindSignature
331
+ module.exports.deriveProofHolderBound = deriveProofHolderBound
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuggetslife/vc",
3
- "version": "0.0.10",
3
+ "version": "0.0.15",
4
4
  "main": "index.js",
5
5
  "types": "index.d.ts",
6
6
  "napi": {
@@ -9,7 +9,6 @@
9
9
  "defaults": false,
10
10
  "additional": [
11
11
  "aarch64-apple-darwin",
12
- "x86_64-apple-darwin",
13
12
  "aarch64-unknown-linux-gnu",
14
13
  "aarch64-unknown-linux-musl",
15
14
  "x86_64-unknown-linux-gnu",
@@ -19,10 +18,11 @@
19
18
  },
20
19
  "license": "MIT",
21
20
  "devDependencies": {
21
+ "@mattrglobal/bls12381-key-pair": "^1.2.1",
22
+ "@mattrglobal/jsonld-signatures-bbs": "^1.2.0",
22
23
  "@napi-rs/cli": "^2.18.3",
23
- "ava": "^6.0.1",
24
24
  "@types/node": "^20.14.9",
25
- "bs58": "^6.0.0"
25
+ "ava": "^6.0.1"
26
26
  },
27
27
  "ava": {
28
28
  "timeout": "3m"
@@ -41,11 +41,11 @@
41
41
  },
42
42
  "packageManager": "yarn@4.3.1",
43
43
  "optionalDependencies": {
44
- "@nuggetslife/vc-darwin-arm64": "0.0.10",
45
- "@nuggetslife/vc-darwin-x64": "0.0.10",
46
- "@nuggetslife/vc-linux-arm64-gnu": "0.0.10",
47
- "@nuggetslife/vc-linux-arm64-musl": "0.0.10",
48
- "@nuggetslife/vc-linux-x64-gnu": "0.0.10",
49
- "@nuggetslife/vc-linux-x64-musl": "0.0.10"
50
- }
44
+ "@nuggetslife/vc-darwin-arm64": "0.0.15",
45
+ "@nuggetslife/vc-linux-arm64-gnu": "0.0.15",
46
+ "@nuggetslife/vc-linux-arm64-musl": "0.0.15",
47
+ "@nuggetslife/vc-linux-x64-gnu": "0.0.15",
48
+ "@nuggetslife/vc-linux-x64-musl": "0.0.15"
49
+ },
50
+ "dependencies": {}
51
51
  }
@@ -0,0 +1,268 @@
1
+ #![allow(dead_code)]
2
+ use napi::bindgen_prelude::*;
3
+ use serde_json::json;
4
+ use types::{BoundCreateProofOptions, BoundSignatureSuiteOptions, BoundVerifyProofOptions};
5
+
6
+ use super::bls_12381_g2_keypair::Bls12381G2KeyPair;
7
+ use crate::ld_signatures::{create_document_loader, parse_contexts};
8
+
9
+ pub mod types;
10
+
11
+ const BBS_CONTEXT_URL: &str = "https://w3id.org/security/bbs/v1";
12
+ const BBS_BOUND_CONTEXT_URL: &str = "https://schemas.nuggets.life/bbsBoundv1.json";
13
+
14
+ #[napi]
15
+ pub struct BbsBlsHolderBoundSignature2022 {
16
+ key: Bls12381G2KeyPair,
17
+ verification_method: Option<String>,
18
+ date: String,
19
+ commitment: Option<Vec<u8>>,
20
+ blinded: Option<Vec<usize>>,
21
+ #[napi(js_name = "type")]
22
+ pub type_: String,
23
+ }
24
+
25
+ #[napi]
26
+ impl BbsBlsHolderBoundSignature2022 {
27
+ #[napi(constructor)]
28
+ pub fn new(options: Option<BoundSignatureSuiteOptions>) -> Self {
29
+ match options {
30
+ Some(opts) => {
31
+ let key = match opts.key {
32
+ Some(kp_opts) => Bls12381G2KeyPair::new(Some(
33
+ super::bls_12381_g2_keypair::types::KeyPairOptions {
34
+ id: kp_opts.id,
35
+ controller: kp_opts.controller,
36
+ public_key_base58: kp_opts.public_key_base58,
37
+ private_key_base58: kp_opts.private_key_base58,
38
+ },
39
+ )),
40
+ None => Bls12381G2KeyPair::new(None),
41
+ };
42
+
43
+ Self {
44
+ verification_method: match opts.verification_method {
45
+ Some(vm) => Some(vm),
46
+ None => key.id.clone(),
47
+ },
48
+ key,
49
+ date: match opts.date {
50
+ Some(d) => d,
51
+ None => chrono::Utc::now().to_rfc3339(),
52
+ },
53
+ commitment: opts.commitment.map(|c| c.to_vec()),
54
+ blinded: opts
55
+ .blinded
56
+ .map(|b| b.iter().map(|&i| i as usize).collect()),
57
+ type_: String::from("sec:BbsBlsHolderBoundSignature2022"),
58
+ }
59
+ }
60
+ None => {
61
+ let key = Bls12381G2KeyPair::new(None);
62
+ Self {
63
+ verification_method: None,
64
+ key,
65
+ date: chrono::Utc::now().to_rfc3339(),
66
+ commitment: None,
67
+ blinded: None,
68
+ type_: String::from("sec:BbsBlsHolderBoundSignature2022"),
69
+ }
70
+ }
71
+ }
72
+ }
73
+
74
+ /// Create a blind sign commitment context.
75
+ ///
76
+ /// Returns `{ knownMessageCount, nonce }` — the holder uses these to create a blind commitment.
77
+ /// Matches JS `BbsBlsHolderBoundSignature2022.createBlindSignCommitmentContext()`.
78
+ #[napi]
79
+ pub async fn create_blind_sign_commitment_context(
80
+ &self,
81
+ options: BoundCreateProofOptions,
82
+ ) -> Result<serde_json::Value> {
83
+ let additional_contexts = options
84
+ .contexts
85
+ .as_ref()
86
+ .map(|v| parse_contexts(&json!({ "contexts": v })))
87
+ .unwrap_or_default();
88
+ let (loader, cr) = create_document_loader(additional_contexts);
89
+
90
+ let rust_key =
91
+ vc::jsonld::signatures::bbs::bls_12381_g2_keypair::Bls12381G2KeyPair::new(Some(
92
+ vc::jsonld::signatures::bbs::bls_12381_g2_keypair::types::KeyPairOptions {
93
+ id: self.key.id.clone(),
94
+ controller: self.key.controller.clone(),
95
+ public_key_base58: self.key.public_key(),
96
+ private_key_base58: self.key.private_key(),
97
+ },
98
+ ));
99
+
100
+ // Commitment/blinded not needed for counting messages — use empty placeholders.
101
+ let bound_key = vc::jsonld::signatures::bbs::bound_keypair::BoundBls12381G2KeyPair::new(
102
+ rust_key,
103
+ vec![],
104
+ vec![],
105
+ );
106
+
107
+ let suite = vc::jsonld::signatures::bbs::holder_bound::BbsBlsHolderBoundSignature2022::new(
108
+ vc::jsonld::signatures::bbs::holder_bound::HolderBoundSignatureSuiteOptions {
109
+ key: bound_key,
110
+ verification_method: self.verification_method.clone(),
111
+ date: None,
112
+ },
113
+ )
114
+ .await;
115
+
116
+ let purpose = vc::jsonld::signatures::AssertionProofPurpose::new();
117
+ suite
118
+ .create_blind_sign_commitment_context(&options.document, &purpose, loader, cr)
119
+ .await
120
+ .map_err(|e| {
121
+ napi::Error::from_reason(format!("createBlindSignCommitmentContext failed: {e}"))
122
+ })
123
+ }
124
+
125
+ /// Create a proof for a document using BbsBlsHolderBoundSignature2022 (blind signing).
126
+ #[napi]
127
+ pub async fn create_proof(
128
+ &self,
129
+ options: BoundCreateProofOptions,
130
+ ) -> Result<serde_json::Value> {
131
+ let additional_contexts = options
132
+ .contexts
133
+ .as_ref()
134
+ .map(|v| parse_contexts(&json!({ "contexts": v })))
135
+ .unwrap_or_default();
136
+ let (loader, cr) = create_document_loader(additional_contexts);
137
+
138
+ let commitment = self
139
+ .commitment
140
+ .as_ref()
141
+ .ok_or_else(|| napi::Error::from_reason("No commitment set on suite"))?;
142
+ let blinded = self
143
+ .blinded
144
+ .as_ref()
145
+ .ok_or_else(|| napi::Error::from_reason("No blinded indices set on suite"))?;
146
+
147
+ let rust_key =
148
+ vc::jsonld::signatures::bbs::bls_12381_g2_keypair::Bls12381G2KeyPair::new(Some(
149
+ vc::jsonld::signatures::bbs::bls_12381_g2_keypair::types::KeyPairOptions {
150
+ id: self.key.id.clone(),
151
+ controller: self.key.controller.clone(),
152
+ public_key_base58: self.key.public_key(),
153
+ private_key_base58: self.key.private_key(),
154
+ },
155
+ ));
156
+
157
+ let bound_key = vc::jsonld::signatures::bbs::bound_keypair::BoundBls12381G2KeyPair::new(
158
+ rust_key,
159
+ commitment.clone(),
160
+ blinded.clone(),
161
+ );
162
+
163
+ let suite = vc::jsonld::signatures::bbs::holder_bound::BbsBlsHolderBoundSignature2022::new(
164
+ vc::jsonld::signatures::bbs::holder_bound::HolderBoundSignatureSuiteOptions {
165
+ key: bound_key,
166
+ verification_method: self.verification_method.clone(),
167
+ date: None,
168
+ },
169
+ )
170
+ .await;
171
+
172
+ let purpose = vc::jsonld::signatures::AssertionProofPurpose::new();
173
+ let signed =
174
+ vc::jsonld::signatures::sign_holder_bound(options.document, &suite, &purpose, loader, cr)
175
+ .await
176
+ .map_err(|e| napi::Error::from_reason(format!("createProof failed: {e}")))?;
177
+
178
+ signed
179
+ .get("proof")
180
+ .cloned()
181
+ .ok_or_else(|| napi::Error::from_reason("createProof: no proof in signed document"))
182
+ }
183
+
184
+ /// Verify a holder-bound signed document.
185
+ ///
186
+ /// Returns `{ verified: boolean, error?: string }`.
187
+ #[napi]
188
+ pub async fn verify_proof(
189
+ &self,
190
+ options: BoundVerifyProofOptions,
191
+ ) -> Result<serde_json::Value> {
192
+ let additional_contexts = options
193
+ .contexts
194
+ .as_ref()
195
+ .map(|v| parse_contexts(&json!({ "contexts": v })))
196
+ .unwrap_or_default();
197
+ let (loader, cr) = create_document_loader(additional_contexts);
198
+
199
+ let blinding_factor = options.blinding_factor.to_vec();
200
+ let blinded_messages: Vec<Vec<u8>> =
201
+ options.blinded_messages.iter().map(|m| m.to_vec()).collect();
202
+
203
+ let purpose = vc::jsonld::signatures::AssertionProofPurpose::new();
204
+ let result = vc::jsonld::signatures::verify_holder_bound(
205
+ &options.document,
206
+ blinding_factor,
207
+ blinded_messages,
208
+ &purpose,
209
+ loader,
210
+ cr,
211
+ )
212
+ .await
213
+ .map_err(|e| napi::Error::from_reason(format!("verifyProof failed: {e}")))?;
214
+
215
+ Ok(json!({
216
+ "verified": result.verified,
217
+ "error": result.error,
218
+ }))
219
+ }
220
+
221
+ /// Ensure both BBS and holder-bound suite contexts are present.
222
+ #[napi]
223
+ pub fn ensure_suite_context(
224
+ &self,
225
+ document: serde_json::Value,
226
+ ) -> Result<serde_json::Value> {
227
+ let mut doc = document;
228
+
229
+ for context_url in &[BBS_CONTEXT_URL, BBS_BOUND_CONTEXT_URL] {
230
+ let has_context = match doc.get("@context") {
231
+ Some(serde_json::Value::String(s)) => s == *context_url,
232
+ Some(serde_json::Value::Array(arr)) => {
233
+ arr.iter().any(|v| v.as_str() == Some(*context_url))
234
+ }
235
+ _ => false,
236
+ };
237
+
238
+ if !has_context {
239
+ if let Some(obj) = doc.as_object_mut() {
240
+ let existing = obj
241
+ .get("@context")
242
+ .cloned()
243
+ .unwrap_or(serde_json::Value::Null);
244
+ let mut new_context = match existing {
245
+ serde_json::Value::Array(arr) => serde_json::Value::Array(arr),
246
+ other => serde_json::Value::Array(vec![other]),
247
+ };
248
+ if let serde_json::Value::Array(arr) = &mut new_context {
249
+ arr.push(serde_json::Value::String(context_url.to_string()));
250
+ }
251
+ obj.insert("@context".to_string(), new_context);
252
+ }
253
+ }
254
+ }
255
+
256
+ Ok(doc)
257
+ }
258
+
259
+ /// Returns the proof types this suite matches.
260
+ #[napi(getter)]
261
+ pub fn proof_type() -> Vec<String> {
262
+ vec![
263
+ "BbsBlsHolderBoundSignature2022".to_string(),
264
+ "sec:BbsBlsHolderBoundSignature2022".to_string(),
265
+ "https://w3id.org/security#BbsBlsHolderBoundSignature2022".to_string(),
266
+ ]
267
+ }
268
+ }