@learncard/holder-continuity 0.1.0

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/BUNDLE_SPEC.md ADDED
@@ -0,0 +1,63 @@
1
+ # LearnCard Holder Continuity Bundle v1.0.0
2
+
3
+ A LearnCard holder continuity bundle is a ZIP file with readable metadata and encrypted holder payloads.
4
+
5
+ ## Container
6
+
7
+ Required readable entries:
8
+
9
+ - `manifest.json` — inventory, hashes, warnings, and encryption metadata.
10
+ - `README.md` — human-readable recovery notes.
11
+ - `BUNDLE_SPEC.md` — this format description.
12
+
13
+ Sensitive entries use JSON encryption envelopes produced by `@learncard/sss-key-manager` `encryptWithPassword`: Argon2id key derivation and AES-GCM authenticated encryption. The ZIP itself is not password encrypted.
14
+
15
+ ## Security model
16
+
17
+ A holder continuity bundle exports the wallet's full raw private-key seed at `keys/private-key-seed.txt.enc`. This is a deliberate trade-off that prioritizes holder self-custody and a zero-cooperation exit path over the live wallet's threshold model.
18
+
19
+ LearnCard's live wallet protects the key with 2-of-4 Shamir Secret Sharing (device, auth, recovery, and email shares), where no single share can reconstruct the key. The bundle does **not** preserve that threshold property: the exported seed alone is sufficient to reconstruct the key and DID and take full control of the identity. The bundle password (Argon2id + AES-256-GCM) is therefore the only barrier protecting the seed.
20
+
21
+ Consequences:
22
+
23
+ - Anyone who obtains both the bundle and its password gains complete control of the wallet, bypassing SSS entirely.
24
+ - The `keys/recovery-phrase.txt.enc` entry is derived from the current SSS recovery share. It is provided for reference and is **not** independently sufficient to recover the key on its own; recovery from a bundle uses the exported seed.
25
+ - Treat the bundle like a password-vault backup: store it offline, use a strong unique password, and rotate the wallet if the bundle is exposed.
26
+
27
+ ## Paths
28
+
29
+ - `keys/recovery-phrase.txt.enc`
30
+ - `keys/private-key-seed.txt.enc`
31
+ - `keys/jwks.json.enc`
32
+ - `keys/did-document.json`
33
+ - `credentials/<sha256>.json.enc`
34
+ - `presentations/<sha256>.json.enc`
35
+ - `index-records/<sha256>.json.enc`
36
+ - `consent-records/<sha256>.json.enc`
37
+ - `status-cache/<sha256>.json.enc`
38
+
39
+ Debug exports MAY use plaintext payloads by setting `encrypt: false`; production exports MUST encrypt sensitive payloads.
40
+
41
+ Status-list snapshot fetching is HTTPS-only and rejects private, loopback, link-local, and single-label hosts. Exporters SHOULD keep the default timeout and response-size caps unless they are running in a trusted local environment.
42
+
43
+ ## Manifest hashing
44
+
45
+ Each `contents[]` entry contains the SHA-256 hash of the bytes stored at `path`. `payloadSha256` is SHA-256 over a deterministic JSON serialization of `contents[]` with entries sorted by path.
46
+
47
+ Each credential or presentation entry MAY reference an encrypted `index-record` companion entry via `indexRecordRef`; the readable manifest does not embed the original index record JSON.
48
+
49
+ ## Restore vs import
50
+
51
+ `restoreLearnCardFromBundle(...)` decrypts `keys/private-key-seed.txt.enc` and passes that seed to `initLearnCard(...)`. It recreates the original wallet identity; it does not upload payloads or recreate index records.
52
+
53
+ `importLearnCardBundle(...)` decrypts credential and presentation payloads, uploads them to the target wallet's LearnCloud store, and recreates index records from the encrypted `index-record` companions.
54
+
55
+ Import writes bundle contents into the target wallet. A bundle author who knows the password can include arbitrary credentials, presentations, and index metadata. Use `verifyBeforeImport: true` to verify VC/VP signatures before upload when the target wallet exposes `invoke.verifyCredential` and `invoke.verifyPresentation`.
56
+
57
+ ## Size limits
58
+
59
+ Readers enforce default compressed-bundle, per-entry, and JSON parse limits to avoid accidentally processing oversized ZIP or JSON payloads. Callers can override these with `maxBundleBytes`, `maxEntryBytes`, and `maxJsonBytes` for trusted local workflows.
60
+
61
+ ## Import expectations
62
+
63
+ Importers MUST verify the stored bytes against each entry hash before trusting decrypted content. Importers SHOULD verify issuer signatures before upload and preserve issuer-signed credential and presentation payloads exactly.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Learning Economy Foundation <sdk@learningeconomy.io>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # @learncard/holder-continuity
2
+
3
+ Helpers for creating, reading, importing, and restoring LearnCard holder continuity bundles.
4
+
5
+ A bundle is a normal ZIP file with a readable `manifest.json` and encrypted payload files for key material, credentials, presentations, LearnCloud index records, consent records, and status-list snapshots.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @learncard/holder-continuity
11
+ ```
12
+
13
+ ## Export a wallet
14
+
15
+ ```ts
16
+ import { exportLearnCardBundle } from '@learncard/holder-continuity';
17
+
18
+ await exportLearnCardBundle(learnCard, {
19
+ out: './learncard-export.zip',
20
+ password: 'use-a-strong-password',
21
+ });
22
+ ```
23
+
24
+ ## Read a bundle
25
+
26
+ ```ts
27
+ import { readLearnCardBundle } from '@learncard/holder-continuity';
28
+
29
+ const bundle = await readLearnCardBundle('./learncard-export.zip', {
30
+ password: 'use-a-strong-password',
31
+ });
32
+ ```
33
+
34
+ `manifest.json` is readable without the password, but sensitive payloads are encrypted by default.
35
+
36
+ ## Restore the original wallet
37
+
38
+ ```ts
39
+ import { restoreLearnCardFromBundle } from '@learncard/holder-continuity';
40
+
41
+ const restored = await restoreLearnCardFromBundle('./learncard-export.zip', {
42
+ password: 'use-a-strong-password',
43
+ init: { network: true },
44
+ });
45
+ ```
46
+
47
+ Restore decrypts the exported `key-private-seed` and passes it to `initLearnCard(...)`, so the returned wallet has the same key material and DID as the original wallet.
48
+
49
+ Restore does not upload or re-index the bundle contents. Use it when you want the original wallet identity back. Use `importLearnCardBundle(...)` when you want to copy credentials and index records into another wallet.
50
+
51
+ ## Import into another wallet
52
+
53
+ ```ts
54
+ import { importLearnCardBundle } from '@learncard/holder-continuity';
55
+
56
+ await importLearnCardBundle('./learncard-export.zip', {
57
+ password: 'use-a-strong-password',
58
+ wallet: freshWallet,
59
+ verifyBeforeImport: true,
60
+ });
61
+ ```
62
+
63
+ Import uploads credential and presentation payloads to the target wallet's LearnCloud store and recreates their LearnCloud index records.
64
+
65
+ If `verifyBeforeImport` is not set, import only proves bundle integrity and successful decryption. It does not prove issuer signatures, so only import bundles from sources you trust.
66
+
67
+ Bundle readers enforce default compressed-bundle, per-entry, and JSON parse size limits. Override `maxBundleBytes`, `maxEntryBytes`, or `maxJsonBytes` only for trusted local workflows.
68
+
69
+ ## Bundle format
70
+
71
+ See [`BUNDLE_SPEC.md`](./BUNDLE_SPEC.md) for the ZIP layout, manifest hashing rules, and encryption model.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bundle.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/bundle.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=restoreBundle.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restoreBundle.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/restoreBundle.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
1
+ export declare const sha256Hex: (bytes: string | Buffer) => string;
2
+ export declare const stableStringify: (value: unknown) => string;
3
+ export declare const encodePayload: (plaintext: string, options: {
4
+ encrypt: boolean;
5
+ password?: string;
6
+ }) => Promise<{
7
+ stored: string;
8
+ encrypted: boolean;
9
+ }>;
10
+ export declare const decodePayload: (stored: string, options: {
11
+ encrypted: boolean;
12
+ password?: string;
13
+ }) => Promise<string>;
14
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,SAAS,UAAW,MAAM,GAAG,MAAM,KAAG,MACC,CAAC;AAErD,eAAO,MAAM,eAAe,UAAW,OAAO,KAAG,MAWhD,CAAC;AAEF,eAAO,MAAM,aAAa,cACX,MAAM,WACR;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,KACjD,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAShD,CAAC;AAEF,eAAO,MAAM,aAAa,WACd,MAAM,WACL;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,KACnD,OAAO,CAAC,MAAM,CAchB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ExportLearnCardBundleOptions, LearnCardBundleOptions, LearnCardBundleResult, LearnCardBundleWallet } from './types';
2
+ export declare const createLearnCardBundle: (wallet: LearnCardBundleWallet, options?: LearnCardBundleOptions) => Promise<LearnCardBundleResult>;
3
+ export declare const exportLearnCardBundle: (wallet: LearnCardBundleWallet, options: ExportLearnCardBundleOptions) => Promise<LearnCardBundleResult>;
4
+ //# sourceMappingURL=exportBundle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exportBundle.d.ts","sourceRoot":"","sources":["../src/exportBundle.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAER,4BAA4B,EAG5B,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACxB,MAAM,SAAS,CAAC;AA+bjB,eAAO,MAAM,qBAAqB,WACtB,qBAAqB,YACpB,sBAAsB,KAChC,OAAO,CAAC,qBAAqB,CA6K/B,CAAC;AAEF,eAAO,MAAM,qBAAqB,WACtB,qBAAqB,WACpB,4BAA4B,KACtC,OAAO,CAAC,qBAAqB,CAO/B,CAAC"}