@openeudi/openid4vp 0.7.0 → 0.8.1

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/dist/index.d.cts CHANGED
@@ -317,6 +317,27 @@ interface ParseOptions {
317
317
  cache?: Cache;
318
318
  /** Clock-skew tolerance in seconds for certificate validity checks. Default 60. */
319
319
  clockSkewTolerance?: number;
320
+ /**
321
+ * An explicit set of trusted issuer JWKs used as an alternate trust path when the
322
+ * SD-JWT VC's issuer JWT lacks an `x5c` header. When the header has no `x5c`:
323
+ * - If this array is provided, the parser looks up the issuer key by `kid`
324
+ * (or by exact `kty`/`crv`/`x`/`y` match when no `kid` is present). The
325
+ * matched JWK is the trust anchor — cert-chain validation is skipped.
326
+ * - If this array is absent (or empty), the parser falls back to the existing
327
+ * behaviour and throws `MalformedCredentialError('Missing or invalid x5c in JWT header')`.
328
+ *
329
+ * **When the JWT header DOES include `x5c`** this option is ignored — the existing
330
+ * x5c/`trustedCertificates` / `trustStore` path runs unchanged.
331
+ *
332
+ * **Security notice**: opting in to this path skips certificate-chain trust
333
+ * evaluation entirely. The caller is explicitly asserting that they trust the
334
+ * supplied JWK(s) out-of-band. This is appropriate for CI harness setups (e.g.
335
+ * OIDF conformance suite, where the wallet signs without x5c and the verifier
336
+ * knows the signing key from the test-plan config). It is **not recommended for
337
+ * production verifiers** — cert-chain trust evaluation via `trustStore` is the
338
+ * secure path.
339
+ */
340
+ trustedIssuerJwks?: JsonWebKey[];
320
341
  }
