@learncard/openid4vc-plugin 0.0.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.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +592 -0
  3. package/dist/dcql/adapt.d.ts +80 -0
  4. package/dist/dcql/adapt.d.ts.map +1 -0
  5. package/dist/dcql/build.d.ts +122 -0
  6. package/dist/dcql/build.d.ts.map +1 -0
  7. package/dist/dcql/compose.d.ts +78 -0
  8. package/dist/dcql/compose.d.ts.map +1 -0
  9. package/dist/dcql/index.d.ts +18 -0
  10. package/dist/dcql/index.d.ts.map +1 -0
  11. package/dist/dcql/parse.d.ts +16 -0
  12. package/dist/dcql/parse.d.ts.map +1 -0
  13. package/dist/dcql/respond.d.ts +115 -0
  14. package/dist/dcql/respond.d.ts.map +1 -0
  15. package/dist/dcql/select.d.ts +63 -0
  16. package/dist/dcql/select.d.ts.map +1 -0
  17. package/dist/dcql/types.d.ts +93 -0
  18. package/dist/dcql/types.d.ts.map +1 -0
  19. package/dist/index.d.ts +8 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +7 -0
  22. package/dist/offer/index.d.ts +4 -0
  23. package/dist/offer/index.d.ts.map +1 -0
  24. package/dist/offer/normalize.d.ts +15 -0
  25. package/dist/offer/normalize.d.ts.map +1 -0
  26. package/dist/offer/parse.d.ts +24 -0
  27. package/dist/offer/parse.d.ts.map +1 -0
  28. package/dist/offer/types.d.ts +92 -0
  29. package/dist/offer/types.d.ts.map +1 -0
  30. package/dist/openid4vc-plugin.cjs.development.js +27402 -0
  31. package/dist/openid4vc-plugin.cjs.development.js.map +7 -0
  32. package/dist/openid4vc-plugin.cjs.production.min.js +146 -0
  33. package/dist/openid4vc-plugin.cjs.production.min.js.map +7 -0
  34. package/dist/openid4vc-plugin.esm.js +27396 -0
  35. package/dist/openid4vc-plugin.esm.js.map +7 -0
  36. package/dist/plugin.d.ts +12 -0
  37. package/dist/plugin.d.ts.map +1 -0
  38. package/dist/siop/index.d.ts +11 -0
  39. package/dist/siop/index.d.ts.map +1 -0
  40. package/dist/siop/sign.d.ts +92 -0
  41. package/dist/siop/sign.d.ts.map +1 -0
  42. package/dist/types.d.ts +359 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/vci/accept.d.ts +24 -0
  45. package/dist/vci/accept.d.ts.map +1 -0
  46. package/dist/vci/auth-code.d.ts +148 -0
  47. package/dist/vci/auth-code.d.ts.map +1 -0
  48. package/dist/vci/decode.d.ts +65 -0
  49. package/dist/vci/decode.d.ts.map +1 -0
  50. package/dist/vci/errors.d.ts +22 -0
  51. package/dist/vci/errors.d.ts.map +1 -0
  52. package/dist/vci/index.d.ts +12 -0
  53. package/dist/vci/index.d.ts.map +1 -0
  54. package/dist/vci/metadata.d.ts +25 -0
  55. package/dist/vci/metadata.d.ts.map +1 -0
  56. package/dist/vci/pkce.d.ts +85 -0
  57. package/dist/vci/pkce.d.ts.map +1 -0
  58. package/dist/vci/proof.d.ts +43 -0
  59. package/dist/vci/proof.d.ts.map +1 -0
  60. package/dist/vci/request.d.ts +48 -0
  61. package/dist/vci/request.d.ts.map +1 -0
  62. package/dist/vci/store.d.ts +89 -0
  63. package/dist/vci/store.d.ts.map +1 -0
  64. package/dist/vci/token.d.ts +16 -0
  65. package/dist/vci/token.d.ts.map +1 -0
  66. package/dist/vci/types.d.ts +156 -0
  67. package/dist/vci/types.d.ts.map +1 -0
  68. package/dist/vp/client-id-prefix.d.ts +115 -0
  69. package/dist/vp/client-id-prefix.d.ts.map +1 -0
  70. package/dist/vp/encrypt.d.ts +221 -0
  71. package/dist/vp/encrypt.d.ts.map +1 -0
  72. package/dist/vp/index.d.ts +27 -0
  73. package/dist/vp/index.d.ts.map +1 -0
  74. package/dist/vp/parse.d.ts +60 -0
  75. package/dist/vp/parse.d.ts.map +1 -0
  76. package/dist/vp/pex.d.ts +83 -0
  77. package/dist/vp/pex.d.ts.map +1 -0
  78. package/dist/vp/present.d.ts +126 -0
  79. package/dist/vp/present.d.ts.map +1 -0
  80. package/dist/vp/request-object.d.ts +99 -0
  81. package/dist/vp/request-object.d.ts.map +1 -0
  82. package/dist/vp/select.d.ts +137 -0
  83. package/dist/vp/select.d.ts.map +1 -0
  84. package/dist/vp/sign.d.ts +125 -0
  85. package/dist/vp/sign.d.ts.map +1 -0
  86. package/dist/vp/submit.d.ts +175 -0
  87. package/dist/vp/submit.d.ts.map +1 -0
  88. package/dist/vp/types.d.ts +255 -0
  89. package/dist/vp/types.d.ts.map +1 -0
  90. package/package.json +52 -0
