@phantom/react-native-sdk 0.1.6 → 0.1.7

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/dist/index.js CHANGED
@@ -295,7 +295,8 @@ var import_sdk_types = require("@phantom/sdk-types");
295
295
  var ReactNativeStamper = class {
296
296
  // Optional for PKI, required for OIDC
297
297
  constructor(config = {}) {
298
- this.keyInfo = null;
298
+ this.activeKeyRecord = null;
299
+ this.pendingKeyRecord = null;
299
300
  this.algorithm = import_sdk_types.Algorithm.ed25519;
300
301
  this.type = "PKI";
301
302
  this.keyPrefix = config.keyPrefix || "phantom-rn-stamper";
@@ -305,32 +306,27 @@ var ReactNativeStamper = class {
305
306
  * Initialize the stamper and generate/load cryptographic keys
306
307
  */
307
308
  async init() {
308
- const storedSecretKey = await this.getStoredSecretKey();
309
- if (storedSecretKey) {
310
- const keyInfo2 = await this.getStoredKeyInfo();
311
- if (keyInfo2) {
312
- this.keyInfo = keyInfo2;
313
- return keyInfo2;
314
- }
309
+ this.activeKeyRecord = await this.loadActiveKeyRecord();
310
+ if (!this.activeKeyRecord) {
311
+ this.activeKeyRecord = await this.generateAndStoreNewKeyRecord("active");
315
312
  }
316
- const keyInfo = await this.generateAndStoreKeyPair();
317
- this.keyInfo = keyInfo;
318
- return keyInfo;
313
+ this.pendingKeyRecord = await this.loadPendingKeyRecord();
314
+ return this.activeKeyRecord.keyInfo;
319
315
  }
320
316
  /**
321
317
  * Get the current key information
322
318
  */
323
319
  getKeyInfo() {
324
- return this.keyInfo;
320
+ return this.activeKeyRecord?.keyInfo || null;
325
321
  }
326
322
  /**
327
323
  * Generate and store a new key pair, replacing any existing keys
328
324
  */
329
325
  async resetKeyPair() {
330
326
  await this.clear();
331
- const keyInfo = await this.generateAndStoreKeyPair();
332
- this.keyInfo = keyInfo;
333
- return keyInfo;
327
+ this.activeKeyRecord = await this.generateAndStoreNewKeyRecord("active");
328
+ this.pendingKeyRecord = null;
329
+ return this.activeKeyRecord.keyInfo;
334
330
  }
335
331
  /**
336
332
  * Create X-Phantom-Stamp header value using stored secret key
@@ -338,79 +334,129 @@ var ReactNativeStamper = class {
338
334
  * @returns Complete X-Phantom-Stamp header value
339
335
  */
340
336
  async stamp(params) {
341
- if (!this.keyInfo) {
337
+ if (!this.activeKeyRecord) {
342
338
  throw new Error("Stamper not initialized. Call init() first.");
343
339
  }
344
- const storedSecretKey = await this.getStoredSecretKey();
345
- if (!storedSecretKey) {
346
- throw new Error("Secret key not found in secure storage");
347
- }
348
- const apiKeyStamper = new import_api_key_stamper.ApiKeyStamper({ apiSecretKey: storedSecretKey });
340
+ const apiKeyStamper = new import_api_key_stamper.ApiKeyStamper({ apiSecretKey: this.activeKeyRecord.secretKey });
349
341
  return await apiKeyStamper.stamp(params);
350
342
  }
351
343
  /**
352
344
  * Clear all stored keys from SecureStore
353
345
  */
354
346
  async clear() {
355
- const infoKey = this.getInfoKey();
356
- const secretKey = this.getSecretKey();
347
+ const activeKey = this.getActiveKeyName();
348
+ const pendingKey = this.getPendingKeyName();
349
+ try {
350
+ await SecureStore2.deleteItemAsync(activeKey);
351
+ } catch (error) {
352
+ }
357
353
  try {
358
- await SecureStore2.deleteItemAsync(infoKey);
354
+ await SecureStore2.deleteItemAsync(pendingKey);
359
355
  } catch (error) {
360
356
  }
357
+ this.activeKeyRecord = null;
358
+ this.pendingKeyRecord = null;
359
+ }
360
+ /**
361
+ * Generate a new keypair for rotation without making it active
362
+ */
363
+ async rotateKeyPair() {
364
+ this.pendingKeyRecord = await this.generateAndStoreNewKeyRecord("pending");
365
+ return this.pendingKeyRecord.keyInfo;
366
+ }
367
+ /**
368
+ * Switch to the pending keypair, making it active and cleaning up the old one
369
+ */
370
+ async commitRotation(authenticatorId) {
371
+ if (!this.pendingKeyRecord) {
372
+ throw new Error("No pending keypair to commit");
373
+ }
374
+ if (this.activeKeyRecord) {
375
+ try {
376
+ await SecureStore2.deleteItemAsync(this.getActiveKeyName());
377
+ } catch (error) {
378
+ }
379
+ }
380
+ this.pendingKeyRecord.status = "active";
381
+ this.pendingKeyRecord.authenticatorId = authenticatorId;
382
+ this.pendingKeyRecord.keyInfo.authenticatorId = authenticatorId;
383
+ this.activeKeyRecord = this.pendingKeyRecord;
384
+ this.pendingKeyRecord = null;
385
+ await this.storeKeyRecord(this.activeKeyRecord, "active");
361
386
  try {
362
- await SecureStore2.deleteItemAsync(secretKey);
387
+ await SecureStore2.deleteItemAsync(this.getPendingKeyName());
363
388
  } catch (error) {
364
389
  }
365
- this.keyInfo = null;
366
390
  }
367
- async generateAndStoreKeyPair() {
391
+ /**
392
+ * Discard the pending keypair on rotation failure
393
+ */
394
+ async rollbackRotation() {
395
+ if (!this.pendingKeyRecord) {
396
+ return;
397
+ }
398
+ try {
399
+ await SecureStore2.deleteItemAsync(this.getPendingKeyName());
400
+ } catch (error) {
401
+ }
402
+ this.pendingKeyRecord = null;
403
+ }
404
+ async generateAndStoreNewKeyRecord(type) {
368
405
  const keypair = (0, import_crypto.generateKeyPair)();
369
406
  const keyId = this.createKeyId(keypair.publicKey);
407
+ const now = Date.now();
370
408
  const keyInfo = {
371
409
  keyId,
372
- publicKey: keypair.publicKey
410
+ publicKey: keypair.publicKey,
411
+ createdAt: now
412
+ };
413
+ const record = {
414
+ keyInfo,
415
+ secretKey: keypair.secretKey,
416
+ createdAt: now,
417
+ expiresAt: 0,
418
+ // Not used anymore, kept for backward compatibility
419
+ status: type
373
420
  };
374
- await this.storeKeyPair(keypair.secretKey, keyInfo);
375
- return keyInfo;
421
+ await this.storeKeyRecord(record, type);
422
+ return record;
376
423
  }
377
424
  createKeyId(publicKey) {
378
425
  return (0, import_base64url.base64urlEncode)(new TextEncoder().encode(publicKey)).substring(0, 16);
379
426
  }
380
- async storeKeyPair(secretKey, keyInfo) {
381
- const infoKey = this.getInfoKey();
382
- const secretKeyName = this.getSecretKey();
383
- await SecureStore2.setItemAsync(infoKey, JSON.stringify(keyInfo), {
384
- requireAuthentication: false
385
- });
386
- await SecureStore2.setItemAsync(secretKeyName, secretKey, {
427
+ async storeKeyRecord(record, type) {
428
+ const keyName = type === "active" ? this.getActiveKeyName() : this.getPendingKeyName();
429
+ await SecureStore2.setItemAsync(keyName, JSON.stringify(record), {
387
430
  requireAuthentication: false
388
431
  });
389
432
  }
390
- async getStoredKeyInfo() {
433
+ async loadActiveKeyRecord() {
391
434
  try {
392
- const infoKey = this.getInfoKey();
393
- const storedInfo = await SecureStore2.getItemAsync(infoKey);
394
- if (storedInfo) {
395
- return JSON.parse(storedInfo);
435
+ const activeKey = this.getActiveKeyName();
436
+ const storedRecord = await SecureStore2.getItemAsync(activeKey);
437
+ if (storedRecord) {
438
+ return JSON.parse(storedRecord);
396
439
  }
397
440
  } catch (error) {
398
441
  }
399
442
  return null;
400
443
  }
401
- async getStoredSecretKey() {
444
+ async loadPendingKeyRecord() {
402
445
  try {
403
- const secretKeyName = this.getSecretKey();
404
- return await SecureStore2.getItemAsync(secretKeyName);
446
+ const pendingKey = this.getPendingKeyName();
447
+ const storedRecord = await SecureStore2.getItemAsync(pendingKey);
448
+ if (storedRecord) {
449
+ return JSON.parse(storedRecord);
450
+ }
405
451
  } catch (error) {
406
- return null;
407
452
  }
453
+ return null;
408
454
  }
409
- getInfoKey() {
410
- return `${this.keyPrefix}-${this.organizationId}-info`;
455
+ getActiveKeyName() {
456
+ return `${this.keyPrefix}-${this.organizationId}-active`;
411
457
  }
412
- getSecretKey() {
413
- return `${this.keyPrefix}-${this.organizationId}-secret`;
458
+ getPendingKeyName() {
459
+ return `${this.keyPrefix}-${this.organizationId}-pending`;
414
460
  }
415
461
  };
416
462
 
package/dist/index.mjs CHANGED
@@ -251,7 +251,8 @@ import { Algorithm } from "@phantom/sdk-types";
251
251
  var ReactNativeStamper = class {
252
252
  // Optional for PKI, required for OIDC
253
253
  constructor(config = {}) {
254
- this.keyInfo = null;
254
+ this.activeKeyRecord = null;
255
+ this.pendingKeyRecord = null;
255
256
  this.algorithm = Algorithm.ed25519;
256
257
  this.type = "PKI";
257
258
  this.keyPrefix = config.keyPrefix || "phantom-rn-stamper";
@@ -261,32 +262,27 @@ var ReactNativeStamper = class {
261
262
  * Initialize the stamper and generate/load cryptographic keys
262
263
  */
263
264
  async init() {
264
- const storedSecretKey = await this.getStoredSecretKey();
265
- if (storedSecretKey) {
266
- const keyInfo2 = await this.getStoredKeyInfo();
267
- if (keyInfo2) {
268
- this.keyInfo = keyInfo2;
269
- return keyInfo2;
270
- }
265
+ this.activeKeyRecord = await this.loadActiveKeyRecord();
266
+ if (!this.activeKeyRecord) {
267
+ this.activeKeyRecord = await this.generateAndStoreNewKeyRecord("active");
271
268
  }
272
- const keyInfo = await this.generateAndStoreKeyPair();
273
- this.keyInfo = keyInfo;
274
- return keyInfo;
269
+ this.pendingKeyRecord = await this.loadPendingKeyRecord();
270
+ return this.activeKeyRecord.keyInfo;
275
271
  }
276
272
  /**
277
273
  * Get the current key information
278
274
  */
279
275
  getKeyInfo() {
280
- return this.keyInfo;
276
+ return this.activeKeyRecord?.keyInfo || null;
281
277
  }
282
278
  /**
283
279
  * Generate and store a new key pair, replacing any existing keys
284
280
  */
285
281
  async resetKeyPair() {
286
282
  await this.clear();
287
- const keyInfo = await this.generateAndStoreKeyPair();
288
- this.keyInfo = keyInfo;
289
- return keyInfo;
283
+ this.activeKeyRecord = await this.generateAndStoreNewKeyRecord("active");
284
+ this.pendingKeyRecord = null;
285
+ return this.activeKeyRecord.keyInfo;
290
286
  }
291
287
  /**
292
288
  * Create X-Phantom-Stamp header value using stored secret key
@@ -294,79 +290,129 @@ var ReactNativeStamper = class {
294
290
  * @returns Complete X-Phantom-Stamp header value
295
291
  */
296
292
  async stamp(params) {
297
- if (!this.keyInfo) {
293
+ if (!this.activeKeyRecord) {
298
294
  throw new Error("Stamper not initialized. Call init() first.");
299
295
  }
300
- const storedSecretKey = await this.getStoredSecretKey();
301
- if (!storedSecretKey) {
302
- throw new Error("Secret key not found in secure storage");
303
- }
304
- const apiKeyStamper = new ApiKeyStamper({ apiSecretKey: storedSecretKey });
296
+ const apiKeyStamper = new ApiKeyStamper({ apiSecretKey: this.activeKeyRecord.secretKey });
305
297
  return await apiKeyStamper.stamp(params);
306
298
  }
307
299
  /**
308
300
  * Clear all stored keys from SecureStore
309
301
  */
310
302
  async clear() {
311
- const infoKey = this.getInfoKey();
312
- const secretKey = this.getSecretKey();
303
+ const activeKey = this.getActiveKeyName();
304
+ const pendingKey = this.getPendingKeyName();
305
+ try {
306
+ await SecureStore2.deleteItemAsync(activeKey);
307
+ } catch (error) {
308
+ }
313
309
  try {
314
- await SecureStore2.deleteItemAsync(infoKey);
310
+ await SecureStore2.deleteItemAsync(pendingKey);
315
311
  } catch (error) {
316
312
  }
313
+ this.activeKeyRecord = null;
314
+ this.pendingKeyRecord = null;
315
+ }
316
+ /**
317
+ * Generate a new keypair for rotation without making it active
318
+ */
319
+ async rotateKeyPair() {
320
+ this.pendingKeyRecord = await this.generateAndStoreNewKeyRecord("pending");
321
+ return this.pendingKeyRecord.keyInfo;
322
+ }
323
+ /**
324
+ * Switch to the pending keypair, making it active and cleaning up the old one
325
+ */
326
+ async commitRotation(authenticatorId) {
327
+ if (!this.pendingKeyRecord) {
328
+ throw new Error("No pending keypair to commit");
329
+ }
330
+ if (this.activeKeyRecord) {
331
+ try {
332
+ await SecureStore2.deleteItemAsync(this.getActiveKeyName());
333
+ } catch (error) {
334
+ }
335
+ }
336
+ this.pendingKeyRecord.status = "active";
337
+ this.pendingKeyRecord.authenticatorId = authenticatorId;
338
+ this.pendingKeyRecord.keyInfo.authenticatorId = authenticatorId;
339
+ this.activeKeyRecord = this.pendingKeyRecord;
340
+ this.pendingKeyRecord = null;
341
+ await this.storeKeyRecord(this.activeKeyRecord, "active");
317
342
  try {
318
- await SecureStore2.deleteItemAsync(secretKey);
343
+ await SecureStore2.deleteItemAsync(this.getPendingKeyName());
319
344
  } catch (error) {
320
345
  }
321
- this.keyInfo = null;
322
346
  }
323
- async generateAndStoreKeyPair() {
347
+ /**
348
+ * Discard the pending keypair on rotation failure
349
+ */
350
+ async rollbackRotation() {
351
+ if (!this.pendingKeyRecord) {
352
+ return;
353
+ }
354
+ try {
355
+ await SecureStore2.deleteItemAsync(this.getPendingKeyName());
356
+ } catch (error) {
357
+ }
358
+ this.pendingKeyRecord = null;
359
+ }
360
+ async generateAndStoreNewKeyRecord(type) {
324
361
  const keypair = generateKeyPair();
325
362
  const keyId = this.createKeyId(keypair.publicKey);
363
+ const now = Date.now();
326
364
  const keyInfo = {
327
365
  keyId,
328
- publicKey: keypair.publicKey
366
+ publicKey: keypair.publicKey,
367
+ createdAt: now
368
+ };
369
+ const record = {
370
+ keyInfo,
371
+ secretKey: keypair.secretKey,
372
+ createdAt: now,
373
+ expiresAt: 0,
374
+ // Not used anymore, kept for backward compatibility
375
+ status: type
329
376
  };
330
- await this.storeKeyPair(keypair.secretKey, keyInfo);
331
- return keyInfo;
377
+ await this.storeKeyRecord(record, type);
378
+ return record;
332
379
  }
333
380
  createKeyId(publicKey) {
334
381
  return base64urlEncode(new TextEncoder().encode(publicKey)).substring(0, 16);
335
382
  }
336
- async storeKeyPair(secretKey, keyInfo) {
337
- const infoKey = this.getInfoKey();
338
- const secretKeyName = this.getSecretKey();
339
- await SecureStore2.setItemAsync(infoKey, JSON.stringify(keyInfo), {
340
- requireAuthentication: false
341
- });
342
- await SecureStore2.setItemAsync(secretKeyName, secretKey, {
383
+ async storeKeyRecord(record, type) {
384
+ const keyName = type === "active" ? this.getActiveKeyName() : this.getPendingKeyName();
385
+ await SecureStore2.setItemAsync(keyName, JSON.stringify(record), {
343
386
  requireAuthentication: false
344
387
  });
345
388
  }
346
- async getStoredKeyInfo() {
389
+ async loadActiveKeyRecord() {
347
390
  try {
348
- const infoKey = this.getInfoKey();
349
- const storedInfo = await SecureStore2.getItemAsync(infoKey);
350
- if (storedInfo) {
351
- return JSON.parse(storedInfo);
391
+ const activeKey = this.getActiveKeyName();
392
+ const storedRecord = await SecureStore2.getItemAsync(activeKey);
393
+ if (storedRecord) {
394
+ return JSON.parse(storedRecord);
352
395
  }
353
396
  } catch (error) {
354
397
  }
355
398
  return null;
356
399
  }
357
- async getStoredSecretKey() {
400
+ async loadPendingKeyRecord() {
358
401
  try {
359
- const secretKeyName = this.getSecretKey();
360
- return await SecureStore2.getItemAsync(secretKeyName);
402
+ const pendingKey = this.getPendingKeyName();
403
+ const storedRecord = await SecureStore2.getItemAsync(pendingKey);
404
+ if (storedRecord) {
405
+ return JSON.parse(storedRecord);
406
+ }
361
407
  } catch (error) {
362
- return null;
363
408
  }
409
+ return null;
364
410
  }
365
- getInfoKey() {
366
- return `${this.keyPrefix}-${this.organizationId}-info`;
411
+ getActiveKeyName() {
412
+ return `${this.keyPrefix}-${this.organizationId}-active`;
367
413
  }
368
- getSecretKey() {
369
- return `${this.keyPrefix}-${this.organizationId}-secret`;
414
+ getPendingKeyName() {
415
+ return `${this.keyPrefix}-${this.organizationId}-pending`;
370
416
  }
371
417
  };
372
418
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phantom/react-native-sdk",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Phantom Wallet SDK for React Native and Expo applications",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -47,11 +47,11 @@
47
47
  "dependencies": {
48
48
  "@phantom/api-key-stamper": "^0.1.5",
49
49
  "@phantom/base64url": "^0.1.0",
50
- "@phantom/client": "^0.1.8",
50
+ "@phantom/client": "^0.1.10",
51
51
  "@phantom/constants": "^0.0.3",
52
52
  "@phantom/crypto": "^0.1.2",
53
- "@phantom/embedded-provider-core": "^0.1.7",
54
- "@phantom/sdk-types": "^0.1.4",
53
+ "@phantom/embedded-provider-core": "^0.1.9",
54
+ "@phantom/sdk-types": "^0.1.5",
55
55
  "@types/bs58": "^5.0.0",
56
56
  "bs58": "^6.0.0",
57
57
  "buffer": "^6.0.3"