@cmdoss/memwal-sdk 0.7.0 → 0.8.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 (192) hide show
  1. package/README.md +129 -0
  2. package/dist/client/ClientMemoryManager.js +2 -2
  3. package/dist/client/ClientMemoryManager.js.map +1 -1
  4. package/dist/client/PersonalDataWallet.d.ts.map +1 -1
  5. package/dist/client/SimplePDWClient.d.ts +28 -0
  6. package/dist/client/SimplePDWClient.d.ts.map +1 -1
  7. package/dist/client/SimplePDWClient.js +29 -6
  8. package/dist/client/SimplePDWClient.js.map +1 -1
  9. package/dist/client/namespaces/MemoryNamespace.d.ts +4 -0
  10. package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
  11. package/dist/client/namespaces/MemoryNamespace.js +168 -39
  12. package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
  13. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts +12 -2
  14. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
  15. package/dist/client/namespaces/consolidated/BlockchainNamespace.js +40 -2
  16. package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
  17. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +67 -2
  18. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
  19. package/dist/client/namespaces/consolidated/StorageNamespace.js +549 -16
  20. package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
  21. package/dist/config/ConfigurationHelper.js +61 -61
  22. package/dist/config/defaults.js +2 -2
  23. package/dist/config/defaults.js.map +1 -1
  24. package/dist/graph/GraphService.js +20 -20
  25. package/dist/infrastructure/seal/EncryptionService.d.ts +9 -5
  26. package/dist/infrastructure/seal/EncryptionService.d.ts.map +1 -1
  27. package/dist/infrastructure/seal/EncryptionService.js +37 -15
  28. package/dist/infrastructure/seal/EncryptionService.js.map +1 -1
  29. package/dist/infrastructure/seal/SealService.d.ts +13 -5
  30. package/dist/infrastructure/seal/SealService.d.ts.map +1 -1
  31. package/dist/infrastructure/seal/SealService.js +36 -34
  32. package/dist/infrastructure/seal/SealService.js.map +1 -1
  33. package/dist/langchain/createPDWRAG.js +30 -30
  34. package/dist/retrieval/MemoryDecryptionPipeline.d.ts.map +1 -1
  35. package/dist/retrieval/MemoryDecryptionPipeline.js +2 -1
  36. package/dist/retrieval/MemoryDecryptionPipeline.js.map +1 -1
  37. package/dist/services/CapabilityService.d.ts.map +1 -1
  38. package/dist/services/CapabilityService.js +30 -14
  39. package/dist/services/CapabilityService.js.map +1 -1
  40. package/dist/services/CrossContextPermissionService.d.ts.map +1 -1
  41. package/dist/services/CrossContextPermissionService.js +9 -7
  42. package/dist/services/CrossContextPermissionService.js.map +1 -1
  43. package/dist/services/EncryptionService.d.ts.map +1 -1
  44. package/dist/services/EncryptionService.js +6 -5
  45. package/dist/services/EncryptionService.js.map +1 -1
  46. package/dist/services/GeminiAIService.js +309 -309
  47. package/dist/services/StorageService.d.ts +1 -0
  48. package/dist/services/StorageService.d.ts.map +1 -1
  49. package/dist/services/StorageService.js +60 -10
  50. package/dist/services/StorageService.js.map +1 -1
  51. package/dist/services/TransactionService.d.ts +20 -0
  52. package/dist/services/TransactionService.d.ts.map +1 -1
  53. package/dist/services/TransactionService.js +43 -0
  54. package/dist/services/TransactionService.js.map +1 -1
  55. package/dist/services/ViewService.js +2 -2
  56. package/dist/services/ViewService.js.map +1 -1
  57. package/package.json +1 -1
  58. package/src/access/PermissionService.ts +635 -635
  59. package/src/access/index.ts +8 -8
  60. package/src/aggregation/AggregationService.ts +389 -389
  61. package/src/aggregation/index.ts +8 -8
  62. package/src/ai-sdk/PDWVectorStore.ts +715 -715
  63. package/src/ai-sdk/index.ts +65 -65
  64. package/src/ai-sdk/tools.ts +460 -460
  65. package/src/ai-sdk/types.ts +404 -404
  66. package/src/batch/BatchManager.ts +597 -597
  67. package/src/batch/BatchingService.ts +429 -429
  68. package/src/batch/MemoryProcessingCache.ts +492 -492
  69. package/src/batch/index.ts +30 -30
  70. package/src/browser.ts +200 -200
  71. package/src/client/ClientMemoryManager.ts +987 -987
  72. package/src/client/PersonalDataWallet.ts +345 -345
  73. package/src/client/SimplePDWClient.ts +1289 -1237
  74. package/src/client/factory.ts +154 -154
  75. package/src/client/namespaces/AnalyticsNamespace.ts +377 -377
  76. package/src/client/namespaces/BatchNamespace.ts +356 -356
  77. package/src/client/namespaces/CacheNamespace.ts +123 -123
  78. package/src/client/namespaces/CapabilityNamespace.ts +217 -217
  79. package/src/client/namespaces/ClassifyNamespace.ts +169 -169
  80. package/src/client/namespaces/ContextNamespace.ts +297 -297
  81. package/src/client/namespaces/EmbeddingsNamespace.ts +99 -99
  82. package/src/client/namespaces/EncryptionNamespace.ts +221 -221
  83. package/src/client/namespaces/GraphNamespace.ts +468 -468
  84. package/src/client/namespaces/IndexNamespace.ts +361 -361
  85. package/src/client/namespaces/MemoryNamespace.ts +1422 -1272
  86. package/src/client/namespaces/PermissionsNamespace.ts +254 -254
  87. package/src/client/namespaces/PipelineNamespace.ts +220 -220
  88. package/src/client/namespaces/SearchNamespace.ts +1049 -1049
  89. package/src/client/namespaces/StorageNamespace.ts +458 -458
  90. package/src/client/namespaces/TxNamespace.ts +260 -260
  91. package/src/client/namespaces/WalletNamespace.ts +243 -243
  92. package/src/client/namespaces/consolidated/AINamespace.ts +449 -449
  93. package/src/client/namespaces/consolidated/BlockchainNamespace.ts +607 -564
  94. package/src/client/namespaces/consolidated/SecurityNamespace.ts +648 -648
  95. package/src/client/namespaces/consolidated/StorageNamespace.ts +1141 -497
  96. package/src/client/namespaces/consolidated/index.ts +39 -39
  97. package/src/client/signers/DappKitSigner.ts +207 -207
  98. package/src/client/signers/KeypairSigner.ts +108 -108
  99. package/src/client/signers/UnifiedSigner.ts +110 -110
  100. package/src/client/signers/WalletAdapterSigner.ts +159 -159
  101. package/src/client/signers/index.ts +26 -26
  102. package/src/config/ConfigurationHelper.ts +412 -412
  103. package/src/config/defaults.ts +51 -51
  104. package/src/config/index.ts +8 -8
  105. package/src/config/validation.ts +70 -70
  106. package/src/core/index.ts +14 -14
  107. package/src/core/interfaces/IService.ts +307 -307
  108. package/src/core/interfaces/index.ts +8 -8
  109. package/src/core/types/capability.ts +297 -297
  110. package/src/core/types/index.ts +870 -870
  111. package/src/core/types/wallet.ts +270 -270
  112. package/src/core/types.ts +9 -9
  113. package/src/core/wallet.ts +222 -222
  114. package/src/embedding/index.ts +19 -19
  115. package/src/embedding/types.ts +357 -357
  116. package/src/errors/index.ts +602 -602
  117. package/src/errors/recovery.ts +461 -461
  118. package/src/errors/validation.ts +567 -567
  119. package/src/generated/pdw/capability.ts +319 -319
  120. package/src/generated/pdw/deps/sui/object.ts +12 -12
  121. package/src/generated/pdw/deps/sui/vec_map.ts +32 -32
  122. package/src/generated/pdw/memory.ts +1087 -1087
  123. package/src/generated/pdw/wallet.ts +123 -123
  124. package/src/generated/utils/index.ts +159 -159
  125. package/src/graph/GraphService.ts +887 -887
  126. package/src/graph/KnowledgeGraphManager.ts +728 -728
  127. package/src/graph/index.ts +25 -25
  128. package/src/index.ts +498 -498
  129. package/src/infrastructure/index.ts +22 -22
  130. package/src/infrastructure/seal/EncryptionService.ts +628 -603
  131. package/src/infrastructure/seal/SealService.ts +613 -615
  132. package/src/infrastructure/seal/index.ts +9 -9
  133. package/src/infrastructure/sui/BlockchainManager.ts +627 -627
  134. package/src/infrastructure/sui/SuiService.ts +888 -888
  135. package/src/infrastructure/sui/index.ts +9 -9
  136. package/src/infrastructure/walrus/StorageManager.ts +604 -604
  137. package/src/infrastructure/walrus/WalrusStorageService.ts +612 -612
  138. package/src/infrastructure/walrus/index.ts +9 -9
  139. package/src/langchain/PDWEmbeddings.ts +145 -145
  140. package/src/langchain/PDWVectorStore.ts +456 -456
  141. package/src/langchain/createPDWRAG.ts +303 -303
  142. package/src/langchain/index.ts +47 -47
  143. package/src/permissions/ConsentRepository.browser.ts +249 -249
  144. package/src/permissions/ConsentRepository.ts +364 -364
  145. package/src/permissions/index.ts +9 -9
  146. package/src/pipeline/MemoryPipeline.ts +862 -862
  147. package/src/pipeline/PipelineManager.ts +683 -683
  148. package/src/pipeline/index.ts +26 -26
  149. package/src/retrieval/AdvancedSearchService.ts +629 -629
  150. package/src/retrieval/MemoryAnalyticsService.ts +711 -711
  151. package/src/retrieval/MemoryDecryptionPipeline.ts +825 -824
  152. package/src/retrieval/MemoryRetrievalService.ts +904 -904
  153. package/src/retrieval/index.ts +42 -42
  154. package/src/services/BatchService.ts +352 -352
  155. package/src/services/CapabilityService.ts +464 -448
  156. package/src/services/ClassifierService.ts +465 -465
  157. package/src/services/CrossContextPermissionService.ts +486 -484
  158. package/src/services/EmbeddingService.ts +771 -771
  159. package/src/services/EncryptionService.ts +712 -711
  160. package/src/services/GeminiAIService.ts +753 -753
  161. package/src/services/IndexManager.ts +977 -977
  162. package/src/services/MemoryIndexService.ts +1003 -1003
  163. package/src/services/MemoryService.ts +369 -369
  164. package/src/services/QueryService.ts +890 -890
  165. package/src/services/StorageService.ts +1182 -1126
  166. package/src/services/TransactionService.ts +838 -790
  167. package/src/services/VectorService.ts +462 -462
  168. package/src/services/ViewService.ts +484 -484
  169. package/src/services/index.ts +25 -25
  170. package/src/services/storage/BlobAttributesManager.ts +333 -333
  171. package/src/services/storage/KnowledgeGraphManager.ts +425 -425
  172. package/src/services/storage/MemorySearchManager.ts +387 -387
  173. package/src/services/storage/QuiltBatchManager.ts +1130 -1130
  174. package/src/services/storage/WalrusMetadataManager.ts +268 -268
  175. package/src/services/storage/WalrusStorageManager.ts +287 -287
  176. package/src/services/storage/index.ts +57 -57
  177. package/src/types/index.ts +13 -13
  178. package/src/utils/LRUCache.ts +378 -378
  179. package/src/utils/index.ts +76 -76
  180. package/src/utils/memoryIndexOnChain.ts +507 -507
  181. package/src/utils/rebuildIndex.ts +290 -290
  182. package/src/utils/rebuildIndexNode.ts +771 -771
  183. package/src/vector/BrowserHnswIndexService.ts +758 -758
  184. package/src/vector/HnswWasmService.ts +731 -731
  185. package/src/vector/IHnswService.ts +233 -233
  186. package/src/vector/NodeHnswService.ts +833 -833
  187. package/src/vector/VectorManager.ts +478 -478
  188. package/src/vector/createHnswService.ts +135 -135
  189. package/src/vector/index.ts +56 -56
  190. package/src/wallet/ContextWalletService.ts +656 -656
  191. package/src/wallet/MainWalletService.ts +317 -317
  192. package/src/wallet/index.ts +17 -17
