@le-space/p2pass 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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +258 -0
  3. package/dist/backup/registry-backup.d.ts +26 -0
  4. package/dist/backup/registry-backup.js +51 -0
  5. package/dist/identity/identity-service.d.ts +116 -0
  6. package/dist/identity/identity-service.js +524 -0
  7. package/dist/identity/mode-detector.d.ts +29 -0
  8. package/dist/identity/mode-detector.js +124 -0
  9. package/dist/identity/signing-preference.d.ts +30 -0
  10. package/dist/identity/signing-preference.js +55 -0
  11. package/dist/index.d.ts +15 -0
  12. package/dist/index.js +91 -0
  13. package/dist/p2p/setup.d.ts +48 -0
  14. package/dist/p2p/setup.js +283 -0
  15. package/dist/recovery/ipns-key.d.ts +41 -0
  16. package/dist/recovery/ipns-key.js +127 -0
  17. package/dist/recovery/manifest.d.ts +106 -0
  18. package/dist/recovery/manifest.js +243 -0
  19. package/dist/registry/device-registry.d.ts +122 -0
  20. package/dist/registry/device-registry.js +275 -0
  21. package/dist/registry/index.d.ts +3 -0
  22. package/dist/registry/index.js +46 -0
  23. package/dist/registry/manager.d.ts +76 -0
  24. package/dist/registry/manager.js +376 -0
  25. package/dist/registry/pairing-protocol.d.ts +61 -0
  26. package/dist/registry/pairing-protocol.js +653 -0
  27. package/dist/ucan/storacha-auth.d.ts +45 -0
  28. package/dist/ucan/storacha-auth.js +164 -0
  29. package/dist/ui/StorachaFab.svelte +134 -0
  30. package/dist/ui/StorachaFab.svelte.d.ts +23 -0
  31. package/dist/ui/StorachaIntegration.svelte +2467 -0
  32. package/dist/ui/StorachaIntegration.svelte.d.ts +23 -0
  33. package/dist/ui/fonts/dm-mono-400.ttf +0 -0
  34. package/dist/ui/fonts/dm-mono-500.ttf +0 -0
  35. package/dist/ui/fonts/dm-sans-400.ttf +0 -0
  36. package/dist/ui/fonts/dm-sans-500.ttf +0 -0
  37. package/dist/ui/fonts/dm-sans-600.ttf +0 -0
  38. package/dist/ui/fonts/dm-sans-700.ttf +0 -0
  39. package/dist/ui/fonts/epilogue-400.ttf +0 -0
  40. package/dist/ui/fonts/epilogue-500.ttf +0 -0
  41. package/dist/ui/fonts/epilogue-600.ttf +0 -0
  42. package/dist/ui/fonts/epilogue-700.ttf +0 -0
  43. package/dist/ui/fonts/storacha-fonts.css +152 -0
  44. package/dist/ui/storacha-backup.d.ts +44 -0
  45. package/dist/ui/storacha-backup.js +218 -0
  46. package/package.json +112 -0