@@ -0,0 +1,83 @@
1
+ import { Field, InputDescriptor } from './types';
2
+ /**
3
+ * Presentation Exchange v2 matcher — tests whether a single candidate VC
4
+ * satisfies an input_descriptor's constraints.
5
+ *
6
+ * Scope (what PEX expressions we support in Slice 6):
7
+ *
8
+ * **JSONPath subset (field.path[])**
9
+ * - `$` root
10
+ * - `$.foo`, `$.foo.bar` dotted properties
11
+ * - `$['foo']`, `$["foo bar"]` bracketed property names (quoted literals)
12
+ * - `$.foo[0]` numeric index
13
+ * - `$.foo[*]` array wildcard
14
+ * - `$..foo` recursive descent
15
+ * - `$.foo[?(@.bar=='x')]` filter predicates — NOT yet supported, and
16
+ * they're rare in real-world PD docs. Paths
17
+ * that need predicates throw a descriptive
18
+ * error rather than silently evaluating to
19
+ * nothing.
20
+ *
21
+ * **JSON Schema filter subset (field.filter)**
22
+ * - `type`, `const`, `enum`, `pattern`
23
+ * - `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`
24
+ * - `minLength`, `maxLength`
25
+ * - `contains` (on arrays)
26
+ * - `items` (on arrays — applied to every element)
27
+ * - Anything else is treated as "unknown constraint" and passes by
28
+ * default. Verifiers relying on more exotic keywords will want the
29
+ * full-ajv upgrade path noted in the README.
30
+ *
31
+ * Format / holder-binding checks live in the higher-level select.ts
32
+ * layer (Slice 6d) since they depend on knowing the candidate's format
33
+ * and the PD's `format` designation together.
34
+ */
35
+ /** Per-field evaluation result. */
36
+ export interface FieldMatch {
37
+ matched: boolean;
38
+ /** First JSONPath from `field.path[]` that yielded a matching value. */
39
+ matchedPath?: string;
40
+ /** The value at `matchedPath`. */
41
+ value?: unknown;
42
+ /** Diagnostic reason when `matched === false`. */
43
+ reason?: string;
44
+ }
45
+ /** Per-descriptor evaluation result. */
46
+ export interface DescriptorMatch {
47
+ matched: boolean;
48
+ descriptorId: string;
49
+ /** One entry per declared field, in order. */
50
+ fields: FieldMatch[];
51
+ /** Diagnostic reason summarizing the first failure, when `matched === false`. */
52
+ reason?: string;
53
+ }
54
+ /**
55
+ * Evaluate a single PEX field against a candidate credential.
56
+ *
57
+ * Walks `field.path[]` in declared order, returning the first path that
58
+ * resolves AND — if `field.filter` is present — whose value satisfies
59
+ * the filter. Optional fields that find no value still succeed.
60
+ */
61
+ export declare const evaluateField: (candidate: unknown, field: Field) => FieldMatch;
62
+ /**
63
+ * Evaluate a full input_descriptor against a candidate credential.
64
+ *
65
+ * Short-circuits on the first required-field failure so a single bad
66
+ * field produces a useful reason instead of a wall of mismatches.
67
+ */
68
+ export declare const matchInputDescriptor: (candidate: unknown, descriptor: InputDescriptor) => DescriptorMatch;
69
+ /**
70
+ * Evaluate a supported JSONPath expression against `root` and return all
71
+ * matching values. Throws if the expression uses unsupported features
72
+ * (filter predicates, slicing, unions, scripting) so callers surface an
73
+ * actionable error rather than silently returning nothing.
74
+ */
75
+ export declare const queryJsonPath: (root: unknown, path: string) => unknown[];
76
+ /**
77
+ * Return whether `value` satisfies the PEX-sanctioned subset of JSON
78
+ * Schema. Unknown keywords are ignored (lenient mode) — wallets that
79
+ * reject on unknown keywords force verifier upgrades in lockstep, which
80
+ * is incompatible with the "meet them where they are" design goal.
81
+ */
82
+ export declare const satisfiesFilter: (value: unknown, filter: Record<string, unknown>) => boolean;
83
+ //# sourceMappingURL=pex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pex.d.ts","sourceRoot":"","sources":["../../src/vp/pex.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAW9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,mCAAmC;AACnC,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,cAAe,OAAO,SAAS,KAAK,KAAG,UA6DhE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,cAClB,OAAO,cACN,eAAe,KAC5B,eAyBF,CAAC;AAYF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,SAAU,OAAO,QAAQ,MAAM,KAAG,OAAO,EA0DlE,CAAC;AAmIF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,UAAW,OAAO,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,OA6GjF,CAAC"}
@@ -0,0 +1,126 @@
1
+ import { UnsignedVP } from '@learncard/types';
2
+ import type { CandidateCredential, PresentationSubmission } from './select';
3
+ import { PresentationDefinition } from './types';
4
+ /**
5
+ * Slice 7a — **Verifiable Presentation construction**.
6
+ *
7
+ * Given a user's per-descriptor credential picks and the verifier's
8
+ * Presentation Definition, produce:
9
+ *
10
+ * 1. An unsigned W3C `VerifiablePresentation` object ready to be
11
+ * signed (via `learnCard.invoke.issuePresentation` for `ldp_vp`, or
12
+ * via the Slice 7b JWT signer for `jwt_vp_json`).
13
+ * 2. A DIF PEX v2 `PresentationSubmission` whose `descriptor_map`
14
+ * paths correctly target each credential inside the final
15
+ * `vp_token` for the chosen envelope format.
16
+ * 3. The envelope format id (`ldp_vp` | `jwt_vp_json`) so the caller
17
+ * knows which signing path to invoke.
18
+ *
19
+ * **Pure and synchronous.** This layer never signs, never POSTs, and
20
+ * never touches the holder's private keys. All transport /
21
+ * cryptographic work happens in Slices 7b and 7c.
22
+ *
23
+ * ## Descriptor map semantics
24
+ *
25
+ * The DIF PEX spec (§6 "Presentation Submission") evaluates
26
+ * `descriptor_map[n].path` as a JSONPath over the vp_token **after
27
+ * decoding** when the outer token is a JWT. We emit paths that match
28
+ * what every mainstream verifier (walt.id, Sphereon, EUDI, Animo)
29
+ * actually resolves:
30
+ *
31
+ * - Outer `ldp_vp` envelope → `path: "$.verifiableCredential[N]"`
32
+ * - Outer `jwt_vp_json` envelope → `path: "$.vp.verifiableCredential[N]"`
33
+ *
34
+ * For mixed inner formats (e.g. a `jwt_vc_json` VC embedded alongside
35
+ * an `ldp_vc` inside an `ldp_vp`), the per-credential `format` field
36
+ * discriminates without needing `path_nested` — the path still
37
+ * resolves directly to the credential value at that index.
38
+ */
39
+ /**
40
+ * Envelope format for the outer Verifiable Presentation.
41
+ *
42
+ * - `ldp_vp` — plain JSON-LD VP, signed with a Linked-Data / Data
43
+ * Integrity proof (Slice 7b routes this through
44
+ * `learnCard.invoke.issuePresentation`).
45
+ * - `jwt_vp_json` — compact JWS whose payload wraps the VP under a
46
+ * top-level `vp` claim (VCDM §6.3.1).
47
+ */
48
+ export type VpFormat = 'ldp_vp' | 'jwt_vp_json';
49
+ /**
50
+ * A single user pick: "this candidate satisfies this input_descriptor".
51
+ * Wallets typically build this list from the SelectionResult by showing
52
+ * the per-descriptor candidate lists and letting the user tap once per
53
+ * row.
54
+ */
55
+ export interface ChosenCredential {
56
+ descriptorId: string;
57
+ candidate: CandidateCredential;
58
+ }
59
+ export interface BuildPresentationOptions {
60
+ /** The verifier's Presentation Definition (used for `definition_id` + format hints). */
61
+ pd: PresentationDefinition;
62
+ /**
63
+ * The holder's picks, in presentation order. The resulting VP's
64
+ * `verifiableCredential` array mirrors this order, and every
65
+ * `descriptor_map` entry points at the matching index.
66
+ */
67
+ chosen: ChosenCredential[];
68
+ /** Holder DID, emitted as `unsignedVp.holder`. Required for VCDM §4.1.1 compliance. */
69
+ holder: string;
70
+ /**
71
+ * Override the envelope format. By default we infer from
72
+ * `pd.format` (if the verifier declared preference) and fall back
73
+ * to matching the chosen credentials' inner format.
74
+ */
75
+ envelopeFormat?: VpFormat;
76
+ /** Override the submission id. Defaults to a short random hex id. */
77
+ submissionId?: string;
78
+ /** Override the VP id. Defaults to `urn:uuid:<v4>`. */
79
+ presentationId?: string;
80
+ /** Seam for deterministic tests. Defaults to a `crypto.getRandomValues`-backed id. */
81
+ makeId?: () => string;
82
+ }
83
+ export interface PreparedPresentation {
84
+ /**
85
+ * Unsigned W3C Verifiable Presentation. Shape is the same for both
86
+ * envelope formats; the signing step differs.
87
+ *
88
+ * - For `ldp_vp`: pass this straight to
89
+ * `learnCard.invoke.issuePresentation(unsignedVp, signingOptions)`.
90
+ * - For `jwt_vp_json`: wrap as the `vp` claim of a JWT and sign
91
+ * (Slice 7b).
92
+ */
93
+ unsignedVp: UnsignedVP;
94
+ /** Ready-to-submit DIF PEX v2 Presentation Submission. */
95
+ submission: PresentationSubmission;
96
+ /** Outer envelope format id. */
97
+ vpFormat: VpFormat;
98
+ /**
99
+ * Per-credential format id, in the same order as
100
+ * `unsignedVp.verifiableCredential`. Slice 7b / 7c use this to
101
+ * decide serialization (compact JWS string vs. JSON object).
102
+ */
103
+ innerFormats: string[];
104
+ }
105
+ export type BuildPresentationErrorCode = 'no_selections' | 'unknown_descriptor' | 'unknown_credential_format' | 'invalid_jwt_vc';
106
+ export declare class BuildPresentationError extends Error {
107
+ readonly code: BuildPresentationErrorCode;
108
+ constructor(code: BuildPresentationErrorCode, message: string);
109
+ }
110
+ /**
111
+ * Build a VerifiablePresentation + PresentationSubmission from a
112
+ * user's credential picks. See module doc for behavior.
113
+ *
114
+ * Validates input eagerly so callers get actionable errors instead of
115
+ * a malformed VP that the verifier silently rejects:
116
+ *
117
+ * - `no_selections` — `chosen` is empty.
118
+ * - `unknown_descriptor` — a pick references an id that doesn't exist
119
+ * in the PD's input_descriptors.
120
+ * - `unknown_credential_format` — a candidate's format can't be
121
+ * inferred AND wasn't explicitly declared.
122
+ * - `invalid_jwt_vc` — a candidate tagged `jwt_vc_json` has neither a
123
+ * compact JWS string nor a `proof.jwt` we can extract.
124
+ */
125
+ export declare const buildPresentation: (options: BuildPresentationOptions) => PreparedPresentation;
126
+ //# sourceMappingURL=present.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"present.d.ts","sourceRoot":"","sources":["../../src/vp/present.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,KAAK,EACR,mBAAmB,EACnB,sBAAsB,EAEzB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAMH;;;;;;;;GAQG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,aAAa,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,mBAAmB,CAAC;CAClC;AAED,MAAM,WAAW,wBAAwB;IACrC,wFAAwF;IACxF,EAAE,EAAE,sBAAsB,CAAC;IAE3B;;;;OAIG;IACH,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAE3B,uFAAuF;IACvF,MAAM,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,cAAc,CAAC,EAAE,QAAQ,CAAC;IAE1B,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACjC;;;;;;;;OAQG;IACH,UAAU,EAAE,UAAU,CAAC;IAEvB,0DAA0D;IAC1D,UAAU,EAAE,sBAAsB,CAAC;IAEnC,gCAAgC;IAChC,QAAQ,EAAE,QAAQ,CAAC;IAEnB;;;;OAIG;IACH,YAAY,EAAE,MAAM,EAAE,CAAC;CAC1B;AAMD,MAAM,MAAM,0BAA0B,GAChC,eAAe,GACf,oBAAoB,GACpB,2BAA2B,GAC3B,gBAAgB,CAAC;AAEvB,qBAAa,sBAAuB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,0BAA0B,CAAC;gBAE9B,IAAI,EAAE,0BAA0B,EAAE,OAAO,EAAE,MAAM;CAKhE;AAMD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,YACjB,wBAAwB,KAClC,oBAwDF,CAAC"}
@@ -0,0 +1,99 @@
1
+ import { JWK } from 'jose';
2
+ import { AuthorizationRequest } from './types';
3
+ export type RequestObjectErrorCode = 'invalid_request_object' | 'request_fetch_failed' | 'missing_client_id_scheme' | 'unsupported_client_id_scheme' | 'client_id_mismatch' | 'request_signature_invalid' | 'request_signer_untrusted' | 'did_resolution_failed' | 'unsafe_mode_rejected';
4
+ export declare class RequestObjectError extends Error {
5
+ readonly code: RequestObjectErrorCode;
6
+ constructor(code: RequestObjectErrorCode, message: string, options?: {
7
+ cause?: unknown;
8
+ });
9
+ }
10
+ /**
11
+ * Minimal DID Document shape the resolver surface consumes. We avoid
12
+ * re-modeling the full spec — consumers only need enough to pull a
13
+ * `publicKeyJwk` out.
14
+ */
15
+ export interface VerificationMethod {
16
+ id: string;
17
+ type: string;
18
+ controller?: string;
19
+ publicKeyJwk?: JWK;
20
+ publicKeyMultibase?: string;
21
+ }
22
+ export interface DidDocument {
23
+ id: string;
24
+ verificationMethod?: VerificationMethod[];
25
+ authentication?: Array<string | VerificationMethod>;
26
+ assertionMethod?: Array<string | VerificationMethod>;
27
+ }
28
+ export type DidResolver = (did: string) => Promise<DidDocument>;
29
+ export interface VerifyRequestObjectOptions {
30
+ /** The `request_uri` to fetch. Mutually exclusive with `inlineJwt`. */
31
+ requestUri?: string;
32
+ /** The inline `request=<jws>` parameter value. Mutually exclusive with `requestUri`. */
33
+ inlineJwt?: string;
34
+ /**
35
+ * `client_id` from the outer URL params. Some verifiers let the
36
+ * `client_id` live in the URL while the JWS payload just repeats
37
+ * it; we cross-check them. Optional — only used as a consistency
38
+ * check when provided.
39
+ */
40
+ urlClientId?: string;
41
+ /**
42
+ * `client_id_scheme` from the outer URL params. Same cross-check
43
+ * rationale as `urlClientId`.
44
+ */
45
+ urlClientIdScheme?: string;
46
+ fetchImpl?: typeof fetch;
47
+ /**
48
+ * DID resolver override. When omitted, a built-in resolver handles
49
+ * `did:jwk` (offline) and `did:web` (over `fetchImpl`). Host apps
50
+ * that ship `did:key`/`did:ion`/… wire their own resolver here.
51
+ */
52
+ didResolver?: DidResolver;
53
+ /**
54
+ * Trusted X.509 root certificates (PEM strings). When scheme is
55
+ * `x509_san_dns`, the chain presented in `x5c` MUST root to one of
56
+ * these. If this list is empty and `unsafeAllowSelfSigned` is
57
+ * false, we refuse to verify x509-signed Request Objects.
58
+ */
59
+ trustedX509Roots?: readonly string[];
60
+ /**
61
+ * **Dangerous.** Accept self-signed / untrusted x509 chains for
62
+ * local development. The harness flips this on when a user
63
+ * explicitly opts in; production wallets must leave it off.
64
+ */
65
+ unsafeAllowSelfSigned?: boolean;
66
+ /**
67
+ * **Maximally dangerous.** Skip JWS signature verification on the
68
+ * Request Object entirely — claims are decoded but their
69
+ * authenticity is not checked at all. Strictly an interop /
70
+ * test-harness escape hatch for verifiers whose signing keys
71
+ * are pre-shared out-of-band and not published in any in-band
72
+ * channel (e.g. EUDI's reference verifier in `pre-registered`
73
+ * mode, which embeds `kid: "access_certificate"` referencing a
74
+ * keystore inside the verifier container with no JWKS endpoint).
75
+ *
76
+ * Production wallets MUST leave this off. Tampered Request
77
+ * Objects bypass detection when this flag is set. The other
78
+ * binding checks (`urlClientId` ↔ signed `client_id`) still run
79
+ * because they're integrity checks against the host-supplied
80
+ * URL, not against the JWS signature.
81
+ */
82
+ unsafeSkipRequestObjectSignatureVerification?: boolean;
83
+ }
84
+ /**
85
+ * Fetch-and-verify a signed OpenID4VP Authorization Request Object.
86
+ *
87
+ * Returns the canonical {@link AuthorizationRequest} shape built from
88
+ * the verified JWS payload. On any failure — fetch, decode, signature,
89
+ * trust-anchor — throws {@link RequestObjectError} with a stable code.
90
+ */
91
+ export declare const verifyAndDecodeRequestObject: (opts: VerifyRequestObjectOptions) => Promise<AuthorizationRequest>;
92
+ /**
93
+ * Built-in DID resolver covering the two methods wallet-side holder
94
+ * traffic most often uses: `did:jwk` (keys inline in the identifier)
95
+ * and `did:web` (DID docs over HTTPS). Other methods should be handled
96
+ * via the caller's `didResolver` override.
97
+ */
98
+ export declare const builtInDidResolver: (fetchImpl?: typeof fetch | undefined) => DidResolver;
99
+ //# sourceMappingURL=request-object.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-object.d.ts","sourceRoot":"","sources":["../../src/vp/request-object.ts"],"names":[],"mappings":"AAwCA,OAAO,EAAoC,GAAG,EAAE,MAAM,MAAM,CAAC;AAE7D,OAAO,EACH,oBAAoB,EAGvB,MAAM,SAAS,CAAC;AAYjB,MAAM,MAAM,sBAAsB,GAC5B,wBAAwB,GACxB,sBAAsB,GACtB,0BAA0B,GAC1B,8BAA8B,GAC9B,oBAAoB,GACpB,2BAA2B,GAC3B,0BAA0B,GAC1B,uBAAuB,GACvB,sBAAsB,CAAC;AAE7B,qBAAa,kBAAmB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;gBAGlC,IAAI,EAAE,sBAAsB,EAC5B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CASpC;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC1C,cAAc,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,kBAAkB,CAAC,CAAC;IACpD,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,kBAAkB,CAAC,CAAC;CACxD;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;AAEhE,MAAM,WAAW,0BAA0B;IACvC,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAEzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAErC;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAEhC;;;;;;;;;;;;;;;OAeG;IACH,4CAA4C,CAAC,EAAE,OAAO,CAAC;CAC1D;AAMD;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,SAC/B,0BAA0B,KACjC,OAAO,CAAC,oBAAoB,CAwG9B,CAAC;AA0fF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,eAChB,OAAO,KAAK,GAAG,SAAS,KACpC,WAaF,CAAC"}
@@ -0,0 +1,137 @@
1
+ import { DescriptorMatch } from './pex';
2
+ import { PresentationDefinition } from './types';
3
+ /**
4
+ * Given the holder's full credential pool and a verifier's Presentation
5
+ * Definition, figure out which credentials are eligible to satisfy each
6
+ * `input_descriptor`, whether the overall submission is possible, and
7
+ * produce a ready-to-serialize PresentationSubmission skeleton.
8
+ *
9
+ * This is the "preview" half of the OID4VP flow: the UI shows the user
10
+ * the candidates returned here, they pick one per descriptor, and Slice
11
+ * 7 builds + signs the actual `vp_token` using those selections.
12
+ */
13
+ /**
14
+ * A single credential paired with its inferred / declared format id.
15
+ * Callers always pass in strings they've indexed themselves; we never
16
+ * attempt to decrypt or verify the credential here.
17
+ */
18
+ export interface CandidateCredential {
19
+ /** The raw credential value — a JSON-LD object for ldp_vc or a compact JWT string for jwt_vc_json. */
20
+ credential: unknown;
21
+ /** Credential format id. Inferred via {@link inferCredentialFormat} when omitted. */
22
+ format?: string;
23
+ /**
24
+ * Caller-provided identifier (URI, DB row id, etc.) used when
25
+ * building audit logs and when binding selections back to storage.
26
+ * Not part of the DIF PEX spec — ours alone.
27
+ */
28
+ id?: string;
29
+ }
30
+ export interface DescriptorCandidate {
31
+ descriptorId: string;
32
+ candidate: CandidateCredential;
33
+ match: DescriptorMatch;
34
+ /**
35
+ * Absolute position in the caller's original `candidates` array.
36
+ * Lets callers reconstruct their own state without string-matching.
37
+ */
38
+ candidateIndex: number;
39
+ }
40
+ export interface DescriptorSelection {
41
+ descriptorId: string;
42
+ /** Candidates that satisfied every required field + format constraint. */
43
+ candidates: DescriptorCandidate[];
44
+ /** Reason this descriptor has zero candidates (when applicable). */
45
+ reason?: string;
46
+ }
47
+ export interface SelectionResult {
48
+ /** One entry per input_descriptor, in the PD's declared order. */
49
+ descriptors: DescriptorSelection[];
50
+ /**
51
+ * `true` when the Presentation Definition's submission requirements
52
+ * (or, if none, all descriptors) can be satisfied by the candidate
53
+ * pool. UIs use this to disable the "Share" button up front.
54
+ */
55
+ canSatisfy: boolean;
56
+ /**
57
+ * Human-readable reason shown to the user when `canSatisfy` is
58
+ * false. Example: `"The verifier requires a UniversityDegree
59
+ * credential and you don't have one."`
60
+ */
61
+ reason?: string;
62
+ }
63
+ /**
64
+ * DIF PEX v2 Presentation Submission — what the wallet returns inside
65
+ * the vp_token to tell the verifier which VC answered which descriptor.
66
+ */
67
+ export interface PresentationSubmission {
68
+ id: string;
69
+ definition_id: string;
70
+ descriptor_map: PresentationSubmissionDescriptor[];
71
+ }
72
+ export interface PresentationSubmissionDescriptor {
73
+ id: string;
74
+ format: string;
75
+ path: string;
76
+ path_nested?: PresentationSubmissionDescriptor;
77
+ }
78
+ /**
79
+ * Input to {@link buildPresentationSubmission}: the user's per-descriptor
80
+ * selection, along with the VP-internal path where the chosen VC will
81
+ * appear (typically `$.verifiableCredential[N]`).
82
+ */
83
+ export interface SelectedDescriptor {
84
+ descriptorId: string;
85
+ format: string;
86
+ /**
87
+ * JSONPath inside the outer vp_token where the credential will sit.
88
+ * For a JSON-LD VP: `$.verifiableCredential[0]`.
89
+ * For a JWT VP wrapping JSON-LD VCs: `$.vp.verifiableCredential[0]`.
90
+ */
91
+ path: string;
92
+ /**
93
+ * JSONPath inside a nested envelope. For jwt_vc_json credentials
94
+ * embedded in a JSON-LD VP: `$` (the VC JWT is the whole value).
95
+ * Only set when the credential is itself a wrapped envelope (JWT).
96
+ */
97
+ pathNested?: string;
98
+ }
99
+ /**
100
+ * Run the holder's candidate pool against every input_descriptor in the
101
+ * PD. Each candidate must pass (a) any format constraint declared on
102
+ * the descriptor (or, if the descriptor has none, on the PD itself),
103
+ * and (b) every required field in `constraints.fields[]`.
104
+ *
105
+ * The result preserves the PD's declared descriptor order so UIs can
106
+ * render "one row per descriptor".
107
+ */
108
+ export declare const selectCredentials: (candidates: CandidateCredential[], pd: PresentationDefinition) => SelectionResult;
109
+ /**
110
+ * Build a DIF PEX v2 Presentation Submission descriptor map. The
111
+ * caller is responsible for ordering selections (and computing the
112
+ * `path` into their final vp_token shape) so this function is purely
113
+ * a well-typed assembler — it never rearranges, never substitutes, and
114
+ * never validates the selection against the PD.
115
+ *
116
+ * The `definition_id` must come from the original PD to satisfy
117
+ * verifiers that cross-check the submission.
118
+ */
119
+ export declare const buildPresentationSubmission: (pd: PresentationDefinition, selections: SelectedDescriptor[], options?: {
120
+ submissionId?: string;
121
+ makeId?: () => string;
122
+ }) => PresentationSubmission;
123
+ /**
124
+ * Best-effort inference of the credential format id from the value's
125
+ * shape. Covers the two formats we've exercised end-to-end:
126
+ *
127
+ * - **`jwt_vc_json`** — compact JWS string (three base64url segments
128
+ * separated by `.`), OR a decoded VC whose `proof.type ===
129
+ * 'JwtProof2020'` (the shape WaltID returns).
130
+ * - **`ldp_vc`** — a JSON-LD VC with a Data Integrity / JCS / legacy
131
+ * `proof.type` (Ed25519Signature2020, DataIntegrityProof, etc.).
132
+ *
133
+ * Unknown shapes fall through to the caller-default `undefined`, which
134
+ * surfaces in the selection pipeline as "no format declared".
135
+ */
136
+ export declare const inferCredentialFormat: (credential: unknown) => string | undefined;
137
+ //# sourceMappingURL=select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/vp/select.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,eAAe,EAAE,MAAM,OAAO,CAAC;AAC9D,OAAO,EAGH,sBAAsB,EAEzB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;GASG;AAMH;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAChC,sGAAsG;IACtG,UAAU,EAAE,OAAO,CAAC;IACpB,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,mBAAmB,CAAC;IAC/B,KAAK,EAAE,eAAe,CAAC;IACvB;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC5B,kEAAkE;IAClE,WAAW,EAAE,mBAAmB,EAAE,CAAC;IAEnC;;;;OAIG;IACH,UAAU,EAAE,OAAO,CAAC;IAEpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,gCAAgC,EAAE,CAAC;CACtD;AAED,MAAM,WAAW,gCAAgC;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,gCAAgC,CAAC;CAClD;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAMD;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,eACd,mBAAmB,EAAE,MAC7B,sBAAsB,KAC3B,eAwDF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,OAChC,sBAAsB,cACd,kBAAkB,EAAE,YACvB;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAA;CAAE,KAC1D,sBAwBF,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,eAAgB,OAAO,KAAG,MAAM,GAAG,SAwBpE,CAAC"}
@@ -0,0 +1,125 @@
1
+ import { UnsignedVP, VP } from '@learncard/types';
2
+ import { ProofJwtSigner } from '../vci/types';
3
+ import { VpFormat } from './present';
4
+ /**
5
+ * Slice 7b — **Verifiable Presentation signing**.
6
+ *
7
+ * Takes the unsigned VP produced by {@link buildPresentation} and turns
8
+ * it into a signed `vp_token` ready for Slice 7c's `direct_post`
9
+ * transport. Two envelope formats are supported, matching
10
+ * {@link VpFormat}:
11
+ *
12
+ * - **`jwt_vp_json`** — wrap the VP as a JWT per VCDM §6.3.1:
13
+ * - `iss` = holder DID
14
+ * - `sub` = holder DID (VCDM §6.3.1.1 — "identifier of the
15
+ * expected holder")
16
+ * - `aud` = verifier's `client_id` (from the OID4VP Authorization
17
+ * Request)
18
+ * - `nonce` = verifier's nonce (replay-binding)
19
+ * - `jti` = VP id
20
+ * - `iat` = now (epoch seconds)
21
+ * - `vp` = the unsigned VP JSON
22
+ * The JWS is signed with the holder's {@link ProofJwtSigner} — the
23
+ * same interface the VCI proof-of-possession path uses.
24
+ *
25
+ * - **`ldp_vp`** — delegate to the host's Linked-Data proof pipeline.
26
+ * The plugin wires this to `learnCard.invoke.issuePresentation` in
27
+ * Slice 7c, passing `domain = audience` and `challenge = nonce` per
28
+ * OID4VP §6.1 so the verifier can detect replay.
29
+ *
30
+ * Pure + dependency-injected: this module never talks to the network
31
+ * and never accesses LearnCard directly. Tests supply trivial mock
32
+ * signers; the plugin supplies real ones.
33
+ */
34
+ /**
35
+ * The vp_token value ready for OID4VP `direct_post`:
36
+ *
37
+ * - `string` — compact JWS (used for `jwt_vp_json`).
38
+ * - `VP` — signed JSON-LD VP object (used for `ldp_vp`).
39
+ *
40
+ * The transport layer (Slice 7c) serializes these two shapes
41
+ * uniformly: both go into `application/x-www-form-urlencoded` as the
42
+ * `vp_token` field, with JSON objects JSON-stringified first.
43
+ */
44
+ /**
45
+ * Wire-shape of the OID4VP `vp_token` form field across BOTH query
46
+ * languages:
47
+ *
48
+ * - `string` — PEX `jwt_vp_json` compact JWS, OR a single DCQL entry
49
+ * that's a `jwt_vc_json` presentation.
50
+ * - `VP` — PEX `ldp_vp` signed VP object.
51
+ * - `Record<string, string | VP>` — DCQL response object keyed by
52
+ * `credential_query_id` (OID4VP 1.0 §6.4); values are
53
+ * per-query signed presentations.
54
+ *
55
+ * The submit layer JSON-encodes whichever non-string variant arrives
56
+ * before form-urlencoding it.
57
+ */
58
+ export type VpToken = string | VP | Record<string, string | VP>;
59
+ /**
60
+ * Linked-Data VP signer contract. The plugin binds this to
61
+ * `learnCard.invoke.issuePresentation`, passing through OID4VP's
62
+ * replay-binding via `domain`/`challenge`.
63
+ */
64
+ export interface LdpVpSigner {
65
+ sign: (unsignedVp: UnsignedVP, options: {
66
+ domain: string;
67
+ challenge: string;
68
+ }) => Promise<VP>;
69
+ }
70
+ export interface SignPresentationOptions {
71
+ /** Unsigned VP from {@link buildPresentation}. */
72
+ unsignedVp: UnsignedVP;
73
+ /** Envelope format from {@link buildPresentation}. */
74
+ vpFormat: VpFormat;
75
+ /**
76
+ * Verifier's `client_id` (from the Authorization Request). For
77
+ * `jwt_vp_json` this becomes the JWT `aud`; for `ldp_vp` it
78
+ * becomes the LD-proof `domain`.
79
+ */
80
+ audience: string;
81
+ /**
82
+ * Verifier's `nonce` (from the Authorization Request). For
83
+ * `jwt_vp_json` this becomes the JWT `nonce`; for `ldp_vp` it
84
+ * becomes the LD-proof `challenge`.
85
+ */
86
+ nonce: string;
87
+ /**
88
+ * Holder DID. Must equal `unsignedVp.holder`; we re-validate to
89
+ * prevent the signer from silently signing a VP whose holder
90
+ * field was tampered with between construction and signing.
91
+ */
92
+ holder: string;
93
+ /** Override the JWT `jti`. Defaults to `unsignedVp.id`. */
94
+ jti?: string;
95
+ /** Override `iat` for deterministic tests. Defaults to `Math.floor(Date.now() / 1000)`. */
96
+ iat?: number;
97
+ }
98
+ export interface SignPresentationHelpers {
99
+ /** Required when `vpFormat === 'jwt_vp_json'`. */
100
+ jwtSigner?: ProofJwtSigner;
101
+ /** Required when `vpFormat === 'ldp_vp'`. */
102
+ ldpVpSigner?: LdpVpSigner;
103
+ }
104
+ export interface SignPresentationResult {
105
+ vpToken: VpToken;
106
+ vpFormat: VpFormat;
107
+ }
108
+ export type VpSignErrorCode = 'missing_jwt_signer' | 'missing_ldp_signer' | 'jwt_sign_failed' | 'ldp_sign_failed' | 'holder_mismatch' | 'invalid_input';
109
+ export declare class VpSignError extends Error {
110
+ readonly code: VpSignErrorCode;
111
+ constructor(code: VpSignErrorCode, message: string, options?: {
112
+ cause?: unknown;
113
+ });
114
+ }
115
+ /**
116
+ * Sign a prepared Verifiable Presentation. See module doc for
117
+ * envelope semantics.
118
+ *
119
+ * All cryptographic work happens inside the injected signer(s) —
120
+ * unsupported combinations (e.g. `jwt_vp_json` with no `jwtSigner`)
121
+ * throw `VpSignError` with a typed code so callers can surface
122
+ * actionable UI instead of a generic "signing failed".
123
+ */
124
+ export declare const signPresentation: (options: SignPresentationOptions, helpers: SignPresentationHelpers) => Promise<SignPresentationResult>;
125
+ //# sourceMappingURL=sign.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../../src/vp/sign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH;;;;;;;;;GASG;AACH;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,CACF,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAC7C,OAAO,CAAC,EAAE,CAAC,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACpC,kDAAkD;IAClD,UAAU,EAAE,UAAU,CAAC;IAEvB,sDAAsD;IACtD,QAAQ,EAAE,QAAQ,CAAC;IAEnB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,2FAA2F;IAC3F,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,uBAAuB;IACpC,kDAAkD;IAClD,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,6CAA6C;IAC7C,WAAW,CAAC,EAAE,WAAW,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;CACtB;AAMD,MAAM,MAAM,eAAe,GACrB,oBAAoB,GACpB,oBAAoB,GACpB,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,CAAC;AAEtB,qBAAa,WAAY,SAAQ,KAAK;IAClC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;gBAEnB,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAQpF;AAMD;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,YAChB,uBAAuB,WACvB,uBAAuB,KACjC,OAAO,CAAC,sBAAsB,CAQhC,CAAC"}