Skip to content

Commit a85afbb

Browse files
committed
ERC-6492 support on passkeys
1 parent 19b51db commit a85afbb

File tree

2 files changed

+68
-6
lines changed

2 files changed

+68
-6
lines changed

Diff for: packages/account/src/account.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,8 @@ export class Account {
520520

521521
async publishWitness(): Promise<void> {
522522
const digest = ethers.id(`This is a Sequence account woo! ${Date.now()}`)
523-
const signature = await this.signDigest(digest, 0, false)
523+
// Apply ERC-6492 to undeployed children
524+
const signature = await this.signDigest(digest, 0, false, 'ignore', {cantValidateBehavior: "eip6492"})
524525
const decoded = this.coders.signature.decode(signature)
525526
const recovered = await this.coders.signature.recover(decoded, { digest, chainId: 0, address: this.address })
526527
const signatures = this.coders.signature.signaturesOf(recovered.config)

Diff for: packages/passkeys/src/index.ts

+66-5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export type PasskeySignerOptions = {
1313
y: string
1414

1515
chainId: ethers.BigNumberish
16+
provider?: ethers.Provider
17+
reader?: commons.reader.Reader
1618

1719
requireUserValidation: boolean
1820
requireBackupSanityCheck: boolean
@@ -31,11 +33,14 @@ export type PasskeySignerOptions = {
3133

3234
export type PasskeySignerContext = {
3335
factory: string
34-
3536
mainModulePasskeys: string
3637
guestModule: string
3738
}
3839

40+
export type PasskeySignMetadata = {
41+
cantValidateBehavior: 'ignore' | 'eip6492' | 'throw',
42+
}
43+
3944
function bytesToBase64URL(bytes: Uint8Array): string {
4045
const base64 = btoa(String.fromCharCode(...bytes))
4146
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
@@ -49,6 +54,9 @@ export class SequencePasskeySigner implements signers.SapientSigner {
4954
public readonly requireBackupSanityCheck: boolean
5055
public readonly chainId: ethers.BigNumberish
5156

57+
public readonly provider?: ethers.Provider
58+
private _reader?: commons.reader.Reader
59+
5260
public readonly context: PasskeySignerContext
5361

5462
private readonly doSign: (
@@ -69,7 +77,16 @@ export class SequencePasskeySigner implements signers.SapientSigner {
6977
this.requireBackupSanityCheck = options.requireBackupSanityCheck
7078
this.chainId = options.chainId
7179
this.context = options.context
80+
this.provider = options.provider
7281
this.doSign = options.doSign
82+
83+
this._reader = options.reader
84+
}
85+
86+
reader(): commons.reader.Reader {
87+
if (this._reader) return this._reader
88+
if (!this.provider) throw new Error('call requires a provider')
89+
return new commons.reader.OnChainReader(this.provider)
7390
}
7491

7592
initCodeHash(): string {
@@ -110,7 +127,15 @@ export class SequencePasskeySigner implements signers.SapientSigner {
110127

111128
notifyStatusChange(_id: string, _status: Status, _metadata: object): void {}
112129

113-
async buildDeployTransaction(metadata: object): Promise<commons.transaction.TransactionBundle | undefined> {
130+
async isDeployed(): Promise<boolean> {
131+
return this.reader().isDeployed(await this.getAddress())
132+
}
133+
134+
async buildDeployTransaction(metadata?: commons.WalletDeployMetadata): Promise<commons.transaction.TransactionBundle | undefined> {
135+
if (metadata?.ignoreDeployed && (await this.isDeployed())) {
136+
return
137+
}
138+
114139
const factoryInterface = new ethers.Interface(walletContracts.eternalFactory.abi)
115140
const imageHash = this.imageHash()
116141

@@ -139,14 +164,20 @@ export class SequencePasskeySigner implements signers.SapientSigner {
139164
return Promise.resolve([])
140165
}
141166

142-
decorateTransactions(
167+
async decorateTransactions(
143168
bundle: commons.transaction.IntendedTransactionBundle,
144-
_metadata: object
169+
metadata?: commons.WalletDeployMetadata
145170
): Promise<commons.transaction.IntendedTransactionBundle> {
171+
// Add deploy transaction
172+
const deployTx = await this.buildDeployTransaction(metadata)
173+
if (deployTx) {
174+
bundle.transactions.unshift(...deployTx.transactions)
175+
}
176+
146177
return Promise.resolve(bundle)
147178
}
148179

149-
async sign(digest: ethers.BytesLike, _metadata: object): Promise<ethers.BytesLike> {
180+
async sign(digest: ethers.BytesLike, metadata: PasskeySignMetadata): Promise<ethers.BytesLike> {
150181
const subdigest = subDigestOf(await this.getAddress(), this.chainId, digest)
151182

152183
const signature = await this.doSign(digest, subdigest)
@@ -197,9 +228,39 @@ export class SequencePasskeySigner implements signers.SapientSigner {
197228
]
198229
)
199230

231+
if (!!metadata && metadata.cantValidateBehavior !== "ignore") {
232+
let isDeployed = false
233+
try {
234+
isDeployed = await this.isDeployed()
235+
} catch (e) {
236+
// Ignore. Handled below
237+
}
238+
if (!isDeployed && metadata.cantValidateBehavior === "eip6492") {
239+
return this.buildEIP6492Signature(signatureBytes)
240+
} else if (!isDeployed && metadata.cantValidateBehavior === "throw") {
241+
throw new Error('Cannot sign with a non-deployed passkey signer')
242+
}
243+
}
244+
200245
return signatureBytes
201246
}
202247

248+
private async buildEIP6492Signature(signature: string): Promise<string> {
249+
const deployTransactions = await this.buildDeployTransaction()
250+
if (!deployTransactions || deployTransactions?.transactions.length === 0) {
251+
throw new Error('Cannot build EIP-6492 signature without deploy transaction')
252+
}
253+
254+
const deployTransaction = deployTransactions.transactions[0]
255+
256+
const encoded = ethers.AbiCoder.defaultAbiCoder().encode(
257+
['address', 'bytes', 'bytes'],
258+
[deployTransaction.to, deployTransaction.data, signature]
259+
)
260+
261+
return ethers.solidityPacked(['bytes', 'bytes32'], [encoded, commons.EIP6492.EIP_6492_SUFFIX])
262+
}
263+
203264
suffix(): ethers.BytesLike {
204265
return new Uint8Array([3])
205266
}

0 commit comments

Comments
 (0)