@zerodev/permissions 5.2.1-alpha.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/_cjs/constants.js +17 -0
- package/_cjs/constants.js.map +1 -0
- package/_cjs/deserializePermissionAccount.js +94 -0
- package/_cjs/deserializePermissionAccount.js.map +1 -0
- package/_cjs/index.js +12 -0
- package/_cjs/index.js.map +1 -0
- package/_cjs/package.json +1 -0
- package/_cjs/policies/callPolicyUtils.js +87 -0
- package/_cjs/policies/callPolicyUtils.js.map +1 -0
- package/_cjs/policies/index.js +16 -0
- package/_cjs/policies/index.js.map +1 -0
- package/_cjs/policies/toCallPolicy.js +42 -0
- package/_cjs/policies/toCallPolicy.js.map +1 -0
- package/_cjs/policies/toGasPolicy.js +30 -0
- package/_cjs/policies/toGasPolicy.js.map +1 -0
- package/_cjs/policies/toRateLimitPolicy.js +30 -0
- package/_cjs/policies/toRateLimitPolicy.js.map +1 -0
- package/_cjs/policies/toSignatureCallerPolicy.js +24 -0
- package/_cjs/policies/toSignatureCallerPolicy.js.map +1 -0
- package/_cjs/policies/toSudoPolicy.js +22 -0
- package/_cjs/policies/toSudoPolicy.js.map +1 -0
- package/_cjs/policies/types.js +19 -0
- package/_cjs/policies/types.js.map +1 -0
- package/_cjs/policies/utils/toPolicyId.js +11 -0
- package/_cjs/policies/utils/toPolicyId.js.map +1 -0
- package/_cjs/serializePermissionAccount.js +27 -0
- package/_cjs/serializePermissionAccount.js.map +1 -0
- package/_cjs/signers/index.js +12 -0
- package/_cjs/signers/index.js.map +1 -0
- package/_cjs/signers/toECDSASigner.js +39 -0
- package/_cjs/signers/toECDSASigner.js.map +1 -0
- package/_cjs/signers/toWebAuthnPubKey.js +97 -0
- package/_cjs/signers/toWebAuthnPubKey.js.map +1 -0
- package/_cjs/signers/toWebAuthnSigner.js +154 -0
- package/_cjs/signers/toWebAuthnSigner.js.map +1 -0
- package/_cjs/signers/utils/toSignerId.js +9 -0
- package/_cjs/signers/utils/toSignerId.js.map +1 -0
- package/_cjs/signers/webAuthnUtils.js +45 -0
- package/_cjs/signers/webAuthnUtils.js.map +1 -0
- package/_cjs/toPermissionValidator.js +102 -0
- package/_cjs/toPermissionValidator.js.map +1 -0
- package/_cjs/types.js +3 -0
- package/_cjs/types.js.map +1 -0
- package/_cjs/utils.js +37 -0
- package/_cjs/utils.js.map +1 -0
- package/_esm/constants.js +14 -0
- package/_esm/constants.js.map +1 -0
- package/_esm/deserializePermissionAccount.js +89 -0
- package/_esm/deserializePermissionAccount.js.map +1 -0
- package/_esm/index.js +5 -0
- package/_esm/index.js.map +1 -0
- package/_esm/package.json +1 -0
- package/_esm/policies/callPolicyUtils.js +82 -0
- package/_esm/policies/callPolicyUtils.js.map +1 -0
- package/_esm/policies/index.js +7 -0
- package/_esm/policies/index.js.map +1 -0
- package/_esm/policies/toCallPolicy.js +38 -0
- package/_esm/policies/toCallPolicy.js.map +1 -0
- package/_esm/policies/toGasPolicy.js +26 -0
- package/_esm/policies/toGasPolicy.js.map +1 -0
- package/_esm/policies/toRateLimitPolicy.js +26 -0
- package/_esm/policies/toRateLimitPolicy.js.map +1 -0
- package/_esm/policies/toSignatureCallerPolicy.js +20 -0
- package/_esm/policies/toSignatureCallerPolicy.js.map +1 -0
- package/_esm/policies/toSudoPolicy.js +18 -0
- package/_esm/policies/toSudoPolicy.js.map +1 -0
- package/_esm/policies/types.js +17 -0
- package/_esm/policies/types.js.map +1 -0
- package/_esm/policies/utils/toPolicyId.js +7 -0
- package/_esm/policies/utils/toPolicyId.js.map +1 -0
- package/_esm/serializePermissionAccount.js +23 -0
- package/_esm/serializePermissionAccount.js.map +1 -0
- package/_esm/signers/index.js +5 -0
- package/_esm/signers/index.js.map +1 -0
- package/_esm/signers/toECDSASigner.js +36 -0
- package/_esm/signers/toECDSASigner.js.map +1 -0
- package/_esm/signers/toWebAuthnPubKey.js +107 -0
- package/_esm/signers/toWebAuthnPubKey.js.map +1 -0
- package/_esm/signers/toWebAuthnSigner.js +164 -0
- package/_esm/signers/toWebAuthnSigner.js.map +1 -0
- package/_esm/signers/utils/toSignerId.js +5 -0
- package/_esm/signers/utils/toSignerId.js.map +1 -0
- package/_esm/signers/webAuthnUtils.js +41 -0
- package/_esm/signers/webAuthnUtils.js.map +1 -0
- package/_esm/toPermissionValidator.js +98 -0
- package/_esm/toPermissionValidator.js.map +1 -0
- package/_esm/types.js +3 -0
- package/_esm/types.js.map +1 -0
- package/_esm/utils.js +32 -0
- package/_esm/utils.js.map +1 -0
- package/_types/constants.d.ts +13 -0
- package/_types/constants.d.ts.map +1 -0
- package/_types/deserializePermissionAccount.d.ts +13 -0
- package/_types/deserializePermissionAccount.d.ts.map +1 -0
- package/_types/index.d.ts +6 -0
- package/_types/index.d.ts.map +1 -0
- package/_types/policies/callPolicyUtils.d.ts +5 -0
- package/_types/policies/callPolicyUtils.d.ts.map +1 -0
- package/_types/policies/index.d.ts +7 -0
- package/_types/policies/index.d.ts.map +1 -0
- package/_types/policies/toCallPolicy.d.ts +9 -0
- package/_types/policies/toCallPolicy.d.ts.map +1 -0
- package/_types/policies/toGasPolicy.d.ts +10 -0
- package/_types/policies/toGasPolicy.d.ts.map +1 -0
- package/_types/policies/toRateLimitPolicy.d.ts +9 -0
- package/_types/policies/toRateLimitPolicy.d.ts.map +1 -0
- package/_types/policies/toSignatureCallerPolicy.d.ts +8 -0
- package/_types/policies/toSignatureCallerPolicy.d.ts.map +1 -0
- package/_types/policies/toSudoPolicy.d.ts +6 -0
- package/_types/policies/toSudoPolicy.d.ts.map +1 -0
- package/_types/policies/types.d.ts +56 -0
- package/_types/policies/types.d.ts.map +1 -0
- package/_types/policies/utils/toPolicyId.d.ts +4 -0
- package/_types/policies/utils/toPolicyId.d.ts.map +1 -0
- package/_types/serializePermissionAccount.d.ts +5 -0
- package/_types/serializePermissionAccount.d.ts.map +1 -0
- package/_types/signers/index.d.ts +5 -0
- package/_types/signers/index.d.ts.map +1 -0
- package/_types/signers/toECDSASigner.d.ts +8 -0
- package/_types/signers/toECDSASigner.d.ts.map +1 -0
- package/_types/signers/toWebAuthnPubKey.d.ts +11 -0
- package/_types/signers/toWebAuthnPubKey.d.ts.map +1 -0
- package/_types/signers/toWebAuthnSigner.d.ts +16 -0
- package/_types/signers/toWebAuthnSigner.d.ts.map +1 -0
- package/_types/signers/utils/toSignerId.d.ts +4 -0
- package/_types/signers/utils/toSignerId.d.ts.map +1 -0
- package/_types/signers/webAuthnUtils.d.ts +13 -0
- package/_types/signers/webAuthnUtils.d.ts.map +1 -0
- package/_types/toPermissionValidator.d.ts +5 -0
- package/_types/toPermissionValidator.d.ts.map +1 -0
- package/_types/types.d.ts +49 -0
- package/_types/types.d.ts.map +1 -0
- package/_types/utils.d.ts +8 -0
- package/_types/utils.d.ts.map +1 -0
- package/constants.ts +17 -0
- package/deserializePermissionAccount.ts +119 -0
- package/index.ts +5 -0
- package/package.json +63 -0
- package/policies/callPolicyUtils.ts +122 -0
- package/policies/index.ts +12 -0
- package/policies/toCallPolicy.ts +65 -0
- package/policies/toGasPolicy.ts +44 -0
- package/policies/toRateLimitPolicy.ts +43 -0
- package/policies/toSignatureCallerPolicy.ts +34 -0
- package/policies/toSudoPolicy.ts +27 -0
- package/policies/types.ts +118 -0
- package/policies/utils/toPolicyId.ts +13 -0
- package/serializePermissionAccount.ts +38 -0
- package/signers/index.ts +10 -0
- package/signers/toECDSASigner.ts +61 -0
- package/signers/toWebAuthnPubKey.ts +152 -0
- package/signers/toWebAuthnSigner.ts +245 -0
- package/signers/utils/toSignerId.ts +9 -0
- package/signers/webAuthnUtils.ts +54 -0
- package/toPermissionValidator.ts +152 -0
- package/types.ts +71 -0
- package/utils.ts +42 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { startAuthentication } from "@simplewebauthn/browser"
|
|
2
|
+
import type { PublicKeyCredentialRequestOptionsJSON } from "@simplewebauthn/typescript-types"
|
|
3
|
+
import type { TypedData } from "abitype"
|
|
4
|
+
import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts"
|
|
5
|
+
import {
|
|
6
|
+
type Chain,
|
|
7
|
+
type Client,
|
|
8
|
+
type Hex,
|
|
9
|
+
type LocalAccount,
|
|
10
|
+
type SignTypedDataParameters,
|
|
11
|
+
type Transport,
|
|
12
|
+
type TypedDataDefinition,
|
|
13
|
+
getTypesForEIP712Domain,
|
|
14
|
+
hashTypedData,
|
|
15
|
+
validateTypedData
|
|
16
|
+
} from "viem"
|
|
17
|
+
import { type SignableMessage, encodeAbiParameters } from "viem"
|
|
18
|
+
import { toAccount } from "viem/accounts"
|
|
19
|
+
import { getChainId } from "viem/actions"
|
|
20
|
+
import { WEBAUTHN_SIGNER_CONTRACT } from "../constants.js"
|
|
21
|
+
import type { ModularSigner, ModularSignerParams } from "../types.js"
|
|
22
|
+
import { WebAuthnMode, toWebAuthnPubKey } from "./toWebAuthnPubKey.js"
|
|
23
|
+
import {
|
|
24
|
+
b64ToBytes,
|
|
25
|
+
findQuoteIndices,
|
|
26
|
+
isRIP7212SupportedNetwork,
|
|
27
|
+
parseAndNormalizeSig,
|
|
28
|
+
uint8ArrayToHexString
|
|
29
|
+
} from "./webAuthnUtils.js"
|
|
30
|
+
|
|
31
|
+
export type WebAuthnKey = {
|
|
32
|
+
pubX: bigint
|
|
33
|
+
pubY: bigint
|
|
34
|
+
authenticatorIdHash: Hex
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type WebAuthnModularSignerParams = ModularSignerParams & {
|
|
38
|
+
passkeyName: string
|
|
39
|
+
passkeyServerUrl: string
|
|
40
|
+
pubKey?: WebAuthnKey
|
|
41
|
+
mode?: WebAuthnMode
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const toWebAuthnSigner = async <
|
|
45
|
+
TTransport extends Transport = Transport,
|
|
46
|
+
TChain extends Chain | undefined = Chain | undefined
|
|
47
|
+
>(
|
|
48
|
+
client: Client<TTransport, TChain, undefined>,
|
|
49
|
+
{
|
|
50
|
+
signerContractAddress = WEBAUTHN_SIGNER_CONTRACT,
|
|
51
|
+
pubKey,
|
|
52
|
+
passkeyServerUrl,
|
|
53
|
+
passkeyName,
|
|
54
|
+
mode = WebAuthnMode.Register
|
|
55
|
+
}: WebAuthnModularSignerParams
|
|
56
|
+
): Promise<ModularSigner> => {
|
|
57
|
+
pubKey =
|
|
58
|
+
pubKey ??
|
|
59
|
+
(await toWebAuthnPubKey({
|
|
60
|
+
passkeyName,
|
|
61
|
+
passkeyServerUrl,
|
|
62
|
+
mode
|
|
63
|
+
}))
|
|
64
|
+
if (!pubKey) {
|
|
65
|
+
throw new Error("WebAuthn public key not found")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const chainId = await getChainId(client)
|
|
69
|
+
|
|
70
|
+
const signMessageUsingWebAuthn = async (message: SignableMessage) => {
|
|
71
|
+
let messageContent: string
|
|
72
|
+
if (typeof message === "string") {
|
|
73
|
+
// message is a string
|
|
74
|
+
messageContent = message
|
|
75
|
+
} else if ("raw" in message && typeof message.raw === "string") {
|
|
76
|
+
// message.raw is a Hex string
|
|
77
|
+
messageContent = message.raw
|
|
78
|
+
} else if ("raw" in message && message.raw instanceof Uint8Array) {
|
|
79
|
+
// message.raw is a ByteArray
|
|
80
|
+
messageContent = message.raw.toString()
|
|
81
|
+
} else {
|
|
82
|
+
throw new Error("Unsupported message format")
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// remove 0x prefix if present
|
|
86
|
+
const formattedMessage = messageContent.startsWith("0x")
|
|
87
|
+
? messageContent.slice(2)
|
|
88
|
+
: messageContent
|
|
89
|
+
|
|
90
|
+
if (window.sessionStorage === undefined) {
|
|
91
|
+
throw new Error("sessionStorage is not available")
|
|
92
|
+
}
|
|
93
|
+
const userId = sessionStorage.getItem("userId")
|
|
94
|
+
|
|
95
|
+
// initiate signing
|
|
96
|
+
const signInitiateResponse = await fetch(
|
|
97
|
+
`${passkeyServerUrl}/sign-initiate`,
|
|
98
|
+
{
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: { "Content-Type": "application/json" },
|
|
101
|
+
body: JSON.stringify({ data: formattedMessage, userId }),
|
|
102
|
+
credentials: "include"
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
const signInitiateResult = await signInitiateResponse.json()
|
|
106
|
+
|
|
107
|
+
// prepare assertion options
|
|
108
|
+
const assertionOptions: PublicKeyCredentialRequestOptionsJSON = {
|
|
109
|
+
challenge: signInitiateResult.challenge,
|
|
110
|
+
allowCredentials: signInitiateResult.allowCredentials,
|
|
111
|
+
userVerification: "required"
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// start authentication (signing)
|
|
115
|
+
const cred = await startAuthentication(assertionOptions)
|
|
116
|
+
|
|
117
|
+
// verify signature from server
|
|
118
|
+
const verifyResponse = await fetch(`${passkeyServerUrl}/sign-verify`, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: { "Content-Type": "application/json" },
|
|
121
|
+
body: JSON.stringify({ cred, userId }),
|
|
122
|
+
credentials: "include"
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const verifyResult = await verifyResponse.json()
|
|
126
|
+
|
|
127
|
+
if (!verifyResult.success) {
|
|
128
|
+
throw new Error("Signature not verified")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// get authenticator data
|
|
132
|
+
const authenticatorData = verifyResult.authenticatorData
|
|
133
|
+
const authenticatorDataHex = uint8ArrayToHexString(
|
|
134
|
+
b64ToBytes(authenticatorData)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
// get client data JSON
|
|
138
|
+
const clientDataJSON = atob(cred.response.clientDataJSON)
|
|
139
|
+
|
|
140
|
+
// get challenge and response type location
|
|
141
|
+
const { beforeType } = findQuoteIndices(clientDataJSON)
|
|
142
|
+
|
|
143
|
+
// get signature r,s
|
|
144
|
+
const signature = verifyResult.signature
|
|
145
|
+
const signatureHex = uint8ArrayToHexString(b64ToBytes(signature))
|
|
146
|
+
const { r, s } = parseAndNormalizeSig(signatureHex)
|
|
147
|
+
|
|
148
|
+
// encode signature
|
|
149
|
+
const encodedSignature = encodeAbiParameters(
|
|
150
|
+
[
|
|
151
|
+
{ name: "authenticatorData", type: "bytes" },
|
|
152
|
+
{ name: "clientDataJSON", type: "string" },
|
|
153
|
+
{ name: "responseTypeLocation", type: "uint256" },
|
|
154
|
+
{ name: "r", type: "uint256" },
|
|
155
|
+
{ name: "s", type: "uint256" },
|
|
156
|
+
{ name: "usePrecompiled", type: "bool" }
|
|
157
|
+
],
|
|
158
|
+
[
|
|
159
|
+
authenticatorDataHex,
|
|
160
|
+
clientDataJSON,
|
|
161
|
+
beforeType,
|
|
162
|
+
BigInt(r),
|
|
163
|
+
BigInt(s),
|
|
164
|
+
isRIP7212SupportedNetwork(chainId)
|
|
165
|
+
]
|
|
166
|
+
)
|
|
167
|
+
return encodedSignature
|
|
168
|
+
}
|
|
169
|
+
const account: LocalAccount = toAccount({
|
|
170
|
+
// note that this address will be overwritten by actual address
|
|
171
|
+
address: "0x0000000000000000000000000000000000000000",
|
|
172
|
+
async signMessage({ message }) {
|
|
173
|
+
return signMessageUsingWebAuthn(message)
|
|
174
|
+
},
|
|
175
|
+
async signTransaction(_, __) {
|
|
176
|
+
throw new SignTransactionNotSupportedBySmartAccount()
|
|
177
|
+
},
|
|
178
|
+
async signTypedData<
|
|
179
|
+
const TTypedData extends TypedData | Record<string, unknown>,
|
|
180
|
+
TPrimaryType extends
|
|
181
|
+
| keyof TTypedData
|
|
182
|
+
| "EIP712Domain" = keyof TTypedData
|
|
183
|
+
>(typedData: TypedDataDefinition<TTypedData, TPrimaryType>) {
|
|
184
|
+
const { domain, message, primaryType } =
|
|
185
|
+
typedData as unknown as SignTypedDataParameters
|
|
186
|
+
|
|
187
|
+
const types = {
|
|
188
|
+
EIP712Domain: getTypesForEIP712Domain({ domain }),
|
|
189
|
+
...typedData.types
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
validateTypedData({ domain, message, primaryType, types })
|
|
193
|
+
|
|
194
|
+
const hash = hashTypedData(typedData)
|
|
195
|
+
return signMessageUsingWebAuthn(hash)
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
account,
|
|
201
|
+
signerContractAddress,
|
|
202
|
+
getSignerData: () => {
|
|
203
|
+
if (!pubKey) {
|
|
204
|
+
throw new Error("WebAuthn public key not found")
|
|
205
|
+
}
|
|
206
|
+
return encodeAbiParameters(
|
|
207
|
+
[
|
|
208
|
+
{
|
|
209
|
+
components: [
|
|
210
|
+
{ name: "pubKeyX", type: "uint256" },
|
|
211
|
+
{ name: "pubKeyY", type: "uint256" }
|
|
212
|
+
],
|
|
213
|
+
name: "WebAuthnSignerData",
|
|
214
|
+
type: "tuple"
|
|
215
|
+
},
|
|
216
|
+
{ name: "authenticatorIdHash", type: "bytes32" }
|
|
217
|
+
],
|
|
218
|
+
[
|
|
219
|
+
{ pubKeyX: pubKey.pubX, pubKeyY: pubKey.pubY },
|
|
220
|
+
pubKey.authenticatorIdHash
|
|
221
|
+
]
|
|
222
|
+
)
|
|
223
|
+
},
|
|
224
|
+
getDummySignature: () => {
|
|
225
|
+
return encodeAbiParameters(
|
|
226
|
+
[
|
|
227
|
+
{ name: "authenticatorData", type: "bytes" },
|
|
228
|
+
{ name: "clientDataJSON", type: "string" },
|
|
229
|
+
{ name: "responseTypeLocation", type: "uint256" },
|
|
230
|
+
{ name: "r", type: "uint256" },
|
|
231
|
+
{ name: "s", type: "uint256" },
|
|
232
|
+
{ name: "usePrecompiled", type: "bool" }
|
|
233
|
+
],
|
|
234
|
+
[
|
|
235
|
+
"0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000",
|
|
236
|
+
'{"type":"webauthn.get","challenge":"tbxXNFS9X_4Byr1cMwqKrIGB-_30a0QhZ6y7ucM0BOE","origin":"http://localhost:3000","crossOrigin":false}',
|
|
237
|
+
1n,
|
|
238
|
+
44941127272049826721201904734628716258498742255959991581049806490182030242267n,
|
|
239
|
+
9910254599581058084911561569808925251374718953855182016200087235935345969636n,
|
|
240
|
+
false
|
|
241
|
+
]
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Hex, concat, encodeAbiParameters } from "viem"
|
|
2
|
+
import type { ModularSigner } from "../../types.js"
|
|
3
|
+
|
|
4
|
+
export const toSignerId = (signer: ModularSigner): Hex => {
|
|
5
|
+
return encodeAbiParameters(
|
|
6
|
+
[{ name: "signerData", type: "bytes" }],
|
|
7
|
+
[concat([signer.signerContractAddress, signer.getSignerData()])]
|
|
8
|
+
)
|
|
9
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { p256 } from "@noble/curves/p256"
|
|
2
|
+
import { type Hex, bytesToBigInt, hexToBytes } from "viem"
|
|
3
|
+
|
|
4
|
+
const RIP7212_SUPPORTED_NETWORKS = [80001]
|
|
5
|
+
|
|
6
|
+
export const uint8ArrayToHexString = (array: Uint8Array): `0x${string}` => {
|
|
7
|
+
return `0x${Array.from(array, (byte) =>
|
|
8
|
+
byte.toString(16).padStart(2, "0")
|
|
9
|
+
).join("")}` as `0x${string}`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const b64ToBytes = (base64: string): Uint8Array => {
|
|
13
|
+
const paddedBase64 = base64
|
|
14
|
+
.replace(/-/g, "+")
|
|
15
|
+
.replace(/_/g, "/")
|
|
16
|
+
.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "=")
|
|
17
|
+
const binString = atob(paddedBase64)
|
|
18
|
+
return Uint8Array.from(binString, (m) => m.codePointAt(0) ?? 0)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const findQuoteIndices = (
|
|
22
|
+
input: string
|
|
23
|
+
): { beforeType: bigint; beforeChallenge: bigint } => {
|
|
24
|
+
const beforeTypeIndex = BigInt(input.lastIndexOf('"type":"webauthn.get"'))
|
|
25
|
+
const beforeChallengeIndex = BigInt(input.indexOf('"challenge'))
|
|
26
|
+
return {
|
|
27
|
+
beforeType: beforeTypeIndex,
|
|
28
|
+
beforeChallenge: beforeChallengeIndex
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Parse DER-encoded P256-SHA256 signature to contract-friendly signature
|
|
33
|
+
// and normalize it so the signature is not malleable.
|
|
34
|
+
export function parseAndNormalizeSig(derSig: Hex): { r: bigint; s: bigint } {
|
|
35
|
+
const parsedSignature = p256.Signature.fromDER(derSig.slice(2))
|
|
36
|
+
const bSig = hexToBytes(`0x${parsedSignature.toCompactHex()}`)
|
|
37
|
+
// assert(bSig.length === 64, "signature is not 64 bytes");
|
|
38
|
+
const bR = bSig.slice(0, 32)
|
|
39
|
+
const bS = bSig.slice(32)
|
|
40
|
+
|
|
41
|
+
// Avoid malleability. Ensure low S (<= N/2 where N is the curve order)
|
|
42
|
+
const r = bytesToBigInt(bR)
|
|
43
|
+
let s = bytesToBigInt(bS)
|
|
44
|
+
const n = BigInt(
|
|
45
|
+
"0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"
|
|
46
|
+
)
|
|
47
|
+
if (s > n / 2n) {
|
|
48
|
+
s = n - s
|
|
49
|
+
}
|
|
50
|
+
return { r, s }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const isRIP7212SupportedNetwork = (chainId: number): boolean =>
|
|
54
|
+
RIP7212_SUPPORTED_NETWORKS.includes(chainId)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { KernelV3AccountAbi } from "@zerodev/sdk"
|
|
2
|
+
import { getAction, getUserOperationHash } from "permissionless"
|
|
3
|
+
import type { EntryPoint } from "permissionless/types/entrypoint"
|
|
4
|
+
import {
|
|
5
|
+
type Address,
|
|
6
|
+
type Chain,
|
|
7
|
+
type Client,
|
|
8
|
+
type Hex,
|
|
9
|
+
type Transport,
|
|
10
|
+
concat,
|
|
11
|
+
encodeAbiParameters,
|
|
12
|
+
keccak256,
|
|
13
|
+
pad,
|
|
14
|
+
slice,
|
|
15
|
+
zeroAddress
|
|
16
|
+
} from "viem"
|
|
17
|
+
import { getChainId, readContract } from "viem/actions"
|
|
18
|
+
import { PolicyFlags } from "./constants.js"
|
|
19
|
+
import { toPolicyId } from "./policies/index.js"
|
|
20
|
+
import { toSignerId } from "./signers/index.js"
|
|
21
|
+
import type {
|
|
22
|
+
PermissionData,
|
|
23
|
+
PermissionPlugin,
|
|
24
|
+
PermissionPluginParams
|
|
25
|
+
} from "./types.js"
|
|
26
|
+
|
|
27
|
+
export async function toPermissionValidator<
|
|
28
|
+
entryPoint extends EntryPoint,
|
|
29
|
+
TTransport extends Transport = Transport,
|
|
30
|
+
TChain extends Chain | undefined = Chain | undefined
|
|
31
|
+
>(
|
|
32
|
+
client: Client<TTransport, TChain, undefined>,
|
|
33
|
+
{
|
|
34
|
+
signer,
|
|
35
|
+
policies,
|
|
36
|
+
entryPoint: entryPointAddress,
|
|
37
|
+
flag = PolicyFlags.FOR_ALL_VALIDATION
|
|
38
|
+
}: PermissionPluginParams
|
|
39
|
+
): Promise<PermissionPlugin<entryPoint>> {
|
|
40
|
+
const chainId = await getChainId(client)
|
|
41
|
+
|
|
42
|
+
const getEnableData = async (
|
|
43
|
+
_kernelAccountAddress?: Address
|
|
44
|
+
): Promise<Hex> => {
|
|
45
|
+
const enableData = encodeAbiParameters(
|
|
46
|
+
[{ name: "policyAndSignerData", type: "bytes[]" }],
|
|
47
|
+
[
|
|
48
|
+
[
|
|
49
|
+
...policies.map((policy) =>
|
|
50
|
+
concat([
|
|
51
|
+
policy.getPolicyInfoInBytes(),
|
|
52
|
+
policy.getPolicyData(getPermissionId())
|
|
53
|
+
])
|
|
54
|
+
),
|
|
55
|
+
concat([
|
|
56
|
+
flag,
|
|
57
|
+
signer.signerContractAddress,
|
|
58
|
+
signer.getSignerData(getPermissionId())
|
|
59
|
+
])
|
|
60
|
+
]
|
|
61
|
+
]
|
|
62
|
+
)
|
|
63
|
+
return enableData
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const getPermissionId = (): Hex => {
|
|
67
|
+
const pIdData = encodeAbiParameters(
|
|
68
|
+
[{ name: "policyAndSignerData", type: "bytes[]" }],
|
|
69
|
+
[[toPolicyId(policies), flag, toSignerId(signer)]]
|
|
70
|
+
)
|
|
71
|
+
return slice(keccak256(pIdData), 0, 2)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
...signer.account,
|
|
76
|
+
validatorType: "PERMISSION",
|
|
77
|
+
address: zeroAddress,
|
|
78
|
+
source: "PermissionValidator",
|
|
79
|
+
getEnableData,
|
|
80
|
+
getIdentifier: () =>
|
|
81
|
+
pad(getPermissionId(), {
|
|
82
|
+
size: 20,
|
|
83
|
+
dir: "right"
|
|
84
|
+
}),
|
|
85
|
+
|
|
86
|
+
signMessage: async ({ message }) => {
|
|
87
|
+
return concat([
|
|
88
|
+
"0xff",
|
|
89
|
+
await signer.account.signMessage({ message })
|
|
90
|
+
])
|
|
91
|
+
},
|
|
92
|
+
signTypedData: async (typedData) => {
|
|
93
|
+
return concat([
|
|
94
|
+
"0xff",
|
|
95
|
+
await signer.account.signTypedData(typedData)
|
|
96
|
+
])
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
signUserOperation: async (userOperation): Promise<Hex> => {
|
|
100
|
+
const userOpHash = getUserOperationHash({
|
|
101
|
+
userOperation: { ...userOperation, signature: "0x" },
|
|
102
|
+
entryPoint: entryPointAddress,
|
|
103
|
+
chainId: chainId
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const signature = await signer.account.signMessage({
|
|
107
|
+
message: { raw: userOpHash }
|
|
108
|
+
})
|
|
109
|
+
return concat(["0xff", signature])
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
getNonceKey: async () => {
|
|
113
|
+
return 0n
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
async getDummySignature(_userOperation) {
|
|
117
|
+
return concat(["0xff", signer.getDummySignature()])
|
|
118
|
+
},
|
|
119
|
+
getPluginSerializationParams: (): PermissionData => {
|
|
120
|
+
return {
|
|
121
|
+
policies
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
isEnabled: async (
|
|
125
|
+
kernelAccountAddress: Address,
|
|
126
|
+
_selector: Hex
|
|
127
|
+
): Promise<boolean> => {
|
|
128
|
+
try {
|
|
129
|
+
const permissionConfig = await getAction(
|
|
130
|
+
client,
|
|
131
|
+
readContract
|
|
132
|
+
)({
|
|
133
|
+
abi: KernelV3AccountAbi,
|
|
134
|
+
address: kernelAccountAddress,
|
|
135
|
+
functionName: "permissionConfig",
|
|
136
|
+
args: [
|
|
137
|
+
concat([
|
|
138
|
+
"0x00",
|
|
139
|
+
pad(getPermissionId(), {
|
|
140
|
+
size: 20,
|
|
141
|
+
dir: "right"
|
|
142
|
+
})
|
|
143
|
+
])
|
|
144
|
+
]
|
|
145
|
+
})
|
|
146
|
+
return permissionConfig.signer === signer.signerContractAddress
|
|
147
|
+
} catch (error) {
|
|
148
|
+
return false
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
package/types.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type KernelValidator } from "@zerodev/sdk"
|
|
2
|
+
import type { Action, PluginValidityData } from "@zerodev/sdk/types"
|
|
3
|
+
import type { EntryPoint } from "permissionless/types/entrypoint"
|
|
4
|
+
import type { Abi, Address, Hex, LocalAccount } from "viem"
|
|
5
|
+
import { PolicyFlags } from "./constants.js"
|
|
6
|
+
import type {
|
|
7
|
+
CallPolicyParams,
|
|
8
|
+
GasPolicyParams,
|
|
9
|
+
RateLimitPolicyParams,
|
|
10
|
+
SignatureCallerPolicyParams,
|
|
11
|
+
SudoPolicyParams
|
|
12
|
+
} from "./policies/index.js"
|
|
13
|
+
|
|
14
|
+
export type PermissionPlugin<entryPoint extends EntryPoint> = KernelValidator<
|
|
15
|
+
entryPoint,
|
|
16
|
+
"PermissionValidator"
|
|
17
|
+
> & {
|
|
18
|
+
getPluginSerializationParams: () => PermissionData
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type ModularSignerParams = {
|
|
22
|
+
signerContractAddress?: Address
|
|
23
|
+
}
|
|
24
|
+
export type PolicyParams = {
|
|
25
|
+
policyAddress?: Address
|
|
26
|
+
policyFlag?: PolicyFlags
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ModularSigner = {
|
|
30
|
+
account: LocalAccount
|
|
31
|
+
signerContractAddress: Address
|
|
32
|
+
getSignerData: (permissionId?: Hex) => Hex
|
|
33
|
+
getDummySignature: () => Hex
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type Policy = {
|
|
37
|
+
getPolicyData: (permissionId?: Hex) => Hex
|
|
38
|
+
getPolicyInfoInBytes: () => Hex
|
|
39
|
+
// return params directly to serialize/deserialize Policy
|
|
40
|
+
policyParams:
|
|
41
|
+
| CallPolicyParams<Abi | readonly unknown[], string>
|
|
42
|
+
| GasPolicyParams
|
|
43
|
+
| RateLimitPolicyParams
|
|
44
|
+
| SignatureCallerPolicyParams
|
|
45
|
+
| SudoPolicyParams
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type PermissionPluginParams = {
|
|
49
|
+
signer: ModularSigner
|
|
50
|
+
policies: Policy[]
|
|
51
|
+
entryPoint: EntryPoint
|
|
52
|
+
flag?: PolicyFlags
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface PermissionData {
|
|
56
|
+
policies?: Policy[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type ExportPermissionAccountParams = {
|
|
60
|
+
initCode: Hex
|
|
61
|
+
accountAddress: Address
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type PermissionAccountParams = {
|
|
65
|
+
permissionParams: PermissionData
|
|
66
|
+
action: Action
|
|
67
|
+
validityData: PluginValidityData
|
|
68
|
+
accountParams: ExportPermissionAccountParams
|
|
69
|
+
enableSignature?: Hex
|
|
70
|
+
privateKey?: Hex
|
|
71
|
+
}
|
package/utils.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EntryPoint } from "permissionless/types"
|
|
2
|
+
import type { PermissionAccountParams, PermissionPlugin } from "./types.js"
|
|
3
|
+
|
|
4
|
+
export function base64ToBytes(base64: string) {
|
|
5
|
+
const binString = atob(base64)
|
|
6
|
+
return Uint8Array.from(binString, (m) => m.codePointAt(0) as number)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function bytesToBase64(bytes: Uint8Array) {
|
|
10
|
+
const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join("")
|
|
11
|
+
return btoa(binString)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isPermissionValidatorPlugin<entryPoint extends EntryPoint>(
|
|
15
|
+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
16
|
+
plugin: any
|
|
17
|
+
): plugin is PermissionPlugin<entryPoint> {
|
|
18
|
+
return plugin?.getPluginSerializationParams !== undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const serializePermissionAccountParams = (
|
|
22
|
+
params: PermissionAccountParams
|
|
23
|
+
) => {
|
|
24
|
+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
25
|
+
const replacer = (_: string, value: any) => {
|
|
26
|
+
if (typeof value === "bigint") {
|
|
27
|
+
return value.toString()
|
|
28
|
+
}
|
|
29
|
+
return value
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const jsonString = JSON.stringify(params, replacer)
|
|
33
|
+
const uint8Array = new TextEncoder().encode(jsonString)
|
|
34
|
+
const base64String = bytesToBase64(uint8Array)
|
|
35
|
+
return base64String
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const deserializePermissionAccountParams = (params: string) => {
|
|
39
|
+
const uint8Array = base64ToBytes(params)
|
|
40
|
+
const jsonString = new TextDecoder().decode(uint8Array)
|
|
41
|
+
return JSON.parse(jsonString) as PermissionAccountParams
|
|
42
|
+
}
|