forked from ethereumjs/ethereumjs-monorepo
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauthorization.ts
More file actions
180 lines (166 loc) · 5.94 KB
/
authorization.ts
File metadata and controls
180 lines (166 loc) · 5.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// Utility helpers to convert authorization lists from the byte format and JSON format and vice versa
import { EthereumJSErrorWithoutCode, RLP } from '@ethereumjs/rlp'
import { secp256k1 } from '@noble/curves/secp256k1.js'
import { keccak_256 } from '@noble/hashes/sha3.js'
import { publicToAddress } from './account.ts'
import { Address } from './address.ts'
import {
bigIntToUnpaddedBytes,
bytesToBigInt,
bytesToHex,
concatBytes,
hexToBytes,
setLengthLeft,
unpadBytes,
} from './bytes.ts'
import { ecrecover } from './signature.ts'
import type {
EOACode7702AuthorizationListBytesItem,
EOACode7702AuthorizationListBytesItemUnsigned,
EOACode7702AuthorizationListItem,
EOACode7702AuthorizationListItemUnsigned,
} from './types.ts'
export const EOA_CODE_7702_AUTHORITY_SIGNING_MAGIC = hexToBytes('0x05')
/**
* Converts an authorization list to a JSON format
* @param authorizationList
* @returns authorizationList in JSON format
*/
export function eoaCode7702AuthorizationListBytesItemToJSON(
authorizationList: EOACode7702AuthorizationListBytesItem,
): EOACode7702AuthorizationListItem {
const [chainId, address, nonce, yParity, r, s] = authorizationList
return {
chainId: bytesToHex(chainId),
address: bytesToHex(address),
nonce: bytesToHex(nonce),
yParity: bytesToHex(yParity),
r: bytesToHex(r),
s: bytesToHex(s),
}
}
/**
* Converts an authority list in JSON to a bytes format
* @param authorizationList
* @returns bytes format of the authority list
*/
export function eoaCode7702AuthorizationListJSONItemToBytes(
authorizationList: EOACode7702AuthorizationListItem,
): EOACode7702AuthorizationListBytesItem {
const requiredFields = ['chainId', 'address', 'nonce', 'yParity', 'r', 's'] as const
// Validate all required fields are present
for (const field of requiredFields) {
if (authorizationList[field] === undefined) {
throw EthereumJSErrorWithoutCode(
`EIP-7702 authorization list invalid: ${field} is not defined`,
)
}
}
return [
unpadBytes(hexToBytes(authorizationList.chainId)),
hexToBytes(authorizationList.address),
unpadBytes(hexToBytes(authorizationList.nonce)),
unpadBytes(hexToBytes(authorizationList.yParity)),
unpadBytes(hexToBytes(authorizationList.r)),
unpadBytes(hexToBytes(authorizationList.s)),
]
}
/** Authorization signing utility methods */
function unsignedAuthorizationListToBytes(input: EOACode7702AuthorizationListItemUnsigned) {
const { chainId: chainIdHex, address: addressHex, nonce: nonceHex } = input
const chainId = unpadBytes(hexToBytes(chainIdHex))
const address = setLengthLeft(hexToBytes(addressHex), 20)
const nonce = unpadBytes(hexToBytes(nonceHex))
return [chainId, address, nonce]
}
/**
* Returns the bytes (RLP-encoded) to sign
* @param input Either the bytes or the object format of the authorization list item
* @returns
*/
export function eoaCode7702AuthorizationMessageToSign(
input: EOACode7702AuthorizationListItemUnsigned | EOACode7702AuthorizationListBytesItemUnsigned,
) {
if (Array.isArray(input)) {
// The address is validated, the chainId and nonce will be `unpadBytes` such that these are valid
const [chainId, address, nonce] = input
if (address.length !== 20) {
throw EthereumJSErrorWithoutCode('Cannot sign authority: address length should be 20 bytes')
}
return concatBytes(
EOA_CODE_7702_AUTHORITY_SIGNING_MAGIC,
RLP.encode([unpadBytes(chainId), address, unpadBytes(nonce)]),
)
} else {
const [chainId, address, nonce] = unsignedAuthorizationListToBytes(input)
return concatBytes(EOA_CODE_7702_AUTHORITY_SIGNING_MAGIC, RLP.encode([chainId, address, nonce]))
}
}
/**
* Hashes the RLP-encoded message to sign
* @param input
* @returns
*/
export function eoaCode7702AuthorizationHashedMessageToSign(
input: EOACode7702AuthorizationListItemUnsigned | EOACode7702AuthorizationListBytesItemUnsigned,
) {
return keccak_256(eoaCode7702AuthorizationMessageToSign(input))
}
/**
* Signs an authorization list item and returns it in `bytes` format.
* To get the JSON format, use `authorizationListBytesToJSON([signed])[0] to convert it`
* @param input
* @param privateKey
* @param ecSign
* @returns
*/
export function eoaCode7702SignAuthorization(
input: EOACode7702AuthorizationListItemUnsigned | EOACode7702AuthorizationListBytesItemUnsigned,
privateKey: Uint8Array,
ecSign?: (
msg: Uint8Array,
pk: Uint8Array,
ecSignOpts?: { extraEntropy?: Uint8Array | boolean },
) => Pick<ReturnType<typeof secp256k1.Signature.fromBytes>, 'recovery' | 'r' | 's'>,
): EOACode7702AuthorizationListBytesItem {
const msgHash = eoaCode7702AuthorizationHashedMessageToSign(input)
const secp256k1Sign = (msgHash: Uint8Array, pk: Uint8Array) => {
return (
ecSign?.(msgHash, pk) ??
secp256k1.Signature.fromBytes(
secp256k1.sign(msgHash, pk, { prehash: false, format: 'recovered' }),
'recovered',
)
)
}
const signed = secp256k1Sign(msgHash, privateKey)
const [chainId, address, nonce] = Array.isArray(input)
? input
: unsignedAuthorizationListToBytes(input)
return [
unpadBytes(chainId),
address,
unpadBytes(nonce),
bigIntToUnpaddedBytes(BigInt(signed.recovery!)),
bigIntToUnpaddedBytes(signed.r),
bigIntToUnpaddedBytes(signed.s),
]
}
export function eoaCode7702RecoverAuthority(
input: EOACode7702AuthorizationListItem | EOACode7702AuthorizationListBytesItem,
): Address {
const inputBytes = Array.isArray(input)
? input
: eoaCode7702AuthorizationListJSONItemToBytes(input)
const [chainId, address, nonce, yParity, r, s] = [
unpadBytes(inputBytes[0]),
inputBytes[1],
unpadBytes(inputBytes[2]),
unpadBytes(inputBytes[3]),
unpadBytes(inputBytes[4]),
unpadBytes(inputBytes[5]),
]
const msgHash = eoaCode7702AuthorizationHashedMessageToSign([chainId, address, nonce])
const pubKey = ecrecover(msgHash, bytesToBigInt(yParity), r, s)
return new Address(publicToAddress(pubKey))
}