@@ -1,604 +1,629 @@
1
- /**
2
- * EncryptionService - SEAL-based encryption and access control
3
- *
4
- * Provides identity-based encryption using Mysten's SEAL SDK with decentralized
5
- * key management and onchain access control policies.
6
- */
7
-
8
- import { SessionKey } from '@mysten/seal';
9
- import { Transaction } from '@mysten/sui/transactions';
10
- import { fromHex, toHex } from '@mysten/sui/utils';
11
- import { SealService } from './SealService';
12
- import { CrossContextPermissionService } from '../../services/CrossContextPermissionService';
13
- import type {
14
- ClientWithCoreApi,
15
- PDWConfig,
16
- AccessPermission,
17
- AccessControlOptions,
18
- Thunk,
19
- SealEncryptionResult,
20
- SealDecryptionOptions
21
- } from '../../core/types';
22
-
23
- export interface AccessGrantOptions {
24
- ownerAddress: string;
25
- recipientAddress: string;
26
- contentId: string;
27
- accessLevel: 'read' | 'write';
28
- expiresIn?: number; // milliseconds from now
29
- }
30
-
31
- export interface AccessRevokeOptions {
32
- ownerAddress: string;
33
- recipientAddress: string;
34
- contentId: string;
35
- }
36
-
37
- export class EncryptionService {
38
- private sealService: SealService;
39
- private suiClient: any;
40
- private packageId: string;
41
- private sessionKeyCache = new Map<string, SessionKey>();
42
- private permissionService: CrossContextPermissionService;
43
-
44
- constructor(
45
- private client: ClientWithCoreApi,
46
- private config: PDWConfig
47
- ) {
48
- this.suiClient = (client as any).client || client;
49
- this.packageId = config.packageId || '';
50
- this.sealService = this.initializeSealService();
51
-
52
- // Initialize permission service for OAuth-style access control
53
- this.permissionService = new CrossContextPermissionService(
54
- {
55
- packageId: this.packageId,
56
- accessRegistryId: config.accessRegistryId || ''
57
- },
58
- this.suiClient
59
- );
60
- }
61
-
62
- /**
63
- * Initialize SEAL service with proper configuration
64
- */
65
- private initializeSealService(): SealService {
66
- const encryptionConfig = this.config.encryptionConfig;
67
-
68
- if (!encryptionConfig?.enabled) {
69
- console.warn('Encryption is disabled in configuration - creating SealService anyway');
70
- }
71
-
72
- // Default testnet key servers (replace with actual server object IDs)
73
- const defaultKeyServers = [
74
- '0x73d05d62c18d9374e3ea529e8e0ed6161da1a141a94d3f76ae3fe4e99356db75',
75
- '0xf5d14a81a982144ae441cd7d64b09027f116a468bd36e7eca494f750591623c8'
76
- ];
77
-
78
- const keyServerIds = encryptionConfig?.keyServers || defaultKeyServers;
79
-
80
- // Create SealService with proper configuration
81
- const sealConfig = {
82
- suiClient: this.suiClient,
83
- packageId: this.packageId,
84
- keyServerUrls: [], // Empty for now, URLs handled separately if needed
85
- keyServerObjectIds: keyServerIds,
86
- network: process.env.NODE_ENV === 'production' ? 'mainnet' as const : 'testnet' as const,
87
- threshold: 2,
88
- enableMetrics: true,
89
- retryAttempts: 3,
90
- timeoutMs: 30000
91
- };
92
-
93
- return new SealService(sealConfig);
94
- }
95
-
96
- /**
97
- * Build access approval transaction for SEAL key servers (LEGACY)
98
- *
99
- * @deprecated Use buildAccessTransactionForWallet instead for wallet-based permissions
100
- */
101
- async buildAccessTransaction(
102
- userAddress: string,
103
- accessType: 'read' | 'write' = 'read'
104
- ): Promise<Transaction> {
105
- console.warn('buildAccessTransaction is deprecated - use buildAccessTransactionForWallet for wallet-based permissions');
106
-
107
- return this.buildAccessTransactionForWallet(userAddress, userAddress, accessType);
108
- }
109
-
110
- /**
111
- * Build access approval transaction for a requesting wallet address
112
- * Uses CrossContextPermissionService for proper permission validation
113
- *
114
- * @param userAddress - User's wallet address (used as SEAL identity)
115
- * @param requestingWallet - Wallet requesting access
116
- * @param accessType - Access level (read/write)
117
- * @returns Transaction for SEAL key server approval
118
- */
119
- async buildAccessTransactionForWallet(
120
- userAddress: string,
121
- requestingWallet: string,
122
- accessType: 'read' | 'write' = 'read'
123
- ): Promise<Transaction> {
124
- // Convert user address to bytes for SEAL identity
125
- const identityBytes = fromHex(userAddress.replace('0x', ''));
126
-
127
- return this.permissionService.buildSealApproveTransaction(
128
- identityBytes,
129
- requestingWallet
130
- );
131
- }
132
-
133
- /**
134
- * Decrypt data using SEAL with session keys via SealService
135
- * Handles both new binary format (Uint8Array) and legacy base64 format
136
- * Validates wallet-based allowlists during approval flow
137
- */
138
- async decrypt(options: SealDecryptionOptions): Promise<Uint8Array> {
139
- try {
140
- console.log('🔓 EncryptionService: Starting SEAL decryption...');
141
- console.log(` User address: ${options.userAddress}`);
142
- const requestingWallet = options.requestingWallet ?? options.userAddress;
143
- console.log(` Requesting wallet: ${requestingWallet}`);
144
- if (!options.requestingWallet) {
145
- console.warn('No requestingWallet provided for decryption - defaulting to user address');
146
- }
147
-
148
- // Get or create session key
149
- let activeSessionKey = options.sessionKey;
150
- if (!activeSessionKey) {
151
- console.log('🔄 Creating session key...');
152
- activeSessionKey = await this.getOrCreateSessionKey(options.userAddress);
153
- }
154
-
155
- // Build access transaction if not provided
156
- let txBytes = options.signedTxBytes;
157
- if (!txBytes) {
158
- console.log('🔄 Building access transaction for requesting wallet...');
159
- const tx = await this.buildAccessTransactionForWallet(
160
- options.userAddress,
161
- requestingWallet,
162
- 'read'
163
- );
164
- txBytes = await tx.build({ client: this.suiClient });
165
- }
166
-
167
- if (!txBytes) {
168
- throw new Error('Failed to build SEAL approval transaction bytes');
169
- }
170
-
171
- // CRITICAL: Handle both binary and legacy formats
172
- let encryptedBytes: Uint8Array;
173
-
174
- if (options.encryptedContent && options.encryptedContent instanceof Uint8Array) {
175
- // **NEW BINARY FORMAT** (preferred - matches memory-workflow-seal.ts)
176
- encryptedBytes = options.encryptedContent;
177
- console.log('✅ Using new binary format (Uint8Array)');
178
- console.log(` Binary data size: ${encryptedBytes.length} bytes`);
179
- console.log(` Format: Direct binary (preserves SEAL integrity)`);
180
- } else if (options.encryptedData) {
181
- // **LEGACY BASE64 FORMAT** (deprecated but supported for backward compatibility)
182
- console.log('⚠️ Using legacy base64 format (deprecated)');
183
- const encryptedDataBase64 = options.encryptedData;
184
- const binaryString = atob(encryptedDataBase64);
185
- encryptedBytes = new Uint8Array(binaryString.length);
186
- for (let i = 0; i < binaryString.length; i++) {
187
- encryptedBytes[i] = binaryString.charCodeAt(i);
188
- }
189
- console.log(` Converted from base64: ${encryptedDataBase64.length} chars → ${encryptedBytes.length} bytes`);
190
- console.log(' Recommendation: Use encryptedContent (Uint8Array) for better performance');
191
- } else {
192
- throw new Error('No encrypted data provided. Use either encryptedContent (Uint8Array) or encryptedData (base64 string)');
193
- }
194
-
195
- console.log('🔄 Calling SEAL decryption...');
196
- console.log(` Encrypted data length: ${encryptedBytes.length} bytes`);
197
- console.log(` Session key available: ${!!activeSessionKey}`);
198
- console.log(` Transaction bytes length: ${txBytes.length} bytes`);
199
-
200
- // Use SealService for decryption (matches memory-workflow-seal.ts pattern)
201
- const decryptResult = await this.sealService.decryptData({
202
- encryptedObject: encryptedBytes,
203
- sessionKey: activeSessionKey,
204
- txBytes
205
- });
206
-
207
- console.log(`✅ EncryptionService: SEAL decryption successful`);
208
- console.log(` Decrypted data size: ${decryptResult.length} bytes`);
209
- console.log(` Binary integrity preserved throughout process`);
210
-
211
- return decryptResult;
212
- } catch (error) {
213
- throw new Error(`Decryption failed: ${error}`);
214
- }
215
- }
216
-
217
- // ==================== SESSION KEY MANAGEMENT ====================
218
-
219
- /**
220
- * Create a new session key for a user via SealService
221
- *
222
- * Two usage patterns:
223
- * 1. Frontend: Pass signPersonalMessageFn from @mysten/dapp-kit useSignPersonalMessage hook
224
- * 2. Backend: Pass keypair for direct signing (auto-converts format)
225
- */
226
- async createSessionKey(
227
- userAddress: string,
228
- signer?: {
229
- // Frontend pattern: dapp-kit signPersonalMessage function
230
- signPersonalMessageFn?: (message: string) => Promise<{ signature: string }>;
231
- // Backend pattern: Ed25519Keypair for direct signing
232
- keypair?: any;
233
- }
234
- ): Promise<SessionKey> {
235
- try {
236
- console.log('🔄 EncryptionService: Creating SEAL session key...');
237
- console.log(` User address: ${userAddress}`);
238
- console.log(` TTL: 30 minutes (maximum allowed)`);
239
-
240
- const sessionResult = await this.sealService.createSession({
241
- address: userAddress,
242
- packageId: this.packageId,
243
- ttlMin: 30, // Use 30 minutes (maximum allowed)
244
- });
245
-
246
- // Handle signing based on provided signer type
247
- if (signer?.signPersonalMessageFn) {
248
- // Frontend pattern: Use dapp-kit signPersonalMessage (RECOMMENDED)
249
- console.log('🔄 Signing with dapp-kit signPersonalMessage (recommended)...');
250
- const personalMessage = sessionResult.personalMessage;
251
-
252
- // Convert to string if it's a byte array
253
- const messageString = typeof personalMessage === 'string'
254
- ? personalMessage
255
- : new TextDecoder().decode(personalMessage);
256
-
257
- console.log(` Message (first 100 chars): ${messageString.substring(0, 100)}...`);
258
-
259
- // Use dapp-kit signPersonalMessage - returns signature in correct format
260
- const result = await signer.signPersonalMessageFn(messageString);
261
-
262
- console.log(` Signature from dapp-kit: ${result.signature.substring(0, 20)}...`);
263
- console.log(` ✅ Using dapp-kit signature format (already compatible with SEAL)`);
264
-
265
- // Set signature directly - dapp-kit returns it in SEAL-compatible format
266
- await sessionResult.sessionKey.setPersonalMessageSignature(result.signature);
267
- console.log('✅ Personal message signed with dapp-kit');
268
-
269
- } else if (signer?.keypair) {
270
- // Backend pattern: Use Ed25519Keypair with format conversion
271
- console.log('🔄 Signing with Ed25519Keypair (backend fallback)...');
272
- const personalMessage = sessionResult.personalMessage;
273
-
274
- // Convert to string if it's a byte array
275
- const messageString = typeof personalMessage === 'string'
276
- ? personalMessage
277
- : new TextDecoder().decode(personalMessage);
278
-
279
- console.log(` Message (first 100 chars): ${messageString.substring(0, 100)}...`);
280
-
281
- // Sign with keypair
282
- const messageSignature = await signer.keypair.signPersonalMessage(new TextEncoder().encode(messageString));
283
-
284
- // CRITICAL FIX: Use signature as-is from Ed25519Keypair (SEAL expects original format)
285
- // According to SEAL documentation, pass signature directly from keypair.signPersonalMessage()
286
- console.log(` Using signature as-is from Ed25519Keypair (SEAL-compatible format)`);
287
- console.log(` Original signature: ${messageSignature.signature.substring(0, 20)}...`);
288
-
289
- // Set signature exactly as returned by keypair (no conversion needed)
290
- await sessionResult.sessionKey.setPersonalMessageSignature(messageSignature.signature);
291
- console.log('✅ Personal message signed with Ed25519Keypair');
292
-
293
- } else {
294
- console.log('⚠️ No signer provided - session key created but not signed');
295
- console.log(' Note: Call setPersonalMessageSignature() later with wallet-signed message');
296
- console.log(' Frontend: Use dapp-kit useSignPersonalMessage hook');
297
- console.log(' Backend: Provide Ed25519Keypair');
298
- }
299
-
300
- // Cache the session key
301
- this.sessionKeyCache.set(userAddress, sessionResult.sessionKey);
302
-
303
- console.log('✅ EncryptionService: Session key created and cached');
304
- return sessionResult.sessionKey;
305
- } catch (error) {
306
- throw new Error(`Failed to create session key: ${error}`);
307
- }
308
- }
309
-
310
- /**
311
- * Get cached session key or create new one
312
- */
313
- async getOrCreateSessionKey(userAddress: string): Promise<SessionKey> {
314
- const cached = this.sessionKeyCache.get(userAddress);
315
- if (cached) {
316
- return cached;
317
- }
318
-
319
- return this.createSessionKey(userAddress);
320
- }
321
-
322
- /**
323
- * Export session key for persistence
324
- */
325
- async exportSessionKey(sessionKey: SessionKey): Promise<string> {
326
- try {
327
- const exported = sessionKey.export();
328
- return JSON.stringify(exported);
329
- } catch (error) {
330
- throw new Error(`Failed to export session key: ${error}`);
331
- }
332
- }
333
-
334
- /**
335
- * Import previously exported session key
336
- */
337
- async importSessionKey(exportedKey: string, userAddress?: string): Promise<SessionKey> {
338
- try {
339
- const keyData = JSON.parse(exportedKey);
340
- const sessionKey = SessionKey.import(keyData, this.suiClient);
341
-
342
- if (userAddress) {
343
- this.sessionKeyCache.set(userAddress, sessionKey);
344
- }
345
-
346
- return sessionKey;
347
- } catch (error) {
348
- throw new Error(`Failed to import session key: ${error}`);
349
- }
350
- }
351
-
352
- // ==================== ACCESS CONTROL TRANSACTIONS ====================
353
-
354
- /**
355
- * Create SEAL approval transaction bytes (matches memory-workflow-seal.ts pattern)
356
- * Returns raw PTB format bytes for SEAL verification
357
- */
358
- async createSealApproveTransaction(userAddress: string, contentOwner: string): Promise<Uint8Array> {
359
- try {
360
- console.log('🔄 EncryptionService: Creating SEAL approval transaction...');
361
- console.log(` User address: ${userAddress}`);
362
- console.log(` Content owner: ${contentOwner}`);
363
-
364
- // Create the approval transaction (not signed, just bytes)
365
- const tx = await this.buildAccessTransactionForWallet(userAddress, contentOwner, 'read');
366
- const txBytes = await tx.build({ client: this.suiClient });
367
-
368
- console.log(`✅ Created SEAL approval transaction bytes (${txBytes.length} bytes)`);
369
- console.log(' Format: Raw PTB (Programmable Transaction Block) for SEAL verification');
370
-
371
- return txBytes;
372
- } catch (error) {
373
- throw new Error(`Failed to create SEAL approval transaction: ${error}`);
374
- }
375
- }
376
-
377
- /**
378
- * Build transaction to grant access to another user
379
- */
380
- async buildGrantAccessTransaction(options: AccessGrantOptions): Promise<Transaction> {
381
- const { ownerAddress, recipientAddress, contentId, accessLevel, expiresIn } = options;
382
- const tx = new Transaction();
383
-
384
- const expiresAt = expiresIn ? Date.now() + expiresIn : Date.now() + 86400000; // 24h default
385
-
386
- tx.moveCall({
387
- target: `${this.packageId}::seal_access_control::grant_access`,
388
- arguments: [
389
- tx.pure.address(ownerAddress),
390
- tx.pure.address(recipientAddress),
391
- tx.pure.string(contentId),
392
- tx.pure.string(accessLevel),
393
- tx.pure.u64(expiresAt),
394
- ],
395
- });
396
-
397
- return tx;
398
- }
399
-
400
- /**
401
- * Build transaction to revoke access from a user
402
- */
403
- async buildRevokeAccessTransaction(options: AccessRevokeOptions): Promise<Transaction> {
404
- const { ownerAddress, recipientAddress, contentId } = options;
405
- const tx = new Transaction();
406
-
407
- tx.moveCall({
408
- target: `${this.packageId}::seal_access_control::revoke_access`,
409
- arguments: [
410
- tx.pure.address(ownerAddress),
411
- tx.pure.address(recipientAddress),
412
- tx.pure.string(contentId),
413
- ],
414
- });
415
-
416
- return tx;
417
- }
418
-
419
- /**
420
- * Build transaction to register content ownership
421
- */
422
- async buildRegisterContentTransaction(
423
- ownerAddress: string,
424
- contentId: string,
425
- contentHash: string
426
- ): Promise<Transaction> {
427
- const tx = new Transaction();
428
-
429
- tx.moveCall({
430
- target: `${this.packageId}::seal_access_control::register_content`,
431
- arguments: [
432
- tx.pure.address(ownerAddress),
433
- tx.pure.string(contentId),
434
- tx.pure.string(contentHash),
435
- tx.pure.string(''), // encryption_info
436
- ],
437
- });
438
-
439
- return tx;
440
- }
441
-
442
- // ==================== TRANSACTION BUILDERS ====================
443
-
444
- get tx() {
445
- return {
446
- /**
447
- * Grant access to encrypted memory
448
- */
449
- grantAccess: (options: AccessGrantOptions) => {
450
- return this.buildGrantAccessTransaction(options);
451
- },
452
-
453
- /**
454
- * Revoke access to encrypted memory
455
- */
456
- revokeAccess: (options: AccessRevokeOptions) => {
457
- return this.buildRevokeAccessTransaction(options);
458
- },
459
-
460
- /**
461
- * Register content ownership
462
- */
463
- registerContent: (ownerAddress: string, contentId: string, contentHash: string) => {
464
- return this.buildRegisterContentTransaction(ownerAddress, contentId, contentHash);
465
- },
466
-
467
- /**
468
- * Build access approval transaction
469
- */
470
- buildAccessTransaction: (userAddress: string, accessType: 'read' | 'write' = 'read') => {
471
- return this.buildAccessTransaction(userAddress, accessType);
472
- },
473
- };
474
- }
475
-
476
- // ==================== MOVE CALL BUILDERS ====================
477
-
478
- get call() {
479
- return {
480
- /**
481
- * Move call for granting access
482
- */
483
- grantAccess: (options: AccessGrantOptions): Thunk => {
484
- return async (tx) => {
485
- const grantTx = await this.buildGrantAccessTransaction(options);
486
- return grantTx;
487
- };
488
- },
489
-
490
- /**
491
- * Move call for revoking access
492
- */
493
- revokeAccess: (options: AccessRevokeOptions): Thunk => {
494
- return async (tx) => {
495
- const revokeTx = await this.buildRevokeAccessTransaction(options);
496
- return revokeTx;
497
- };
498
- },
499
- };
500
- }
501
-
502
- // ==================== ACCESS CONTROL QUERIES ====================
503
-
504
- /**
505
- * Check if a user has access to decrypt content
506
- */
507
- async hasAccess(
508
- userAddress: string,
509
- contentId: string,
510
- ownerAddress: string
511
- ): Promise<boolean> {
512
- try {
513
- if (userAddress === ownerAddress) {
514
- return true;
515
- }
516
-
517
- const tx = new Transaction();
518
- tx.moveCall({
519
- target: `${this.packageId}::seal_access_control::check_access`,
520
- arguments: [
521
- tx.pure.address(userAddress),
522
- tx.pure.string(contentId),
523
- tx.pure.address(ownerAddress),
524
- ],
525
- });
526
-
527
- const result = await this.suiClient.devInspectTransactionBlock({
528
- transactionBlock: tx,
529
- sender: userAddress,
530
- });
531
-
532
- return result.effects.status.status === 'success';
533
- } catch (error) {
534
- console.error(`Error checking access: ${error}`);
535
- return false;
536
- }
537
- }
538
-
539
- // ==================== VIEW METHODS ====================
540
-
541
- get view() {
542
- return {
543
- /**
544
- * Get access permissions for memories
545
- */
546
- getAccessPermissions: async (userAddress: string, memoryId?: string): Promise<AccessPermission[]> => {
547
- // Note: This would typically require event queries or indexing
548
- // For now, return empty array as this requires additional infrastructure
549
- console.warn('getAccessPermissions: This method requires event indexing infrastructure');
550
- return [];
551
- },
552
-
553
- /**
554
- * Check if user has access to content
555
- */
556
- hasAccess: (userAddress: string, contentId: string, ownerAddress: string) => {
557
- return this.hasAccess(userAddress, contentId, ownerAddress);
558
- },
559
- };
560
- }
561
-
562
- // ==================== UTILITY METHODS ====================
563
-
564
- /**
565
- * Generate content hash for verification
566
- */
567
- private async generateContentHash(data: Uint8Array): Promise<string> {
568
- // Create a new Uint8Array to ensure proper typing
569
- const dataArray = new Uint8Array(data);
570
- const hashBuffer = await crypto.subtle.digest('SHA-256', dataArray);
571
- const hashArray = Array.from(new Uint8Array(hashBuffer));
572
- return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
573
- }
574
-
575
- /**
576
- * Verify content integrity
577
- */
578
- async verifyContentHash(data: Uint8Array, expectedHash: string): Promise<boolean> {
579
- const actualHash = await this.generateContentHash(data);
580
- return actualHash === expectedHash;
581
- }
582
-
583
- /**
584
- * Check if SEAL service is available
585
- */
586
- isAvailable(): boolean {
587
- return this.sealService !== null;
588
- }
589
-
590
- /**
591
- * Get SEAL service configuration info
592
- */
593
- getClientInfo(): {
594
- isInitialized: boolean;
595
- packageId: string;
596
- encryptionEnabled: boolean;
597
- } {
598
- return {
599
- isInitialized: this.sealService !== null,
600
- packageId: this.packageId,
601
- encryptionEnabled: this.config.encryptionConfig?.enabled || false,
602
- };
603
- }
1
+ /**
2
+ * EncryptionService - SEAL-based encryption and access control
3
+ *
4
+ * Provides identity-based encryption using Mysten's SEAL SDK with decentralized
5
+ * key management and onchain access control policies.
6
+ */
7
+
8
+ import { SessionKey } from '@mysten/seal';
9
+ import { Transaction } from '@mysten/sui/transactions';
10
+ import { fromHex, toHex } from '@mysten/sui/utils';
11
+ import { SealService } from './SealService';
12
+ import { CrossContextPermissionService } from '../../services/CrossContextPermissionService';
13
+ import type {
14
+ ClientWithCoreApi,
15
+ PDWConfig,
16
+ AccessPermission,
17
+ AccessControlOptions,
18
+ Thunk,
19
+ SealEncryptionResult,
20
+ SealDecryptionOptions
21
+ } from '../../core/types';
22
+
23
+ export interface AccessGrantOptions {
24
+ ownerAddress: string;
25
+ recipientAddress: string;
26
+ contentId: string;
27
+ accessLevel: 'read' | 'write';
28
+ expiresIn?: number; // milliseconds from now
29
+ }
30
+
31
+ export interface AccessRevokeOptions {
32
+ ownerAddress: string;
33
+ recipientAddress: string;
34
+ contentId: string;
35
+ }
36
+
37
+ export class EncryptionService {
38
+ private sealService: SealService;
39
+ private suiClient: any;
40
+ private packageId: string;
41
+ private sessionKeyCache = new Map<string, SessionKey>();
42
+ private permissionService: CrossContextPermissionService;
43
+
44
+ constructor(
45
+ private client: ClientWithCoreApi,
46
+ private config: PDWConfig
47
+ ) {
48
+ this.suiClient = (client as any).client || client;
49
+ this.packageId = config.packageId || '';
50
+ this.sealService = this.initializeSealService();
51
+
52
+ // Initialize permission service for OAuth-style access control
53
+ this.permissionService = new CrossContextPermissionService(
54
+ {
55
+ packageId: this.packageId,
56
+ accessRegistryId: config.accessRegistryId || ''
57
+ },
58
+ this.suiClient
59
+ );
60
+ }
61
+
62
+ /**
63
+ * Initialize SEAL service with proper configuration
64
+ */
65
+ private initializeSealService(): SealService {
66
+ const encryptionConfig = this.config.encryptionConfig;
67
+
68
+ if (!encryptionConfig?.enabled) {
69
+ console.warn('Encryption is disabled in configuration - creating SealService anyway');
70
+ }
71
+
72
+ // Default testnet key servers (replace with actual server object IDs)
73
+ const defaultKeyServers = [
74
+ '0x73d05d62c18d9374e3ea529e8e0ed6161da1a141a94d3f76ae3fe4e99356db75',
75
+ '0xf5d14a81a982144ae441cd7d64b09027f116a468bd36e7eca494f750591623c8'
76
+ ];
77
+
78
+ const keyServerIds = encryptionConfig?.keyServers || defaultKeyServers;
79
+
80
+ // Create SealService with proper configuration
81
+ const sealConfig = {
82
+ suiClient: this.suiClient,
83
+ packageId: this.packageId,
84
+ keyServerUrls: [], // Empty for now, URLs handled separately if needed
85
+ keyServerObjectIds: keyServerIds,
86
+ network: process.env.NODE_ENV === 'production' ? 'mainnet' as const : 'testnet' as const,
87
+ threshold: 2,
88
+ enableMetrics: true,
89
+ retryAttempts: 3,
90
+ timeoutMs: 30000
91
+ };
92
+
93
+ return new SealService(sealConfig);
94
+ }
95
+
96
+ /**
97
+ * Build access approval transaction for SEAL key servers (LEGACY)
98
+ *
99
+ * @deprecated Use buildAccessTransactionForWallet instead for wallet-based permissions
100
+ */
101
+ async buildAccessTransaction(
102
+ userAddress: string,
103
+ accessType: 'read' | 'write' = 'read'
104
+ ): Promise<Transaction> {
105
+ console.warn('buildAccessTransaction is deprecated - use buildAccessTransactionForWallet for wallet-based permissions');
106
+
107
+ return this.buildAccessTransactionForWallet(userAddress, userAddress, accessType);
108
+ }
109
+
110
+ /**
111
+ * Build access approval transaction using capability pattern
112
+ * Uses CrossContextPermissionService for proper permission validation
113
+ *
114
+ * @param keyId - SEAL key ID bytes (required for capability pattern)
115
+ * @param memoryCapId - MemoryCap object ID (required for capability pattern)
116
+ * @returns Transaction for SEAL key server approval
117
+ */
118
+ buildAccessTransactionForCapability(
119
+ keyId: Uint8Array,
120
+ memoryCapId: string
121
+ ): Transaction {
122
+ return this.permissionService.buildSealApproveTransaction(keyId, memoryCapId);
123
+ }
124
+
125
+ /**
126
+ * Build access approval transaction for a requesting wallet address
127
+ * @deprecated Use buildAccessTransactionForCapability for capability-based access
128
+ */
129
+ async buildAccessTransactionForWallet(
130
+ userAddress: string,
131
+ requestingWallet: string,
132
+ _accessType: 'read' | 'write' = 'read'
133
+ ): Promise<Transaction> {
134
+ // Legacy fallback - use user address as identity bytes
135
+ const identityBytes = fromHex(userAddress.replace('0x', ''));
136
+
137
+ // Note: This uses the legacy buildSealApproveTransactionLegacy
138
+ return this.permissionService.buildSealApproveTransactionLegacy(
139
+ identityBytes,
140
+ requestingWallet
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Decrypt data using SEAL with session keys via SealService
146
+ * Handles both new binary format (Uint8Array) and legacy base64 format
147
+ * Validates wallet-based allowlists during approval flow
148
+ */
149
+ async decrypt(options: SealDecryptionOptions): Promise<Uint8Array> {
150
+ try {
151
+ console.log('🔓 EncryptionService: Starting SEAL decryption...');
152
+ console.log(` User address: ${options.userAddress}`);
153
+ const requestingWallet = options.requestingWallet ?? options.userAddress;
154
+ console.log(` Requesting wallet: ${requestingWallet}`);
155
+ if (!options.requestingWallet) {
156
+ console.warn('No requestingWallet provided for decryption - defaulting to user address');
157
+ }
158
+
159
+ // Get or create session key
160
+ let activeSessionKey = options.sessionKey;
161
+ if (!activeSessionKey) {
162
+ console.log('🔄 Creating session key...');
163
+ activeSessionKey = await this.getOrCreateSessionKey(options.userAddress);
164
+ }
165
+
166
+ // Build access transaction if not provided
167
+ let txBytes = options.signedTxBytes;
168
+ if (!txBytes) {
169
+ // Check if capability pattern is being used (preferred)
170
+ if (options.memoryCapId && options.keyId) {
171
+ console.log('🔄 Building capability-based access transaction...');
172
+ console.log(` MemoryCap ID: ${options.memoryCapId}`);
173
+ console.log(` Key ID length: ${options.keyId.length} bytes`);
174
+ const tx = this.buildAccessTransactionForCapability(options.keyId, options.memoryCapId);
175
+ // CRITICAL: onlyTransactionKind: true is REQUIRED for SEAL key server!
176
+ txBytes = await tx.build({ client: this.suiClient, onlyTransactionKind: true });
177
+ } else {
178
+ // Legacy fallback
179
+ console.log('🔄 Building legacy access transaction for requesting wallet...');
180
+ console.warn('⚠️ Using legacy allowlist pattern - consider using capability pattern');
181
+ const tx = await this.buildAccessTransactionForWallet(
182
+ options.userAddress,
183
+ requestingWallet,
184
+ 'read'
185
+ );
186
+ // CRITICAL: onlyTransactionKind: true is REQUIRED for SEAL key server!
187
+ txBytes = await tx.build({ client: this.suiClient, onlyTransactionKind: true });
188
+ }
189
+ }
190
+
191
+ if (!txBytes) {
192
+ throw new Error('Failed to build SEAL approval transaction bytes');
193
+ }
194
+
195
+ // CRITICAL: Handle both binary and legacy formats
196
+ let encryptedBytes: Uint8Array;
197
+
198
+ if (options.encryptedContent && options.encryptedContent instanceof Uint8Array) {
199
+ // **NEW BINARY FORMAT** (preferred - matches memory-workflow-seal.ts)
200
+ encryptedBytes = options.encryptedContent;
201
+ console.log('✅ Using new binary format (Uint8Array)');
202
+ console.log(` Binary data size: ${encryptedBytes.length} bytes`);
203
+ console.log(` Format: Direct binary (preserves SEAL integrity)`);
204
+ } else if (options.encryptedData) {
205
+ // **LEGACY BASE64 FORMAT** (deprecated but supported for backward compatibility)
206
+ console.log('⚠️ Using legacy base64 format (deprecated)');
207
+ const encryptedDataBase64 = options.encryptedData;
208
+ const binaryString = atob(encryptedDataBase64);
209
+ encryptedBytes = new Uint8Array(binaryString.length);
210
+ for (let i = 0; i < binaryString.length; i++) {
211
+ encryptedBytes[i] = binaryString.charCodeAt(i);
212
+ }
213
+ console.log(` Converted from base64: ${encryptedDataBase64.length} chars → ${encryptedBytes.length} bytes`);
214
+ console.log(' Recommendation: Use encryptedContent (Uint8Array) for better performance');
215
+ } else {
216
+ throw new Error('No encrypted data provided. Use either encryptedContent (Uint8Array) or encryptedData (base64 string)');
217
+ }
218
+
219
+ console.log('🔄 Calling SEAL decryption...');
220
+ console.log(` Encrypted data length: ${encryptedBytes.length} bytes`);
221
+ console.log(` Session key available: ${!!activeSessionKey}`);
222
+ console.log(` Transaction bytes length: ${txBytes.length} bytes`);
223
+
224
+ // Use SealService for decryption (matches memory-workflow-seal.ts pattern)
225
+ const decryptResult = await this.sealService.decryptData({
226
+ encryptedObject: encryptedBytes,
227
+ sessionKey: activeSessionKey,
228
+ txBytes
229
+ });
230
+
231
+ console.log(`✅ EncryptionService: SEAL decryption successful`);
232
+ console.log(` Decrypted data size: ${decryptResult.length} bytes`);
233
+ console.log(` Binary integrity preserved throughout process`);
234
+
235
+ return decryptResult;
236
+ } catch (error) {
237
+ throw new Error(`Decryption failed: ${error}`);
238
+ }
239
+ }
240
+
241
+ // ==================== SESSION KEY MANAGEMENT ====================
242
+
243
+ /**
244
+ * Create a new session key for a user via SealService
245
+ *
246
+ * Two usage patterns:
247
+ * 1. Frontend: Pass signPersonalMessageFn from @mysten/dapp-kit useSignPersonalMessage hook
248
+ * 2. Backend: Pass keypair for direct signing (auto-converts format)
249
+ */
250
+ async createSessionKey(
251
+ userAddress: string,
252
+ signer?: {
253
+ // Frontend pattern: dapp-kit signPersonalMessage function
254
+ signPersonalMessageFn?: (message: string) => Promise<{ signature: string }>;
255
+ // Backend pattern: Ed25519Keypair for direct signing
256
+ keypair?: any;
257
+ }
258
+ ): Promise<SessionKey> {
259
+ try {
260
+ console.log('🔄 EncryptionService: Creating SEAL session key...');
261
+ console.log(` User address: ${userAddress}`);
262
+ console.log(` TTL: 30 minutes (maximum allowed)`);
263
+
264
+ const sessionResult = await this.sealService.createSession({
265
+ address: userAddress,
266
+ packageId: this.packageId,
267
+ ttlMin: 30, // Use 30 minutes (maximum allowed)
268
+ });
269
+
270
+ // Handle signing based on provided signer type
271
+ if (signer?.signPersonalMessageFn) {
272
+ // Frontend pattern: Use dapp-kit signPersonalMessage (RECOMMENDED)
273
+ console.log('🔄 Signing with dapp-kit signPersonalMessage (recommended)...');
274
+ const personalMessage = sessionResult.personalMessage;
275
+
276
+ // Convert to string if it's a byte array
277
+ const messageString = typeof personalMessage === 'string'
278
+ ? personalMessage
279
+ : new TextDecoder().decode(personalMessage);
280
+
281
+ console.log(` Message (first 100 chars): ${messageString.substring(0, 100)}...`);
282
+
283
+ // Use dapp-kit signPersonalMessage - returns signature in correct format
284
+ const result = await signer.signPersonalMessageFn(messageString);
285
+
286
+ console.log(` Signature from dapp-kit: ${result.signature.substring(0, 20)}...`);
287
+ console.log(` Using dapp-kit signature format (already compatible with SEAL)`);
288
+
289
+ // Set signature directly - dapp-kit returns it in SEAL-compatible format
290
+ await sessionResult.sessionKey.setPersonalMessageSignature(result.signature);
291
+ console.log('✅ Personal message signed with dapp-kit');
292
+
293
+ } else if (signer?.keypair) {
294
+ // Backend pattern: Use Ed25519Keypair with format conversion
295
+ console.log('🔄 Signing with Ed25519Keypair (backend fallback)...');
296
+ const personalMessage = sessionResult.personalMessage;
297
+
298
+ // Convert to string if it's a byte array
299
+ const messageString = typeof personalMessage === 'string'
300
+ ? personalMessage
301
+ : new TextDecoder().decode(personalMessage);
302
+
303
+ console.log(` Message (first 100 chars): ${messageString.substring(0, 100)}...`);
304
+
305
+ // Sign with keypair
306
+ const messageSignature = await signer.keypair.signPersonalMessage(new TextEncoder().encode(messageString));
307
+
308
+ // CRITICAL FIX: Use signature as-is from Ed25519Keypair (SEAL expects original format)
309
+ // According to SEAL documentation, pass signature directly from keypair.signPersonalMessage()
310
+ console.log(` ✅ Using signature as-is from Ed25519Keypair (SEAL-compatible format)`);
311
+ console.log(` Original signature: ${messageSignature.signature.substring(0, 20)}...`);
312
+
313
+ // Set signature exactly as returned by keypair (no conversion needed)
314
+ await sessionResult.sessionKey.setPersonalMessageSignature(messageSignature.signature);
315
+ console.log('✅ Personal message signed with Ed25519Keypair');
316
+
317
+ } else {
318
+ console.log('⚠️ No signer provided - session key created but not signed');
319
+ console.log(' Note: Call setPersonalMessageSignature() later with wallet-signed message');
320
+ console.log(' Frontend: Use dapp-kit useSignPersonalMessage hook');
321
+ console.log(' Backend: Provide Ed25519Keypair');
322
+ }
323
+
324
+ // Cache the session key
325
+ this.sessionKeyCache.set(userAddress, sessionResult.sessionKey);
326
+
327
+ console.log('✅ EncryptionService: Session key created and cached');
328
+ return sessionResult.sessionKey;
329
+ } catch (error) {
330
+ throw new Error(`Failed to create session key: ${error}`);
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Get cached session key or create new one
336
+ */
337
+ async getOrCreateSessionKey(userAddress: string): Promise<SessionKey> {
338
+ const cached = this.sessionKeyCache.get(userAddress);
339
+ if (cached) {
340
+ return cached;
341
+ }
342
+
343
+ return this.createSessionKey(userAddress);
344
+ }
345
+
346
+ /**
347
+ * Export session key for persistence
348
+ */
349
+ async exportSessionKey(sessionKey: SessionKey): Promise<string> {
350
+ try {
351
+ const exported = sessionKey.export();
352
+ return JSON.stringify(exported);
353
+ } catch (error) {
354
+ throw new Error(`Failed to export session key: ${error}`);
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Import previously exported session key
360
+ */
361
+ async importSessionKey(exportedKey: string, userAddress?: string): Promise<SessionKey> {
362
+ try {
363
+ const keyData = JSON.parse(exportedKey);
364
+ const sessionKey = SessionKey.import(keyData, this.suiClient);
365
+
366
+ if (userAddress) {
367
+ this.sessionKeyCache.set(userAddress, sessionKey);
368
+ }
369
+
370
+ return sessionKey;
371
+ } catch (error) {
372
+ throw new Error(`Failed to import session key: ${error}`);
373
+ }
374
+ }
375
+
376
+ // ==================== ACCESS CONTROL TRANSACTIONS ====================
377
+
378
+ /**
379
+ * Create SEAL approval transaction bytes (matches memory-workflow-seal.ts pattern)
380
+ * Returns raw PTB format bytes for SEAL verification
381
+ */
382
+ async createSealApproveTransaction(userAddress: string, contentOwner: string): Promise<Uint8Array> {
383
+ try {
384
+ console.log('🔄 EncryptionService: Creating SEAL approval transaction...');
385
+ console.log(` User address: ${userAddress}`);
386
+ console.log(` Content owner: ${contentOwner}`);
387
+
388
+ // Create the approval transaction (not signed, just bytes)
389
+ const tx = await this.buildAccessTransactionForWallet(userAddress, contentOwner, 'read');
390
+ // SEAL REQUIREMENT: Must use onlyTransactionKind: true for PTB validation
391
+ const txBytes = await tx.build({ client: this.suiClient, onlyTransactionKind: true });
392
+
393
+ console.log(`✅ Created SEAL approval transaction bytes (${txBytes.length} bytes)`);
394
+ console.log(' Format: Raw PTB (Programmable Transaction Block) for SEAL verification');
395
+
396
+ return txBytes;
397
+ } catch (error) {
398
+ throw new Error(`Failed to create SEAL approval transaction: ${error}`);
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Build transaction to grant access to another user
404
+ */
405
+ async buildGrantAccessTransaction(options: AccessGrantOptions): Promise<Transaction> {
406
+ const { ownerAddress, recipientAddress, contentId, accessLevel, expiresIn } = options;
407
+ const tx = new Transaction();
408
+
409
+ const expiresAt = expiresIn ? Date.now() + expiresIn : Date.now() + 86400000; // 24h default
410
+
411
+ tx.moveCall({
412
+ target: `${this.packageId}::capability::grant_access`,
413
+ arguments: [
414
+ tx.pure.address(ownerAddress),
415
+ tx.pure.address(recipientAddress),
416
+ tx.pure.string(contentId),
417
+ tx.pure.string(accessLevel),
418
+ tx.pure.u64(expiresAt),
419
+ ],
420
+ });
421
+
422
+ return tx;
423
+ }
424
+
425
+ /**
426
+ * Build transaction to revoke access from a user
427
+ */
428
+ async buildRevokeAccessTransaction(options: AccessRevokeOptions): Promise<Transaction> {
429
+ const { ownerAddress, recipientAddress, contentId } = options;
430
+ const tx = new Transaction();
431
+
432
+ tx.moveCall({
433
+ target: `${this.packageId}::capability::revoke_access`,
434
+ arguments: [
435
+ tx.pure.address(ownerAddress),
436
+ tx.pure.address(recipientAddress),
437
+ tx.pure.string(contentId),
438
+ ],
439
+ });
440
+
441
+ return tx;
442
+ }
443
+
444
+ /**
445
+ * Build transaction to register content ownership
446
+ */
447
+ async buildRegisterContentTransaction(
448
+ ownerAddress: string,
449
+ contentId: string,
450
+ contentHash: string
451
+ ): Promise<Transaction> {
452
+ const tx = new Transaction();
453
+
454
+ tx.moveCall({
455
+ target: `${this.packageId}::capability::register_content`,
456
+ arguments: [
457
+ tx.pure.address(ownerAddress),
458
+ tx.pure.string(contentId),
459
+ tx.pure.string(contentHash),
460
+ tx.pure.string(''), // encryption_info
461
+ ],
462
+ });
463
+
464
+ return tx;
465
+ }
466
+
467
+ // ==================== TRANSACTION BUILDERS ====================
468
+
469
+ get tx() {
470
+ return {
471
+ /**
472
+ * Grant access to encrypted memory
473
+ */
474
+ grantAccess: (options: AccessGrantOptions) => {
475
+ return this.buildGrantAccessTransaction(options);
476
+ },
477
+
478
+ /**
479
+ * Revoke access to encrypted memory
480
+ */
481
+ revokeAccess: (options: AccessRevokeOptions) => {
482
+ return this.buildRevokeAccessTransaction(options);
483
+ },
484
+
485
+ /**
486
+ * Register content ownership
487
+ */
488
+ registerContent: (ownerAddress: string, contentId: string, contentHash: string) => {
489
+ return this.buildRegisterContentTransaction(ownerAddress, contentId, contentHash);
490
+ },
491
+
492
+ /**
493
+ * Build access approval transaction
494
+ */
495
+ buildAccessTransaction: (userAddress: string, accessType: 'read' | 'write' = 'read') => {
496
+ return this.buildAccessTransaction(userAddress, accessType);
497
+ },
498
+ };
499
+ }
500
+
501
+ // ==================== MOVE CALL BUILDERS ====================
502
+
503
+ get call() {
504
+ return {
505
+ /**
506
+ * Move call for granting access
507
+ */
508
+ grantAccess: (options: AccessGrantOptions): Thunk => {
509
+ return async (tx) => {
510
+ const grantTx = await this.buildGrantAccessTransaction(options);
511
+ return grantTx;
512
+ };
513
+ },
514
+
515
+ /**
516
+ * Move call for revoking access
517
+ */
518
+ revokeAccess: (options: AccessRevokeOptions): Thunk => {
519
+ return async (tx) => {
520
+ const revokeTx = await this.buildRevokeAccessTransaction(options);
521
+ return revokeTx;
522
+ };
523
+ },
524
+ };
525
+ }
526
+
527
+ // ==================== ACCESS CONTROL QUERIES ====================
528
+
529
+ /**
530
+ * Check if a user has access to decrypt content
531
+ */
532
+ async hasAccess(
533
+ userAddress: string,
534
+ contentId: string,
535
+ ownerAddress: string
536
+ ): Promise<boolean> {
537
+ try {
538
+ if (userAddress === ownerAddress) {
539
+ return true;
540
+ }
541
+
542
+ const tx = new Transaction();
543
+ tx.moveCall({
544
+ target: `${this.packageId}::capability::check_access`,
545
+ arguments: [
546
+ tx.pure.address(userAddress),
547
+ tx.pure.string(contentId),
548
+ tx.pure.address(ownerAddress),
549
+ ],
550
+ });
551
+
552
+ const result = await this.suiClient.devInspectTransactionBlock({
553
+ transactionBlock: tx,
554
+ sender: userAddress,
555
+ });
556
+
557
+ return result.effects.status.status === 'success';
558
+ } catch (error) {
559
+ console.error(`Error checking access: ${error}`);
560
+ return false;
561
+ }
562
+ }
563
+
564
+ // ==================== VIEW METHODS ====================
565
+
566
+ get view() {
567
+ return {
568
+ /**
569
+ * Get access permissions for memories
570
+ */
571
+ getAccessPermissions: async (userAddress: string, memoryId?: string): Promise<AccessPermission[]> => {
572
+ // Note: This would typically require event queries or indexing
573
+ // For now, return empty array as this requires additional infrastructure
574
+ console.warn('getAccessPermissions: This method requires event indexing infrastructure');
575
+ return [];
576
+ },
577
+
578
+ /**
579
+ * Check if user has access to content
580
+ */
581
+ hasAccess: (userAddress: string, contentId: string, ownerAddress: string) => {
582
+ return this.hasAccess(userAddress, contentId, ownerAddress);
583
+ },
584
+ };
585
+ }
586
+
587
+ // ==================== UTILITY METHODS ====================
588
+
589
+ /**
590
+ * Generate content hash for verification
591
+ */
592
+ private async generateContentHash(data: Uint8Array): Promise<string> {
593
+ // Create a new Uint8Array to ensure proper typing
594
+ const dataArray = new Uint8Array(data);
595
+ const hashBuffer = await crypto.subtle.digest('SHA-256', dataArray);
596
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
597
+ return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
598
+ }
599
+
600
+ /**
601
+ * Verify content integrity
602
+ */
603
+ async verifyContentHash(data: Uint8Array, expectedHash: string): Promise<boolean> {
604
+ const actualHash = await this.generateContentHash(data);
605
+ return actualHash === expectedHash;
606
+ }
607
+
608
+ /**
609
+ * Check if SEAL service is available
610
+ */
611
+ isAvailable(): boolean {
612
+ return this.sealService !== null;
613
+ }
614
+
615
+ /**
616
+ * Get SEAL service configuration info
617
+ */
618
+ getClientInfo(): {
619
+ isInitialized: boolean;
620
+ packageId: string;
621
+ encryptionEnabled: boolean;
622
+ } {
623
+ return {
624
+ isInitialized: this.sealService !== null,
625
+ packageId: this.packageId,
626
+ encryptionEnabled: this.config.encryptionConfig?.enabled || false,
627
+ };
628
+ }
604
629
  }