@snaha/swarm-id 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.
- package/README.md +431 -0
- package/dist/chunk/bmt.d.ts +17 -0
- package/dist/chunk/bmt.d.ts.map +1 -0
- package/dist/chunk/cac.d.ts +18 -0
- package/dist/chunk/cac.d.ts.map +1 -0
- package/dist/chunk/constants.d.ts +10 -0
- package/dist/chunk/constants.d.ts.map +1 -0
- package/dist/chunk/encrypted-cac.d.ts +48 -0
- package/dist/chunk/encrypted-cac.d.ts.map +1 -0
- package/dist/chunk/encryption.d.ts +86 -0
- package/dist/chunk/encryption.d.ts.map +1 -0
- package/dist/chunk/index.d.ts +6 -0
- package/dist/chunk/index.d.ts.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/proxy/act/act.d.ts +78 -0
- package/dist/proxy/act/act.d.ts.map +1 -0
- package/dist/proxy/act/crypto.d.ts +44 -0
- package/dist/proxy/act/crypto.d.ts.map +1 -0
- package/dist/proxy/act/grantee-list.d.ts +82 -0
- package/dist/proxy/act/grantee-list.d.ts.map +1 -0
- package/dist/proxy/act/history.d.ts +183 -0
- package/dist/proxy/act/history.d.ts.map +1 -0
- package/dist/proxy/act/index.d.ts +104 -0
- package/dist/proxy/act/index.d.ts.map +1 -0
- package/dist/proxy/chunking-encrypted.d.ts +14 -0
- package/dist/proxy/chunking-encrypted.d.ts.map +1 -0
- package/dist/proxy/chunking.d.ts +15 -0
- package/dist/proxy/chunking.d.ts.map +1 -0
- package/dist/proxy/download-data.d.ts +16 -0
- package/dist/proxy/download-data.d.ts.map +1 -0
- package/dist/proxy/feed-manifest.d.ts +62 -0
- package/dist/proxy/feed-manifest.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts +77 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts +88 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/finder.d.ts +67 -0
- package/dist/proxy/feeds/epochs/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/index.d.ts +35 -0
- package/dist/proxy/feeds/epochs/index.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts +93 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/types.d.ts +109 -0
- package/dist/proxy/feeds/epochs/types.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/updater.d.ts +68 -0
- package/dist/proxy/feeds/epochs/updater.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/utils.d.ts +22 -0
- package/dist/proxy/feeds/epochs/utils.d.ts.map +1 -0
- package/dist/proxy/feeds/index.d.ts +5 -0
- package/dist/proxy/feeds/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/finder.d.ts +17 -0
- package/dist/proxy/feeds/sequence/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/index.d.ts +23 -0
- package/dist/proxy/feeds/sequence/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/types.d.ts +80 -0
- package/dist/proxy/feeds/sequence/types.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/updater.d.ts +26 -0
- package/dist/proxy/feeds/sequence/updater.d.ts.map +1 -0
- package/dist/proxy/index.d.ts +6 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/manifest-builder.d.ts +183 -0
- package/dist/proxy/manifest-builder.d.ts.map +1 -0
- package/dist/proxy/mantaray-encrypted.d.ts +27 -0
- package/dist/proxy/mantaray-encrypted.d.ts.map +1 -0
- package/dist/proxy/mantaray.d.ts +26 -0
- package/dist/proxy/mantaray.d.ts.map +1 -0
- package/dist/proxy/types.d.ts +29 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/upload-data.d.ts +17 -0
- package/dist/proxy/upload-data.d.ts.map +1 -0
- package/dist/proxy/upload-encrypted-data.d.ts +103 -0
- package/dist/proxy/upload-encrypted-data.d.ts.map +1 -0
- package/dist/schemas.d.ts +240 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/storage/debounced-uploader.d.ts +62 -0
- package/dist/storage/debounced-uploader.d.ts.map +1 -0
- package/dist/storage/utilization-store.d.ts +108 -0
- package/dist/storage/utilization-store.d.ts.map +1 -0
- package/dist/swarm-id-auth.d.ts +74 -0
- package/dist/swarm-id-auth.d.ts.map +1 -0
- package/dist/swarm-id-auth.js +2 -0
- package/dist/swarm-id-auth.js.map +1 -0
- package/dist/swarm-id-client.d.ts +878 -0
- package/dist/swarm-id-client.d.ts.map +1 -0
- package/dist/swarm-id-client.js +2 -0
- package/dist/swarm-id-client.js.map +1 -0
- package/dist/swarm-id-proxy.d.ts +236 -0
- package/dist/swarm-id-proxy.d.ts.map +1 -0
- package/dist/swarm-id-proxy.js +2 -0
- package/dist/swarm-id-proxy.js.map +1 -0
- package/dist/swarm-id.esm.js +2 -0
- package/dist/swarm-id.esm.js.map +1 -0
- package/dist/swarm-id.umd.js +2 -0
- package/dist/swarm-id.umd.js.map +1 -0
- package/dist/sync/index.d.ts +9 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/key-derivation.d.ts +25 -0
- package/dist/sync/key-derivation.d.ts.map +1 -0
- package/dist/sync/restore-account.d.ts +28 -0
- package/dist/sync/restore-account.d.ts.map +1 -0
- package/dist/sync/serialization.d.ts +16 -0
- package/dist/sync/serialization.d.ts.map +1 -0
- package/dist/sync/store-interfaces.d.ts +53 -0
- package/dist/sync/store-interfaces.d.ts.map +1 -0
- package/dist/sync/sync-account.d.ts +44 -0
- package/dist/sync/sync-account.d.ts.map +1 -0
- package/dist/sync/types.d.ts +13 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/test-fixtures.d.ts +17 -0
- package/dist/test-fixtures.d.ts.map +1 -0
- package/dist/types-BD_VkNn0.js +2 -0
- package/dist/types-BD_VkNn0.js.map +1 -0
- package/dist/types-lJCaT-50.js +2 -0
- package/dist/types-lJCaT-50.js.map +1 -0
- package/dist/types.d.ts +2157 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/account-payload.d.ts +94 -0
- package/dist/utils/account-payload.d.ts.map +1 -0
- package/dist/utils/account-state-snapshot.d.ts +38 -0
- package/dist/utils/account-state-snapshot.d.ts.map +1 -0
- package/dist/utils/backup-encryption.d.ts +127 -0
- package/dist/utils/backup-encryption.d.ts.map +1 -0
- package/dist/utils/batch-utilization.d.ts +432 -0
- package/dist/utils/batch-utilization.d.ts.map +1 -0
- package/dist/utils/constants.d.ts +11 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/hex.d.ts +17 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/key-derivation.d.ts +92 -0
- package/dist/utils/key-derivation.d.ts.map +1 -0
- package/dist/utils/storage-managers.d.ts +65 -0
- package/dist/utils/storage-managers.d.ts.map +1 -0
- package/dist/utils/swarm-id-export.d.ts +24 -0
- package/dist/utils/swarm-id-export.d.ts.map +1 -0
- package/dist/utils/ttl.d.ts +49 -0
- package/dist/utils/ttl.d.ts.map +1 -0
- package/dist/utils/url.d.ts +41 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/versioned-storage.d.ts +131 -0
- package/dist/utils/versioned-storage.d.ts.map +1 -0
- package/package.json +78 -0
- package/src/chunk/bmt.test.ts +217 -0
- package/src/chunk/bmt.ts +57 -0
- package/src/chunk/cac.test.ts +214 -0
- package/src/chunk/cac.ts +65 -0
- package/src/chunk/constants.ts +18 -0
- package/src/chunk/encrypted-cac.test.ts +385 -0
- package/src/chunk/encrypted-cac.ts +131 -0
- package/src/chunk/encryption.test.ts +352 -0
- package/src/chunk/encryption.ts +300 -0
- package/src/chunk/index.ts +47 -0
- package/src/index.ts +430 -0
- package/src/proxy/act/act.test.ts +278 -0
- package/src/proxy/act/act.ts +158 -0
- package/src/proxy/act/bee-compat.test.ts +948 -0
- package/src/proxy/act/crypto.test.ts +436 -0
- package/src/proxy/act/crypto.ts +376 -0
- package/src/proxy/act/grantee-list.test.ts +393 -0
- package/src/proxy/act/grantee-list.ts +239 -0
- package/src/proxy/act/history.test.ts +360 -0
- package/src/proxy/act/history.ts +413 -0
- package/src/proxy/act/index.test.ts +748 -0
- package/src/proxy/act/index.ts +853 -0
- package/src/proxy/chunking-encrypted.ts +95 -0
- package/src/proxy/chunking.ts +65 -0
- package/src/proxy/download-data.ts +448 -0
- package/src/proxy/feed-manifest.ts +174 -0
- package/src/proxy/feeds/epochs/async-finder.ts +372 -0
- package/src/proxy/feeds/epochs/epoch.test.ts +249 -0
- package/src/proxy/feeds/epochs/epoch.ts +181 -0
- package/src/proxy/feeds/epochs/finder.ts +282 -0
- package/src/proxy/feeds/epochs/index.ts +73 -0
- package/src/proxy/feeds/epochs/integration.test.ts +1336 -0
- package/src/proxy/feeds/epochs/test-utils.ts +274 -0
- package/src/proxy/feeds/epochs/types.ts +128 -0
- package/src/proxy/feeds/epochs/updater.ts +192 -0
- package/src/proxy/feeds/epochs/utils.ts +62 -0
- package/src/proxy/feeds/index.ts +5 -0
- package/src/proxy/feeds/sequence/async-finder.ts +31 -0
- package/src/proxy/feeds/sequence/finder.ts +73 -0
- package/src/proxy/feeds/sequence/index.ts +54 -0
- package/src/proxy/feeds/sequence/integration.test.ts +966 -0
- package/src/proxy/feeds/sequence/types.ts +103 -0
- package/src/proxy/feeds/sequence/updater.ts +71 -0
- package/src/proxy/index.ts +5 -0
- package/src/proxy/manifest-builder.test.ts +427 -0
- package/src/proxy/manifest-builder.ts +679 -0
- package/src/proxy/mantaray-encrypted.ts +78 -0
- package/src/proxy/mantaray.ts +104 -0
- package/src/proxy/types.ts +32 -0
- package/src/proxy/upload-data.ts +189 -0
- package/src/proxy/upload-encrypted-data.ts +658 -0
- package/src/schemas.ts +299 -0
- package/src/storage/debounced-uploader.ts +192 -0
- package/src/storage/utilization-store.ts +397 -0
- package/src/swarm-id-client.test.ts +99 -0
- package/src/swarm-id-client.ts +3095 -0
- package/src/swarm-id-proxy.ts +3891 -0
- package/src/sync/index.ts +28 -0
- package/src/sync/restore-account.ts +90 -0
- package/src/sync/serialization.ts +39 -0
- package/src/sync/store-interfaces.ts +62 -0
- package/src/sync/sync-account.test.ts +302 -0
- package/src/sync/sync-account.ts +396 -0
- package/src/sync/types.ts +11 -0
- package/src/test-fixtures.ts +109 -0
- package/src/types.ts +1651 -0
- package/src/utils/account-state-snapshot.test.ts +595 -0
- package/src/utils/account-state-snapshot.ts +94 -0
- package/src/utils/backup-encryption.test.ts +442 -0
- package/src/utils/backup-encryption.ts +352 -0
- package/src/utils/batch-utilization.ts +1309 -0
- package/src/utils/constants.ts +20 -0
- package/src/utils/hex.ts +27 -0
- package/src/utils/key-derivation.ts +197 -0
- package/src/utils/storage-managers.ts +365 -0
- package/src/utils/ttl.ts +129 -0
- package/src/utils/url.test.ts +136 -0
- package/src/utils/url.ts +71 -0
- package/src/utils/versioned-storage.ts +323 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time and Session Constants
|
|
3
|
+
*
|
|
4
|
+
* Centralized constants for time units and default session durations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Time Units (in milliseconds)
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
export const SECOND = 1_000
|
|
12
|
+
export const MINUTE = 60 * SECOND
|
|
13
|
+
export const HOUR = 60 * MINUTE
|
|
14
|
+
export const DAY = 24 * HOUR
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Session Defaults
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
export const DEFAULT_SESSION_DURATION = 30 * DAY
|
package/src/utils/hex.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hex utilities
|
|
3
|
+
*
|
|
4
|
+
* Re-exports hex conversion functions from key-derivation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Address } from "../schemas"
|
|
8
|
+
|
|
9
|
+
export { hexToUint8Array, uint8ArrayToHex } from "./key-derivation"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create a validated hex address string (40 lowercase hex chars).
|
|
13
|
+
* Accepts optional 0x prefix and any case.
|
|
14
|
+
*
|
|
15
|
+
* @param input - Ethereum address string (with or without 0x prefix)
|
|
16
|
+
* @returns Normalized 40-character lowercase hex string
|
|
17
|
+
* @throws {Error} If the input is not a valid 40-char hex address
|
|
18
|
+
*/
|
|
19
|
+
export function hexAddress(input: string): Address {
|
|
20
|
+
const clean = input.replace(/^0x/i, "").toLowerCase()
|
|
21
|
+
if (!/^[0-9a-f]{40}$/.test(clean)) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`Invalid hex address: expected 40 hex characters (with optional 0x prefix), got "${input}"`,
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
return clean
|
|
27
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm Identity - Key Derivation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides cryptographic functions for deriving app-specific secrets
|
|
5
|
+
* from a master identity key.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { PrivateKey } from "@ethersphere/bee-js"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Derive an app-specific secret from a master key and app origin
|
|
12
|
+
*
|
|
13
|
+
* Uses HMAC-SHA256 to create a deterministic, unique secret for each app.
|
|
14
|
+
* The same master key + app origin will always produce the same secret.
|
|
15
|
+
*
|
|
16
|
+
* @param masterKey - The master identity key (hex string)
|
|
17
|
+
* @param appOrigin - The app's origin (e.g., "https://swarm-app.local:8080")
|
|
18
|
+
* @returns The derived secret as a hex string
|
|
19
|
+
*/
|
|
20
|
+
export async function deriveSecret(
|
|
21
|
+
masterKey: string,
|
|
22
|
+
appOrigin: string,
|
|
23
|
+
): Promise<string> {
|
|
24
|
+
const encoder = new TextEncoder()
|
|
25
|
+
|
|
26
|
+
// Convert master key from hex string to Uint8Array
|
|
27
|
+
const keyData = hexToUint8Array(masterKey)
|
|
28
|
+
const message = encoder.encode(appOrigin)
|
|
29
|
+
|
|
30
|
+
// Import the master key for HMAC
|
|
31
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
32
|
+
"raw",
|
|
33
|
+
keyData,
|
|
34
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
35
|
+
false,
|
|
36
|
+
["sign"],
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
// Sign the app origin with the master key
|
|
40
|
+
const signature = await crypto.subtle.sign("HMAC", cryptoKey, message)
|
|
41
|
+
|
|
42
|
+
// Convert to hex string
|
|
43
|
+
const secretHex = uint8ArrayToHex(new Uint8Array(signature))
|
|
44
|
+
|
|
45
|
+
return secretHex
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generate a random master key for testing/demo purposes
|
|
50
|
+
*
|
|
51
|
+
* In production, this would be derived from a user's mnemonic or
|
|
52
|
+
* imported from an existing identity.
|
|
53
|
+
*
|
|
54
|
+
* @returns A random 32-byte key as a hex string
|
|
55
|
+
*/
|
|
56
|
+
export async function generateMasterKey(): Promise<string> {
|
|
57
|
+
const randomBytes = new Uint8Array(32)
|
|
58
|
+
crypto.getRandomValues(randomBytes)
|
|
59
|
+
|
|
60
|
+
const masterKey = uint8ArrayToHex(randomBytes)
|
|
61
|
+
|
|
62
|
+
return masterKey
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Convert a hex string to Uint8Array
|
|
67
|
+
*
|
|
68
|
+
* @param hexString - Hex string (e.g., "deadbeef")
|
|
69
|
+
* @returns Uint8Array
|
|
70
|
+
*/
|
|
71
|
+
export function hexToUint8Array(hexString: string): Uint8Array {
|
|
72
|
+
// Remove any whitespace and ensure even length
|
|
73
|
+
const hex = hexString.replace(/\s/g, "")
|
|
74
|
+
if (hex.length % 2 !== 0) {
|
|
75
|
+
throw new Error("Invalid hex string: length must be even")
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const bytes = new Uint8Array(hex.length / 2)
|
|
79
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
80
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return bytes
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Convert a Uint8Array to hex string
|
|
88
|
+
*
|
|
89
|
+
* @param bytes - Uint8Array to convert
|
|
90
|
+
* @returns Hex string (e.g., "deadbeef")
|
|
91
|
+
*/
|
|
92
|
+
export function uint8ArrayToHex(bytes: Uint8Array): string {
|
|
93
|
+
return Array.from(bytes)
|
|
94
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
95
|
+
.join("")
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Verify that a derived secret matches the expected value
|
|
100
|
+
*
|
|
101
|
+
* Useful for testing.
|
|
102
|
+
*
|
|
103
|
+
* @param masterKey - Master key hex string
|
|
104
|
+
* @param appOrigin - App origin
|
|
105
|
+
* @param expectedSecret - Expected secret hex string
|
|
106
|
+
* @returns true if the derived secret matches the expected secret
|
|
107
|
+
*/
|
|
108
|
+
export async function verifySecret(
|
|
109
|
+
masterKey: string,
|
|
110
|
+
appOrigin: string,
|
|
111
|
+
expectedSecret: string,
|
|
112
|
+
): Promise<boolean> {
|
|
113
|
+
const derived = await deriveSecret(masterKey, appOrigin)
|
|
114
|
+
return derived === expectedSecret
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Derive an identity-specific master key from account master key and identity ID
|
|
119
|
+
*
|
|
120
|
+
* Uses HMAC-SHA256 to create a deterministic, unique key for each identity.
|
|
121
|
+
* This enables hierarchical key derivation: Account → Identity → App.
|
|
122
|
+
* The same account master key + identity ID will always produce the same identity key.
|
|
123
|
+
*
|
|
124
|
+
* @param accountMasterKey - The account's master key (hex string)
|
|
125
|
+
* @param identityId - The identity's unique identifier
|
|
126
|
+
* @returns The derived identity master key as a hex string
|
|
127
|
+
*/
|
|
128
|
+
export async function deriveIdentityKey(
|
|
129
|
+
accountMasterKey: string,
|
|
130
|
+
identityId: string,
|
|
131
|
+
): Promise<string> {
|
|
132
|
+
const encoder = new TextEncoder()
|
|
133
|
+
|
|
134
|
+
// Convert account master key to Uint8Array
|
|
135
|
+
const keyData = hexToUint8Array(accountMasterKey)
|
|
136
|
+
const message = encoder.encode(identityId)
|
|
137
|
+
|
|
138
|
+
// Import the account master key for HMAC
|
|
139
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
140
|
+
"raw",
|
|
141
|
+
keyData,
|
|
142
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
143
|
+
false,
|
|
144
|
+
["sign"],
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
// Sign the identity ID with the account master key
|
|
148
|
+
const signature = await crypto.subtle.sign("HMAC", cryptoKey, message)
|
|
149
|
+
|
|
150
|
+
// Convert to hex string
|
|
151
|
+
const identityKeyHex = uint8ArrayToHex(new Uint8Array(signature))
|
|
152
|
+
|
|
153
|
+
return identityKeyHex
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Derive account backup key from account master key
|
|
158
|
+
*
|
|
159
|
+
* Used for signing account feed updates
|
|
160
|
+
*
|
|
161
|
+
* @param accountMasterKey - Account master key (hex string)
|
|
162
|
+
* @param accountId - Account ID (EthAddress hex string)
|
|
163
|
+
* @returns 32-byte account backup key (as hex string)
|
|
164
|
+
*/
|
|
165
|
+
export async function deriveAccountBackupKey(
|
|
166
|
+
accountMasterKey: string,
|
|
167
|
+
accountId: string,
|
|
168
|
+
): Promise<string> {
|
|
169
|
+
return deriveSecret(accountMasterKey, `account:${accountId}`)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Derive account Swarm encryption key from account master key
|
|
174
|
+
*
|
|
175
|
+
* Used for encrypting account snapshot data before upload to Swarm
|
|
176
|
+
*
|
|
177
|
+
* @param accountMasterKey - Account master key (hex string)
|
|
178
|
+
* @returns 32-byte encryption key (as hex string)
|
|
179
|
+
*/
|
|
180
|
+
export async function deriveAccountSwarmEncryptionKey(
|
|
181
|
+
accountMasterKey: string,
|
|
182
|
+
): Promise<string> {
|
|
183
|
+
return deriveSecret(accountMasterKey, `swarm-encryption`)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Convert backup key to PrivateKey for feed signing
|
|
188
|
+
*/
|
|
189
|
+
export function backupKeyToPrivateKey(backupKeyHex: string): PrivateKey {
|
|
190
|
+
return new PrivateKey(backupKeyHex)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Export utility functions for testing
|
|
194
|
+
export const utils = {
|
|
195
|
+
hexToUint8Array,
|
|
196
|
+
uint8ArrayToHex,
|
|
197
|
+
}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-configured Storage Managers for Entity Types
|
|
3
|
+
*
|
|
4
|
+
* Provides ready-to-use storage managers for accounts, identities,
|
|
5
|
+
* connected apps, and postage stamps with versioning support.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from "zod"
|
|
9
|
+
import {
|
|
10
|
+
VersionedStorageManager,
|
|
11
|
+
createLocalStorageManager,
|
|
12
|
+
type VersionParser,
|
|
13
|
+
} from "./versioned-storage"
|
|
14
|
+
import type { Account, Identity, ConnectedApp, PostageStamp } from "../types"
|
|
15
|
+
import {
|
|
16
|
+
STORAGE_KEY_ACCOUNTS,
|
|
17
|
+
STORAGE_KEY_IDENTITIES,
|
|
18
|
+
STORAGE_KEY_CONNECTED_APPS,
|
|
19
|
+
STORAGE_KEY_POSTAGE_STAMPS,
|
|
20
|
+
STORAGE_KEY_NETWORK_SETTINGS,
|
|
21
|
+
} from "../types"
|
|
22
|
+
import {
|
|
23
|
+
AccountSchemaV1,
|
|
24
|
+
IdentitySchemaV1,
|
|
25
|
+
ConnectedAppSchemaV1,
|
|
26
|
+
PostageStampSchemaV1,
|
|
27
|
+
NetworkSettingsSchemaV1,
|
|
28
|
+
type NetworkSettings,
|
|
29
|
+
} from "../schemas"
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Parsers (Zod transforms handle primitive → bee-js conversion)
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse accounts - Zod transforms handle type conversion
|
|
37
|
+
*/
|
|
38
|
+
const parseAccountsV1: VersionParser<Account> = (data: unknown) => {
|
|
39
|
+
const result = z.array(AccountSchemaV1).safeParse(data)
|
|
40
|
+
|
|
41
|
+
if (!result.success) {
|
|
42
|
+
console.error("Parse failed:", result.error.format())
|
|
43
|
+
return []
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result.data
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parse identities - Zod transforms handle type conversion
|
|
51
|
+
*/
|
|
52
|
+
const parseIdentitiesV1: VersionParser<Identity> = (data: unknown) => {
|
|
53
|
+
const result = z.array(IdentitySchemaV1).safeParse(data)
|
|
54
|
+
|
|
55
|
+
if (!result.success) {
|
|
56
|
+
console.error("Parse failed:", result.error.format())
|
|
57
|
+
return []
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result.data
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parse connected apps
|
|
65
|
+
*/
|
|
66
|
+
const parseConnectedAppsV1: VersionParser<ConnectedApp> = (data: unknown) => {
|
|
67
|
+
const result = z.array(ConnectedAppSchemaV1).safeParse(data)
|
|
68
|
+
|
|
69
|
+
if (!result.success) {
|
|
70
|
+
console.error("Parse failed:", result.error.format())
|
|
71
|
+
return []
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return result.data
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Parse postage stamps - Zod transforms handle type conversion
|
|
79
|
+
*/
|
|
80
|
+
const parsePostageStampsV1: VersionParser<PostageStamp> = (data: unknown) => {
|
|
81
|
+
const result = z.array(PostageStampSchemaV1).safeParse(data)
|
|
82
|
+
|
|
83
|
+
if (!result.success) {
|
|
84
|
+
console.error("Parse failed:", result.error.format())
|
|
85
|
+
return []
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return result.data
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Serializers
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Serialize Account for storage
|
|
97
|
+
*/
|
|
98
|
+
export function serializeAccount(account: Account): Record<string, unknown> {
|
|
99
|
+
if (account.type === "passkey") {
|
|
100
|
+
return {
|
|
101
|
+
id: account.id.toString(),
|
|
102
|
+
name: account.name,
|
|
103
|
+
createdAt: account.createdAt,
|
|
104
|
+
type: account.type,
|
|
105
|
+
credentialId: account.credentialId,
|
|
106
|
+
swarmEncryptionKey: account.swarmEncryptionKey,
|
|
107
|
+
defaultPostageStampBatchID:
|
|
108
|
+
account.defaultPostageStampBatchID?.toString(),
|
|
109
|
+
}
|
|
110
|
+
} else if (account.type === "agent") {
|
|
111
|
+
return {
|
|
112
|
+
id: account.id.toString(),
|
|
113
|
+
name: account.name,
|
|
114
|
+
createdAt: account.createdAt,
|
|
115
|
+
type: account.type,
|
|
116
|
+
swarmEncryptionKey: account.swarmEncryptionKey,
|
|
117
|
+
defaultPostageStampBatchID:
|
|
118
|
+
account.defaultPostageStampBatchID?.toString(),
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
return {
|
|
122
|
+
id: account.id.toString(),
|
|
123
|
+
name: account.name,
|
|
124
|
+
createdAt: account.createdAt,
|
|
125
|
+
type: account.type,
|
|
126
|
+
ethereumAddress: account.ethereumAddress.toString(),
|
|
127
|
+
encryptedMasterKey: Array.from(account.encryptedMasterKey.toUint8Array()),
|
|
128
|
+
encryptionSalt: Array.from(account.encryptionSalt.toUint8Array()),
|
|
129
|
+
encryptedSecretSeed: Array.from(
|
|
130
|
+
account.encryptedSecretSeed.toUint8Array(),
|
|
131
|
+
),
|
|
132
|
+
swarmEncryptionKey: account.swarmEncryptionKey,
|
|
133
|
+
defaultPostageStampBatchID:
|
|
134
|
+
account.defaultPostageStampBatchID?.toString(),
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Serialize Identity for storage
|
|
141
|
+
*/
|
|
142
|
+
export function serializeIdentity(identity: Identity): Record<string, unknown> {
|
|
143
|
+
return {
|
|
144
|
+
id: identity.id,
|
|
145
|
+
accountId: identity.accountId.toString(),
|
|
146
|
+
name: identity.name,
|
|
147
|
+
defaultPostageStampBatchID: identity.defaultPostageStampBatchID?.toString(),
|
|
148
|
+
createdAt: identity.createdAt,
|
|
149
|
+
settings: identity.settings,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Serialize ConnectedApp for storage
|
|
155
|
+
*/
|
|
156
|
+
export function serializeConnectedApp(
|
|
157
|
+
app: ConnectedApp,
|
|
158
|
+
): Record<string, unknown> {
|
|
159
|
+
return {
|
|
160
|
+
appUrl: app.appUrl,
|
|
161
|
+
appName: app.appName,
|
|
162
|
+
lastConnectedAt: app.lastConnectedAt,
|
|
163
|
+
identityId: app.identityId,
|
|
164
|
+
appIcon: app.appIcon,
|
|
165
|
+
appDescription: app.appDescription,
|
|
166
|
+
connectedUntil: app.connectedUntil,
|
|
167
|
+
appSecret: app.appSecret,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Serialize PostageStamp for storage
|
|
173
|
+
*/
|
|
174
|
+
export function serializePostageStamp(
|
|
175
|
+
stamp: PostageStamp,
|
|
176
|
+
): Record<string, unknown> {
|
|
177
|
+
return {
|
|
178
|
+
accountId: stamp.accountId,
|
|
179
|
+
batchID: stamp.batchID.toString(),
|
|
180
|
+
signerKey: stamp.signerKey.toString(),
|
|
181
|
+
utilization: stamp.utilization,
|
|
182
|
+
usable: stamp.usable,
|
|
183
|
+
depth: stamp.depth,
|
|
184
|
+
amount: stamp.amount.toString(), // Convert bigint to string for JSON
|
|
185
|
+
bucketDepth: stamp.bucketDepth,
|
|
186
|
+
blockNumber: stamp.blockNumber,
|
|
187
|
+
immutableFlag: stamp.immutableFlag,
|
|
188
|
+
exists: stamp.exists,
|
|
189
|
+
batchTTL: stamp.batchTTL,
|
|
190
|
+
createdAt: stamp.createdAt,
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// Storage Manager Factories
|
|
196
|
+
// ============================================================================
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Create storage manager for accounts
|
|
200
|
+
*/
|
|
201
|
+
export function createAccountsStorageManager(): VersionedStorageManager<Account> {
|
|
202
|
+
return createLocalStorageManager<Account>({
|
|
203
|
+
key: STORAGE_KEY_ACCOUNTS,
|
|
204
|
+
currentVersion: 1,
|
|
205
|
+
parsers: {
|
|
206
|
+
1: parseAccountsV1,
|
|
207
|
+
},
|
|
208
|
+
serializer: serializeAccount,
|
|
209
|
+
loggerName: "AccountsStorage",
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Create storage manager for identities
|
|
215
|
+
*/
|
|
216
|
+
export function createIdentitiesStorageManager(): VersionedStorageManager<Identity> {
|
|
217
|
+
return createLocalStorageManager<Identity>({
|
|
218
|
+
key: STORAGE_KEY_IDENTITIES,
|
|
219
|
+
currentVersion: 1,
|
|
220
|
+
parsers: {
|
|
221
|
+
1: parseIdentitiesV1,
|
|
222
|
+
},
|
|
223
|
+
serializer: serializeIdentity,
|
|
224
|
+
loggerName: "IdentitiesStorage",
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Create storage manager for connected apps
|
|
230
|
+
*/
|
|
231
|
+
export function createConnectedAppsStorageManager(): VersionedStorageManager<ConnectedApp> {
|
|
232
|
+
return createLocalStorageManager<ConnectedApp>({
|
|
233
|
+
key: STORAGE_KEY_CONNECTED_APPS,
|
|
234
|
+
currentVersion: 1,
|
|
235
|
+
parsers: {
|
|
236
|
+
1: parseConnectedAppsV1,
|
|
237
|
+
},
|
|
238
|
+
serializer: serializeConnectedApp,
|
|
239
|
+
loggerName: "ConnectedAppsStorage",
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Invalidate all connected app entries for a given app URL.
|
|
245
|
+
* Sets lastConnectedAt to 0 and connectedUntil to undefined so
|
|
246
|
+
* isConnectionValid() returns false and reconnect won't happen on refresh.
|
|
247
|
+
*/
|
|
248
|
+
export function disconnectApp(appUrl: string): void {
|
|
249
|
+
const manager = createConnectedAppsStorageManager()
|
|
250
|
+
const apps = manager.load()
|
|
251
|
+
const updated = apps.map((app) =>
|
|
252
|
+
app.appUrl === appUrl
|
|
253
|
+
? { ...app, lastConnectedAt: 0, connectedUntil: undefined }
|
|
254
|
+
: app,
|
|
255
|
+
)
|
|
256
|
+
manager.save(updated)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Create storage manager for postage stamps
|
|
261
|
+
*/
|
|
262
|
+
export function createPostageStampsStorageManager(): VersionedStorageManager<PostageStamp> {
|
|
263
|
+
return createLocalStorageManager<PostageStamp>({
|
|
264
|
+
key: STORAGE_KEY_POSTAGE_STAMPS,
|
|
265
|
+
currentVersion: 1,
|
|
266
|
+
parsers: {
|
|
267
|
+
1: parsePostageStampsV1,
|
|
268
|
+
},
|
|
269
|
+
serializer: serializePostageStamp,
|
|
270
|
+
loggerName: "PostageStampsStorage",
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ============================================================================
|
|
275
|
+
// Network Settings Storage (Singleton)
|
|
276
|
+
// ============================================================================
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Parse network settings - Zod validates URL format
|
|
280
|
+
*/
|
|
281
|
+
function parseNetworkSettingsV1(data: unknown): NetworkSettings | undefined {
|
|
282
|
+
const result = NetworkSettingsSchemaV1.safeParse(data)
|
|
283
|
+
|
|
284
|
+
if (!result.success) {
|
|
285
|
+
console.error(
|
|
286
|
+
"[NetworkSettingsStorage] Parse failed:",
|
|
287
|
+
result.error.format(),
|
|
288
|
+
)
|
|
289
|
+
return undefined
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return result.data
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Serialize NetworkSettings for storage
|
|
297
|
+
*/
|
|
298
|
+
export function serializeNetworkSettings(
|
|
299
|
+
settings: NetworkSettings,
|
|
300
|
+
): Record<string, unknown> {
|
|
301
|
+
return {
|
|
302
|
+
beeNodeUrl: settings.beeNodeUrl,
|
|
303
|
+
gnosisRpcUrl: settings.gnosisRpcUrl,
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Singleton storage manager interface for network settings
|
|
309
|
+
*/
|
|
310
|
+
export interface NetworkSettingsStorageManager {
|
|
311
|
+
load(): NetworkSettings | undefined
|
|
312
|
+
save(settings: NetworkSettings): void
|
|
313
|
+
clear(): void
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Create storage manager for network settings (singleton)
|
|
318
|
+
* Unlike other storage managers, this stores a single object, not an array
|
|
319
|
+
*/
|
|
320
|
+
export function createNetworkSettingsStorageManager(): NetworkSettingsStorageManager {
|
|
321
|
+
return {
|
|
322
|
+
load(): NetworkSettings | undefined {
|
|
323
|
+
if (typeof localStorage === "undefined") {
|
|
324
|
+
return undefined
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const raw = localStorage.getItem(STORAGE_KEY_NETWORK_SETTINGS)
|
|
328
|
+
if (!raw) {
|
|
329
|
+
return undefined
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
const parsed = JSON.parse(raw)
|
|
334
|
+
return parseNetworkSettingsV1(parsed)
|
|
335
|
+
} catch (e) {
|
|
336
|
+
console.error(
|
|
337
|
+
"[NetworkSettingsStorage] Failed to parse stored data:",
|
|
338
|
+
e,
|
|
339
|
+
)
|
|
340
|
+
return undefined
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
save(settings: NetworkSettings): void {
|
|
345
|
+
if (typeof localStorage === "undefined") {
|
|
346
|
+
console.warn("[NetworkSettingsStorage] localStorage not available")
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const serialized = serializeNetworkSettings(settings)
|
|
351
|
+
localStorage.setItem(
|
|
352
|
+
STORAGE_KEY_NETWORK_SETTINGS,
|
|
353
|
+
JSON.stringify(serialized),
|
|
354
|
+
)
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
clear(): void {
|
|
358
|
+
if (typeof localStorage === "undefined") {
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
localStorage.removeItem(STORAGE_KEY_NETWORK_SETTINGS)
|
|
363
|
+
},
|
|
364
|
+
}
|
|
365
|
+
}
|