11import { Hex , Address , PublicKey , Secp256k1 , Bytes } from 'ox'
2+ import { resolveCoreEnv , type CoreEnv , type CryptoLike , type StorageLike , type TextEncodingLike } from '../../env.js'
23import { PkStore } from './index.js'
34
45export interface EncryptedData {
@@ -17,6 +18,7 @@ export class EncryptedPksDb {
1718 constructor (
1819 private readonly localStorageKeyPrefix : string = 'e_pk_key_' ,
1920 tableName : string = 'e_pk' ,
21+ private readonly env ?: CoreEnv ,
2022 ) {
2123 this . tableName = tableName
2224 }
@@ -25,9 +27,59 @@ export class EncryptedPksDb {
2527 return `pk_${ address . toLowerCase ( ) } `
2628 }
2729
30+ private getIndexedDB ( ) : IDBFactory {
31+ const globalObj = globalThis as any
32+ const indexedDb = this . env ?. indexedDB ?? globalObj . indexedDB ?? globalObj . window ?. indexedDB
33+ if ( ! indexedDb ) {
34+ throw new Error ( 'indexedDB is not available' )
35+ }
36+ return indexedDb
37+ }
38+
39+ private getStorage ( ) : StorageLike {
40+ const storage = resolveCoreEnv ( this . env ) . storage
41+ if ( ! storage ) {
42+ throw new Error ( 'storage is not available' )
43+ }
44+ return storage
45+ }
46+
47+ private getCrypto ( ) : CryptoLike {
48+ const globalObj = globalThis as any
49+ const crypto = this . env ?. crypto ?? globalObj . crypto ?? globalObj . window ?. crypto
50+ if ( ! crypto ?. subtle || ! crypto ?. getRandomValues ) {
51+ throw new Error ( 'crypto.subtle is not available' )
52+ }
53+ return crypto
54+ }
55+
56+ private getTextEncoderCtor ( ) : TextEncodingLike [ 'TextEncoder' ] {
57+ const globalObj = globalThis as any
58+ if ( this . env ?. text && ( ! this . env . text . TextEncoder || ! this . env . text . TextDecoder ) ) {
59+ throw new Error ( 'env.text must provide both TextEncoder and TextDecoder' )
60+ }
61+ const encoderCtor = this . env ?. text ?. TextEncoder ?? globalObj . TextEncoder ?? globalObj . window ?. TextEncoder
62+ if ( ! encoderCtor ) {
63+ throw new Error ( 'TextEncoder is not available' )
64+ }
65+ return encoderCtor
66+ }
67+
68+ private getTextDecoderCtor ( ) : TextEncodingLike [ 'TextDecoder' ] {
69+ const globalObj = globalThis as any
70+ if ( this . env ?. text && ( ! this . env . text . TextEncoder || ! this . env . text . TextDecoder ) ) {
71+ throw new Error ( 'env.text must provide both TextEncoder and TextDecoder' )
72+ }
73+ const decoderCtor = this . env ?. text ?. TextDecoder ?? globalObj . TextDecoder ?? globalObj . window ?. TextDecoder
74+ if ( ! decoderCtor ) {
75+ throw new Error ( 'TextDecoder is not available' )
76+ }
77+ return decoderCtor
78+ }
79+
2880 private openDB ( ) : Promise < IDBDatabase > {
2981 return new Promise ( ( resolve , reject ) => {
30- const request = indexedDB . open ( this . dbName , this . dbVersion )
82+ const request = this . getIndexedDB ( ) . open ( this . dbName , this . dbVersion )
3183 request . onupgradeneeded = ( ) => {
3284 const db = request . result
3385 if ( ! db . objectStoreNames . contains ( this . tableName ) ) {
@@ -73,7 +125,11 @@ export class EncryptedPksDb {
73125 }
74126
75127 async generateAndStore ( ) : Promise < EncryptedData > {
76- const encryptionKey = await window . crypto . subtle . generateKey ( { name : 'AES-GCM' , length : 256 } , true , [
128+ const crypto = this . getCrypto ( )
129+ const storage = this . getStorage ( )
130+ const TextEncoderCtor = this . getTextEncoderCtor ( )
131+
132+ const encryptionKey = await crypto . subtle . generateKey ( { name : 'AES-GCM' , length : 256 } , true , [
77133 'encrypt' ,
78134 'decrypt' ,
79135 ] )
@@ -84,13 +140,13 @@ export class EncryptedPksDb {
84140 const address = Address . fromPublicKey ( publicKey )
85141 const keyPointer = this . localStorageKeyPrefix + address
86142
87- const exportedKey = await window . crypto . subtle . exportKey ( 'jwk' , encryptionKey )
88- window . localStorage . setItem ( keyPointer , JSON . stringify ( exportedKey ) )
143+ const exportedKey = await crypto . subtle . exportKey ( 'jwk' , encryptionKey )
144+ storage . setItem ( keyPointer , JSON . stringify ( exportedKey ) )
89145
90- const encoder = new TextEncoder ( )
146+ const encoder = new TextEncoderCtor ( )
91147 const encodedPk = encoder . encode ( privateKey )
92- const iv = window . crypto . getRandomValues ( new Uint8Array ( 12 ) )
93- const encryptedBuffer = await window . crypto . subtle . encrypt ( { name : 'AES-GCM' , iv } , encryptionKey , encodedPk )
148+ const iv = crypto . getRandomValues ( new Uint8Array ( 12 ) )
149+ const encryptedBuffer = await crypto . subtle . encrypt ( { name : 'AES-GCM' , iv } , encryptionKey , encodedPk )
94150
95151 const encrypted : EncryptedData = {
96152 iv,
@@ -113,7 +169,7 @@ export class EncryptedPksDb {
113169 async getEncryptedPkStore ( address : Address . Address ) : Promise < EncryptedPkStore | undefined > {
114170 const entry = await this . getEncryptedEntry ( address )
115171 if ( ! entry ) return
116- return new EncryptedPkStore ( entry )
172+ return new EncryptedPkStore ( entry , this . env )
117173 }
118174
119175 async listAddresses ( ) : Promise < Address . Address [ ] > {
@@ -125,12 +181,41 @@ export class EncryptedPksDb {
125181 const dbKey = this . computeDbKey ( address )
126182 await this . putData ( dbKey , undefined )
127183 const keyPointer = this . localStorageKeyPrefix + address
128- window . localStorage . removeItem ( keyPointer )
184+ this . getStorage ( ) . removeItem ( keyPointer )
129185 }
130186}
131187
132188export class EncryptedPkStore implements PkStore {
133- constructor ( private readonly encrypted : EncryptedData ) { }
189+ constructor (
190+ private readonly encrypted : EncryptedData ,
191+ private readonly env ?: CoreEnv ,
192+ ) { }
193+
194+ private getStorage ( ) : StorageLike {
195+ const storage = resolveCoreEnv ( this . env ) . storage
196+ if ( ! storage ) {
197+ throw new Error ( 'storage is not available' )
198+ }
199+ return storage
200+ }
201+
202+ private getCrypto ( ) : CryptoLike {
203+ const globalObj = globalThis as any
204+ const crypto = this . env ?. crypto ?? globalObj . crypto ?? globalObj . window ?. crypto
205+ if ( ! crypto ?. subtle ) {
206+ throw new Error ( 'crypto.subtle is not available' )
207+ }
208+ return crypto
209+ }
210+
211+ private getTextDecoderCtor ( ) : TextEncodingLike [ 'TextDecoder' ] {
212+ const globalObj = globalThis as any
213+ const decoderCtor = this . env ?. text ?. TextDecoder ?? globalObj . TextDecoder ?? globalObj . window ?. TextDecoder
214+ if ( ! decoderCtor ) {
215+ throw new Error ( 'TextDecoder is not available' )
216+ }
217+ return decoderCtor
218+ }
134219
135220 address ( ) : Address . Address {
136221 return this . encrypted . address
@@ -141,16 +226,20 @@ export class EncryptedPkStore implements PkStore {
141226 }
142227
143228 async signDigest ( digest : Bytes . Bytes ) : Promise < { r : bigint ; s : bigint ; yParity : number } > {
144- const keyJson = window . localStorage . getItem ( this . encrypted . keyPointer )
229+ const storage = this . getStorage ( )
230+ const crypto = this . getCrypto ( )
231+ const TextDecoderCtor = this . getTextDecoderCtor ( )
232+
233+ const keyJson = storage . getItem ( this . encrypted . keyPointer )
145234 if ( ! keyJson ) throw new Error ( 'Encryption key not found in localStorage' )
146235 const jwk = JSON . parse ( keyJson )
147- const encryptionKey = await window . crypto . subtle . importKey ( 'jwk' , jwk , { name : 'AES-GCM' } , false , [ 'decrypt' ] )
148- const decryptedBuffer = await window . crypto . subtle . decrypt (
236+ const encryptionKey = await crypto . subtle . importKey ( 'jwk' , jwk , { name : 'AES-GCM' } , false , [ 'decrypt' ] )
237+ const decryptedBuffer = await crypto . subtle . decrypt (
149238 { name : 'AES-GCM' , iv : this . encrypted . iv } ,
150239 encryptionKey ,
151240 this . encrypted . data ,
152241 )
153- const decoder = new TextDecoder ( )
242+ const decoder = new TextDecoderCtor ( )
154243 const privateKey = decoder . decode ( decryptedBuffer ) as Hex . Hex
155244 return Secp256k1 . sign ( { payload : digest , privateKey } )
156245 }
0 commit comments