@@ -0,0 +1,106 @@
1
+ /**
2
+ * @typedef {object} Manifest
3
+ * @property {number} version - schema version (currently 1)
4
+ * @property {string} registryAddress - OrbitDB address, e.g. "/orbitdb/zdpu..."
5
+ * @property {string} delegation - base64-encoded UCAN delegation
6
+ * @property {string} ownerDid - owner DID, e.g. "did:key:z6Mk..."
7
+ * @property {string|null} [archiveCID] - CID of encrypted archive on IPFS (for auth-free recovery)
8
+ * @property {number} updatedAt - unix epoch ms
9
+ */
10
+ /**
11
+ * Create a manifest object.
12
+ *
13
+ * Pure function — no side effects.
14
+ *
15
+ * @param {object} params
16
+ * @param {string} params.registryAddress
17
+ * @param {string} params.delegation
18
+ * @param {string} params.ownerDid
19
+ * @param {string} [params.archiveCID] - CID of encrypted archive on IPFS
20
+ * @returns {Manifest}
21
+ */
22
+ export function createManifest({ registryAddress, delegation, ownerDid, archiveCID }: {
23
+ registryAddress: string;
24
+ delegation: string;
25
+ ownerDid: string;
26
+ archiveCID?: string | undefined;
27
+ }): Manifest;
28
+ /**
29
+ * Upload an encrypted archive to IPFS via Storacha.
30
+ * The archive is stored as a JSON blob accessible via public gateway
31
+ * without authentication.
32
+ *
33
+ * @param {object} storachaClient - Storacha client with `uploadFile`
34
+ * @param {{ ciphertext: string, iv: string }} archiveData - hex-encoded
35
+ * @returns {Promise<string>} CID string
36
+ */
37
+ export function uploadArchiveToIPFS(storachaClient: object, archiveData: {
38
+ ciphertext: string;
39
+ iv: string;
40
+ }): Promise<string>;
41
+ /**
42
+ * Fetch an encrypted archive from the IPFS gateway (no auth needed).
43
+ *
44
+ * @param {string} cid - IPFS CID of the archive JSON
45
+ * @returns {Promise<{ ciphertext: string, iv: string }|null>}
46
+ */
47
+ export function fetchArchiveFromIPFS(cid: string): Promise<{
48
+ ciphertext: string;
49
+ iv: string;
50
+ } | null>;
51
+ /**
52
+ * Publish a manifest to IPFS (via Storacha) and point an IPNS name at it.
53
+ *
54
+ * If an existing IPNS record is found, the revision sequence is incremented.
55
+ * Otherwise a new v0 revision is created. The encoded revision is persisted
56
+ * in localStorage so subsequent publishes can increment correctly.
57
+ *
58
+ * @param {object} storachaClient - Storacha storage client with `uploadFile`
59
+ * @param {object} ipnsPrivateKey - libp2p Ed25519 private key (from deriveIPNSKeyPair)
60
+ * @param {Manifest} manifest
61
+ * @returns {Promise<{ nameString: string, manifestCID: string }>}
62
+ */
63
+ export function publishManifest(storachaClient: object, ipnsPrivateKey: object, manifest: Manifest): Promise<{
64
+ nameString: string;
65
+ manifestCID: string;
66
+ }>;
67
+ /**
68
+ * Resolve a manifest from w3name using the IPNS private key.
69
+ *
70
+ * @param {object} ipnsPrivateKey - libp2p Ed25519 private key (from deriveIPNSKeyPair)
71
+ * @returns {Promise<Manifest|null>} parsed manifest, or null on failure
72
+ */
73
+ export function resolveManifest(ipnsPrivateKey: object): Promise<Manifest | null>;
74
+ /**
75
+ * Resolve a manifest by its w3name string (read-only, no private key needed).
76
+ *
77
+ * @param {string} nameString - w3name identifier, e.g. "k51qzi5uqu5di..."
78
+ * @returns {Promise<Manifest|null>} parsed manifest, or null on failure
79
+ */
80
+ export function resolveManifestByName(nameString: string): Promise<Manifest | null>;
81
+ export type Manifest = {
82
+ /**
83
+ * - schema version (currently 1)
84
+ */
85
+ version: number;
86
+ /**
87
+ * - OrbitDB address, e.g. "/orbitdb/zdpu..."
88
+ */
89
+ registryAddress: string;
90
+ /**
91
+ * - base64-encoded UCAN delegation
92
+ */
93
+ delegation: string;
94
+ /**
95
+ * - owner DID, e.g. "did:key:z6Mk..."
96
+ */
97
+ ownerDid: string;
98
+ /**
99
+ * - CID of encrypted archive on IPFS (for auth-free recovery)
100
+ */
101
+ archiveCID?: string | null | undefined;
102
+ /**
103
+ * - unix epoch ms
104
+ */
105
+ updatedAt: number;
106
+ };
@@ -0,0 +1,243 @@
1
+ /**
2
+ * IPNS manifest publishing and resolution via Storacha w3name.
3
+ *
4
+ * A recovery manifest is a small JSON document pinned on IPFS (via Storacha)
5
+ * and pointed to by a deterministic IPNS name derived from the user's PRF
6
+ * seed. It contains enough information to locate and restore the user's
7
+ * OrbitDB registry from any device.
8
+ *
9
+ * @module recovery/manifest
10
+ */
11
+
12
+ import * as W3Name from 'w3name';
13
+
14
+ const PREFIX = '[recovery]';
15
+
16
+ /** Gateway URL template for fetching IPFS content */
17
+ const GATEWAY = 'https://{cid}.ipfs.w3s.link/';
18
+
19
+ /** localStorage key for persisting the latest IPNS revision */
20
+ const REVISION_KEY = 'p2p_passkeys_ipns_revision';
21
+
22
+ /** Fetch timeout for gateway requests (ms) */
23
+ const FETCH_TIMEOUT_MS = 30_000;
24
+
25
+ /**
26
+ * @typedef {object} Manifest
27
+ * @property {number} version - schema version (currently 1)
28
+ * @property {string} registryAddress - OrbitDB address, e.g. "/orbitdb/zdpu..."
29
+ * @property {string} delegation - base64-encoded UCAN delegation
30
+ * @property {string} ownerDid - owner DID, e.g. "did:key:z6Mk..."
31
+ * @property {string|null} [archiveCID] - CID of encrypted archive on IPFS (for auth-free recovery)
32
+ * @property {number} updatedAt - unix epoch ms
33
+ */
34
+
35
+ /**
36
+ * Create a manifest object.
37
+ *
38
+ * Pure function — no side effects.
39
+ *
40
+ * @param {object} params
41
+ * @param {string} params.registryAddress
42
+ * @param {string} params.delegation
43
+ * @param {string} params.ownerDid
44
+ * @param {string} [params.archiveCID] - CID of encrypted archive on IPFS
45
+ * @returns {Manifest}
46
+ */
47
+ export function createManifest({ registryAddress, delegation, ownerDid, archiveCID }) {
48
+ return {
49
+ version: 1,
50
+ registryAddress,
51
+ delegation,
52
+ ownerDid,
53
+ archiveCID: archiveCID || null,
54
+ updatedAt: Date.now(),
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Upload an encrypted archive to IPFS via Storacha.
60
+ * The archive is stored as a JSON blob accessible via public gateway
61
+ * without authentication.
62
+ *
63
+ * @param {object} storachaClient - Storacha client with `uploadFile`
64
+ * @param {{ ciphertext: string, iv: string }} archiveData - hex-encoded
65
+ * @returns {Promise<string>} CID string
66
+ */
67
+ export async function uploadArchiveToIPFS(storachaClient, archiveData) {
68
+ const blob = new Blob(
69
+ [JSON.stringify({ ciphertext: archiveData.ciphertext, iv: archiveData.iv })],
70
+ { type: 'application/json' }
71
+ );
72
+ const cid = await storachaClient.uploadFile(blob);
73
+ console.log(PREFIX, 'Encrypted archive uploaded to IPFS:', cid.toString());
74
+ return cid.toString();
75
+ }
76
+
77
+ /**
78
+ * Fetch an encrypted archive from the IPFS gateway (no auth needed).
79
+ *
80
+ * @param {string} cid - IPFS CID of the archive JSON
81
+ * @returns {Promise<{ ciphertext: string, iv: string }|null>}
82
+ */
83
+ export async function fetchArchiveFromIPFS(cid) {
84
+ const controller = new AbortController();
85
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
86
+ try {
87
+ const url = GATEWAY.replace('{cid}', cid);
88
+ console.log(PREFIX, 'Fetching encrypted archive from gateway:', cid);
89
+ const res = await fetch(url, { signal: controller.signal });
90
+ if (!res.ok) {
91
+ console.log(PREFIX, `Gateway returned ${res.status} for archive CID ${cid}`);
92
+ return null;
93
+ }
94
+ const data = await res.json();
95
+ if (!data.ciphertext || !data.iv) {
96
+ console.log(PREFIX, 'Invalid archive format — missing ciphertext or iv');
97
+ return null;
98
+ }
99
+ return data;
100
+ } catch (err) {
101
+ console.log(PREFIX, 'Failed to fetch archive from gateway:', err?.message ?? err);
102
+ return null;
103
+ } finally {
104
+ clearTimeout(timer);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Publish a manifest to IPFS (via Storacha) and point an IPNS name at it.
110
+ *
111
+ * If an existing IPNS record is found, the revision sequence is incremented.
112
+ * Otherwise a new v0 revision is created. The encoded revision is persisted
113
+ * in localStorage so subsequent publishes can increment correctly.
114
+ *
115
+ * @param {object} storachaClient - Storacha storage client with `uploadFile`
116
+ * @param {object} ipnsPrivateKey - libp2p Ed25519 private key (from deriveIPNSKeyPair)
117
+ * @param {Manifest} manifest
118
+ * @returns {Promise<{ nameString: string, manifestCID: string }>}
119
+ */
120
+ export async function publishManifest(storachaClient, ipnsPrivateKey, manifest) {
121
+ // 1. Derive the WritableName from the raw key bytes
122
+ const name = await W3Name.from(ipnsPrivateKey.raw);
123
+
124
+ // 2. Upload the manifest JSON to Storacha
125
+ const manifestBlob = new Blob([JSON.stringify(manifest)], { type: 'application/json' });
126
+ const cid = await storachaClient.uploadFile(manifestBlob);
127
+
128
+ // 3. Build the IPNS value pointing at the uploaded CID
129
+ const value = `/ipfs/${cid.toString()}`;
130
+
131
+ // 4. Create or increment the IPNS revision
132
+ let revision;
133
+ try {
134
+ const current = await W3Name.resolve(name);
135
+ revision = await W3Name.increment(current, value);
136
+ } catch {
137
+ // No existing record — create the initial revision
138
+ revision = await W3Name.v0(name, value);
139
+ }
140
+
141
+ // 5. Publish the revision
142
+ await W3Name.publish(revision, name.key);
143
+
144
+ // 6. Persist the revision locally for future increments
145
+ try {
146
+ const encoded = W3Name.Revision.encode(revision);
147
+ localStorage.setItem(REVISION_KEY, JSON.stringify(Array.from(encoded)));
148
+ } catch {
149
+ console.log(PREFIX, 'Could not persist IPNS revision to localStorage');
150
+ }
151
+
152
+ console.log(PREFIX, `Published manifest to w3name: ${name.toString()}`);
153
+
154
+ return {
155
+ nameString: name.toString(),
156
+ manifestCID: cid.toString(),
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Resolve a manifest from w3name using the IPNS private key.
162
+ *
163
+ * @param {object} ipnsPrivateKey - libp2p Ed25519 private key (from deriveIPNSKeyPair)
164
+ * @returns {Promise<Manifest|null>} parsed manifest, or null on failure
165
+ */
166
+ export async function resolveManifest(ipnsPrivateKey) {
167
+ try {
168
+ const name = await W3Name.from(ipnsPrivateKey.raw);
169
+ console.log(PREFIX, `Resolving manifest from w3name: ${name.toString()}`);
170
+ return await fetchManifestForName(name);
171
+ } catch (err) {
172
+ console.log(PREFIX, 'Failed to resolve manifest:', err?.message ?? err);
173
+ return null;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Resolve a manifest by its w3name string (read-only, no private key needed).
179
+ *
180
+ * @param {string} nameString - w3name identifier, e.g. "k51qzi5uqu5di..."
181
+ * @returns {Promise<Manifest|null>} parsed manifest, or null on failure
182
+ */
183
+ export async function resolveManifestByName(nameString) {
184
+ try {
185
+ const name = W3Name.parse(nameString);
186
+ console.log(PREFIX, `Resolving manifest from w3name: ${nameString}`);
187
+ return await fetchManifestForName(name);
188
+ } catch (err) {
189
+ console.log(PREFIX, 'Failed to resolve manifest by name:', err?.message ?? err);
190
+ return null;
191
+ }
192
+ }
193
+
194
+ // ---------------------------------------------------------------------------
195
+ // Internal helpers
196
+ // ---------------------------------------------------------------------------
197
+
198
+ /**
199
+ * Resolve the IPNS name, fetch the manifest from the IPFS gateway, and
200
+ * validate the basic structure.
201
+ *
202
+ * @param {object} name - W3Name Name object (writable or read-only)
203
+ * @returns {Promise<Manifest|null>}
204
+ * @private
205
+ */
206
+ async function fetchManifestForName(name) {
207
+ // Resolve the IPNS record to get the /ipfs/... value
208
+ const revision = await W3Name.resolve(name);
209
+ const ipfsPath = revision.value; // e.g. "/ipfs/bafyabc..."
210
+
211
+ // Extract the CID from the path
212
+ const cid = ipfsPath.replace(/^\/ipfs\//, '');
213
+ if (!cid) {
214
+ console.log(PREFIX, 'Resolved IPNS value has no CID:', ipfsPath);
215
+ return null;
216
+ }
217
+
218
+ // Fetch the manifest from the Storacha/IPFS gateway with a timeout
219
+ const controller = new AbortController();
220
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
221
+
222
+ try {
223
+ const url = GATEWAY.replace('{cid}', cid);
224
+ const res = await fetch(url, { signal: controller.signal });
225
+
226
+ if (!res.ok) {
227
+ console.log(PREFIX, `Gateway returned ${res.status} for CID ${cid}`);
228
+ return null;
229
+ }
230
+
231
+ const manifest = await res.json();
232
+
233
+ // Basic validation
234
+ if (!manifest.version || !manifest.registryAddress) {
235
+ console.log(PREFIX, 'Invalid manifest schema — missing version or registryAddress');
236
+ return null;
237
+ }
238
+
239
+ return manifest;
240
+ } finally {
241
+ clearTimeout(timer);
242
+ }
243
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Convert P-256 x/y byte arrays from a WebAuthn attestation into JWK format.
3
+ * @param {Uint8Array} x - 32-byte x coordinate
4
+ * @param {Uint8Array} y - 32-byte y coordinate
5
+ * @returns {Object} JWK object
6
+ */
7
+ export function coseToJwk(x: Uint8Array, y: Uint8Array): Object;
8
+ /**
9
+ * Hash a string to a 64-char lowercase hex key for DB storage.
10
+ * @param {string} input - string to hash
11
+ * @returns {Promise<string>} 64-char hex string
12
+ */
13
+ export function hashCredentialId(input: string): Promise<string>;
14
+ /**
15
+ * Open (or create) the multi-device registry KV database.
16
+ *
17
+ * @param {Object} orbitdb - OrbitDB instance
18
+ * @param {string} ownerIdentityId - Ed25519 DID of the device creating the registry
19
+ * @param {string} [address] - Existing DB address to reopen (for Device B)
20
+ * @returns {Promise<Object>} Opened OrbitDB KeyValue database
21
+ */
22
+ export function openDeviceRegistry(orbitdb: Object, ownerIdentityId: string, address?: string): Promise<Object>;
23
+ /**
24
+ * Register a device entry in the registry.
25
+ * @param {Object} db - OrbitDB KV database
26
+ * @param {Object} entry - { credential_id, public_key, device_label, created_at, status, ed25519_did }
27
+ */
28
+ export function registerDevice(db: Object, entry: Object): Promise<void>;
29
+ /**
30
+ * List all registered devices from the registry.
31
+ * @param {Object} db - OrbitDB KV database
32
+ * @returns {Promise<Array>} Array of device entry objects
33
+ */
34
+ export function listDevices(db: Object): Promise<any[]>;
35
+ /**
36
+ * Look up a device by its credential ID.
37
+ * @param {Object} db - OrbitDB KV database
38
+ * @param {string} credentialId - base64url credential ID
39
+ * @returns {Promise<Object|null>}
40
+ */
41
+ export function getDeviceByCredentialId(db: Object, credentialId: string): Promise<Object | null>;
42
+ /**
43
+ * Look up a device by its Ed25519 DID.
44
+ * @param {Object} db - OrbitDB KV database
45
+ * @param {string} did - Ed25519 DID (did:key:z6Mk...)
46
+ * @returns {Promise<Object|null>}
47
+ */
48
+ export function getDeviceByDID(db: Object, did: string): Promise<Object | null>;
49
+ /**
50
+ * Grant write access to a new device DID via OrbitDBAccessController.
51
+ * @param {Object} db - OrbitDB KV database (must use OrbitDBAccessController)
52
+ * @param {string} did - Ed25519 DID of the new device
53
+ */
54
+ export function grantDeviceWriteAccess(db: Object, did: string): Promise<void>;
55
+ /**
56
+ * Revoke write access from a device DID and mark its registry entry as 'revoked'.
57
+ * @param {Object} db - OrbitDB KV database
58
+ * @param {string} did - Ed25519 DID to revoke
59
+ */
60
+ export function revokeDeviceAccess(db: Object, did: string): Promise<void>;
61
+ /**
62
+ * Store a UCAN delegation in the registry.
63
+ * @param {Object} db - OrbitDB KV database
64
+ * @param {string} delegationBase64 - raw delegation string
65
+ * @param {string} [spaceDid] - Storacha space DID
66
+ * @param {string} [label] - human-readable label
67
+ */
68
+ export function storeDelegationEntry(db: Object, delegationBase64: string, spaceDid?: string, label?: string): Promise<void>;
69
+ /**
70
+ * List all stored UCAN delegations.
71
+ * @param {Object} db - OrbitDB KV database
72
+ * @returns {Promise<Array>}
73
+ */
74
+ export function listDelegations(db: Object): Promise<any[]>;
75
+ /**
76
+ * Get a specific delegation by its base64 string.
77
+ * @param {Object} db - OrbitDB KV database
78
+ * @param {string} delegationBase64
79
+ * @returns {Promise<Object|null>}
80
+ */
81
+ export function getDelegation(db: Object, delegationBase64: string): Promise<Object | null>;
82
+ /**
83
+ * Remove a delegation from the registry.
84
+ * @param {Object} db - OrbitDB KV database
85
+ * @param {string} delegationBase64
86
+ */
87
+ export function removeDelegation(db: Object, delegationBase64: string): Promise<void>;
88
+ /**
89
+ * Store an encrypted Ed25519 archive in the registry.
90
+ * @param {Object} db - OrbitDB KV database
91
+ * @param {string} did - Ed25519 DID
92
+ * @param {string} ciphertext - hex-encoded ciphertext
93
+ * @param {string} iv - hex-encoded IV
94
+ */
95
+ export function storeArchiveEntry(db: Object, did: string, ciphertext: string, iv: string): Promise<void>;
96
+ /**
97
+ * Get an encrypted archive entry by DID.
98
+ * @param {Object} db - OrbitDB KV database
99
+ * @param {string} did
100
+ * @returns {Promise<Object|null>}
101
+ */
102
+ export function getArchiveEntry(db: Object, did: string): Promise<Object | null>;
103
+ /**
104
+ * Store Ed25519 keypair metadata (no private key) in the registry.
105
+ * @param {Object} db - OrbitDB KV database
106
+ * @param {string} did - Ed25519 DID
107
+ * @param {string} publicKeyHex - hex-encoded public key
108
+ */
109
+ export function storeKeypairEntry(db: Object, did: string, publicKeyHex: string): Promise<void>;
110
+ /**
111
+ * Get keypair metadata by DID.
112
+ * @param {Object} db - OrbitDB KV database
113
+ * @param {string} did
114
+ * @returns {Promise<Object|null>}
115
+ */
116
+ export function getKeypairEntry(db: Object, did: string): Promise<Object | null>;
117
+ /**
118
+ * List all stored keypair entries.
119
+ * @param {Object} db - OrbitDB KV database
120
+ * @returns {Promise<Array>}
121
+ */
122
+ export function listKeypairs(db: Object): Promise<any[]>;