@obolnetwork/obol-sdk 1.0.12 → 1.0.14
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/LICENCE +21 -0
- package/README.md +33 -3
- package/dist/cjs/package.json +128 -0
- package/dist/cjs/src/ajv.js +2 -1
- package/dist/cjs/src/base.js +2 -2
- package/dist/cjs/src/constants.js +38 -13
- package/dist/cjs/src/errors.js +1 -1
- package/dist/cjs/src/index.js +24 -21
- package/dist/cjs/src/schema.js +24 -36
- package/dist/cjs/src/services.js +2 -2
- package/dist/cjs/src/types.js +0 -1
- package/dist/cjs/src/utils.js +36 -1
- package/dist/cjs/src/{verify.js → verification/common.js} +128 -155
- package/dist/cjs/src/verification/sszTypes.js +69 -0
- package/dist/cjs/src/verification/v1.6.0.js +149 -0
- package/dist/cjs/src/verification/v1.7.0.js +173 -0
- package/dist/cjs/src/verification/v1.8.0.js +183 -0
- package/dist/cjs/test/fixtures.js +281 -63
- package/dist/cjs/test/methods.test.js +58 -35
- package/dist/esm/package.json +128 -0
- package/dist/esm/src/ajv.js +2 -1
- package/dist/esm/src/base.js +2 -2
- package/dist/esm/src/constants.js +14 -12
- package/dist/esm/src/errors.js +1 -1
- package/dist/esm/src/index.js +27 -24
- package/dist/esm/src/schema.js +24 -36
- package/dist/esm/src/services.js +1 -1
- package/dist/esm/src/types.js +0 -1
- package/dist/esm/src/utils.js +34 -0
- package/dist/esm/src/{verify.js → verification/common.js} +114 -146
- package/dist/esm/src/verification/sszTypes.js +64 -0
- package/dist/esm/src/verification/v1.6.0.js +142 -0
- package/dist/esm/src/verification/v1.7.0.js +166 -0
- package/dist/esm/src/verification/v1.8.0.js +176 -0
- package/dist/esm/test/fixtures.js +280 -62
- package/dist/esm/test/methods.test.js +59 -36
- package/dist/types/src/ajv.d.ts +1 -1
- package/dist/types/src/base.d.ts +3 -3
- package/dist/types/src/constants.d.ts +8 -10
- package/dist/types/src/index.d.ts +16 -16
- package/dist/types/src/services.d.ts +1 -1
- package/dist/types/src/types.d.ts +33 -27
- package/dist/types/src/utils.d.ts +3 -0
- package/dist/types/src/verification/common.d.ts +32 -0
- package/dist/types/src/verification/sszTypes.d.ts +63 -0
- package/dist/types/src/verification/v1.6.0.d.ts +34 -0
- package/dist/types/src/verification/v1.7.0.d.ts +34 -0
- package/dist/types/src/verification/v1.8.0.d.ts +35 -0
- package/dist/types/test/fixtures.d.ts +104 -2
- package/package.json +42 -8
- package/src/ajv.ts +15 -10
- package/src/base.ts +25 -21
- package/src/constants.ts +80 -78
- package/src/errors.ts +6 -9
- package/src/index.ts +118 -81
- package/src/schema.ts +24 -36
- package/src/services.ts +15 -13
- package/src/types.ts +72 -74
- package/src/utils.ts +53 -4
- package/src/verification/common.ts +420 -0
- package/src/verification/sszTypes.ts +79 -0
- package/src/verification/v1.6.0.ts +226 -0
- package/src/verification/v1.7.0.ts +274 -0
- package/src/verification/v1.8.0.ts +281 -0
- package/dist/cjs/src/hash.js +0 -172
- package/dist/esm/src/hash.js +0 -165
- package/dist/types/src/hash.d.ts +0 -56
- package/dist/types/src/verify.d.ts +0 -4
- package/src/hash.ts +0 -250
- package/src/verify.ts +0 -479
|
@@ -7,72 +7,66 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import
|
|
11
|
-
import { init, aggregateSignatures, verifyMultiple, verifyAggregate } from "@chainsafe/bls";
|
|
12
|
-
import { ENR } from '@chainsafe/discv5';
|
|
13
|
-
import { ByteVectorType, ContainerType, fromHexString } from '@chainsafe/ssz';
|
|
14
|
-
import { UintNumberType } from '@chainsafe/ssz/lib/type/uint.js';
|
|
15
|
-
import { SignTypedDataVersion, TypedDataUtils, } from '@metamask/eth-sig-util';
|
|
10
|
+
import { fromHexString, } from '@chainsafe/ssz';
|
|
16
11
|
import elliptic from 'elliptic';
|
|
12
|
+
import { init, } from '@chainsafe/bls';
|
|
13
|
+
import { FORK_MAPPING } from '../types.js';
|
|
14
|
+
import * as semver from 'semver';
|
|
15
|
+
import { clusterDefinitionContainerTypeV1X6, hashClusterDefinitionV1X6, hashClusterLockV1X6, verifyDVV1X6 } from './v1.6.0.js';
|
|
16
|
+
import { clusterDefinitionContainerTypeV1X7, hashClusterDefinitionV1X7, hashClusterLockV1X7, verifyDVV1X7 } from './v1.7.0.js';
|
|
17
17
|
import { ethers } from 'ethers';
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { hexWithout0x } from '
|
|
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
|
-
return (operator.address &&
|
|
47
|
-
operator.config_signature &&
|
|
48
|
-
operator.enr &&
|
|
49
|
-
operator.enr_signature);
|
|
50
|
-
})) {
|
|
51
|
-
return DefinitionFlow.Group;
|
|
18
|
+
import { DOMAIN_APPLICATION_BUILDER, DOMAIN_DEPOSIT, DefinitionFlow, GENESIS_VALIDATOR_ROOT, signCreatorConfigHashPayload, signEnrPayload, signOperatorConfigHashPayload } from '../constants.js';
|
|
19
|
+
import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util';
|
|
20
|
+
import { builderRegistrationMessageType, depositMessageType, forkDataType, signingRootType } from './sszTypes.js';
|
|
21
|
+
import { definitionFlow, hexWithout0x } from '../utils.js';
|
|
22
|
+
import { ENR } from '@chainsafe/discv5';
|
|
23
|
+
import { clusterDefinitionContainerTypeV1X8, hashClusterDefinitionV1X8, hashClusterLockV1X8, verifyDVV1X8 } from './v1.8.0.js';
|
|
24
|
+
// cluster-definition hash
|
|
25
|
+
/**
|
|
26
|
+
* @param cluster The cluster configuration or the cluster definition
|
|
27
|
+
* @param configOnly a boolean to indicate config hash or definition hash
|
|
28
|
+
* @returns The config hash or the definition hash in of the corresponding cluster
|
|
29
|
+
*/
|
|
30
|
+
export const clusterConfigOrDefinitionHash = (cluster, configOnly) => {
|
|
31
|
+
let definitionType, val;
|
|
32
|
+
if (semver.eq(cluster.version, 'v1.6.0')) {
|
|
33
|
+
definitionType = clusterDefinitionContainerTypeV1X6(configOnly);
|
|
34
|
+
val = hashClusterDefinitionV1X6(cluster, configOnly);
|
|
35
|
+
return ('0x' + Buffer.from(definitionType.hashTreeRoot(val).buffer).toString('hex'));
|
|
36
|
+
}
|
|
37
|
+
if (semver.eq(cluster.version, 'v1.7.0')) {
|
|
38
|
+
definitionType = clusterDefinitionContainerTypeV1X7(configOnly);
|
|
39
|
+
val = hashClusterDefinitionV1X7(cluster, configOnly);
|
|
40
|
+
return ('0x' + Buffer.from(definitionType.hashTreeRoot(val).buffer).toString('hex'));
|
|
41
|
+
}
|
|
42
|
+
if (semver.eq(cluster.version, 'v1.8.0')) {
|
|
43
|
+
definitionType = clusterDefinitionContainerTypeV1X8(configOnly);
|
|
44
|
+
val = hashClusterDefinitionV1X8(cluster, configOnly);
|
|
45
|
+
return ('0x' + Buffer.from(definitionType.hashTreeRoot(val).buffer).toString('hex'));
|
|
52
46
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
47
|
+
throw new Error('unsupported version');
|
|
48
|
+
};
|
|
49
|
+
// cluster-lock hash
|
|
50
|
+
/**
|
|
51
|
+
* Returns the SSZ cluster lock hash of the given cluster lock object
|
|
52
|
+
* @param cluster The cluster lock whose lock hash needs to be calculated
|
|
53
|
+
* @returns The cluster lock hash in of the corresponding cluster lock
|
|
54
|
+
*/
|
|
55
|
+
export const clusterLockHash = (clusterLock) => {
|
|
56
|
+
if (semver.eq(clusterLock.cluster_definition.version, 'v1.6.0')) {
|
|
57
|
+
return hashClusterLockV1X6(clusterLock);
|
|
58
|
+
}
|
|
59
|
+
if (semver.eq(clusterLock.cluster_definition.version, 'v1.7.0')) {
|
|
60
|
+
return hashClusterLockV1X7(clusterLock);
|
|
62
61
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
clusterDefinition.operators.every(operator => {
|
|
66
|
-
return (!operator.address &&
|
|
67
|
-
!operator.config_signature &&
|
|
68
|
-
operator.enr &&
|
|
69
|
-
!operator.enr_signature);
|
|
70
|
-
})) {
|
|
71
|
-
return DefinitionFlow.Charon;
|
|
62
|
+
if (semver.eq(clusterLock.cluster_definition.version, 'v1.8.0')) {
|
|
63
|
+
return hashClusterLockV1X8(clusterLock);
|
|
72
64
|
}
|
|
73
|
-
|
|
65
|
+
// other versions
|
|
66
|
+
throw new Error('unsupported version');
|
|
74
67
|
};
|
|
75
|
-
//
|
|
68
|
+
// Lock verification
|
|
69
|
+
// cluster-definition signatures verification
|
|
76
70
|
const getPOSTConfigHashSigner = (signature, configHash, chainId) => {
|
|
77
71
|
try {
|
|
78
72
|
const sig = ethers.Signature.from(signature);
|
|
@@ -107,7 +101,7 @@ const getEnrSigner = (signature, payload, chainId) => {
|
|
|
107
101
|
}
|
|
108
102
|
};
|
|
109
103
|
const verifyDefinitionSignatures = (clusterDefinition, definitionType) => {
|
|
110
|
-
if (definitionType ===
|
|
104
|
+
if (definitionType === DefinitionFlow.Charon) {
|
|
111
105
|
return true;
|
|
112
106
|
}
|
|
113
107
|
else {
|
|
@@ -115,10 +109,10 @@ const verifyDefinitionSignatures = (clusterDefinition, definitionType) => {
|
|
|
115
109
|
if (configSigner !== clusterDefinition.creator.address.toLowerCase()) {
|
|
116
110
|
return false;
|
|
117
111
|
}
|
|
118
|
-
if (definitionType ===
|
|
112
|
+
if (definitionType === DefinitionFlow.Solo) {
|
|
119
113
|
return true;
|
|
120
114
|
}
|
|
121
|
-
return clusterDefinition.operators.every(operator => {
|
|
115
|
+
return clusterDefinition.operators.every((operator) => {
|
|
122
116
|
const configSigner = getPUTConfigHashSigner(operator.config_signature, clusterDefinition.config_hash, FORK_MAPPING[clusterDefinition.fork_version]);
|
|
123
117
|
const enrSigner = getEnrSigner(operator.enr_signature, operator.enr, FORK_MAPPING[clusterDefinition.fork_version]);
|
|
124
118
|
if (configSigner !== operator.address.toLowerCase() ||
|
|
@@ -129,12 +123,12 @@ const verifyDefinitionSignatures = (clusterDefinition, definitionType) => {
|
|
|
129
123
|
});
|
|
130
124
|
}
|
|
131
125
|
};
|
|
132
|
-
//cluster-lock data verification
|
|
126
|
+
// cluster-lock data verification
|
|
133
127
|
const computeSigningRoot = (sszObjectRoot, domain) => {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return Buffer.from(signingRootType.hashTreeRoot(
|
|
128
|
+
const signingRootDefaultValue = signingRootType.defaultValue();
|
|
129
|
+
signingRootDefaultValue.objectRoot = sszObjectRoot;
|
|
130
|
+
signingRootDefaultValue.domain = domain;
|
|
131
|
+
return Buffer.from(signingRootType.hashTreeRoot(signingRootDefaultValue).buffer);
|
|
138
132
|
};
|
|
139
133
|
const computeDepositMsgRoot = (msg) => {
|
|
140
134
|
const depositMsgVal = depositMessageType.defaultValue();
|
|
@@ -158,7 +152,6 @@ const computebuilderRegistrationMsgRoot = (msg) => {
|
|
|
158
152
|
return Buffer.from(builderRegistrationMessageType.hashTreeRoot(builderRegistrationMsgVal)
|
|
159
153
|
.buffer);
|
|
160
154
|
};
|
|
161
|
-
// DV data verification
|
|
162
155
|
const computeDomain = (domainType, lockForkVersion, genesisValidatorsRoot = fromHexString(GENESIS_VALIDATOR_ROOT)) => {
|
|
163
156
|
const forkVersion = fromHexString(lockForkVersion.substring(2, lockForkVersion.length));
|
|
164
157
|
const forkDataRoot = computeForkDataRoot(forkVersion, genesisValidatorsRoot);
|
|
@@ -167,27 +160,61 @@ const computeDomain = (domainType, lockForkVersion, genesisValidatorsRoot = from
|
|
|
167
160
|
domain.set(forkDataRoot.subarray(0, 28), 4);
|
|
168
161
|
return domain;
|
|
169
162
|
};
|
|
170
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Verify deposit data withdrawal credintials and signature
|
|
165
|
+
* @param {string} forkVersion - fork version in definition file.
|
|
166
|
+
* @param {DistributedValidatorDto} validator - distributed validator.
|
|
167
|
+
* @param {string} withdrawalAddress - withdrawal address in definition file.
|
|
168
|
+
* @returns {boolean} - return if deposit data is valid.
|
|
169
|
+
*/
|
|
170
|
+
export const verifyDepositData = (distributedPublicKey, depositData, withdrawalAddress, forkVersion) => {
|
|
171
|
+
const depositDomain = computeDomain(fromHexString(DOMAIN_DEPOSIT), forkVersion);
|
|
171
172
|
const eth1AddressWithdrawalPrefix = '0x01';
|
|
172
173
|
if (eth1AddressWithdrawalPrefix +
|
|
173
174
|
'0'.repeat(22) +
|
|
174
175
|
withdrawalAddress.toLowerCase().slice(2) !==
|
|
175
|
-
|
|
176
|
-
return false;
|
|
176
|
+
depositData.withdrawal_credentials) {
|
|
177
|
+
return { isValidDepositData: false, depositDataMsg: new Uint8Array(0) };
|
|
177
178
|
}
|
|
178
|
-
if (
|
|
179
|
-
return false;
|
|
179
|
+
if (distributedPublicKey !== depositData.pubkey) {
|
|
180
|
+
return { isValidDepositData: false, depositDataMsg: new Uint8Array(0) };
|
|
180
181
|
}
|
|
181
|
-
|
|
182
|
+
const depositMessageBuffer = computeDepositMsgRoot(depositData);
|
|
183
|
+
const depositDataMessage = signingRoot(depositDomain, depositMessageBuffer);
|
|
184
|
+
return { isValidDepositData: true, depositDataMsg: depositDataMessage };
|
|
182
185
|
};
|
|
183
|
-
const verifyBuilderRegistration = (validator, feeRecipientAddress) => {
|
|
186
|
+
export const verifyBuilderRegistration = (validator, feeRecipientAddress, forkVersion) => {
|
|
187
|
+
var _a;
|
|
188
|
+
const builderDomain = computeDomain(fromHexString(DOMAIN_APPLICATION_BUILDER), forkVersion);
|
|
184
189
|
if (validator.distributed_public_key !==
|
|
185
|
-
validator.builder_registration.message.pubkey) {
|
|
186
|
-
return false;
|
|
190
|
+
((_a = validator.builder_registration) === null || _a === void 0 ? void 0 : _a.message.pubkey)) {
|
|
191
|
+
return { isValidBuilderRegistration: false, builderRegistrationMsg: new Uint8Array(0) };
|
|
187
192
|
}
|
|
188
193
|
if (feeRecipientAddress.toLowerCase() !==
|
|
189
194
|
validator.builder_registration.message.fee_recipient.toLowerCase()) {
|
|
190
|
-
return false;
|
|
195
|
+
return { isValidBuilderRegistration: false, builderRegistrationMsg: new Uint8Array(0) };
|
|
196
|
+
}
|
|
197
|
+
const builderRegistrationMessageBuffer = computebuilderRegistrationMsgRoot(validator.builder_registration.message);
|
|
198
|
+
const builderRegistrationMessage = signingRoot(builderDomain, builderRegistrationMessageBuffer);
|
|
199
|
+
return { isValidBuilderRegistration: true, builderRegistrationMsg: builderRegistrationMessage };
|
|
200
|
+
};
|
|
201
|
+
export const verifyNodeSignatures = (clusterLock) => {
|
|
202
|
+
const ec = new elliptic.ec('secp256k1');
|
|
203
|
+
const nodeSignatures = clusterLock.node_signatures;
|
|
204
|
+
const lockHashWithout0x = hexWithout0x(clusterLock.lock_hash);
|
|
205
|
+
// node(ENR) signatures
|
|
206
|
+
for (let i = 0; i < nodeSignatures.length; i++) {
|
|
207
|
+
const pubkey = ENR.decodeTxt(clusterLock.cluster_definition.operators[i].enr).publicKey.toString('hex');
|
|
208
|
+
const ENRsignature = {
|
|
209
|
+
r: nodeSignatures[i].slice(2, 66),
|
|
210
|
+
s: nodeSignatures[i].slice(66, 130),
|
|
211
|
+
};
|
|
212
|
+
const nodeSignatureVerification = ec
|
|
213
|
+
.keyFromPublic(pubkey, 'hex')
|
|
214
|
+
.verify(lockHashWithout0x, ENRsignature);
|
|
215
|
+
if (!nodeSignatureVerification) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
191
218
|
}
|
|
192
219
|
return true;
|
|
193
220
|
};
|
|
@@ -195,81 +222,22 @@ export const signingRoot = (domain, messageBuffer) => {
|
|
|
195
222
|
return computeSigningRoot(messageBuffer, domain);
|
|
196
223
|
};
|
|
197
224
|
const verifyLockData = (clusterLock) => __awaiter(void 0, void 0, void 0, function* () {
|
|
198
|
-
const ec = new elliptic.ec('secp256k1');
|
|
199
|
-
const validators = clusterLock.distributed_validators;
|
|
200
|
-
const nodeSignatures = clusterLock.node_signatures;
|
|
201
|
-
const depositDomain = computeDomain(fromHexString(DOMAIN_DEPOSIT), clusterLock.cluster_definition.fork_version);
|
|
202
|
-
const builderDomain = computeDomain(fromHexString(DOMAIN_APPLICATION_BUILDER), clusterLock.cluster_definition.fork_version);
|
|
203
|
-
const pubShares = [];
|
|
204
|
-
const pubKeys = [];
|
|
205
|
-
const builderRegistrationAndDepositDataMessages = [];
|
|
206
|
-
const blsSignatures = [];
|
|
207
225
|
yield init('herumi');
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const validatorPublicShares = validator['public_shares'];
|
|
211
|
-
for (const element of validatorPublicShares) {
|
|
212
|
-
pubShares.push(fromHexString(element));
|
|
213
|
-
}
|
|
214
|
-
//Deposit data signature
|
|
215
|
-
if (semver.gte(clusterLock.cluster_definition.version, 'v1.6.0')) {
|
|
216
|
-
if (!verifyDepositData(validator, clusterLock.cluster_definition.validators[i].withdrawal_address)) {
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
const depositMessageBuffer = computeDepositMsgRoot(validator.deposit_data);
|
|
220
|
-
const depositDataMessage = signingRoot(depositDomain, depositMessageBuffer);
|
|
221
|
-
pubKeys.push(fromHexString(validator.distributed_public_key));
|
|
222
|
-
builderRegistrationAndDepositDataMessages.push(depositDataMessage);
|
|
223
|
-
blsSignatures.push(fromHexString(validator.deposit_data.signature));
|
|
224
|
-
}
|
|
225
|
-
//Builder registration signature
|
|
226
|
-
if (semver.gte(clusterLock.cluster_definition.version, 'v1.7.0')) {
|
|
227
|
-
if (!verifyBuilderRegistration(validator, clusterLock.cluster_definition.validators[i].fee_recipient_address)) {
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
const builderRegistrationMessageBuffer = computebuilderRegistrationMsgRoot(validator.builder_registration.message);
|
|
231
|
-
const builderRegistrationMessage = signingRoot(builderDomain, builderRegistrationMessageBuffer);
|
|
232
|
-
pubKeys.push(fromHexString(validator.distributed_public_key));
|
|
233
|
-
builderRegistrationAndDepositDataMessages.push(builderRegistrationMessage);
|
|
234
|
-
blsSignatures.push(fromHexString(validator.builder_registration.signature));
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (blsSignatures.length &&
|
|
238
|
-
pubKeys.length &&
|
|
239
|
-
builderRegistrationAndDepositDataMessages.length) {
|
|
240
|
-
// verify all deposit data and builder registration bls signatures
|
|
241
|
-
const aggregateBLSSignature = aggregateSignatures(blsSignatures);
|
|
242
|
-
if (!verifyMultiple(pubKeys, builderRegistrationAndDepositDataMessages, aggregateBLSSignature)) {
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
226
|
+
if (semver.eq(clusterLock.cluster_definition.version, 'v1.6.0')) {
|
|
227
|
+
return verifyDVV1X6(clusterLock);
|
|
245
228
|
}
|
|
246
|
-
if (semver.
|
|
247
|
-
|
|
248
|
-
//node(ENR) signatures
|
|
249
|
-
for (let i = 0; i < nodeSignatures.length; i++) {
|
|
250
|
-
const pubkey = ENR.decodeTxt(clusterLock.cluster_definition.operators[i].enr).publicKey.toString('hex');
|
|
251
|
-
const ENRsignature = {
|
|
252
|
-
r: nodeSignatures[i].slice(2, 66),
|
|
253
|
-
s: nodeSignatures[i].slice(66, 130),
|
|
254
|
-
};
|
|
255
|
-
const nodeSignatureVerification = ec
|
|
256
|
-
.keyFromPublic(pubkey, 'hex')
|
|
257
|
-
.verify(lockHashWithout0x, ENRsignature);
|
|
258
|
-
if (!nodeSignatureVerification) {
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
229
|
+
if (semver.eq(clusterLock.cluster_definition.version, 'v1.7.0')) {
|
|
230
|
+
return verifyDVV1X7(clusterLock);
|
|
262
231
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return false;
|
|
232
|
+
if (semver.eq(clusterLock.cluster_definition.version, 'v1.8.0')) {
|
|
233
|
+
return verifyDVV1X8(clusterLock);
|
|
266
234
|
}
|
|
267
|
-
return
|
|
235
|
+
return false;
|
|
268
236
|
});
|
|
269
237
|
export const isValidClusterLock = (clusterLock) => __awaiter(void 0, void 0, void 0, function* () {
|
|
270
238
|
try {
|
|
271
239
|
const definitionType = definitionFlow(clusterLock.cluster_definition);
|
|
272
|
-
if (
|
|
240
|
+
if (definitionType == null) {
|
|
273
241
|
return false;
|
|
274
242
|
}
|
|
275
243
|
const isValidDefinitionData = verifyDefinitionSignatures(clusterLock.cluster_definition, definitionType);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ByteListType, ByteVectorType, ContainerType, UintNumberType } from '@chainsafe/ssz';
|
|
2
|
+
export const operatorAddressWrapperType = new ContainerType({
|
|
3
|
+
address: new ByteVectorType(20),
|
|
4
|
+
});
|
|
5
|
+
export const creatorAddressWrapperType = new ContainerType({
|
|
6
|
+
address: new ByteVectorType(20),
|
|
7
|
+
});
|
|
8
|
+
export const operatorContainerType = new ContainerType({
|
|
9
|
+
address: new ByteVectorType(20),
|
|
10
|
+
enr: new ByteListType(1024), // This needs to be dynamic, since ENRs do not have a fixed length.
|
|
11
|
+
config_signature: new ByteVectorType(65),
|
|
12
|
+
enr_signature: new ByteVectorType(65),
|
|
13
|
+
});
|
|
14
|
+
export const creatorContainerType = new ContainerType({
|
|
15
|
+
address: new ByteVectorType(20),
|
|
16
|
+
config_signature: new ByteVectorType(65),
|
|
17
|
+
});
|
|
18
|
+
export const validatorsContainerType = new ContainerType({
|
|
19
|
+
fee_recipient_address: new ByteVectorType(20),
|
|
20
|
+
withdrawal_address: new ByteVectorType(20),
|
|
21
|
+
});
|
|
22
|
+
export const newCreatorContainerType = (configOnly) => {
|
|
23
|
+
return configOnly ? creatorAddressWrapperType : creatorContainerType;
|
|
24
|
+
};
|
|
25
|
+
export const newOperatorContainerType = (configOnly) => {
|
|
26
|
+
return configOnly ? operatorAddressWrapperType : operatorContainerType;
|
|
27
|
+
};
|
|
28
|
+
// Lock
|
|
29
|
+
export const depositDataContainer = new ContainerType({
|
|
30
|
+
pubkey: new ByteVectorType(48),
|
|
31
|
+
withdrawal_credentials: new ByteVectorType(32),
|
|
32
|
+
amount: new UintNumberType(8),
|
|
33
|
+
signature: new ByteVectorType(96),
|
|
34
|
+
});
|
|
35
|
+
export const builderRegistrationMessageContainer = new ContainerType({
|
|
36
|
+
fee_recipient: new ByteVectorType(20),
|
|
37
|
+
gas_limit: new UintNumberType(8),
|
|
38
|
+
timestamp: new UintNumberType(8),
|
|
39
|
+
pubkey: new ByteVectorType(48),
|
|
40
|
+
});
|
|
41
|
+
export const builderRegistrationContainer = new ContainerType({
|
|
42
|
+
message: builderRegistrationMessageContainer,
|
|
43
|
+
signature: new ByteVectorType(96),
|
|
44
|
+
});
|
|
45
|
+
export const builderRegistrationMessageType = new ContainerType({
|
|
46
|
+
fee_recipient: new ByteVectorType(20),
|
|
47
|
+
gas_limit: new UintNumberType(8),
|
|
48
|
+
timestamp: new UintNumberType(8),
|
|
49
|
+
pubkey: new ByteVectorType(48),
|
|
50
|
+
});
|
|
51
|
+
// For domain computation that is used in deposit data and builder registration verification for dv
|
|
52
|
+
export const forkDataType = new ContainerType({
|
|
53
|
+
currentVersion: new ByteVectorType(4),
|
|
54
|
+
genesisValidatorsRoot: new ByteVectorType(32),
|
|
55
|
+
});
|
|
56
|
+
export const depositMessageType = new ContainerType({
|
|
57
|
+
pubkey: new ByteVectorType(48),
|
|
58
|
+
withdrawal_credentials: new ByteVectorType(32),
|
|
59
|
+
amount: new UintNumberType(8),
|
|
60
|
+
});
|
|
61
|
+
export const signingRootType = new ContainerType({
|
|
62
|
+
objectRoot: new ByteVectorType(32),
|
|
63
|
+
domain: new ByteVectorType(32),
|
|
64
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { UintNumberType } from '@chainsafe/ssz/lib/type/uint';
|
|
2
|
+
import { strToUint8Array } from '../utils';
|
|
3
|
+
import { newCreatorContainerType, newOperatorContainerType, validatorsContainerType } from './sszTypes';
|
|
4
|
+
import { ByteListType, ByteVectorType, ContainerType, ListCompositeType, fromHexString } from '@chainsafe/ssz';
|
|
5
|
+
import { verifyDepositData } from './common';
|
|
6
|
+
import { aggregateSignatures, verifyAggregate, verifyMultiple } from '@chainsafe/bls';
|
|
7
|
+
/**
|
|
8
|
+
* Returns the containerized cluster definition
|
|
9
|
+
* @param cluster ClusterDefinition to calculate the type from
|
|
10
|
+
* @returns SSZ Containerized type of cluster input
|
|
11
|
+
*/
|
|
12
|
+
export const clusterDefinitionContainerTypeV1X6 = (configOnly) => {
|
|
13
|
+
let returnedContainerType = {
|
|
14
|
+
uuid: new ByteListType(64),
|
|
15
|
+
name: new ByteListType(256),
|
|
16
|
+
version: new ByteListType(16),
|
|
17
|
+
timestamp: new ByteListType(32),
|
|
18
|
+
num_validators: new UintNumberType(8),
|
|
19
|
+
threshold: new UintNumberType(8),
|
|
20
|
+
dkg_algorithm: new ByteListType(32),
|
|
21
|
+
fork_version: new ByteVectorType(4),
|
|
22
|
+
operators: new ListCompositeType(newOperatorContainerType(configOnly), 256),
|
|
23
|
+
creator: newCreatorContainerType(configOnly),
|
|
24
|
+
validators: new ListCompositeType(validatorsContainerType, 65536),
|
|
25
|
+
};
|
|
26
|
+
if (!configOnly) {
|
|
27
|
+
returnedContainerType = Object.assign(Object.assign({}, returnedContainerType), { config_hash: new ByteVectorType(32) });
|
|
28
|
+
}
|
|
29
|
+
return new ContainerType(returnedContainerType);
|
|
30
|
+
};
|
|
31
|
+
export const hashClusterDefinitionV1X6 = (cluster, configOnly) => {
|
|
32
|
+
const definitionType = clusterDefinitionContainerTypeV1X6(configOnly);
|
|
33
|
+
const val = definitionType.defaultValue();
|
|
34
|
+
// order should be same as charon https://github.com/ObolNetwork/charon/blob/main/cluster/ssz.go#L276
|
|
35
|
+
val.uuid = strToUint8Array(cluster.uuid);
|
|
36
|
+
val.name = strToUint8Array(cluster.name);
|
|
37
|
+
val.version = strToUint8Array(cluster.version);
|
|
38
|
+
val.timestamp = strToUint8Array(cluster.timestamp);
|
|
39
|
+
val.num_validators = cluster.num_validators;
|
|
40
|
+
val.threshold = cluster.threshold;
|
|
41
|
+
val.dkg_algorithm = strToUint8Array(cluster.dkg_algorithm);
|
|
42
|
+
val.fork_version = fromHexString(cluster.fork_version);
|
|
43
|
+
val.operators = cluster.operators.map(operator => {
|
|
44
|
+
return configOnly
|
|
45
|
+
? { address: fromHexString(operator.address) }
|
|
46
|
+
: {
|
|
47
|
+
address: fromHexString(operator.address),
|
|
48
|
+
enr: strToUint8Array(operator.enr),
|
|
49
|
+
config_signature: fromHexString(operator.config_signature),
|
|
50
|
+
enr_signature: fromHexString(operator.enr_signature),
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
val.creator = configOnly
|
|
54
|
+
? { address: fromHexString(cluster.creator.address) }
|
|
55
|
+
: {
|
|
56
|
+
address: fromHexString(cluster.creator.address),
|
|
57
|
+
config_signature: fromHexString(cluster.creator.config_signature),
|
|
58
|
+
};
|
|
59
|
+
val.validators = cluster.validators.map((validator) => {
|
|
60
|
+
return {
|
|
61
|
+
fee_recipient_address: fromHexString(validator.fee_recipient_address),
|
|
62
|
+
withdrawal_address: fromHexString(validator.withdrawal_address),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
if (!configOnly) {
|
|
66
|
+
val.config_hash = fromHexString(cluster.config_hash);
|
|
67
|
+
}
|
|
68
|
+
return val;
|
|
69
|
+
};
|
|
70
|
+
// cluster lock
|
|
71
|
+
const dvContainerTypeV1X6 = new ContainerType({
|
|
72
|
+
distributed_public_key: new ByteVectorType(48),
|
|
73
|
+
public_shares: new ListCompositeType(new ByteVectorType(48), 256),
|
|
74
|
+
pubkey: new ByteVectorType(48),
|
|
75
|
+
withdrawal_credentials: new ByteVectorType(32),
|
|
76
|
+
amount: new UintNumberType(8),
|
|
77
|
+
signature: new ByteVectorType(96),
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* @returns SSZ Containerized type of cluster lock
|
|
81
|
+
*/
|
|
82
|
+
const clusterLockContainerTypeV1X6 = () => {
|
|
83
|
+
return new ContainerType({
|
|
84
|
+
cluster_definition: clusterDefinitionContainerTypeV1X6(false),
|
|
85
|
+
distributed_validators: new ListCompositeType(dvContainerTypeV1X6, 65536),
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* @param cluster The published cluster lock
|
|
90
|
+
* @returns The lock hash in of the corresponding cluster
|
|
91
|
+
*/
|
|
92
|
+
export const hashClusterLockV1X6 = (cluster) => {
|
|
93
|
+
const lockType = clusterLockContainerTypeV1X6();
|
|
94
|
+
const val = lockType.defaultValue();
|
|
95
|
+
// Check if we can replace with definition_hash
|
|
96
|
+
val.cluster_definition = hashClusterDefinitionV1X6(cluster.cluster_definition, false);
|
|
97
|
+
val.distributed_validators = cluster.distributed_validators.map(dValidator => {
|
|
98
|
+
var _a, _b, _c, _d;
|
|
99
|
+
return {
|
|
100
|
+
distributed_public_key: fromHexString(dValidator.distributed_public_key),
|
|
101
|
+
public_shares: dValidator.public_shares.map(publicShare => fromHexString(publicShare)),
|
|
102
|
+
pubkey: fromHexString((_a = dValidator.deposit_data) === null || _a === void 0 ? void 0 : _a.pubkey),
|
|
103
|
+
withdrawal_credentials: fromHexString((_b = dValidator.deposit_data) === null || _b === void 0 ? void 0 : _b.withdrawal_credentials),
|
|
104
|
+
amount: parseInt((_c = dValidator.deposit_data) === null || _c === void 0 ? void 0 : _c.amount),
|
|
105
|
+
signature: fromHexString((_d = dValidator.deposit_data) === null || _d === void 0 ? void 0 : _d.signature),
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
return '0x' + Buffer.from(lockType.hashTreeRoot(val).buffer).toString('hex');
|
|
109
|
+
};
|
|
110
|
+
// DV verification
|
|
111
|
+
export const verifyDVV1X6 = (clusterLock) => {
|
|
112
|
+
var _a;
|
|
113
|
+
const validators = clusterLock.distributed_validators;
|
|
114
|
+
const pubShares = [];
|
|
115
|
+
const pubKeys = [];
|
|
116
|
+
const builderRegistrationAndDepositDataMessages = [];
|
|
117
|
+
const blsSignatures = [];
|
|
118
|
+
for (let i = 0; i < validators.length; i++) {
|
|
119
|
+
const validator = validators[i];
|
|
120
|
+
const validatorPublicShares = validator.public_shares;
|
|
121
|
+
const distributedPublicKey = validator.distributed_public_key;
|
|
122
|
+
// Needed in signature_aggregate verification
|
|
123
|
+
for (const element of validatorPublicShares) {
|
|
124
|
+
pubShares.push(fromHexString(element));
|
|
125
|
+
}
|
|
126
|
+
const { isValidDepositData, depositDataMsg } = verifyDepositData(distributedPublicKey, validator.deposit_data, clusterLock.cluster_definition.validators[i].withdrawal_address, clusterLock.cluster_definition.fork_version);
|
|
127
|
+
if (!isValidDepositData) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
pubKeys.push(fromHexString(validator.distributed_public_key));
|
|
131
|
+
builderRegistrationAndDepositDataMessages.push(depositDataMsg);
|
|
132
|
+
blsSignatures.push(fromHexString((_a = validator.deposit_data) === null || _a === void 0 ? void 0 : _a.signature));
|
|
133
|
+
}
|
|
134
|
+
const aggregateBLSSignature = aggregateSignatures(blsSignatures);
|
|
135
|
+
if (!verifyMultiple(pubKeys, builderRegistrationAndDepositDataMessages, aggregateBLSSignature)) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (!verifyAggregate(pubShares, fromHexString(clusterLock.lock_hash), fromHexString(clusterLock.signature_aggregate))) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
};
|