@phantom/indexed-db-stamper 0.1.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 ADDED
@@ -0,0 +1,165 @@
1
+ # @phantom/indexed-db-stamper
2
+
3
+ A secure IndexedDB-based key stamper for the Phantom Wallet SDK that stores cryptographic keys directly in the browser's IndexedDB without ever exposing private key material.
4
+
5
+ ## Features
6
+
7
+ - **Maximum Security**: Uses non-extractable Ed25519 keys that never exist in JavaScript memory
8
+ - **Web Crypto API**: Leverages browser's native cryptographic secure context
9
+ - **Secure Storage**: Keys stored as non-extractable CryptoKey objects in IndexedDB
10
+ - **Raw Signatures**: Uses Ed25519 raw signature format for maximum efficiency
11
+ - **Hardware Integration**: Utilizes browser's hardware-backed cryptographic isolation when available
12
+ - **Compatible Interface**: Drop-in replacement for other stamper implementations
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @phantom/indexed-db-stamper
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### Basic Usage
23
+
24
+ ```typescript
25
+ import { IndexedDbStamper } from '@phantom/indexed-db-stamper';
26
+
27
+ // Create stamper instance
28
+ const stamper = new IndexedDbStamper({
29
+ dbName: 'my-app-keys', // optional, defaults to 'phantom-indexed-db-stamper'
30
+ storeName: 'crypto-keys', // optional, defaults to 'crypto-keys'
31
+ keyName: 'signing-key' // optional, defaults to 'signing-key'
32
+ });
33
+
34
+ // Initialize and generate/load keys
35
+ const keyInfo = await stamper.init();
36
+ console.log('Key ID:', keyInfo.keyId);
37
+ console.log('Public Key:', keyInfo.publicKey);
38
+
39
+ // Create X-Phantom-Stamp header value for API requests
40
+ const requestData = Buffer.from(JSON.stringify({ action: 'transfer', amount: 100 }), 'utf8');
41
+ const stamp = await stamper.stamp({ data: requestData });
42
+ console.log('X-Phantom-Stamp:', stamp);
43
+ ```
44
+
45
+ ### Advanced Usage
46
+
47
+ ```typescript
48
+ // Check if already initialized
49
+ if (stamper.getKeyInfo()) {
50
+ console.log('Stamper already has keys');
51
+ } else {
52
+ await stamper.init();
53
+ }
54
+
55
+ // Reset keys (generate new keypair)
56
+ const newKeyInfo = await stamper.resetKeyPair();
57
+
58
+ // Stamp different data types with PKI (default)
59
+ const stringData = Buffer.from('string data', 'utf8');
60
+ const binaryData = Buffer.from([1, 2, 3]);
61
+ const jsonData = Buffer.from(JSON.stringify({ key: 'value' }), 'utf8');
62
+
63
+ await stamper.stamp({ data: stringData });
64
+ await stamper.stamp({ data: binaryData, type: 'PKI' }); // explicit PKI type
65
+ await stamper.stamp({ data: jsonData });
66
+
67
+ // OIDC type stamping (requires idToken and salt)
68
+ const oidcStamp = await stamper.stamp({
69
+ data: requestData,
70
+ type: 'OIDC',
71
+ idToken: 'your-id-token',
72
+ salt: 'your-salt-value'
73
+ });
74
+
75
+ // Clear all stored keys
76
+ await stamper.clear();
77
+ ```
78
+
79
+ ## API Reference
80
+
81
+ ### Constructor
82
+
83
+ ```typescript
84
+ new IndexedDbStamper(config?: IndexedDbStamperConfig)
85
+ ```
86
+
87
+ **Config Options:**
88
+ - `dbName?: string` - IndexedDB database name (default: 'phantom-indexed-db-stamper')
89
+ - `storeName?: string` - Object store name (default: 'crypto-keys')
90
+ - `keyName?: string` - Key identifier prefix (default: 'signing-key')
91
+
92
+ ### Methods
93
+
94
+ #### `init(): Promise<StamperKeyInfo>`
95
+ Initialize the stamper and generate/load cryptographic keys.
96
+
97
+ **Returns:** `StamperKeyInfo` with `keyId` and `publicKey`
98
+
99
+ #### `getKeyInfo(): StamperKeyInfo | null`
100
+ Get current key information without async operation.
101
+
102
+ #### `resetKeyPair(): Promise<StamperKeyInfo>`
103
+ Generate and store a new key pair, replacing any existing keys.
104
+
105
+ #### `stamp(params: { data: Buffer; type?: 'PKI'; idToken?: never; salt?: never; } | { data: Buffer; type: 'OIDC'; idToken: string; salt: string; }): Promise<string>`
106
+ Create X-Phantom-Stamp header value using the stored private key.
107
+
108
+ **Parameters:**
109
+ - `params.data: Buffer` - Data to sign (typically JSON stringified request body)
110
+ - `params.type?: 'PKI' | 'OIDC'` - Stamp type (defaults to 'PKI')
111
+ - `params.idToken?: string` - Required for OIDC type
112
+ - `params.salt?: string` - Required for OIDC type
113
+
114
+ **Returns:** Complete X-Phantom-Stamp header value (base64url-encoded JSON with base64url-encoded publicKey, signature, and kind fields)
115
+
116
+ **Note:** The public key is stored internally in base58 format but converted to base64url when creating stamps for API compatibility.
117
+
118
+ #### `clear(): Promise<void>`
119
+ Remove all stored keys from IndexedDB.
120
+
121
+ ## Security Features
122
+
123
+ ### Non-Extractable Keys
124
+ The stamper generates Ed25519 CryptoKey objects with `extractable: false`, meaning private keys cannot be exported, extracted, or accessed outside of Web Crypto API signing operations. This provides the strongest possible security in browser environments.
125
+
126
+ ### Cryptographic Isolation
127
+ Keys are generated and stored entirely within the browser's secure cryptographic context:
128
+ - Private keys never exist in JavaScript memory at any point
129
+ - Signing operations happen within Web Crypto API secure boundaries
130
+ - Secure elements used when available by the browser
131
+ - Origin-based security isolation through IndexedDB
132
+
133
+ ### Signature Format
134
+ The stamper uses Ed25519 signatures in their native 64-byte format, providing efficient and secure signing operations.
135
+
136
+ ## Error Handling
137
+
138
+ The stamper includes comprehensive error handling for:
139
+
140
+ ```typescript
141
+ // Environment validation
142
+ if (typeof window === 'undefined') {
143
+ throw new Error('IndexedDbStamper requires a browser environment');
144
+ }
145
+
146
+ // Initialization checks
147
+ if (!stamper.getKeyInfo()) {
148
+ throw new Error('Stamper not initialized. Call init() first.');
149
+ }
150
+
151
+ // Storage errors
152
+ try {
153
+ await stamper.init();
154
+ } catch (error) {
155
+ console.error('Failed to initialize stamper:', error);
156
+ }
157
+ ```
158
+
159
+ ## Browser Compatibility
160
+
161
+ Requires IndexedDB and Web Crypto API support.
162
+
163
+ ## License
164
+
165
+ MIT
@@ -0,0 +1,69 @@
1
+ import { Buffer } from 'buffer';
2
+ import { StamperWithKeyManagement, Algorithm, StamperKeyInfo } from '@phantom/sdk-types';
3
+
4
+ type IndexedDbStamperConfig = {
5
+ dbName?: string;
6
+ storeName?: string;
7
+ keyName?: string;
8
+ };
9
+ /**
10
+ * IndexedDB-based key manager that stores cryptographic keys securely in IndexedDB
11
+ * and performs signing operations without ever exposing private key material.
12
+ *
13
+ * Security model:
14
+ * - Generates non-extractable Ed25519 keypairs using Web Crypto API
15
+ * - Stores keys entirely within Web Crypto API secure context
16
+ * - Private keys NEVER exist in JavaScript memory
17
+ * - Provides signing methods without exposing private keys
18
+ * - Maximum security using browser's native cryptographic isolation
19
+ */
20
+ declare class IndexedDbStamper implements StamperWithKeyManagement {
21
+ private dbName;
22
+ private storeName;
23
+ private keyName;
24
+ private db;
25
+ private keyInfo;
26
+ private cryptoKeyPair;
27
+ algorithm: Algorithm;
28
+ constructor(config?: IndexedDbStamperConfig);
29
+ /**
30
+ * Initialize the stamper by opening IndexedDB and retrieving or generating keys
31
+ */
32
+ init(): Promise<StamperKeyInfo>;
33
+ /**
34
+ * Get the public key information
35
+ */
36
+ getKeyInfo(): StamperKeyInfo | null;
37
+ /**
38
+ * Reset the key pair by generating a new one
39
+ */
40
+ resetKeyPair(): Promise<StamperKeyInfo>;
41
+ /**
42
+ * Create X-Phantom-Stamp header value using stored private key
43
+ * @param params - Parameters object with data and optional type/options
44
+ * @returns Complete X-Phantom-Stamp header value
45
+ */
46
+ stamp(params: {
47
+ data: Buffer;
48
+ type?: 'PKI';
49
+ idToken?: never;
50
+ salt?: never;
51
+ } | {
52
+ data: Buffer;
53
+ type: 'OIDC';
54
+ idToken: string;
55
+ salt: string;
56
+ }): Promise<string>;
57
+ /**
58
+ * Clear all stored keys
59
+ */
60
+ clear(): Promise<void>;
61
+ private clearStoredKeys;
62
+ private openDB;
63
+ private generateAndStoreKeyPair;
64
+ private storeKeyPair;
65
+ private loadKeyPair;
66
+ private getStoredKeyInfo;
67
+ }
68
+
69
+ export { IndexedDbStamper, IndexedDbStamperConfig };
package/dist/index.js ADDED
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ IndexedDbStamper: () => IndexedDbStamper
34
+ });
35
+ module.exports = __toCommonJS(src_exports);
36
+ var import_base64url = require("@phantom/base64url");
37
+ var import_bs58 = __toESM(require("bs58"));
38
+ var import_sdk_types = require("@phantom/sdk-types");
39
+ var IndexedDbStamper = class {
40
+ // Use Ed25519 for maximum security and performance
41
+ constructor(config = {}) {
42
+ this.db = null;
43
+ this.keyInfo = null;
44
+ this.cryptoKeyPair = null;
45
+ this.algorithm = import_sdk_types.Algorithm.ed25519;
46
+ if (typeof window === "undefined" || !window.indexedDB) {
47
+ throw new Error("IndexedDbStamper requires a browser environment with IndexedDB support");
48
+ }
49
+ this.dbName = config.dbName || "phantom-indexed-db-stamper";
50
+ this.storeName = config.storeName || "crypto-keys";
51
+ this.keyName = config.keyName || "signing-key";
52
+ }
53
+ /**
54
+ * Initialize the stamper by opening IndexedDB and retrieving or generating keys
55
+ */
56
+ async init() {
57
+ await this.openDB();
58
+ let keyInfo = await this.getStoredKeyInfo();
59
+ if (!keyInfo) {
60
+ keyInfo = await this.generateAndStoreKeyPair();
61
+ } else {
62
+ await this.loadKeyPair();
63
+ }
64
+ this.keyInfo = keyInfo;
65
+ return keyInfo;
66
+ }
67
+ /**
68
+ * Get the public key information
69
+ */
70
+ getKeyInfo() {
71
+ return this.keyInfo;
72
+ }
73
+ /**
74
+ * Reset the key pair by generating a new one
75
+ */
76
+ async resetKeyPair() {
77
+ await this.clearStoredKeys();
78
+ const keyInfo = await this.generateAndStoreKeyPair();
79
+ this.keyInfo = keyInfo;
80
+ return keyInfo;
81
+ }
82
+ /**
83
+ * Create X-Phantom-Stamp header value using stored private key
84
+ * @param params - Parameters object with data and optional type/options
85
+ * @returns Complete X-Phantom-Stamp header value
86
+ */
87
+ async stamp(params) {
88
+ const { data, type = "PKI" } = params;
89
+ if (!this.keyInfo || !this.cryptoKeyPair) {
90
+ throw new Error("Stamper not initialized. Call init() first.");
91
+ }
92
+ const dataBytes = new Uint8Array(data);
93
+ const signature = await crypto.subtle.sign(
94
+ {
95
+ name: this.algorithm,
96
+ hash: "SHA-256"
97
+ },
98
+ this.cryptoKeyPair.privateKey,
99
+ dataBytes
100
+ );
101
+ const signatureBase64url = (0, import_base64url.base64urlEncode)(new Uint8Array(signature));
102
+ const stampData = type === "PKI" ? {
103
+ // Decode base58 public key to bytes, then encode as base64url (consistent with ApiKeyStamper)
104
+ publicKey: (0, import_base64url.base64urlEncode)(import_bs58.default.decode(this.keyInfo.publicKey)),
105
+ signature: signatureBase64url,
106
+ kind: "PKI",
107
+ algorithm: this.algorithm
108
+ } : {
109
+ kind: "OIDC",
110
+ idToken: params.idToken,
111
+ publicKey: (0, import_base64url.base64urlEncode)(import_bs58.default.decode(this.keyInfo.publicKey)),
112
+ salt: params.salt,
113
+ algorithm: this.algorithm,
114
+ signature: signatureBase64url
115
+ };
116
+ const stampJson = JSON.stringify(stampData);
117
+ return (0, import_base64url.base64urlEncode)(stampJson);
118
+ }
119
+ /**
120
+ * Clear all stored keys
121
+ */
122
+ async clear() {
123
+ await this.clearStoredKeys();
124
+ this.keyInfo = null;
125
+ this.cryptoKeyPair = null;
126
+ }
127
+ async clearStoredKeys() {
128
+ if (!this.db) {
129
+ await this.openDB();
130
+ }
131
+ return new Promise((resolve, reject) => {
132
+ const transaction = this.db.transaction([this.storeName], "readwrite");
133
+ const store = transaction.objectStore(this.storeName);
134
+ const deleteKeyPair = store.delete(`${this.keyName}-keypair`);
135
+ const deleteKeyInfo = store.delete(`${this.keyName}-info`);
136
+ let completed = 0;
137
+ const total = 2;
138
+ const checkComplete = () => {
139
+ completed++;
140
+ if (completed === total) {
141
+ resolve();
142
+ }
143
+ };
144
+ deleteKeyPair.onsuccess = checkComplete;
145
+ deleteKeyInfo.onsuccess = checkComplete;
146
+ deleteKeyPair.onerror = () => reject(deleteKeyPair.error);
147
+ deleteKeyInfo.onerror = () => reject(deleteKeyInfo.error);
148
+ });
149
+ }
150
+ async openDB() {
151
+ return new Promise((resolve, reject) => {
152
+ const request = indexedDB.open(this.dbName, 1);
153
+ request.onerror = () => reject(request.error);
154
+ request.onsuccess = () => {
155
+ this.db = request.result;
156
+ resolve();
157
+ };
158
+ request.onupgradeneeded = (event) => {
159
+ const db = event.target.result;
160
+ if (!db.objectStoreNames.contains(this.storeName)) {
161
+ db.createObjectStore(this.storeName);
162
+ }
163
+ };
164
+ });
165
+ }
166
+ async generateAndStoreKeyPair() {
167
+ this.cryptoKeyPair = await crypto.subtle.generateKey(
168
+ {
169
+ name: "Ed25519"
170
+ },
171
+ false,
172
+ // non-extractable - private key can never be exported
173
+ ["sign", "verify"]
174
+ );
175
+ const rawPublicKeyBuffer = await crypto.subtle.exportKey("raw", this.cryptoKeyPair.publicKey);
176
+ const publicKeyBase58 = import_bs58.default.encode(new Uint8Array(rawPublicKeyBuffer));
177
+ const keyIdBuffer = await crypto.subtle.digest("SHA-256", rawPublicKeyBuffer);
178
+ const keyId = (0, import_base64url.base64urlEncode)(new Uint8Array(keyIdBuffer)).substring(0, 16);
179
+ const keyInfo = {
180
+ keyId,
181
+ publicKey: publicKeyBase58
182
+ };
183
+ await this.storeKeyPair(this.cryptoKeyPair, keyInfo);
184
+ return keyInfo;
185
+ }
186
+ async storeKeyPair(keyPair, keyInfo) {
187
+ if (!this.db) {
188
+ throw new Error("Database not initialized");
189
+ }
190
+ return new Promise((resolve, reject) => {
191
+ const transaction = this.db.transaction([this.storeName], "readwrite");
192
+ const store = transaction.objectStore(this.storeName);
193
+ const keyPairRequest = store.put(keyPair, `${this.keyName}-keypair`);
194
+ const keyInfoRequest = store.put(keyInfo, `${this.keyName}-info`);
195
+ let completed = 0;
196
+ const total = 2;
197
+ const checkComplete = () => {
198
+ completed++;
199
+ if (completed === total) {
200
+ resolve();
201
+ }
202
+ };
203
+ keyPairRequest.onsuccess = checkComplete;
204
+ keyInfoRequest.onsuccess = checkComplete;
205
+ keyPairRequest.onerror = () => reject(keyPairRequest.error);
206
+ keyInfoRequest.onerror = () => reject(keyInfoRequest.error);
207
+ });
208
+ }
209
+ async loadKeyPair() {
210
+ if (!this.db) {
211
+ return;
212
+ }
213
+ return new Promise((resolve, reject) => {
214
+ const transaction = this.db.transaction([this.storeName], "readonly");
215
+ const store = transaction.objectStore(this.storeName);
216
+ const request = store.get(`${this.keyName}-keypair`);
217
+ request.onsuccess = () => {
218
+ this.cryptoKeyPair = request.result || null;
219
+ resolve();
220
+ };
221
+ request.onerror = () => reject(request.error);
222
+ });
223
+ }
224
+ async getStoredKeyInfo() {
225
+ if (!this.db) {
226
+ return null;
227
+ }
228
+ return new Promise((resolve, reject) => {
229
+ const transaction = this.db.transaction([this.storeName], "readonly");
230
+ const store = transaction.objectStore(this.storeName);
231
+ const request = store.get(`${this.keyName}-info`);
232
+ request.onsuccess = () => resolve(request.result || null);
233
+ request.onerror = () => reject(request.error);
234
+ });
235
+ }
236
+ };
package/dist/index.mjs ADDED
@@ -0,0 +1,205 @@
1
+ // src/index.ts
2
+ import { base64urlEncode } from "@phantom/base64url";
3
+ import bs58 from "bs58";
4
+ import { Algorithm } from "@phantom/sdk-types";
5
+ var IndexedDbStamper = class {
6
+ // Use Ed25519 for maximum security and performance
7
+ constructor(config = {}) {
8
+ this.db = null;
9
+ this.keyInfo = null;
10
+ this.cryptoKeyPair = null;
11
+ this.algorithm = Algorithm.ed25519;
12
+ if (typeof window === "undefined" || !window.indexedDB) {
13
+ throw new Error("IndexedDbStamper requires a browser environment with IndexedDB support");
14
+ }
15
+ this.dbName = config.dbName || "phantom-indexed-db-stamper";
16
+ this.storeName = config.storeName || "crypto-keys";
17
+ this.keyName = config.keyName || "signing-key";
18
+ }
19
+ /**
20
+ * Initialize the stamper by opening IndexedDB and retrieving or generating keys
21
+ */
22
+ async init() {
23
+ await this.openDB();
24
+ let keyInfo = await this.getStoredKeyInfo();
25
+ if (!keyInfo) {
26
+ keyInfo = await this.generateAndStoreKeyPair();
27
+ } else {
28
+ await this.loadKeyPair();
29
+ }
30
+ this.keyInfo = keyInfo;
31
+ return keyInfo;
32
+ }
33
+ /**
34
+ * Get the public key information
35
+ */
36
+ getKeyInfo() {
37
+ return this.keyInfo;
38
+ }
39
+ /**
40
+ * Reset the key pair by generating a new one
41
+ */
42
+ async resetKeyPair() {
43
+ await this.clearStoredKeys();
44
+ const keyInfo = await this.generateAndStoreKeyPair();
45
+ this.keyInfo = keyInfo;
46
+ return keyInfo;
47
+ }
48
+ /**
49
+ * Create X-Phantom-Stamp header value using stored private key
50
+ * @param params - Parameters object with data and optional type/options
51
+ * @returns Complete X-Phantom-Stamp header value
52
+ */
53
+ async stamp(params) {
54
+ const { data, type = "PKI" } = params;
55
+ if (!this.keyInfo || !this.cryptoKeyPair) {
56
+ throw new Error("Stamper not initialized. Call init() first.");
57
+ }
58
+ const dataBytes = new Uint8Array(data);
59
+ const signature = await crypto.subtle.sign(
60
+ {
61
+ name: this.algorithm,
62
+ hash: "SHA-256"
63
+ },
64
+ this.cryptoKeyPair.privateKey,
65
+ dataBytes
66
+ );
67
+ const signatureBase64url = base64urlEncode(new Uint8Array(signature));
68
+ const stampData = type === "PKI" ? {
69
+ // Decode base58 public key to bytes, then encode as base64url (consistent with ApiKeyStamper)
70
+ publicKey: base64urlEncode(bs58.decode(this.keyInfo.publicKey)),
71
+ signature: signatureBase64url,
72
+ kind: "PKI",
73
+ algorithm: this.algorithm
74
+ } : {
75
+ kind: "OIDC",
76
+ idToken: params.idToken,
77
+ publicKey: base64urlEncode(bs58.decode(this.keyInfo.publicKey)),
78
+ salt: params.salt,
79
+ algorithm: this.algorithm,
80
+ signature: signatureBase64url
81
+ };
82
+ const stampJson = JSON.stringify(stampData);
83
+ return base64urlEncode(stampJson);
84
+ }
85
+ /**
86
+ * Clear all stored keys
87
+ */
88
+ async clear() {
89
+ await this.clearStoredKeys();
90
+ this.keyInfo = null;
91
+ this.cryptoKeyPair = null;
92
+ }
93
+ async clearStoredKeys() {
94
+ if (!this.db) {
95
+ await this.openDB();
96
+ }
97
+ return new Promise((resolve, reject) => {
98
+ const transaction = this.db.transaction([this.storeName], "readwrite");
99
+ const store = transaction.objectStore(this.storeName);
100
+ const deleteKeyPair = store.delete(`${this.keyName}-keypair`);
101
+ const deleteKeyInfo = store.delete(`${this.keyName}-info`);
102
+ let completed = 0;
103
+ const total = 2;
104
+ const checkComplete = () => {
105
+ completed++;
106
+ if (completed === total) {
107
+ resolve();
108
+ }
109
+ };
110
+ deleteKeyPair.onsuccess = checkComplete;
111
+ deleteKeyInfo.onsuccess = checkComplete;
112
+ deleteKeyPair.onerror = () => reject(deleteKeyPair.error);
113
+ deleteKeyInfo.onerror = () => reject(deleteKeyInfo.error);
114
+ });
115
+ }
116
+ async openDB() {
117
+ return new Promise((resolve, reject) => {
118
+ const request = indexedDB.open(this.dbName, 1);
119
+ request.onerror = () => reject(request.error);
120
+ request.onsuccess = () => {
121
+ this.db = request.result;
122
+ resolve();
123
+ };
124
+ request.onupgradeneeded = (event) => {
125
+ const db = event.target.result;
126
+ if (!db.objectStoreNames.contains(this.storeName)) {
127
+ db.createObjectStore(this.storeName);
128
+ }
129
+ };
130
+ });
131
+ }
132
+ async generateAndStoreKeyPair() {
133
+ this.cryptoKeyPair = await crypto.subtle.generateKey(
134
+ {
135
+ name: "Ed25519"
136
+ },
137
+ false,
138
+ // non-extractable - private key can never be exported
139
+ ["sign", "verify"]
140
+ );
141
+ const rawPublicKeyBuffer = await crypto.subtle.exportKey("raw", this.cryptoKeyPair.publicKey);
142
+ const publicKeyBase58 = bs58.encode(new Uint8Array(rawPublicKeyBuffer));
143
+ const keyIdBuffer = await crypto.subtle.digest("SHA-256", rawPublicKeyBuffer);
144
+ const keyId = base64urlEncode(new Uint8Array(keyIdBuffer)).substring(0, 16);
145
+ const keyInfo = {
146
+ keyId,
147
+ publicKey: publicKeyBase58
148
+ };
149
+ await this.storeKeyPair(this.cryptoKeyPair, keyInfo);
150
+ return keyInfo;
151
+ }
152
+ async storeKeyPair(keyPair, keyInfo) {
153
+ if (!this.db) {
154
+ throw new Error("Database not initialized");
155
+ }
156
+ return new Promise((resolve, reject) => {
157
+ const transaction = this.db.transaction([this.storeName], "readwrite");
158
+ const store = transaction.objectStore(this.storeName);
159
+ const keyPairRequest = store.put(keyPair, `${this.keyName}-keypair`);
160
+ const keyInfoRequest = store.put(keyInfo, `${this.keyName}-info`);
161
+ let completed = 0;
162
+ const total = 2;
163
+ const checkComplete = () => {
164
+ completed++;
165
+ if (completed === total) {
166
+ resolve();
167
+ }
168
+ };
169
+ keyPairRequest.onsuccess = checkComplete;
170
+ keyInfoRequest.onsuccess = checkComplete;
171
+ keyPairRequest.onerror = () => reject(keyPairRequest.error);
172
+ keyInfoRequest.onerror = () => reject(keyInfoRequest.error);
173
+ });
174
+ }
175
+ async loadKeyPair() {
176
+ if (!this.db) {
177
+ return;
178
+ }
179
+ return new Promise((resolve, reject) => {
180
+ const transaction = this.db.transaction([this.storeName], "readonly");
181
+ const store = transaction.objectStore(this.storeName);
182
+ const request = store.get(`${this.keyName}-keypair`);
183
+ request.onsuccess = () => {
184
+ this.cryptoKeyPair = request.result || null;
185
+ resolve();
186
+ };
187
+ request.onerror = () => reject(request.error);
188
+ });
189
+ }
190
+ async getStoredKeyInfo() {
191
+ if (!this.db) {
192
+ return null;
193
+ }
194
+ return new Promise((resolve, reject) => {
195
+ const transaction = this.db.transaction([this.storeName], "readonly");
196
+ const store = transaction.objectStore(this.storeName);
197
+ const request = store.get(`${this.keyName}-info`);
198
+ request.onsuccess = () => resolve(request.result || null);
199
+ request.onerror = () => reject(request.error);
200
+ });
201
+ }
202
+ };
203
+ export {
204
+ IndexedDbStamper
205
+ };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@phantom/indexed-db-stamper",
3
+ "version": "0.1.1",
4
+ "description": "IndexedDB stamper for Phantom Wallet SDK with non-extractable key storage",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "?pack-release": "When https://github.com/changesets/changesets/issues/432 has a solution we can remove this trick",
17
+ "pack-release": "rimraf ./_release && yarn pack && mkdir ./_release && tar zxvf ./package.tgz --directory ./_release && rm ./package.tgz",
18
+ "build": "rimraf ./dist && tsup",
19
+ "dev": "tsc --watch",
20
+ "clean": "rm -rf dist",
21
+ "test": "jest",
22
+ "test:watch": "jest --watch",
23
+ "lint": "tsc --noEmit && eslint --cache . --ext .ts,.tsx",
24
+ "check-types": "tsc --noEmit",
25
+ "prettier": "prettier --write \"src/**/*.{ts,tsx}\""
26
+ },
27
+ "devDependencies": {
28
+ "@types/bs58": "^5.0.0",
29
+ "@types/jest": "^29.5.12",
30
+ "@types/node": "^20.11.0",
31
+ "eslint": "8.53.0",
32
+ "fake-indexeddb": "^6.0.0",
33
+ "jest": "^29.7.0",
34
+ "jest-environment-jsdom": "^29.7.0",
35
+ "prettier": "^3.5.2",
36
+ "rimraf": "^6.0.1",
37
+ "ts-jest": "^29.1.2",
38
+ "tsup": "^6.7.0",
39
+ "typescript": "^5.0.4"
40
+ },
41
+ "dependencies": {
42
+ "@phantom/base64url": "^0.1.0",
43
+ "@phantom/crypto": "^0.1.1",
44
+ "@phantom/embedded-provider-core": "^0.1.2",
45
+ "@phantom/sdk-types": "^0.1.1",
46
+ "bs58": "^6.0.0",
47
+ "buffer": "^6.0.3"
48
+ },
49
+ "files": [
50
+ "dist"
51
+ ],
52
+ "publishConfig": {
53
+ "directory": "_release/package"
54
+ }
55
+ }