@obolnetwork/obol-sdk 1.0.16 → 1.0.17
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/README.md +6 -3
- package/dist/cjs/package.json +11 -6
- package/dist/cjs/src/ajv.js +27 -0
- package/dist/cjs/src/constants.js +8 -4
- package/dist/cjs/src/index.js +10 -9
- package/dist/cjs/src/schema.js +8 -0
- package/dist/cjs/src/utils.js +3 -3
- package/dist/cjs/src/verification/common.js +19 -7
- package/dist/cjs/src/verification/termsAndConditions.js +1 -1
- package/dist/cjs/src/verification/v1.6.0.js +1 -1
- package/dist/cjs/src/verification/v1.7.0.js +3 -3
- package/dist/cjs/src/verification/v1.8.0.js +3 -3
- package/dist/cjs/test/fixtures.js +27 -33
- package/dist/cjs/test/methods.test.js +54 -27
- package/dist/esm/package.json +11 -6
- package/dist/esm/src/ajv.js +27 -0
- package/dist/esm/src/base.js +1 -1
- package/dist/esm/src/constants.js +7 -3
- package/dist/esm/src/index.js +11 -10
- package/dist/esm/src/schema.js +8 -0
- package/dist/esm/src/utils.js +3 -3
- package/dist/esm/src/verification/common.js +27 -15
- package/dist/esm/src/verification/sszTypes.js +1 -1
- package/dist/esm/src/verification/termsAndConditions.js +1 -1
- package/dist/esm/src/verification/v1.6.0.js +5 -5
- package/dist/esm/src/verification/v1.7.0.js +8 -8
- package/dist/esm/src/verification/v1.8.0.js +8 -8
- package/dist/esm/test/fixtures.js +27 -33
- package/dist/esm/test/methods.test.js +55 -28
- package/dist/types/src/constants.d.ts +3 -1
- package/dist/types/src/schema.d.ts +8 -0
- package/dist/types/src/types.d.ts +18 -18
- package/package.json +12 -7
- package/src/ajv.ts +38 -7
- package/src/base.ts +22 -18
- package/src/constants.ts +42 -36
- package/src/errors.ts +4 -4
- package/src/index.ts +85 -75
- package/src/schema.ts +10 -2
- package/src/services.ts +6 -6
- package/src/types.ts +65 -65
- package/src/utils.ts +17 -17
- package/src/verification/common.ts +374 -333
- package/src/verification/sszTypes.ts +60 -51
- package/src/verification/termsAndConditions.ts +16 -14
- package/src/verification/v1.6.0.ts +214 -184
- package/src/verification/v1.7.0.ts +268 -233
- package/src/verification/v1.8.0.ts +266 -225
|
@@ -1,22 +1,53 @@
|
|
|
1
|
+
import { fromHexString } from '@chainsafe/ssz';
|
|
2
|
+
import elliptic from 'elliptic';
|
|
3
|
+
import { init } from '@chainsafe/bls';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
FORK_MAPPING,
|
|
7
|
+
type ClusterDefinition,
|
|
8
|
+
type ClusterLock,
|
|
9
|
+
type DepositData,
|
|
10
|
+
type BuilderRegistrationMessage,
|
|
11
|
+
type DistributedValidator,
|
|
12
|
+
} from '../types.js';
|
|
13
|
+
import * as semver from 'semver';
|
|
14
|
+
import {
|
|
15
|
+
clusterDefinitionContainerTypeV1X6,
|
|
16
|
+
hashClusterDefinitionV1X6,
|
|
17
|
+
hashClusterLockV1X6,
|
|
18
|
+
verifyDVV1X6,
|
|
19
|
+
} from './v1.6.0.js';
|
|
1
20
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
21
|
+
clusterDefinitionContainerTypeV1X7,
|
|
22
|
+
hashClusterDefinitionV1X7,
|
|
23
|
+
hashClusterLockV1X7,
|
|
24
|
+
verifyDVV1X7,
|
|
25
|
+
} from './v1.7.0.js';
|
|
26
|
+
import { ethers } from 'ethers';
|
|
5
27
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
DOMAIN_APPLICATION_BUILDER,
|
|
29
|
+
DOMAIN_DEPOSIT,
|
|
30
|
+
DefinitionFlow,
|
|
31
|
+
GENESIS_VALIDATOR_ROOT,
|
|
32
|
+
signCreatorConfigHashPayload,
|
|
33
|
+
signEnrPayload,
|
|
34
|
+
signOperatorConfigHashPayload,
|
|
35
|
+
} from '../constants.js';
|
|
36
|
+
import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util';
|
|
37
|
+
import {
|
|
38
|
+
builderRegistrationMessageType,
|
|
39
|
+
depositMessageType,
|
|
40
|
+
forkDataType,
|
|
41
|
+
signingRootType,
|
|
42
|
+
} from './sszTypes.js';
|
|
43
|
+
import { definitionFlow, hexWithout0x } from '../utils.js';
|
|
44
|
+
import { ENR } from '@chainsafe/discv5';
|
|
45
|
+
import {
|
|
46
|
+
clusterDefinitionContainerTypeV1X8,
|
|
47
|
+
hashClusterDefinitionV1X8,
|
|
48
|
+
hashClusterLockV1X8,
|
|
49
|
+
verifyDVV1X8,
|
|
50
|
+
} from './v1.8.0.js';
|
|
20
51
|
|
|
21
52
|
// cluster-definition hash
|
|
22
53
|
|
|
@@ -26,37 +57,40 @@ import { clusterDefinitionContainerTypeV1X8, hashClusterDefinitionV1X8, hashClus
|
|
|
26
57
|
* @returns The config hash or the definition hash in of the corresponding cluster
|
|
27
58
|
*/
|
|
28
59
|
export const clusterConfigOrDefinitionHash = (
|
|
29
|
-
|
|
30
|
-
|
|
60
|
+
cluster: ClusterDefinition,
|
|
61
|
+
configOnly: boolean,
|
|
31
62
|
): string => {
|
|
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
|
-
}
|
|
63
|
+
let definitionType, val;
|
|
64
|
+
|
|
65
|
+
if (semver.eq(cluster.version, 'v1.6.0')) {
|
|
66
|
+
definitionType = clusterDefinitionContainerTypeV1X6(configOnly);
|
|
67
|
+
val = hashClusterDefinitionV1X6(cluster, configOnly);
|
|
68
|
+
return (
|
|
69
|
+
'0x' +
|
|
70
|
+
Buffer.from(definitionType.hashTreeRoot(val).buffer).toString('hex')
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (semver.eq(cluster.version, 'v1.7.0')) {
|
|
75
|
+
definitionType = clusterDefinitionContainerTypeV1X7(configOnly);
|
|
76
|
+
val = hashClusterDefinitionV1X7(cluster, configOnly);
|
|
77
|
+
return (
|
|
78
|
+
'0x' +
|
|
79
|
+
Buffer.from(definitionType.hashTreeRoot(val).buffer).toString('hex')
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (semver.eq(cluster.version, 'v1.8.0')) {
|
|
84
|
+
definitionType = clusterDefinitionContainerTypeV1X8(configOnly);
|
|
85
|
+
val = hashClusterDefinitionV1X8(cluster, configOnly);
|
|
86
|
+
return (
|
|
87
|
+
'0x' +
|
|
88
|
+
Buffer.from(definitionType.hashTreeRoot(val).buffer).toString('hex')
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
throw new Error('unsupported version');
|
|
93
|
+
};
|
|
60
94
|
|
|
61
95
|
// cluster-lock hash
|
|
62
96
|
|
|
@@ -66,191 +100,193 @@ export const clusterConfigOrDefinitionHash = (
|
|
|
66
100
|
* @returns The cluster lock hash in of the corresponding cluster lock
|
|
67
101
|
*/
|
|
68
102
|
export const clusterLockHash = (clusterLock: ClusterLock): string => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
103
|
+
if (semver.eq(clusterLock.cluster_definition.version, 'v1.6.0')) {
|
|
104
|
+
return hashClusterLockV1X6(clusterLock);
|
|
105
|
+
}
|
|
72
106
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
107
|
+
if (semver.eq(clusterLock.cluster_definition.version, 'v1.7.0')) {
|
|
108
|
+
return hashClusterLockV1X7(clusterLock);
|
|
109
|
+
}
|
|
76
110
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
111
|
+
if (semver.eq(clusterLock.cluster_definition.version, 'v1.8.0')) {
|
|
112
|
+
return hashClusterLockV1X8(clusterLock);
|
|
113
|
+
}
|
|
80
114
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
115
|
+
// other versions
|
|
116
|
+
throw new Error('unsupported version');
|
|
117
|
+
};
|
|
84
118
|
|
|
85
119
|
// Lock verification
|
|
86
120
|
|
|
87
121
|
// cluster-definition signatures verification
|
|
88
122
|
|
|
89
123
|
const getPOSTConfigHashSigner = (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
124
|
+
signature: string,
|
|
125
|
+
configHash: string,
|
|
126
|
+
chainId: FORK_MAPPING,
|
|
93
127
|
): string => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
128
|
+
try {
|
|
129
|
+
const sig = ethers.Signature.from(signature);
|
|
130
|
+
const data = signCreatorConfigHashPayload(
|
|
131
|
+
{ creator_config_hash: configHash },
|
|
132
|
+
chainId,
|
|
133
|
+
);
|
|
134
|
+
const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
|
|
135
|
+
|
|
136
|
+
return ethers.recoverAddress(digest, sig).toLowerCase();
|
|
137
|
+
} catch (err) {
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
107
141
|
|
|
108
142
|
const getPUTConfigHashSigner = (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
143
|
+
signature: string,
|
|
144
|
+
configHash: string,
|
|
145
|
+
chainId: number,
|
|
112
146
|
): string => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
147
|
+
try {
|
|
148
|
+
const sig = ethers.Signature.from(signature);
|
|
149
|
+
const data = signOperatorConfigHashPayload(
|
|
150
|
+
{ operator_config_hash: configHash },
|
|
151
|
+
chainId,
|
|
152
|
+
);
|
|
153
|
+
const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
|
|
154
|
+
|
|
155
|
+
return ethers.recoverAddress(digest, sig).toLowerCase();
|
|
156
|
+
} catch (err) {
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
126
160
|
|
|
127
161
|
const getEnrSigner = (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
162
|
+
signature: string,
|
|
163
|
+
payload: string,
|
|
164
|
+
chainId: number,
|
|
131
165
|
): string => {
|
|
132
|
-
|
|
133
|
-
|
|
166
|
+
try {
|
|
167
|
+
const sig = ethers.Signature.from(signature);
|
|
134
168
|
|
|
135
|
-
|
|
136
|
-
|
|
169
|
+
const data = signEnrPayload({ enr: payload }, chainId);
|
|
170
|
+
const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
|
|
137
171
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
172
|
+
return ethers.recoverAddress(digest, sig).toLowerCase();
|
|
173
|
+
} catch (err) {
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
143
177
|
|
|
144
178
|
const verifyDefinitionSignatures = (
|
|
145
|
-
|
|
146
|
-
|
|
179
|
+
clusterDefinition: ClusterDefinition,
|
|
180
|
+
definitionType: DefinitionFlow,
|
|
147
181
|
): boolean => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
return clusterDefinition.operators.every((operator) => {
|
|
164
|
-
const configSigner = getPUTConfigHashSigner(
|
|
165
|
-
operator.config_signature as string,
|
|
166
|
-
clusterDefinition.config_hash,
|
|
167
|
-
FORK_MAPPING[
|
|
168
|
-
clusterDefinition.fork_version as keyof typeof FORK_MAPPING
|
|
169
|
-
],
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
const enrSigner = getEnrSigner(
|
|
173
|
-
operator.enr_signature as string,
|
|
174
|
-
operator.enr as string,
|
|
175
|
-
FORK_MAPPING[
|
|
176
|
-
clusterDefinition.fork_version as keyof typeof FORK_MAPPING
|
|
177
|
-
],
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
configSigner !== operator.address.toLowerCase() ||
|
|
182
|
-
enrSigner !== operator.address.toLowerCase()
|
|
183
|
-
) {
|
|
184
|
-
return false
|
|
185
|
-
}
|
|
186
|
-
return true
|
|
187
|
-
})
|
|
182
|
+
if (definitionType === DefinitionFlow.Charon) {
|
|
183
|
+
return true;
|
|
184
|
+
} else {
|
|
185
|
+
const configSigner = getPOSTConfigHashSigner(
|
|
186
|
+
clusterDefinition.creator.config_signature as string,
|
|
187
|
+
clusterDefinition.config_hash,
|
|
188
|
+
FORK_MAPPING[clusterDefinition.fork_version as keyof typeof FORK_MAPPING],
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (configSigner !== clusterDefinition.creator.address.toLowerCase()) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
if (definitionType === DefinitionFlow.Solo) {
|
|
195
|
+
return true;
|
|
188
196
|
}
|
|
189
|
-
|
|
197
|
+
return clusterDefinition.operators.every(operator => {
|
|
198
|
+
const configSigner = getPUTConfigHashSigner(
|
|
199
|
+
operator.config_signature as string,
|
|
200
|
+
clusterDefinition.config_hash,
|
|
201
|
+
FORK_MAPPING[
|
|
202
|
+
clusterDefinition.fork_version as keyof typeof FORK_MAPPING
|
|
203
|
+
],
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const enrSigner = getEnrSigner(
|
|
207
|
+
operator.enr_signature as string,
|
|
208
|
+
operator.enr as string,
|
|
209
|
+
FORK_MAPPING[
|
|
210
|
+
clusterDefinition.fork_version as keyof typeof FORK_MAPPING
|
|
211
|
+
],
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
if (
|
|
215
|
+
configSigner !== operator.address.toLowerCase() ||
|
|
216
|
+
enrSigner !== operator.address.toLowerCase()
|
|
217
|
+
) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
return true;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
};
|
|
190
224
|
|
|
191
225
|
// cluster-lock data verification
|
|
192
226
|
const computeSigningRoot = (
|
|
193
|
-
|
|
194
|
-
|
|
227
|
+
sszObjectRoot: Uint8Array,
|
|
228
|
+
domain: Uint8Array,
|
|
195
229
|
): Uint8Array => {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
230
|
+
const signingRootDefaultValue = signingRootType.defaultValue();
|
|
231
|
+
signingRootDefaultValue.objectRoot = sszObjectRoot;
|
|
232
|
+
signingRootDefaultValue.domain = domain;
|
|
233
|
+
return Buffer.from(
|
|
234
|
+
signingRootType.hashTreeRoot(signingRootDefaultValue).buffer,
|
|
235
|
+
);
|
|
236
|
+
};
|
|
201
237
|
|
|
202
238
|
const computeDepositMsgRoot = (msg: Partial<DepositData>): Buffer => {
|
|
203
|
-
|
|
239
|
+
const depositMsgVal = depositMessageType.defaultValue();
|
|
204
240
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
241
|
+
depositMsgVal.pubkey = fromHexString(msg.pubkey as string);
|
|
242
|
+
depositMsgVal.withdrawal_credentials = fromHexString(
|
|
243
|
+
msg.withdrawal_credentials as string,
|
|
244
|
+
);
|
|
245
|
+
depositMsgVal.amount = parseInt(msg.amount as string);
|
|
246
|
+
return Buffer.from(depositMessageType.hashTreeRoot(depositMsgVal).buffer);
|
|
247
|
+
};
|
|
212
248
|
|
|
213
249
|
const computeForkDataRoot = (
|
|
214
|
-
|
|
215
|
-
|
|
250
|
+
currentVersion: Uint8Array,
|
|
251
|
+
genesisValidatorsRoot: Uint8Array,
|
|
216
252
|
): Uint8Array => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
253
|
+
const forkDataVal = forkDataType.defaultValue();
|
|
254
|
+
forkDataVal.currentVersion = currentVersion;
|
|
255
|
+
forkDataVal.genesisValidatorsRoot = genesisValidatorsRoot;
|
|
256
|
+
return Buffer.from(forkDataType.hashTreeRoot(forkDataVal).buffer);
|
|
257
|
+
};
|
|
222
258
|
|
|
223
259
|
const computebuilderRegistrationMsgRoot = (
|
|
224
|
-
|
|
260
|
+
msg: BuilderRegistrationMessage,
|
|
225
261
|
): Buffer => {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
262
|
+
const builderRegistrationMsgVal =
|
|
263
|
+
builderRegistrationMessageType.defaultValue();
|
|
264
|
+
|
|
265
|
+
builderRegistrationMsgVal.fee_recipient = fromHexString(msg.fee_recipient);
|
|
266
|
+
builderRegistrationMsgVal.gas_limit = msg.gas_limit;
|
|
267
|
+
builderRegistrationMsgVal.timestamp = msg.timestamp;
|
|
268
|
+
builderRegistrationMsgVal.pubkey = fromHexString(msg.pubkey);
|
|
269
|
+
return Buffer.from(
|
|
270
|
+
builderRegistrationMessageType.hashTreeRoot(builderRegistrationMsgVal)
|
|
271
|
+
.buffer,
|
|
272
|
+
);
|
|
273
|
+
};
|
|
238
274
|
|
|
239
275
|
const computeDomain = (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
276
|
+
domainType: Uint8Array,
|
|
277
|
+
lockForkVersion: string,
|
|
278
|
+
genesisValidatorsRoot: Uint8Array = fromHexString(GENESIS_VALIDATOR_ROOT),
|
|
243
279
|
): Uint8Array => {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
280
|
+
const forkVersion = fromHexString(
|
|
281
|
+
lockForkVersion.substring(2, lockForkVersion.length),
|
|
282
|
+
);
|
|
247
283
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
284
|
+
const forkDataRoot = computeForkDataRoot(forkVersion, genesisValidatorsRoot);
|
|
285
|
+
const domain = new Uint8Array(32);
|
|
286
|
+
domain.set(domainType);
|
|
287
|
+
domain.set(forkDataRoot.subarray(0, 28), 4);
|
|
288
|
+
return domain;
|
|
289
|
+
};
|
|
254
290
|
|
|
255
291
|
/**
|
|
256
292
|
* Verify deposit data withdrawal credintials and signature
|
|
@@ -260,161 +296,166 @@ const computeDomain = (
|
|
|
260
296
|
* @returns {boolean} - return if deposit data is valid.
|
|
261
297
|
*/
|
|
262
298
|
export const verifyDepositData = (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
): { isValidDepositData: boolean
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
depositMessageBuffer,
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
return { isValidDepositData: true, depositDataMsg: depositDataMessage }
|
|
295
|
-
}
|
|
299
|
+
distributedPublicKey: string,
|
|
300
|
+
depositData: Partial<DepositData>,
|
|
301
|
+
withdrawalAddress: string,
|
|
302
|
+
forkVersion: string,
|
|
303
|
+
): { isValidDepositData: boolean; depositDataMsg: Uint8Array } => {
|
|
304
|
+
const depositDomain = computeDomain(
|
|
305
|
+
fromHexString(DOMAIN_DEPOSIT),
|
|
306
|
+
forkVersion,
|
|
307
|
+
);
|
|
308
|
+
const eth1AddressWithdrawalPrefix = '0x01';
|
|
309
|
+
if (
|
|
310
|
+
eth1AddressWithdrawalPrefix +
|
|
311
|
+
'0'.repeat(22) +
|
|
312
|
+
withdrawalAddress.toLowerCase().slice(2) !==
|
|
313
|
+
depositData.withdrawal_credentials
|
|
314
|
+
) {
|
|
315
|
+
return { isValidDepositData: false, depositDataMsg: new Uint8Array(0) };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (distributedPublicKey !== depositData.pubkey) {
|
|
319
|
+
return { isValidDepositData: false, depositDataMsg: new Uint8Array(0) };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const depositMessageBuffer = computeDepositMsgRoot(depositData);
|
|
323
|
+
const depositDataMessage = signingRoot(depositDomain, depositMessageBuffer);
|
|
324
|
+
|
|
325
|
+
return { isValidDepositData: true, depositDataMsg: depositDataMessage };
|
|
326
|
+
};
|
|
296
327
|
|
|
297
328
|
export const verifyBuilderRegistration = (
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
): {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
329
|
+
validator: DistributedValidator,
|
|
330
|
+
feeRecipientAddress: string,
|
|
331
|
+
forkVersion: string,
|
|
332
|
+
): {
|
|
333
|
+
isValidBuilderRegistration: boolean;
|
|
334
|
+
builderRegistrationMsg: Uint8Array;
|
|
335
|
+
} => {
|
|
336
|
+
const builderDomain = computeDomain(
|
|
337
|
+
fromHexString(DOMAIN_APPLICATION_BUILDER),
|
|
338
|
+
forkVersion,
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
if (
|
|
342
|
+
validator.distributed_public_key !==
|
|
343
|
+
validator.builder_registration?.message.pubkey
|
|
344
|
+
) {
|
|
345
|
+
return {
|
|
346
|
+
isValidBuilderRegistration: false,
|
|
347
|
+
builderRegistrationMsg: new Uint8Array(0),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
if (
|
|
351
|
+
feeRecipientAddress.toLowerCase() !==
|
|
352
|
+
validator.builder_registration.message.fee_recipient.toLowerCase()
|
|
353
|
+
) {
|
|
354
|
+
return {
|
|
355
|
+
isValidBuilderRegistration: false,
|
|
356
|
+
builderRegistrationMsg: new Uint8Array(0),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const builderRegistrationMessageBuffer = computebuilderRegistrationMsgRoot(
|
|
361
|
+
validator.builder_registration.message,
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const builderRegistrationMessage = signingRoot(
|
|
365
|
+
builderDomain,
|
|
366
|
+
builderRegistrationMessageBuffer,
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
isValidBuilderRegistration: true,
|
|
371
|
+
builderRegistrationMsg: builderRegistrationMessage,
|
|
372
|
+
};
|
|
373
|
+
};
|
|
331
374
|
|
|
332
375
|
export const verifyNodeSignatures = (clusterLock: ClusterLock): boolean => {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
376
|
+
const ec = new elliptic.ec('secp256k1');
|
|
377
|
+
const nodeSignatures = clusterLock.node_signatures;
|
|
378
|
+
|
|
379
|
+
const lockHashWithout0x = hexWithout0x(clusterLock.lock_hash);
|
|
380
|
+
// node(ENR) signatures
|
|
381
|
+
for (let i = 0; i < (nodeSignatures as string[]).length; i++) {
|
|
382
|
+
const pubkey = ENR.decodeTxt(
|
|
383
|
+
clusterLock.cluster_definition.operators[i].enr as string,
|
|
384
|
+
).publicKey.toString('hex');
|
|
385
|
+
|
|
386
|
+
const ENRsignature = {
|
|
387
|
+
r: (nodeSignatures as string[])[i].slice(2, 66),
|
|
388
|
+
s: (nodeSignatures as string[])[i].slice(66, 130),
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const nodeSignatureVerification = ec
|
|
392
|
+
.keyFromPublic(pubkey, 'hex')
|
|
393
|
+
.verify(lockHashWithout0x, ENRsignature);
|
|
394
|
+
|
|
395
|
+
if (!nodeSignatureVerification) {
|
|
396
|
+
return false;
|
|
355
397
|
}
|
|
398
|
+
}
|
|
356
399
|
|
|
357
|
-
|
|
358
|
-
}
|
|
400
|
+
return true;
|
|
401
|
+
};
|
|
359
402
|
|
|
360
403
|
export const signingRoot = (
|
|
361
|
-
|
|
362
|
-
|
|
404
|
+
domain: Uint8Array,
|
|
405
|
+
messageBuffer: Buffer,
|
|
363
406
|
): Uint8Array => {
|
|
364
|
-
|
|
365
|
-
}
|
|
407
|
+
return computeSigningRoot(messageBuffer, domain);
|
|
408
|
+
};
|
|
366
409
|
|
|
367
410
|
const verifyLockData = async (clusterLock: ClusterLock): Promise<boolean> => {
|
|
368
|
-
|
|
411
|
+
await init('herumi');
|
|
369
412
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
413
|
+
if (semver.eq(clusterLock.cluster_definition.version, 'v1.6.0')) {
|
|
414
|
+
return verifyDVV1X6(clusterLock);
|
|
415
|
+
}
|
|
373
416
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
417
|
+
if (semver.eq(clusterLock.cluster_definition.version, 'v1.7.0')) {
|
|
418
|
+
return verifyDVV1X7(clusterLock);
|
|
419
|
+
}
|
|
377
420
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
421
|
+
if (semver.eq(clusterLock.cluster_definition.version, 'v1.8.0')) {
|
|
422
|
+
return verifyDVV1X8(clusterLock);
|
|
423
|
+
}
|
|
424
|
+
return false;
|
|
425
|
+
};
|
|
383
426
|
|
|
384
427
|
export const isValidClusterLock = async (
|
|
385
|
-
|
|
428
|
+
clusterLock: ClusterLock,
|
|
386
429
|
): Promise<boolean> => {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
if (!isValidLockData) {
|
|
414
|
-
return false
|
|
415
|
-
}
|
|
416
|
-
return true
|
|
417
|
-
} catch (err) {
|
|
418
|
-
return false
|
|
430
|
+
try {
|
|
431
|
+
const definitionType = definitionFlow(clusterLock.cluster_definition);
|
|
432
|
+
if (definitionType == null) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
const isValidDefinitionData = verifyDefinitionSignatures(
|
|
436
|
+
clusterLock.cluster_definition,
|
|
437
|
+
definitionType,
|
|
438
|
+
);
|
|
439
|
+
if (!isValidDefinitionData) {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (
|
|
444
|
+
clusterConfigOrDefinitionHash(clusterLock.cluster_definition, false) !==
|
|
445
|
+
clusterLock.cluster_definition.definition_hash
|
|
446
|
+
) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
if (clusterLockHash(clusterLock) !== clusterLock.lock_hash) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const isValidLockData = await verifyLockData(clusterLock);
|
|
454
|
+
if (!isValidLockData) {
|
|
455
|
+
return false;
|
|
419
456
|
}
|
|
420
|
-
|
|
457
|
+
return true;
|
|
458
|
+
} catch (err) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
};
|