@0xsequence/wallet-core 3.0.0-beta.9 → 3.0.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.
- package/.turbo/turbo-build.log +2 -2
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +148 -0
- package/dist/bundler/bundlers/pimlico.d.ts +3 -2
- package/dist/bundler/bundlers/pimlico.d.ts.map +1 -1
- package/dist/bundler/bundlers/pimlico.js +9 -3
- package/dist/env.d.ts +22 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +42 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/signers/index.d.ts +1 -1
- package/dist/signers/passkey.d.ts +11 -3
- package/dist/signers/passkey.d.ts.map +1 -1
- package/dist/signers/passkey.js +12 -6
- package/dist/signers/pk/encrypted.d.ts +13 -2
- package/dist/signers/pk/encrypted.d.ts.map +1 -1
- package/dist/signers/pk/encrypted.js +94 -15
- package/dist/signers/pk/index.d.ts +1 -1
- package/dist/signers/session/explicit.d.ts.map +1 -1
- package/dist/signers/session/explicit.js +2 -2
- package/dist/signers/session/implicit.d.ts.map +1 -1
- package/dist/signers/session/implicit.js +1 -1
- package/dist/state/local/index.d.ts.map +1 -1
- package/dist/state/local/index.js +3 -2
- package/dist/state/local/indexed-db.d.ts +4 -1
- package/dist/state/local/indexed-db.d.ts.map +1 -1
- package/dist/state/local/indexed-db.js +12 -2
- package/dist/state/remote/dev-http.d.ts +2 -1
- package/dist/state/remote/dev-http.d.ts.map +1 -1
- package/dist/state/remote/dev-http.js +11 -5
- package/dist/state/sequence/index.d.ts +2 -1
- package/dist/state/sequence/index.d.ts.map +1 -1
- package/dist/state/sequence/index.js +14 -5
- package/dist/wallet.js +2 -2
- package/eslint.config.js +12 -0
- package/package.json +12 -10
- package/src/bundler/bundlers/pimlico.ts +10 -4
- package/src/env.ts +68 -0
- package/src/index.ts +1 -0
- package/src/signers/index.ts +1 -1
- package/src/signers/passkey.ts +21 -5
- package/src/signers/pk/encrypted.ts +103 -14
- package/src/signers/pk/index.ts +1 -1
- package/src/signers/session/explicit.ts +2 -9
- package/src/signers/session/implicit.ts +1 -2
- package/src/state/local/index.ts +4 -2
- package/src/state/local/indexed-db.ts +15 -2
- package/src/state/remote/dev-http.ts +11 -5
- package/src/state/sequence/index.ts +15 -6
- package/src/wallet.ts +2 -2
- package/test/constants.ts +2 -0
- package/test/envelope.test.ts +0 -1
- package/test/signers-pk.test.ts +1 -1
- package/test/signers-session-implicit.test.ts +0 -2
- package/test/state/debug.test.ts +2 -3
package/src/env.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export type StorageLike = {
|
|
2
|
+
getItem: (key: string) => string | null
|
|
3
|
+
setItem: (key: string, value: string) => void
|
|
4
|
+
removeItem: (key: string) => void
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type CryptoLike = {
|
|
8
|
+
subtle: SubtleCrypto
|
|
9
|
+
getRandomValues: <T extends ArrayBufferView>(array: T) => T
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type TextEncodingLike = {
|
|
13
|
+
TextEncoder: typeof TextEncoder
|
|
14
|
+
TextDecoder: typeof TextDecoder
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type CoreEnv = {
|
|
18
|
+
fetch?: typeof fetch
|
|
19
|
+
crypto?: CryptoLike
|
|
20
|
+
storage?: StorageLike
|
|
21
|
+
indexedDB?: IDBFactory
|
|
22
|
+
text?: Partial<TextEncodingLike>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isStorageLike(value: unknown): value is StorageLike {
|
|
26
|
+
if (!value || typeof value !== 'object') return false
|
|
27
|
+
const candidate = value as StorageLike
|
|
28
|
+
return (
|
|
29
|
+
typeof candidate.getItem === 'function' &&
|
|
30
|
+
typeof candidate.setItem === 'function' &&
|
|
31
|
+
typeof candidate.removeItem === 'function'
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolveCoreEnv(env?: CoreEnv): CoreEnv {
|
|
36
|
+
const globalObj = globalThis as any
|
|
37
|
+
const windowObj = typeof window !== 'undefined' ? window : (globalObj.window ?? {})
|
|
38
|
+
let storage: StorageLike | undefined
|
|
39
|
+
let text: Partial<TextEncodingLike> | undefined
|
|
40
|
+
|
|
41
|
+
if (isStorageLike(env?.storage)) {
|
|
42
|
+
storage = env.storage
|
|
43
|
+
} else if (isStorageLike(windowObj.localStorage)) {
|
|
44
|
+
storage = windowObj.localStorage
|
|
45
|
+
} else if (isStorageLike(globalObj.localStorage)) {
|
|
46
|
+
storage = globalObj.localStorage
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (env?.text) {
|
|
50
|
+
if (!env.text.TextEncoder || !env.text.TextDecoder) {
|
|
51
|
+
throw new Error('env.text must provide both TextEncoder and TextDecoder')
|
|
52
|
+
}
|
|
53
|
+
text = env.text
|
|
54
|
+
} else {
|
|
55
|
+
text = {
|
|
56
|
+
TextEncoder: windowObj.TextEncoder ?? globalObj.TextEncoder,
|
|
57
|
+
TextDecoder: windowObj.TextDecoder ?? globalObj.TextDecoder,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
fetch: env?.fetch ?? windowObj.fetch ?? globalObj.fetch,
|
|
63
|
+
crypto: env?.crypto ?? windowObj.crypto ?? globalObj.crypto,
|
|
64
|
+
storage,
|
|
65
|
+
indexedDB: env?.indexedDB ?? windowObj.indexedDB ?? globalObj.indexedDB,
|
|
66
|
+
text,
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * as State from './state/index.js'
|
|
|
5
5
|
export * as Bundler from './bundler/index.js'
|
|
6
6
|
export * as Envelope from './envelope.js'
|
|
7
7
|
export * as Utils from './utils/index.js'
|
|
8
|
+
export * from './env.js'
|
|
8
9
|
export {
|
|
9
10
|
type ExplicitSessionConfig,
|
|
10
11
|
type ExplicitSession,
|
package/src/signers/index.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface SapientSigner {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export interface Witnessable {
|
|
34
|
-
witness: (stateWriter: State.Writer, wallet: Address.Address, extra?:
|
|
34
|
+
witness: (stateWriter: State.Writer, wallet: Address.Address, extra?: object) => Promise<void>
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
type MaybePromise<T> = T | Promise<T>
|
package/src/signers/passkey.ts
CHANGED
|
@@ -5,12 +5,15 @@ import { WebAuthnP256 } from 'ox'
|
|
|
5
5
|
import { State } from '../index.js'
|
|
6
6
|
import { SapientSigner, Witnessable } from './index.js'
|
|
7
7
|
|
|
8
|
+
export type WebAuthnLike = Pick<typeof WebAuthnP256, 'createCredential' | 'sign'>
|
|
9
|
+
|
|
8
10
|
export type PasskeyOptions = {
|
|
9
11
|
extensions: Pick<Extensions.Extensions, 'passkeys'>
|
|
10
12
|
publicKey: Extensions.Passkeys.PublicKey
|
|
11
13
|
credentialId: string
|
|
12
14
|
embedMetadata?: boolean
|
|
13
15
|
metadata?: Extensions.Passkeys.PasskeyMetadata
|
|
16
|
+
webauthn?: WebAuthnLike
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
export type CreatePasskeyOptions = {
|
|
@@ -18,6 +21,11 @@ export type CreatePasskeyOptions = {
|
|
|
18
21
|
requireUserVerification?: boolean
|
|
19
22
|
credentialName?: string
|
|
20
23
|
embedMetadata?: boolean
|
|
24
|
+
webauthn?: WebAuthnLike
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type FindPasskeyOptions = {
|
|
28
|
+
webauthn?: WebAuthnLike
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
export type WitnessMessage = {
|
|
@@ -45,6 +53,7 @@ export class Passkey implements SapientSigner, Witnessable {
|
|
|
45
53
|
public readonly imageHash: Hex.Hex
|
|
46
54
|
public readonly embedMetadata: boolean
|
|
47
55
|
public readonly metadata?: Extensions.Passkeys.PasskeyMetadata
|
|
56
|
+
private readonly webauthn: WebAuthnLike
|
|
48
57
|
|
|
49
58
|
constructor(options: PasskeyOptions) {
|
|
50
59
|
this.address = options.extensions.passkeys
|
|
@@ -53,6 +62,7 @@ export class Passkey implements SapientSigner, Witnessable {
|
|
|
53
62
|
this.embedMetadata = options.embedMetadata ?? false
|
|
54
63
|
this.imageHash = Extensions.Passkeys.rootFor(options.publicKey)
|
|
55
64
|
this.metadata = options.metadata
|
|
65
|
+
this.webauthn = options.webauthn ?? WebAuthnP256
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
static async loadFromWitness(
|
|
@@ -60,6 +70,7 @@ export class Passkey implements SapientSigner, Witnessable {
|
|
|
60
70
|
extensions: Pick<Extensions.Extensions, 'passkeys'>,
|
|
61
71
|
wallet: Address.Address,
|
|
62
72
|
imageHash: Hex.Hex,
|
|
73
|
+
options?: FindPasskeyOptions,
|
|
63
74
|
) {
|
|
64
75
|
// In the witness we will find the public key, and may find the credential id
|
|
65
76
|
const witness = await stateReader.getWitnessForSapient(wallet, extensions.passkeys, imageHash)
|
|
@@ -90,13 +101,15 @@ export class Passkey implements SapientSigner, Witnessable {
|
|
|
90
101
|
publicKey: message.publicKey,
|
|
91
102
|
embedMetadata: decodedSignature.embedMetadata,
|
|
92
103
|
metadata,
|
|
104
|
+
webauthn: options?.webauthn,
|
|
93
105
|
})
|
|
94
106
|
}
|
|
95
107
|
|
|
96
108
|
static async create(extensions: Pick<Extensions.Extensions, 'passkeys'>, options?: CreatePasskeyOptions) {
|
|
109
|
+
const webauthn = options?.webauthn ?? WebAuthnP256
|
|
97
110
|
const name = options?.credentialName ?? `Sequence (${Date.now()})`
|
|
98
111
|
|
|
99
|
-
const credential = await
|
|
112
|
+
const credential = await webauthn.createCredential({
|
|
100
113
|
user: {
|
|
101
114
|
name,
|
|
102
115
|
},
|
|
@@ -120,6 +133,7 @@ export class Passkey implements SapientSigner, Witnessable {
|
|
|
120
133
|
},
|
|
121
134
|
embedMetadata: options?.embedMetadata,
|
|
122
135
|
metadata,
|
|
136
|
+
webauthn,
|
|
123
137
|
})
|
|
124
138
|
|
|
125
139
|
if (options?.stateProvider) {
|
|
@@ -132,8 +146,10 @@ export class Passkey implements SapientSigner, Witnessable {
|
|
|
132
146
|
static async find(
|
|
133
147
|
stateReader: State.Reader,
|
|
134
148
|
extensions: Pick<Extensions.Extensions, 'passkeys'>,
|
|
149
|
+
options?: FindPasskeyOptions,
|
|
135
150
|
): Promise<Passkey | undefined> {
|
|
136
|
-
const
|
|
151
|
+
const webauthn = options?.webauthn ?? WebAuthnP256
|
|
152
|
+
const response = await webauthn.sign({ challenge: Hex.random(32) })
|
|
137
153
|
if (!response.raw) throw new Error('No credential returned')
|
|
138
154
|
|
|
139
155
|
const authenticatorDataBytes = Bytes.fromHex(response.metadata.authenticatorData)
|
|
@@ -218,7 +234,7 @@ export class Passkey implements SapientSigner, Witnessable {
|
|
|
218
234
|
console.warn('Multiple signers found for passkey', flattened)
|
|
219
235
|
}
|
|
220
236
|
|
|
221
|
-
return Passkey.loadFromWitness(stateReader, extensions, flattened[0]!.wallet, flattened[0]!.imageHash)
|
|
237
|
+
return Passkey.loadFromWitness(stateReader, extensions, flattened[0]!.wallet, flattened[0]!.imageHash, options)
|
|
222
238
|
}
|
|
223
239
|
|
|
224
240
|
async signSapient(
|
|
@@ -234,7 +250,7 @@ export class Passkey implements SapientSigner, Witnessable {
|
|
|
234
250
|
|
|
235
251
|
const challenge = Hex.fromBytes(Payload.hash(wallet, chainId, payload))
|
|
236
252
|
|
|
237
|
-
const response = await
|
|
253
|
+
const response = await this.webauthn.sign({
|
|
238
254
|
challenge,
|
|
239
255
|
credentialId: this.credentialId,
|
|
240
256
|
userVerification: this.publicKey.requireUserVerification ? 'required' : 'discouraged',
|
|
@@ -260,7 +276,7 @@ export class Passkey implements SapientSigner, Witnessable {
|
|
|
260
276
|
}
|
|
261
277
|
}
|
|
262
278
|
|
|
263
|
-
async witness(stateWriter: State.Writer, wallet: Address.Address, extra?:
|
|
279
|
+
async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: object): Promise<void> {
|
|
264
280
|
const payload = Payload.fromMessage(
|
|
265
281
|
Hex.fromString(
|
|
266
282
|
JSON.stringify({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Hex, Address, PublicKey, Secp256k1, Bytes } from 'ox'
|
|
2
|
+
import { resolveCoreEnv, type CoreEnv, type CryptoLike, type StorageLike, type TextEncodingLike } from '../../env.js'
|
|
2
3
|
import { PkStore } from './index.js'
|
|
3
4
|
|
|
4
5
|
export interface EncryptedData {
|
|
@@ -17,6 +18,7 @@ export class EncryptedPksDb {
|
|
|
17
18
|
constructor(
|
|
18
19
|
private readonly localStorageKeyPrefix: string = 'e_pk_key_',
|
|
19
20
|
tableName: string = 'e_pk',
|
|
21
|
+
private readonly env?: CoreEnv,
|
|
20
22
|
) {
|
|
21
23
|
this.tableName = tableName
|
|
22
24
|
}
|
|
@@ -25,9 +27,59 @@ export class EncryptedPksDb {
|
|
|
25
27
|
return `pk_${address.toLowerCase()}`
|
|
26
28
|
}
|
|
27
29
|
|
|
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
|
+
|
|
28
80
|
private openDB(): Promise<IDBDatabase> {
|
|
29
81
|
return new Promise((resolve, reject) => {
|
|
30
|
-
const request =
|
|
82
|
+
const request = this.getIndexedDB().open(this.dbName, this.dbVersion)
|
|
31
83
|
request.onupgradeneeded = () => {
|
|
32
84
|
const db = request.result
|
|
33
85
|
if (!db.objectStoreNames.contains(this.tableName)) {
|
|
@@ -73,7 +125,11 @@ export class EncryptedPksDb {
|
|
|
73
125
|
}
|
|
74
126
|
|
|
75
127
|
async generateAndStore(): Promise<EncryptedData> {
|
|
76
|
-
const
|
|
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, [
|
|
77
133
|
'encrypt',
|
|
78
134
|
'decrypt',
|
|
79
135
|
])
|
|
@@ -84,13 +140,13 @@ export class EncryptedPksDb {
|
|
|
84
140
|
const address = Address.fromPublicKey(publicKey)
|
|
85
141
|
const keyPointer = this.localStorageKeyPrefix + address
|
|
86
142
|
|
|
87
|
-
const exportedKey = await
|
|
88
|
-
|
|
143
|
+
const exportedKey = await crypto.subtle.exportKey('jwk', encryptionKey)
|
|
144
|
+
storage.setItem(keyPointer, JSON.stringify(exportedKey))
|
|
89
145
|
|
|
90
|
-
const encoder = new
|
|
146
|
+
const encoder = new TextEncoderCtor()
|
|
91
147
|
const encodedPk = encoder.encode(privateKey)
|
|
92
|
-
const iv =
|
|
93
|
-
const encryptedBuffer = await
|
|
148
|
+
const iv = crypto.getRandomValues(new Uint8Array(12))
|
|
149
|
+
const encryptedBuffer = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, encryptionKey, encodedPk)
|
|
94
150
|
|
|
95
151
|
const encrypted: EncryptedData = {
|
|
96
152
|
iv,
|
|
@@ -113,7 +169,7 @@ export class EncryptedPksDb {
|
|
|
113
169
|
async getEncryptedPkStore(address: Address.Address): Promise<EncryptedPkStore | undefined> {
|
|
114
170
|
const entry = await this.getEncryptedEntry(address)
|
|
115
171
|
if (!entry) return
|
|
116
|
-
return new EncryptedPkStore(entry)
|
|
172
|
+
return new EncryptedPkStore(entry, this.env)
|
|
117
173
|
}
|
|
118
174
|
|
|
119
175
|
async listAddresses(): Promise<Address.Address[]> {
|
|
@@ -125,12 +181,41 @@ export class EncryptedPksDb {
|
|
|
125
181
|
const dbKey = this.computeDbKey(address)
|
|
126
182
|
await this.putData(dbKey, undefined)
|
|
127
183
|
const keyPointer = this.localStorageKeyPrefix + address
|
|
128
|
-
|
|
184
|
+
this.getStorage().removeItem(keyPointer)
|
|
129
185
|
}
|
|
130
186
|
}
|
|
131
187
|
|
|
132
188
|
export class EncryptedPkStore implements PkStore {
|
|
133
|
-
constructor(
|
|
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
|
+
}
|
|
134
219
|
|
|
135
220
|
address(): Address.Address {
|
|
136
221
|
return this.encrypted.address
|
|
@@ -141,16 +226,20 @@ export class EncryptedPkStore implements PkStore {
|
|
|
141
226
|
}
|
|
142
227
|
|
|
143
228
|
async signDigest(digest: Bytes.Bytes): Promise<{ r: bigint; s: bigint; yParity: number }> {
|
|
144
|
-
const
|
|
229
|
+
const storage = this.getStorage()
|
|
230
|
+
const crypto = this.getCrypto()
|
|
231
|
+
const TextDecoderCtor = this.getTextDecoderCtor()
|
|
232
|
+
|
|
233
|
+
const keyJson = storage.getItem(this.encrypted.keyPointer)
|
|
145
234
|
if (!keyJson) throw new Error('Encryption key not found in localStorage')
|
|
146
235
|
const jwk = JSON.parse(keyJson)
|
|
147
|
-
const encryptionKey = await
|
|
148
|
-
const decryptedBuffer = await
|
|
236
|
+
const encryptionKey = await crypto.subtle.importKey('jwk', jwk, { name: 'AES-GCM' }, false, ['decrypt'])
|
|
237
|
+
const decryptedBuffer = await crypto.subtle.decrypt(
|
|
149
238
|
{ name: 'AES-GCM', iv: this.encrypted.iv },
|
|
150
239
|
encryptionKey,
|
|
151
240
|
this.encrypted.data,
|
|
152
241
|
)
|
|
153
|
-
const decoder = new
|
|
242
|
+
const decoder = new TextDecoderCtor()
|
|
154
243
|
const privateKey = decoder.decode(decryptedBuffer) as Hex.Hex
|
|
155
244
|
return Secp256k1.sign({ payload: digest, privateKey })
|
|
156
245
|
}
|
package/src/signers/pk/index.ts
CHANGED
|
@@ -52,7 +52,7 @@ export class Pk implements SignerInterface, Witnessable {
|
|
|
52
52
|
return { ...signature, type: 'hash' }
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
async witness(stateWriter: State.Writer, wallet: Address.Address, extra?:
|
|
55
|
+
async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: object): Promise<void> {
|
|
56
56
|
const payload = Payload.fromMessage(
|
|
57
57
|
Hex.fromString(
|
|
58
58
|
JSON.stringify({
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Constants,
|
|
3
|
-
Extensions,
|
|
4
|
-
Payload,
|
|
5
|
-
Permission,
|
|
6
|
-
SessionConfig,
|
|
7
|
-
SessionSignature,
|
|
8
|
-
} from '@0xsequence/wallet-primitives'
|
|
1
|
+
import { Constants, Payload, Permission, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives'
|
|
9
2
|
import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex, Provider } from 'ox'
|
|
10
3
|
import { MemoryPkStore, PkStore } from '../pk/index.js'
|
|
11
4
|
import { ExplicitSessionSigner, SessionSignerValidity, UsageLimit } from './session.js'
|
|
@@ -323,7 +316,7 @@ export class Explicit implements ExplicitSessionSigner {
|
|
|
323
316
|
Bytes.fromHex(call.data).slice(Number(rule.offset), Number(rule.offset) + 32),
|
|
324
317
|
32,
|
|
325
318
|
)
|
|
326
|
-
|
|
319
|
+
const value: Bytes.Bytes = callDataValue.map((b, i) => b & rule.mask[i]!)
|
|
327
320
|
if (Bytes.toBigInt(value) === 0n) continue
|
|
328
321
|
|
|
329
322
|
// Add to list
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Attestation,
|
|
3
|
-
Extensions,
|
|
4
3
|
Payload,
|
|
5
4
|
Signature as SequenceSignature,
|
|
6
5
|
SessionConfig,
|
|
@@ -96,7 +95,7 @@ export class Implicit implements ImplicitSessionSigner {
|
|
|
96
95
|
)
|
|
97
96
|
const expectedResult = Bytes.toHex(Attestation.generateImplicitRequestMagic(this._attestation, wallet))
|
|
98
97
|
return acceptImplicitRequest === expectedResult
|
|
99
|
-
} catch
|
|
98
|
+
} catch {
|
|
100
99
|
// console.log('implicit signer unsupported call', call, error)
|
|
101
100
|
return false
|
|
102
101
|
}
|
package/src/state/local/index.ts
CHANGED
|
@@ -203,7 +203,7 @@ export class Provider implements ProviderInterface {
|
|
|
203
203
|
fromImageHash: Hex.Hex,
|
|
204
204
|
options?: { allUpdates?: boolean },
|
|
205
205
|
): Promise<{ imageHash: Hex.Hex; signature: Signature.RawSignature }[]> {
|
|
206
|
-
|
|
206
|
+
const fromConfig = await this.store.loadConfig(fromImageHash)
|
|
207
207
|
if (!fromConfig) {
|
|
208
208
|
return []
|
|
209
209
|
}
|
|
@@ -384,7 +384,7 @@ export class Provider implements ProviderInterface {
|
|
|
384
384
|
|
|
385
385
|
if (Signature.isSignatureOfSapientSignerLeaf(topology.signature)) {
|
|
386
386
|
switch (topology.signature.address.toLowerCase()) {
|
|
387
|
-
case this.extensions.passkeys.toLowerCase():
|
|
387
|
+
case this.extensions.passkeys.toLowerCase(): {
|
|
388
388
|
const decoded = Extensions.Passkeys.decode(Bytes.fromHex(topology.signature.data))
|
|
389
389
|
|
|
390
390
|
if (!Extensions.Passkeys.isValidSignature(subdigest, decoded)) {
|
|
@@ -397,6 +397,8 @@ export class Provider implements ProviderInterface {
|
|
|
397
397
|
Extensions.Passkeys.rootFor(decoded.publicKey),
|
|
398
398
|
topology.signature,
|
|
399
399
|
)
|
|
400
|
+
}
|
|
401
|
+
|
|
400
402
|
default:
|
|
401
403
|
throw new Error(`Unsupported sapient signer: ${topology.signature.address}`)
|
|
402
404
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Context, Payload, Signature, Config, GenericTree } from '@0xsequence/wallet-primitives'
|
|
2
2
|
import { Address, Hex } from 'ox'
|
|
3
|
+
import type { CoreEnv } from '../../env.js'
|
|
3
4
|
import { Store } from './index.js'
|
|
4
5
|
|
|
5
6
|
const DB_VERSION = 1
|
|
@@ -16,15 +17,27 @@ export class IndexedDbStore implements Store {
|
|
|
16
17
|
private _db: IDBDatabase | null = null
|
|
17
18
|
private dbName: string
|
|
18
19
|
|
|
19
|
-
constructor(
|
|
20
|
+
constructor(
|
|
21
|
+
dbName: string = 'sequence-indexeddb',
|
|
22
|
+
private readonly env?: CoreEnv,
|
|
23
|
+
) {
|
|
20
24
|
this.dbName = dbName
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
private getIndexedDB(): IDBFactory {
|
|
28
|
+
const globalObj = globalThis as any
|
|
29
|
+
const indexedDb = this.env?.indexedDB ?? globalObj.indexedDB ?? globalObj.window?.indexedDB
|
|
30
|
+
if (!indexedDb) {
|
|
31
|
+
throw new Error('indexedDB is not available')
|
|
32
|
+
}
|
|
33
|
+
return indexedDb
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
private async openDB(): Promise<IDBDatabase> {
|
|
24
37
|
if (this._db) return this._db
|
|
25
38
|
|
|
26
39
|
return new Promise((resolve, reject) => {
|
|
27
|
-
const request =
|
|
40
|
+
const request = this.getIndexedDB().open(this.dbName, DB_VERSION)
|
|
28
41
|
|
|
29
42
|
request.onupgradeneeded = () => {
|
|
30
43
|
const db = request.result
|
|
@@ -4,10 +4,16 @@ import { Provider } from '../index.js'
|
|
|
4
4
|
|
|
5
5
|
export class DevHttpProvider implements Provider {
|
|
6
6
|
private readonly baseUrl: string
|
|
7
|
+
private readonly fetcher: typeof fetch
|
|
7
8
|
|
|
8
|
-
constructor(baseUrl: string) {
|
|
9
|
+
constructor(baseUrl: string, fetcher?: typeof fetch) {
|
|
9
10
|
// Remove trailing slash if present
|
|
10
11
|
this.baseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl
|
|
12
|
+
const resolvedFetch = fetcher ?? (globalThis as any).fetch
|
|
13
|
+
if (!resolvedFetch) {
|
|
14
|
+
throw new Error('fetch is not available')
|
|
15
|
+
}
|
|
16
|
+
this.fetcher = resolvedFetch
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
private async request<T>(method: 'GET' | 'POST', path: string, body?: any): Promise<T> {
|
|
@@ -24,7 +30,7 @@ export class DevHttpProvider implements Provider {
|
|
|
24
30
|
|
|
25
31
|
let response: Response
|
|
26
32
|
try {
|
|
27
|
-
response = await
|
|
33
|
+
response = await this.fetcher(url, options)
|
|
28
34
|
} catch (networkError) {
|
|
29
35
|
// Handle immediate network errors (e.g., DNS resolution failure, refused connection)
|
|
30
36
|
console.error(`Network error during ${method} request to ${url}:`, networkError)
|
|
@@ -38,12 +44,12 @@ export class DevHttpProvider implements Provider {
|
|
|
38
44
|
const errorText = await response.text()
|
|
39
45
|
const errorJson = await Utils.fromJSON(errorText)
|
|
40
46
|
errorPayload = { ...errorPayload, ...errorJson }
|
|
41
|
-
} catch
|
|
47
|
+
} catch {
|
|
42
48
|
try {
|
|
43
49
|
// If JSON parsing fails, try getting text for better error message
|
|
44
50
|
const errorText = await response.text()
|
|
45
51
|
errorPayload.body = errorText
|
|
46
|
-
} catch
|
|
52
|
+
} catch {
|
|
47
53
|
// Ignore if reading text also fails
|
|
48
54
|
}
|
|
49
55
|
}
|
|
@@ -99,7 +105,7 @@ export class DevHttpProvider implements Provider {
|
|
|
99
105
|
throw new Error(
|
|
100
106
|
`Failed to parse JSON response from server. Status: ${response.status}. Body: "${text}". Original error: ${error instanceof Error ? error.message : String(error)}`,
|
|
101
107
|
)
|
|
102
|
-
} catch
|
|
108
|
+
} catch {
|
|
103
109
|
throw new Error(
|
|
104
110
|
`Failed to parse JSON response from server and could not read response body as text. Status: ${response.status}. Original error: ${error instanceof Error ? error.message : String(error)}`,
|
|
105
111
|
)
|
|
@@ -9,13 +9,17 @@ import {
|
|
|
9
9
|
TransactionRequest,
|
|
10
10
|
} from 'ox'
|
|
11
11
|
import { normalizeAddressKeys, Provider as ProviderInterface } from '../index.js'
|
|
12
|
-
import { Sessions, SignatureType } from './sessions.gen.js'
|
|
12
|
+
import { Sessions, SignatureType, type Fetch } from './sessions.gen.js'
|
|
13
13
|
|
|
14
14
|
export class Provider implements ProviderInterface {
|
|
15
15
|
private readonly service: Sessions
|
|
16
16
|
|
|
17
|
-
constructor(host = 'https://keymachine.sequence.app') {
|
|
18
|
-
|
|
17
|
+
constructor(host = 'https://keymachine.sequence.app', fetcher?: Fetch) {
|
|
18
|
+
const resolvedFetch = fetcher ?? (globalThis as any).fetch
|
|
19
|
+
if (!resolvedFetch) {
|
|
20
|
+
throw new Error('fetch is not available')
|
|
21
|
+
}
|
|
22
|
+
this.service = new Sessions(host, resolvedFetch)
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
async getConfiguration(imageHash: Hex.Hex): Promise<Config.Config | undefined> {
|
|
@@ -186,7 +190,9 @@ export class Provider implements ProviderInterface {
|
|
|
186
190
|
case SignatureType.SapientCompact:
|
|
187
191
|
throw new Error(`unexpected compact sapient signature by ${signer}`)
|
|
188
192
|
}
|
|
189
|
-
} catch {
|
|
193
|
+
} catch {
|
|
194
|
+
// ignore
|
|
195
|
+
}
|
|
190
196
|
}
|
|
191
197
|
|
|
192
198
|
async getWitnessForSapient(
|
|
@@ -221,7 +227,9 @@ export class Provider implements ProviderInterface {
|
|
|
221
227
|
signature: { type: 'sapient_compact', address: signer, data: witness.signature },
|
|
222
228
|
}
|
|
223
229
|
}
|
|
224
|
-
} catch {
|
|
230
|
+
} catch {
|
|
231
|
+
// ignore
|
|
232
|
+
}
|
|
225
233
|
}
|
|
226
234
|
|
|
227
235
|
async getConfigurationUpdates(
|
|
@@ -380,7 +388,7 @@ const recoverSapientSignatureCompactFunction = AbiFunction.from(recoverSapientSi
|
|
|
380
388
|
class PasskeySignatureValidator implements oxProvider.Provider {
|
|
381
389
|
request: oxProvider.Provider['request'] = (async (request) => {
|
|
382
390
|
switch (request.method) {
|
|
383
|
-
case 'eth_call':
|
|
391
|
+
case 'eth_call': {
|
|
384
392
|
if (!request.params || !Array.isArray(request.params) || request.params.length === 0) {
|
|
385
393
|
throw new Error('eth_call requires transaction parameters')
|
|
386
394
|
}
|
|
@@ -406,6 +414,7 @@ class PasskeySignatureValidator implements oxProvider.Provider {
|
|
|
406
414
|
} else {
|
|
407
415
|
throw new Error(`invalid passkey signature ${signature} for digest ${digest}`)
|
|
408
416
|
}
|
|
417
|
+
}
|
|
409
418
|
|
|
410
419
|
default:
|
|
411
420
|
throw new Error(`method ${request.method} not implemented`)
|
package/src/wallet.ts
CHANGED
|
@@ -355,7 +355,7 @@ export class Wallet {
|
|
|
355
355
|
throw new Error('4337 is not enabled in this wallet')
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
-
const noncePromise = this.get4337Nonce(provider, status.context.capabilities
|
|
358
|
+
const noncePromise = this.get4337Nonce(provider, status.context.capabilities.erc4337.entrypoint, space)
|
|
359
359
|
|
|
360
360
|
// If the wallet is not deployed, then we need to include the initCode on
|
|
361
361
|
// the 4337 transaction
|
|
@@ -570,7 +570,7 @@ export class Wallet {
|
|
|
570
570
|
if (typeof message !== 'string') {
|
|
571
571
|
encodedMessage = TypedData.encode(message)
|
|
572
572
|
} else {
|
|
573
|
-
|
|
573
|
+
const hexMessage = Hex.validate(message) ? message : Hex.fromString(message)
|
|
574
574
|
const messageSize = Hex.size(hexMessage)
|
|
575
575
|
encodedMessage = Hex.concat(Hex.fromString(`${`\x19Ethereum Signed Message:\n${messageSize}`}`), hexMessage)
|
|
576
576
|
}
|
package/test/constants.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { config as dotenvConfig } from 'dotenv'
|
|
2
2
|
import { Abi, AbiEvent, Address } from 'ox'
|
|
3
3
|
|
|
4
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
4
5
|
const envFile = process.env.CI ? '.env.test' : '.env.test.local'
|
|
5
6
|
dotenvConfig({ path: envFile })
|
|
6
7
|
|
|
@@ -16,4 +17,5 @@ export const EMITTER_EVENT_TOPICS = [
|
|
|
16
17
|
export const USDC_ADDRESS: Address.Address = '0xaf88d065e77c8cc2239327c5edb3a432268e5831'
|
|
17
18
|
|
|
18
19
|
// Environment variables
|
|
20
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
19
21
|
export const LOCAL_RPC_URL = process.env.LOCAL_RPC_URL || 'http://localhost:8545'
|
package/test/envelope.test.ts
CHANGED
|
@@ -6,7 +6,6 @@ import * as Envelope from '../src/envelope.js'
|
|
|
6
6
|
|
|
7
7
|
// Test addresses and data
|
|
8
8
|
const TEST_ADDRESS_1 = Address.from('0x1234567890123456789012345678901234567890')
|
|
9
|
-
const TEST_ADDRESS_2 = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd')
|
|
10
9
|
const TEST_ADDRESS_3 = Address.from('0x9876543210987654321098765432109876543210')
|
|
11
10
|
const TEST_WALLET = Address.from('0xfedcbafedcbafedcbafedcbafedcbafedcbafe00')
|
|
12
11
|
const TEST_IMAGE_HASH = Hex.from('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef')
|
package/test/signers-pk.test.ts
CHANGED
|
@@ -143,7 +143,7 @@ describe('Private Key Signers', () => {
|
|
|
143
143
|
await pk.witness(mockStateWriter, testWallet)
|
|
144
144
|
|
|
145
145
|
expect(mockStateWriter.saveWitnesses).toHaveBeenCalledTimes(1)
|
|
146
|
-
const [wallet, chainId,
|
|
146
|
+
const [wallet, chainId, _payload, witness] = vi.mocked(mockStateWriter.saveWitnesses).mock.calls[0]
|
|
147
147
|
|
|
148
148
|
expect(wallet).toBe(testWallet)
|
|
149
149
|
expect(chainId).toBe(0)
|
|
@@ -236,7 +236,6 @@ describe('Implicit Session', () => {
|
|
|
236
236
|
},
|
|
237
237
|
}
|
|
238
238
|
const identitySignature = createValidIdentitySignature(attestation)
|
|
239
|
-
const topology = createValidTopology()
|
|
240
239
|
|
|
241
240
|
// This should throw an error during construction due to future issued time
|
|
242
241
|
expect(() => {
|
|
@@ -250,7 +249,6 @@ describe('Implicit Session', () => {
|
|
|
250
249
|
approvedSigner: randomAddress(), // Different approved signer
|
|
251
250
|
}
|
|
252
251
|
const identitySignature = createValidIdentitySignature(attestation)
|
|
253
|
-
const topology = createValidTopology()
|
|
254
252
|
|
|
255
253
|
// This should throw an error during construction due to mismatched approved signer
|
|
256
254
|
expect(() => {
|