Skip to content

Commit 7e7813b

Browse files
Merge pull request #449 from pimlicolabs/kernel-1271
throw error on only for non delegated kernel accounts
2 parents 838d8b3 + 6ba059a commit 7e7813b

8 files changed

Lines changed: 132 additions & 19 deletions

File tree

.changeset/eighty-bugs-grab.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"permissionless": patch
3+
---
4+
5+
Added 1271 support for kernel 0.3.3 + 7702 post delegation

packages/permissionless-test/src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ export const getCoreSmartAccounts = (): Array<{
793793
supportsEntryPointV07: true,
794794
supportsEntryPointV08: false,
795795
isEip7702Compliant: true,
796-
isEip1271Compliant: false
796+
isEip1271Compliant: true
797797
},
798798
{
799799
name: "Biconomy",

packages/permissionless/accounts/kernel/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ export const ROOT_MODE_KERNEL_V2 = "0x00000000"
55
export const VALIDATOR_TYPE = {
66
ROOT: "0x00",
77
VALIDATOR: "0x01",
8-
PERMISSION: "0x02"
8+
PERMISSION: "0x02",
9+
EIP7702: "0x00"
910
} as const
1011
export enum VALIDATOR_MODE {
1112
DEFAULT = "0x00",

packages/permissionless/accounts/kernel/toKernelSmartAccount.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,13 @@ const getDefaultAddresses = ({
224224
}
225225

226226
export const getEcdsaRootIdentifierForKernelV3 = (
227-
validatorAddress: Address
227+
validatorAddress: Address,
228+
eip7702 = false
228229
) => {
229-
return concatHex([VALIDATOR_TYPE.VALIDATOR, validatorAddress])
230+
return concatHex([
231+
eip7702 ? VALIDATOR_TYPE.EIP7702 : VALIDATOR_TYPE.VALIDATOR,
232+
eip7702 ? "0x" : validatorAddress
233+
])
230234
}
231235

232236
/**
@@ -723,46 +727,60 @@ export async function toKernelSmartAccount<
723727
return this.signMessage({ message: hash })
724728
},
725729
async signMessage({ message }) {
726-
if (eip7702) {
727-
throw new Error("Kernel with EIP-7702 isn't 1271 compliant")
730+
if (
731+
"isDeployed" in this &&
732+
!(await (this as any).isDeployed()) &&
733+
eip7702
734+
) {
735+
throw new Error(
736+
"Kernel with EIP-7702 isn't 1271 compliant before delegation."
737+
)
728738
}
729739

730740
const signature = await signMessage({
731741
owner,
732742
message,
733743
accountAddress: await this.getAddress(),
734744
kernelVersion: kernelVersion,
735-
chainId: await getMemoizedChainId()
745+
chainId: await getMemoizedChainId(),
746+
eip7702: eip7702
736747
})
737748

738749
if (isKernelV2(kernelVersion)) {
739750
return signature
740751
}
741752

742753
return concatHex([
743-
getEcdsaRootIdentifierForKernelV3(validatorAddress),
754+
getEcdsaRootIdentifierForKernelV3(validatorAddress, eip7702),
744755
signature
745756
])
746757
},
747758
async signTypedData(typedData) {
748-
if (eip7702) {
749-
throw new Error("Kernel with EIP-7702 isn't 1271 compliant")
759+
if (
760+
"isDeployed" in this &&
761+
!(await (this as any).isDeployed()) &&
762+
eip7702
763+
) {
764+
throw new Error(
765+
"Kernel with EIP-7702 isn't 1271 compliant before delegation."
766+
)
750767
}
751768

752769
const signature = await signTypedData({
753770
owner: owner,
754771
chainId: await getMemoizedChainId(),
755772
...(typedData as TypedDataDefinition),
756773
accountAddress: await this.getAddress(),
757-
kernelVersion: kernelVersion
774+
kernelVersion: kernelVersion,
775+
eip7702
758776
})
759777

760778
if (isKernelV2(kernelVersion)) {
761779
return signature
762780
}
763781

764782
return concatHex([
765-
getEcdsaRootIdentifierForKernelV3(validatorAddress),
783+
getEcdsaRootIdentifierForKernelV3(validatorAddress, eip7702),
766784
signature
767785
])
768786
},
@@ -788,7 +806,8 @@ export async function toKernelSmartAccount<
788806
message: { raw: hash },
789807
chainId,
790808
accountAddress: await this.getAddress(),
791-
kernelVersion
809+
kernelVersion,
810+
eip7702: false
792811
})
793812
: await owner.signMessage({
794813
message: { raw: hash }

packages/permissionless/accounts/kernel/utils/signMessage.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ export async function signMessage({
2020
owner,
2121
accountAddress,
2222
kernelVersion: accountVersion,
23-
chainId
23+
chainId,
24+
eip7702
2425
}: {
2526
chainId: number
2627
message: SignableMessage
2728
owner: LocalAccount | WebAuthnAccount
29+
eip7702: boolean
2830
} & WrapMessageHashParams): Promise<SignMessageReturnType> {
2931
if (isWebAuthnAccount(owner)) {
3032
let messageContent: string
@@ -75,6 +77,22 @@ export async function signMessage({
7577
return encodedSignature
7678
}
7779

80+
if (eip7702) {
81+
return owner.signTypedData({
82+
message: { hash: hashMessage(message) },
83+
primaryType: "Kernel",
84+
types: {
85+
Kernel: [{ name: "hash", type: "bytes32" }]
86+
},
87+
domain: {
88+
name: "Kernel",
89+
version: accountVersion,
90+
chainId: chainId,
91+
verifyingContract: accountAddress
92+
}
93+
})
94+
}
95+
7896
if (accountVersion === "0.2.1" || accountVersion === "0.2.2") {
7997
return owner.signMessage({
8098
message

packages/permissionless/accounts/kernel/utils/signTypedData.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ export async function signTypedData(
1818
parameters: TypedDataDefinition &
1919
WrapMessageHashParams & {
2020
owner: LocalAccount | WebAuthnAccount
21+
eip7702: boolean
2122
}
2223
): Promise<SignTypedDataReturnType> {
2324
const {
2425
owner,
2526
accountAddress,
2627
kernelVersion: accountVersion,
2728
chainId,
29+
eip7702,
2830
...typedData
2931
} = parameters
3032

@@ -56,6 +58,22 @@ export async function signTypedData(
5658

5759
const typedHash = hashTypedData({ message, primaryType, types, domain })
5860

61+
if (eip7702 && !isWebAuthnAccount(owner)) {
62+
return owner.signTypedData({
63+
message: { hash: typedHash },
64+
primaryType: "Kernel",
65+
types: {
66+
Kernel: [{ name: "hash", type: "bytes32" }]
67+
},
68+
domain: {
69+
name: "Kernel",
70+
version: accountVersion,
71+
chainId: chainId,
72+
verifyingContract: accountAddress
73+
}
74+
})
75+
}
76+
5977
const wrappedMessageHash = wrapMessageHash(typedHash, {
6078
kernelVersion: accountVersion,
6179
accountAddress,
@@ -68,7 +86,8 @@ export async function signTypedData(
6886
owner,
6987
accountAddress,
7088
kernelVersion: accountVersion,
71-
chainId
89+
chainId,
90+
eip7702: false
7291
})
7392
}
7493

packages/permissionless/actions/smartAccount/signMessage.test.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { zeroAddress } from "viem"
2+
import { privateKeyToAccount } from "viem/accounts"
23
import { describe, expect } from "vitest"
34
import { testWithRpc } from "../../../permissionless-test/src/testWithRpc"
45
import {
@@ -7,6 +8,9 @@ import {
78
} from "../../../permissionless-test/src/utils"
89
import { signMessage } from "./signMessage"
910

11+
const privateKey =
12+
"0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356"
13+
1014
describe.each(getCoreSmartAccounts())(
1115
"signMessage $name",
1216
({
@@ -15,6 +19,7 @@ describe.each(getCoreSmartAccounts())(
1519
supportsEntryPointV06,
1620
supportsEntryPointV07,
1721
supportsEntryPointV08,
22+
isEip7702Compliant,
1823
name
1924
}) => {
2025
testWithRpc.skipIf(isEip1271Compliant || !supportsEntryPointV06)(
@@ -98,10 +103,13 @@ describe.each(getCoreSmartAccounts())(
98103
async ({ rpc }) => {
99104
const { anvilRpc } = rpc
100105

106+
const privateKeyAccount = privateKeyToAccount(privateKey)
107+
101108
const smartClient = await getSmartAccountClient({
102109
entryPoint: {
103110
version: "0.7"
104111
},
112+
privateKey, // anvil private key
105113
...rpc
106114
})
107115

@@ -110,10 +118,27 @@ describe.each(getCoreSmartAccounts())(
110118
return
111119
}
112120

113-
if (name.includes("Safe 7579")) {
121+
if (
122+
name.includes("Safe 7579") ||
123+
name.includes("Kernel 0.3.3 + EIP-7702")
124+
) {
125+
const publicClient = getPublicClient(anvilRpc)
126+
114127
// Due to 7579 launchpad, we can't verify the signature before deploying the account.
115128
await smartClient.sendTransaction({
116-
calls: [{ to: zeroAddress, value: 0n }]
129+
calls: [{ to: zeroAddress, value: 0n }],
130+
authorization: isEip7702Compliant
131+
? await privateKeyAccount.signAuthorization({
132+
address: (smartClient.account as any)
133+
.implementation,
134+
chainId: smartClient.chain.id,
135+
nonce: await publicClient.getTransactionCount(
136+
{
137+
address: smartClient.account.address
138+
}
139+
)
140+
})
141+
: undefined
117142
})
118143
}
119144

packages/permissionless/actions/smartAccount/signTypedData.test.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getAddress, zeroAddress } from "viem"
2+
import { privateKeyToAccount } from "viem/accounts"
23
import { describe, expect } from "vitest"
34
import { testWithRpc } from "../../../permissionless-test/src/testWithRpc"
45
import {
@@ -7,6 +8,9 @@ import {
78
} from "../../../permissionless-test/src/utils"
89
import { signTypedData } from "./signTypedData"
910

11+
const privateKey =
12+
"0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356"
13+
1014
const typedData = {
1115
domain: {
1216
name: "Ether Mail",
@@ -49,6 +53,7 @@ describe.each(getCoreSmartAccounts())(
4953
supportsEntryPointV06,
5054
supportsEntryPointV07,
5155
supportsEntryPointV08,
56+
isEip7702Compliant,
5257
name
5358
}) => {
5459
testWithRpc.skipIf(isEip1271Compliant || !supportsEntryPointV06)(
@@ -114,10 +119,13 @@ describe.each(getCoreSmartAccounts())(
114119
async ({ rpc }) => {
115120
const { anvilRpc } = rpc
116121

122+
const privateKeyAccount = privateKeyToAccount(privateKey)
123+
117124
const smartClient = await getSmartAccountClient({
118125
entryPoint: {
119126
version: "0.7"
120127
},
128+
privateKey, // anvil private key
121129
...rpc
122130
})
123131

@@ -130,12 +138,30 @@ describe.each(getCoreSmartAccounts())(
130138
return
131139
}
132140

133-
if (name.includes("Safe 7579")) {
141+
if (
142+
name.includes("Safe 7579") ||
143+
name.includes("Kernel 0.3.3 + EIP-7702")
144+
) {
145+
const publicClient = getPublicClient(anvilRpc)
146+
134147
// Due to 7579 launchpad, we can't verify the signature before deploying the account.
135148
await smartClient.sendTransaction({
136-
calls: [{ to: zeroAddress, value: 0n }]
149+
calls: [{ to: zeroAddress, value: 0n }],
150+
authorization: isEip7702Compliant
151+
? await privateKeyAccount.signAuthorization({
152+
address: (smartClient.account as any)
153+
.implementation,
154+
chainId: smartClient.chain.id,
155+
nonce: await publicClient.getTransactionCount(
156+
{
157+
address: smartClient.account.address
158+
}
159+
)
160+
})
161+
: undefined
137162
})
138163
}
164+
139165
const isVerified = await publicClient.verifyTypedData({
140166
...typedData,
141167
address: smartClient.account.address,

0 commit comments

Comments
 (0)