321
342
  interface ICredentialParser {
322
343
  readonly format: CredentialFormat;
@@ -555,6 +576,6 @@ declare class LotlTrustStore implements TrustStore {
555
576
  private refresh;
556
577
  }
557
578
 
558
- declare const VERSION = "0.7.0";
579
+ declare const VERSION = "0.8.0";
559
580
 
560
581
  export { type AuthorizationRequest, type AuthorizationRequestInput, type AuthorizationResponse, type Cache, CertificateChainError, type ChainErrorReason, CompositeTrustStore, type CredentialClaims, type CredentialFormat, DecryptionFailedError, type EncryptedResponse, ExpiredCredentialError, type Fetcher, HAIP_DOCTYPE_NAMESPACES, type HaipQueryInput, type HaipValidationCode, HaipValidationError, type ICredentialParser, InMemoryCache, InvalidSignatureError, type IssuerInfo, type LotlAnchorMetadata, LotlFetchError, LotlSignatureError, LotlTrustStore, type LotlTrustStoreOptions, MalformedCredentialError, MdocParser, MissingDecryptionKeyError, MultipleCredentialsNotSupportedError, NonceValidationError, OpenID4VPError, type ParseOptions, type PresentationResult, RevocationCheckFailedError, RevokedCertificateError, SdJwtParser, type SignedAuthorizationRequest, type SignedAuthorizationRequestInput, SignedRequestBuildError, type SignedRequestBuildErrorCode, StaticTrustStore, type TrustAnchor, TrustAnchorNotFoundError, type TrustStore, type TrustStoreHint, type TrustStoreInput, UnsupportedFormatError, UnsupportedJweError, VERSION, type VerifyAuthorizationResponseOptions, type VerifyOptions, type VerifyResult, buildHaipQuery, createAuthorizationRequest, createSignedAuthorizationRequest, decryptAuthorizationResponse, isHaipQuery, parsePresentation, validateHaipQuery, verifyAuthorizationResponse, verifyPresentation };
package/dist/index.d.ts CHANGED
@@ -317,6 +317,27 @@ interface ParseOptions {
317
317
  cache?: Cache;
318
318
  /** Clock-skew tolerance in seconds for certificate validity checks. Default 60. */
319
319
  clockSkewTolerance?: number;
320
+ /**
321
+ * An explicit set of trusted issuer JWKs used as an alternate trust path when the
322
+ * SD-JWT VC's issuer JWT lacks an `x5c` header. When the header has no `x5c`:
323
+ * - If this array is provided, the parser looks up the issuer key by `kid`
324
+ * (or by exact `kty`/`crv`/`x`/`y` match when no `kid` is present). The
325
+ * matched JWK is the trust anchor — cert-chain validation is skipped.
326
+ * - If this array is absent (or empty), the parser falls back to the existing
327
+ * behaviour and throws `MalformedCredentialError('Missing or invalid x5c in JWT header')`.
328
+ *
329
+ * **When the JWT header DOES include `x5c`** this option is ignored — the existing
330
+ * x5c/`trustedCertificates` / `trustStore` path runs unchanged.
331
+ *
332
+ * **Security notice**: opting in to this path skips certificate-chain trust
333
+ * evaluation entirely. The caller is explicitly asserting that they trust the
334
+ * supplied JWK(s) out-of-band. This is appropriate for CI harness setups (e.g.
335
+ * OIDF conformance suite, where the wallet signs without x5c and the verifier
336
+ * knows the signing key from the test-plan config). It is **not recommended for
337
+ * production verifiers** — cert-chain trust evaluation via `trustStore` is the
338
+ * secure path.
339
+ */
340
+ trustedIssuerJwks?: JsonWebKey[];
320
341
  }
321
342
  interface ICredentialParser {
322
343
  readonly format: CredentialFormat;
@@ -555,6 +576,6 @@ declare class LotlTrustStore implements TrustStore {
555
576
  private refresh;
556
577
  }
557
578
 
558
- declare const VERSION = "0.7.0";
579
+ declare const VERSION = "0.8.0";
559
580
 
560
581
  export { type AuthorizationRequest, type AuthorizationRequestInput, type AuthorizationResponse, type Cache, CertificateChainError, type ChainErrorReason, CompositeTrustStore, type CredentialClaims, type CredentialFormat, DecryptionFailedError, type EncryptedResponse, ExpiredCredentialError, type Fetcher, HAIP_DOCTYPE_NAMESPACES, type HaipQueryInput, type HaipValidationCode, HaipValidationError, type ICredentialParser, InMemoryCache, InvalidSignatureError, type IssuerInfo, type LotlAnchorMetadata, LotlFetchError, LotlSignatureError, LotlTrustStore, type LotlTrustStoreOptions, MalformedCredentialError, MdocParser, MissingDecryptionKeyError, MultipleCredentialsNotSupportedError, NonceValidationError, OpenID4VPError, type ParseOptions, type PresentationResult, RevocationCheckFailedError, RevokedCertificateError, SdJwtParser, type SignedAuthorizationRequest, type SignedAuthorizationRequestInput, SignedRequestBuildError, type SignedRequestBuildErrorCode, StaticTrustStore, type TrustAnchor, TrustAnchorNotFoundError, type TrustStore, type TrustStoreHint, type TrustStoreInput, UnsupportedFormatError, UnsupportedJweError, VERSION, type VerifyAuthorizationResponseOptions, type VerifyOptions, type VerifyResult, buildHaipQuery, createAuthorizationRequest, createSignedAuthorizationRequest, decryptAuthorizationResponse, isHaipQuery, parsePresentation, validateHaipQuery, verifyAuthorizationResponse, verifyPresentation };
package/dist/index.js CHANGED
@@ -6361,6 +6361,33 @@ var init_es20152 = __esm({
6361
6361
  init_validity();
6362
6362
  }
6363
6363
  });
6364
+ function getSkiHex(cert) {
6365
+ const ext = cert.getExtension(SubjectKeyIdentifierExtension);
6366
+ return ext ? ext.keyId.toLowerCase() : null;
6367
+ }
6368
+ function certificatesEqual(a, b) {
6369
+ const ab = new Uint8Array(a.rawData);
6370
+ const bb = new Uint8Array(b.rawData);
6371
+ if (ab.length !== bb.length) return false;
6372
+ for (let i = 0; i < ab.length; i++) {
6373
+ if (ab[i] !== bb[i]) return false;
6374
+ }
6375
+ return true;
6376
+ }
6377
+ function getSkiBytes(cert) {
6378
+ const ext = cert.getExtension(SubjectKeyIdentifierExtension);
6379
+ if (!ext) return null;
6380
+ const hex = ext.keyId;
6381
+ const out = new Uint8Array(hex.length / 2);
6382
+ for (let i = 0; i < out.length; i++) {
6383
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
6384
+ }
6385
+ return out;
6386
+ }
6387
+ var init_x509_utils = __esm({
6388
+ "src/trust/x509-utils.ts"() {
6389
+ }
6390
+ });
6364
6391
  function mapX509AlgoToJwaName(cert) {
6365
6392
  const alg = cert.signatureAlgorithm;
6366
6393
  const name = alg?.name ?? "";
@@ -6370,8 +6397,11 @@ function mapX509AlgoToJwaName(cert) {
6370
6397
  if (name === "ECDSA" && hash === "SHA-512") return "ES512";
6371
6398
  if (name === "Ed25519" || name === "EdDSA") return "EdDSA";
6372
6399
  if (name === "RSASSA-PKCS1-v1_5" && hash === "SHA-256") return "RS256";
6400
+ if (name === "RSASSA-PKCS1-v1_5" && hash === "SHA-384") return "RS384";
6401
+ if (name === "RSASSA-PKCS1-v1_5" && hash === "SHA-512") return "RS512";
6373
6402
  if (name === "RSA-PSS" && hash === "SHA-256") return "PS256";
6374
6403
  if (name === "RSA-PSS" && hash === "SHA-384") return "PS384";
6404
+ if (name === "RSA-PSS" && hash === "SHA-512") return "PS512";
6375
6405
  return `${name}-${hash}`;
6376
6406
  }
6377
6407
  function collectLeafNames(leaf) {
@@ -6464,8 +6494,20 @@ var init_ChainBuilder = __esm({
6464
6494
  init_es2015();
6465
6495
  init_es20152();
6466
6496
  init_errors();
6497
+ init_x509_utils();
6467
6498
  NAME_CONSTRAINTS_OID = "2.5.29.30";
6468
- DEFAULT_ALGORITHMS = ["ES256", "ES384", "EdDSA", "RS256", "PS256"];
6499
+ DEFAULT_ALGORITHMS = [
6500
+ "ES256",
6501
+ "ES384",
6502
+ "ES512",
6503
+ "EdDSA",
6504
+ "RS256",
6505
+ "RS384",
6506
+ "RS512",
6507
+ "PS256",
6508
+ "PS384",
6509
+ "PS512"
6510
+ ];
6469
6511
  ChainBuilder = class {
6470
6512
  constructor(opts = {}) {
6471
6513
  this.opts = opts;
@@ -6524,6 +6566,12 @@ var init_ChainBuilder = __esm({
6524
6566
  current = issuer;
6525
6567
  nonLeafDepth += 1;
6526
6568
  }
6569
+ if (!certificatesEqual(current, anchor)) {
6570
+ throw new CertificateChainError(
6571
+ `certificate ${current.subject} shares the trust anchor's Subject DN but is not the anchor (no signature closure)`,
6572
+ { reason: "signature" }
6573
+ );
6574
+ }
6527
6575
  this.checkNameConstraints(leaf, chain.slice(1));
6528
6576
  return chain;
6529
6577
  }
@@ -7764,7 +7812,10 @@ var init_RevocationChecker = __esm({
7764
7812
  if (hints.ocspUrl) {
7765
7813
  try {
7766
7814
  return await this.ocsp.checkCached(subjectCert, issuerCert, hints.ocspUrl, now);
7767
- } catch {
7815
+ } catch (err) {
7816
+ console.warn(
7817
+ `[openid4vp] OCSP check failed for ${subjectCert.subject} via ${hints.ocspUrl}, falling back to CRL: ${err.message}`
7818
+ );
7768
7819
  }
7769
7820
  }
7770
7821
  for (const url of hints.crlUrls) {
@@ -7792,6 +7843,9 @@ var init_RevocationChecker = __esm({
7792
7843
  return { status: "good", source: "crl", checkedAt: now };
7793
7844
  } catch (err) {
7794
7845
  if (err instanceof RevocationCheckFailedError) throw err;
7846
+ console.warn(
7847
+ `[openid4vp] CRL check failed for ${subjectCert.subject} via ${url}: ${err.message}`
7848
+ );
7795
7849
  continue;
7796
7850
  }
7797
7851
  }
@@ -7810,24 +7864,6 @@ var init_RevocationChecker = __esm({
7810
7864
  };
7811
7865
  }
7812
7866
  });
7813
- function getSkiHex(cert) {
7814
- const ext = cert.getExtension(SubjectKeyIdentifierExtension);
7815
- return ext ? ext.keyId.toLowerCase() : null;
7816
- }
7817
- function getSkiBytes(cert) {
7818
- const ext = cert.getExtension(SubjectKeyIdentifierExtension);
7819
- if (!ext) return null;
7820
- const hex = ext.keyId;
7821
- const out = new Uint8Array(hex.length / 2);
7822
- for (let i = 0; i < out.length; i++) {
7823
- out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
7824
- }
7825
- return out;
7826
- }
7827
- var init_x509_utils = __esm({
7828
- "src/trust/x509-utils.ts"() {
7829
- }
7830
- });
7831
7867
 
7832
7868
  // src/trust/lotl-loa-mapping.ts
7833
7869
  function mapLoA(uris) {
@@ -8009,8 +8045,13 @@ var init_TrustEvaluator = __esm({
8009
8045
  );
8010
8046
  const terminusCert = chain[chain.length - 1];
8011
8047
  const anchor = anchors.find(
8012
- (a) => a.certificate.serialNumber === terminusCert.serialNumber
8013
- ) ?? anchors[0];
8048
+ (a) => certificatesEqual(a.certificate, terminusCert)
8049
+ );
8050
+ if (!anchor) {
8051
+ throw new TrustAnchorNotFoundError(
8052
+ `chain terminus ${terminusCert.subject} does not match any trust anchor certificate`
8053
+ );
8054
+ }
8014
8055
  const issuerForRevocation = chain.length > 1 ? chain[1] : terminusCert;
8015
8056
  const revocation = await this.revocationChecker.check(
8016
8057
  leaf,
@@ -8138,7 +8179,10 @@ async function createSignedAuthorizationRequest(input, query) {
8138
8179
  }
8139
8180
  const jwk = { ...input.encryptionKey.publicJwk, use: "enc" };
8140
8181
  clientMetadata.jwks = { keys: [jwk] };
8141
- clientMetadata.encrypted_response_enc_values_supported = enc ?? [...DEFAULT_SUPPORTED_ENC_VALUES];
8182
+ const encValues = enc ?? [...DEFAULT_SUPPORTED_ENC_VALUES];
8183
+ clientMetadata.encrypted_response_enc_values_supported = encValues;
8184
+ clientMetadata.authorization_encrypted_response_alg = "ECDH-ES";
8185
+ clientMetadata.authorization_encrypted_response_enc = encValues[0];
8142
8186
  }
8143
8187
  const payload = {
8144
8188
  iss: clientId,
@@ -8311,7 +8355,7 @@ function isHaipQuery(query) {
8311
8355
 
8312
8356
  // src/crypto/cose-sign1.ts
8313
8357
  init_errors();
8314
- var decoder = new Encoder({ mapsAsObjects: false, useRecords: false });
8358
+ var decoder = new Encoder({ mapsAsObjects: false, useRecords: false, tagUint8Array: false });
8315
8359
  var HEADER_ALG = 1;
8316
8360
  var HEADER_X5CHAIN = 33;
8317
8361
  var COSE_ALG_TO_NAME = {
@@ -8397,7 +8441,7 @@ async function verifyCoseSign1(coseSign1, publicKey, allowedAlgorithms) {
8397
8441
  throw new MalformedCredentialError(`no hash mapping for alg ${coseSign1.alg}`);
8398
8442
  }
8399
8443
  const sigStructure = ["Signature1", coseSign1.protectedHeaderBytes, new Uint8Array(0), coseSign1.payload];
8400
- const sigInput = decoder.encode(sigStructure);
8444
+ const sigInput = new Uint8Array(decoder.encode(sigStructure));
8401
8445
  const ok = await crypto.subtle.verify(
8402
8446
  { name: "ECDSA", hash: hashAlg },
8403
8447
  publicKey,
@@ -8936,32 +8980,74 @@ var SdJwtParser = class {
8936
8980
  return invalidResult(`Unsupported algorithm: ${alg}`);
8937
8981
  }
8938
8982
  const x5cArray = header.x5c;
8939
- if (!Array.isArray(x5cArray) || x5cArray.length === 0 || typeof x5cArray[0] !== "string") {
8940
- throw new MalformedCredentialError("Missing or invalid x5c in JWT header");
8941
- }
8983
+ const hasX5c = Array.isArray(x5cArray) && x5cArray.length > 0 && typeof x5cArray[0] === "string";
8942
8984
  let issuerKey;
8943
8985
  let issuerCertBytes;
8944
- try {
8945
- issuerKey = await importX509(derToPem2(x5cArray[0]), alg);
8946
- issuerCertBytes = base64ToBytes(x5cArray[0]);
8947
- } catch (err) {
8948
- if (err instanceof MalformedCredentialError) throw err;
8949
- throw new MalformedCredentialError("Failed to import issuer certificate");
8986
+ if (hasX5c) {
8987
+ try {
8988
+ issuerKey = await importX509(derToPem2(x5cArray[0]), alg);
8989
+ issuerCertBytes = base64ToBytes(x5cArray[0]);
8990
+ } catch (err) {
8991
+ if (err instanceof MalformedCredentialError) throw err;
8992
+ throw new MalformedCredentialError("Failed to import issuer certificate");
8993
+ }
8994
+ } else if (options.trustedIssuerJwks && options.trustedIssuerJwks.length > 0) {
8995
+ issuerKey = void 0;
8996
+ } else {
8997
+ throw new MalformedCredentialError("Missing or invalid x5c in JWT header");
8950
8998
  }
8951
8999
  let payload;
8952
- try {
8953
- const result2 = await jwtVerify(parts.jwt, issuerKey, { algorithms: allowedAlgorithms });
8954
- payload = result2.payload;
8955
- } catch {
8956
- return invalidResult("Issuer JWT signature verification failed");
9000
+ if (issuerKey) {
9001
+ try {
9002
+ const result2 = await jwtVerify(parts.jwt, issuerKey, { algorithms: allowedAlgorithms });
9003
+ payload = result2.payload;
9004
+ } catch {
9005
+ return invalidResult("Issuer JWT signature verification failed");
9006
+ }
9007
+ } else {
9008
+ const jwks = options.trustedIssuerJwks;
9009
+ const headerKid = header["kid"];
9010
+ let candidates;
9011
+ if (typeof headerKid === "string") {
9012
+ const matched = jwks.find((k) => k["kid"] === headerKid);
9013
+ if (!matched) {
9014
+ return invalidResult(
9015
+ `No matching JWK found in trustedIssuerJwks for kid=${headerKid}`
9016
+ );
9017
+ }
9018
+ candidates = [matched];
9019
+ } else {
9020
+ candidates = jwks;
9021
+ }
9022
+ let verifiedPayload;
9023
+ for (const jwk of candidates) {
9024
+ let candidateKey;
9025
+ try {
9026
+ candidateKey = await importJWK(jwk, alg);
9027
+ } catch {
9028
+ continue;
9029
+ }
9030
+ try {
9031
+ const result2 = await jwtVerify(parts.jwt, candidateKey, {
9032
+ algorithms: allowedAlgorithms
9033
+ });
9034
+ verifiedPayload = result2.payload;
9035
+ break;
9036
+ } catch {
9037
+ }
9038
+ }
9039
+ if (!verifiedPayload) {
9040
+ return invalidResult("Issuer JWT signature verification failed");
9041
+ }
9042
+ payload = verifiedPayload;
8957
9043
  }
8958
9044
  const issuerString = typeof payload["iss"] === "string" ? payload["iss"] : "";
8959
9045
  const issuerInfo = {
8960
- certificate: issuerCertBytes,
9046
+ certificate: issuerCertBytes ?? new Uint8Array(),
8961
9047
  country: extractCountryHint(issuerString)
8962
9048
  };
8963
9049
  let trustResult;
8964
- if (options.trustStore) {
9050
+ if (issuerCertBytes === void 0) ; else if (options.trustStore) {
8965
9051
  const { TrustEvaluator: TrustEvaluator2 } = await Promise.resolve().then(() => (init_TrustEvaluator(), TrustEvaluator_exports));
8966
9052
  const evaluator = new TrustEvaluator2({
8967
9053
  trustStore: options.trustStore,
@@ -8981,24 +9067,56 @@ var SdJwtParser = class {
8981
9067
  );
8982
9068
  }
8983
9069
  } else {
8984
- const isTrusted = options.trustedCertificates.some((trusted) => bytesEqual4(trusted, issuerCertBytes));
9070
+ const isTrusted = options.trustedCertificates.some(
9071
+ (trusted) => bytesEqual4(trusted, issuerCertBytes)
9072
+ );
8985
9073
  if (!isTrusted) {
8986
9074
  return invalidResult("Issuer certificate is not trusted");
8987
9075
  }
8988
9076
  }
8989
9077
  }
8990
- const sdArray = payload["_sd"];
8991
9078
  if (parts.disclosures.length > 0) {
8992
- if (!Array.isArray(sdArray) || sdArray.length === 0) {
8993
- return invalidResult("Disclosures present but no _sd array in issuer JWT");
8994
- }
9079
+ const decodedDisclosures = [];
8995
9080
  for (const disclosure of parts.disclosures) {
9081
+ let parsed;
9082
+ try {
9083
+ parsed = JSON.parse(new TextDecoder().decode(base64ToBytes(disclosure)));
9084
+ } catch {
9085
+ return invalidResult("Disclosure is not valid base64url-encoded JSON");
9086
+ }
9087
+ if (!Array.isArray(parsed) || parsed.length !== 2 && parsed.length !== 3) {
9088
+ return invalidResult("Disclosure must be a 2- or 3-element array");
9089
+ }
8996
9090
  const hashBytes = await sha256Hasher(disclosure);
8997
- const hashB64url = bytesToBase64url(hashBytes);
8998
- if (!sdArray.includes(hashB64url)) {
8999
- return invalidResult("Disclosure hash mismatch");
9091
+ decodedDisclosures.push({
9092
+ raw: disclosure,
9093
+ hash: bytesToBase64url(hashBytes),
9094
+ length: parsed.length,
9095
+ value: parsed.length === 3 ? parsed[2] : parsed[1]
9096
+ });
9097
+ }
9098
+ const seed = collectAnchorHashes(payload);
9099
+ const kvHashes = seed.kvHashes;
9100
+ const arrayElemHashes = seed.arrayElemHashes;
9101
+ const remaining = [...decodedDisclosures];
9102
+ for (; ; ) {
9103
+ const anchored = [];
9104
+ for (const d of remaining) {
9105
+ const valid = d.length === 3 ? kvHashes.has(d.hash) : arrayElemHashes.has(d.hash);
9106
+ if (valid) anchored.push(d);
9107
+ }
9108
+ if (anchored.length === 0) break;
9109
+ for (const a of anchored) {
9110
+ const sub = collectAnchorHashes(a.value);
9111
+ for (const h of sub.kvHashes) kvHashes.add(h);
9112
+ for (const h of sub.arrayElemHashes) arrayElemHashes.add(h);
9113
+ const idx = remaining.indexOf(a);
9114
+ if (idx >= 0) remaining.splice(idx, 1);
9000
9115
  }
9001
9116
  }
9117
+ if (remaining.length > 0) {
9118
+ return invalidResult("Disclosure hash mismatch");
9119
+ }
9002
9120
  }
9003
9121
  if (parts.kbJwt) {
9004
9122
  const cnf = payload["cnf"];
@@ -9061,6 +9179,35 @@ var SdJwtParser = class {
9061
9179
  return result;
9062
9180
  }
9063
9181
  };
9182
+ function collectAnchorHashes(node) {
9183
+ const kvHashes = /* @__PURE__ */ new Set();
9184
+ const arrayElemHashes = /* @__PURE__ */ new Set();
9185
+ const visit = (value) => {
9186
+ if (Array.isArray(value)) {
9187
+ for (const item of value) {
9188
+ if (item && typeof item === "object" && !Array.isArray(item)) {
9189
+ const obj = item;
9190
+ const keys = Object.keys(obj);
9191
+ if (keys.length === 1 && keys[0] === "..." && typeof obj["..."] === "string") {
9192
+ arrayElemHashes.add(obj["..."]);
9193
+ continue;
9194
+ }
9195
+ }
9196
+ visit(item);
9197
+ }
9198
+ } else if (value && typeof value === "object") {
9199
+ for (const [k, v] of Object.entries(value)) {
9200
+ if (k === "_sd" && Array.isArray(v)) {
9201
+ for (const h of v) if (typeof h === "string") kvHashes.add(h);
9202
+ continue;
9203
+ }
9204
+ visit(v);
9205
+ }
9206
+ }
9207
+ };
9208
+ visit(node);
9209
+ return { kvHashes, arrayElemHashes };
9210
+ }
9064
9211
 
9065
9212
  // src/presentation.ts
9066
9213
  var parsers = [new SdJwtParser(), new MdocParser()];
@@ -9800,7 +9947,7 @@ function equalBytes(a, b) {
9800
9947
 
9801
9948
  // src/index.ts
9802
9949
  init_errors();
9803
- var VERSION = "0.7.0";
9950
+ var VERSION = "0.8.0";
9804
9951
  /*! Bundled license information:
9805
9952
 
9806
9953
  pvtsutils/build/index.es.js: