@obolnetwork/obol-sdk 1.0.9 → 1.0.12
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 +2 -1
- package/dist/cjs/src/ajv.js +17 -0
- package/dist/cjs/src/base.js +39 -0
- package/dist/cjs/src/constants.js +86 -0
- package/dist/cjs/src/errors.js +11 -0
- package/dist/cjs/src/hash.js +172 -0
- package/dist/cjs/src/index.js +158 -0
- package/dist/cjs/src/schema.js +72 -0
- package/dist/cjs/src/services.js +32 -0
- package/dist/cjs/src/types.js +18 -0
- package/dist/cjs/src/utils.js +11 -0
- package/dist/cjs/src/verify.js +326 -0
- package/dist/cjs/test/fixtures.js +101 -0
- package/dist/cjs/test/methods.test.js +128 -0
- package/dist/esm/src/ajv.js +10 -0
- package/dist/esm/src/base.js +35 -0
- package/dist/esm/src/constants.js +79 -0
- package/dist/esm/src/errors.js +7 -0
- package/dist/esm/src/hash.js +165 -0
- package/dist/esm/src/index.js +140 -0
- package/dist/esm/src/schema.js +69 -0
- package/dist/esm/src/services.js +28 -0
- package/dist/esm/src/types.js +15 -0
- package/dist/esm/src/utils.js +6 -0
- package/dist/esm/src/verify.js +295 -0
- package/dist/esm/test/fixtures.js +98 -0
- package/dist/esm/test/methods.test.js +126 -0
- package/dist/{ajv.d.ts → types/src/ajv.d.ts} +2 -3
- package/dist/{base.d.ts → types/src/base.d.ts} +13 -14
- package/dist/types/src/constants.d.ts +79 -0
- package/dist/{errors.d.ts → types/src/errors.d.ts} +4 -5
- package/dist/{hash.d.ts → types/src/hash.d.ts} +56 -64
- package/dist/{index.d.ts → types/src/index.d.ts} +64 -60
- package/dist/{schema.d.ts → types/src/schema.d.ts} +57 -58
- package/dist/types/src/services.d.ts +11 -0
- package/dist/{types.d.ts → types/src/types.d.ts} +153 -154
- package/dist/types/src/utils.d.ts +2 -0
- package/dist/types/src/verify.d.ts +4 -0
- package/dist/{fixtures.d.ts → types/test/fixtures.d.ts} +61 -62
- package/dist/types/test/methods.test.d.ts +1 -0
- package/package.json +55 -20
- package/src/ajv.ts +11 -0
- package/src/base.ts +44 -0
- package/src/constants.ts +118 -0
- package/src/errors.ts +11 -0
- package/src/hash.ts +250 -0
- package/src/index.ts +160 -0
- package/src/schema.ts +70 -0
- package/src/services.ts +20 -0
- package/src/types.ts +211 -0
- package/src/utils.ts +7 -0
- package/src/verify.ts +479 -0
- package/dist/ajv.d.ts.map +0 -1
- package/dist/base.d.ts.map +0 -1
- package/dist/cluster.test.d.ts +0 -2
- package/dist/cluster.test.d.ts.map +0 -1
- package/dist/constants.d.ts +0 -30
- package/dist/constants.d.ts.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/fixtures.d.ts.map +0 -1
- package/dist/hash.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.es.js +0 -15582
- package/dist/index.js +0 -15586
- package/dist/schema.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
package/src/types.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permitted ChainID's
|
|
3
|
+
*/
|
|
4
|
+
export enum FORK_MAPPING {
|
|
5
|
+
|
|
6
|
+
/** Mainnet. */
|
|
7
|
+
"0x00000000" = 1,
|
|
8
|
+
|
|
9
|
+
/** Goerli/Prater. */
|
|
10
|
+
"0x00001020" = 5,
|
|
11
|
+
|
|
12
|
+
/** Gnosis Chain. */
|
|
13
|
+
"0x00000064" = 100,
|
|
14
|
+
|
|
15
|
+
/** Holesky. */
|
|
16
|
+
"0x01017000" = 17000
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Node operator data
|
|
21
|
+
*/
|
|
22
|
+
export type ClusterOperator = {
|
|
23
|
+
|
|
24
|
+
/** The operator address. */
|
|
25
|
+
address: string;
|
|
26
|
+
|
|
27
|
+
/** The operator ethereum node record. */
|
|
28
|
+
enr?: string;
|
|
29
|
+
|
|
30
|
+
/** The cluster fork_version. */
|
|
31
|
+
fork_version?: string;
|
|
32
|
+
|
|
33
|
+
/** The cluster version. */
|
|
34
|
+
version?: string;
|
|
35
|
+
|
|
36
|
+
/** The operator enr signature. */
|
|
37
|
+
enr_signature?: string;
|
|
38
|
+
|
|
39
|
+
/** The operator configuration signature. */
|
|
40
|
+
config_signature?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A partial view of `ClusterOperator` with `enr` and `version` as required properties.
|
|
45
|
+
*/
|
|
46
|
+
export type OperatorPayload = Partial<ClusterOperator> & Required<Pick<ClusterOperator, 'enr' | 'version'>>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Cluster creator data
|
|
50
|
+
*/
|
|
51
|
+
export type ClusterCreator = {
|
|
52
|
+
|
|
53
|
+
/** The creator address. */
|
|
54
|
+
address: string;
|
|
55
|
+
/** The cluster configuration signature. */
|
|
56
|
+
config_signature?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Validator withdrawal configuration
|
|
61
|
+
*/
|
|
62
|
+
export type ClusterValidator = {
|
|
63
|
+
|
|
64
|
+
/** The validator fee recipient address. */
|
|
65
|
+
fee_recipient_address: string;
|
|
66
|
+
|
|
67
|
+
/** The validator reward address. */
|
|
68
|
+
withdrawal_address: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Cluster configuration
|
|
73
|
+
*/
|
|
74
|
+
export type ClusterPayload = {
|
|
75
|
+
|
|
76
|
+
/** The cluster name. */
|
|
77
|
+
name: string;
|
|
78
|
+
|
|
79
|
+
/** The cluster nodes operators addresses. */
|
|
80
|
+
operators: ClusterOperator[];
|
|
81
|
+
|
|
82
|
+
/** The clusters validators information. */
|
|
83
|
+
validators: ClusterValidator[];
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Cluster definition data needed for dkg
|
|
88
|
+
*/
|
|
89
|
+
export interface ClusterDefintion extends ClusterPayload {
|
|
90
|
+
|
|
91
|
+
/** The creator of the cluster. */
|
|
92
|
+
creator: ClusterCreator;
|
|
93
|
+
|
|
94
|
+
/** The cluster configuration version. */
|
|
95
|
+
version: string;
|
|
96
|
+
|
|
97
|
+
/** The cluster dkg algorithm. */
|
|
98
|
+
dkg_algorithm: string;
|
|
99
|
+
|
|
100
|
+
/** The cluster fork version. */
|
|
101
|
+
fork_version: string;
|
|
102
|
+
|
|
103
|
+
/** The cluster uuid. */
|
|
104
|
+
uuid: string;
|
|
105
|
+
|
|
106
|
+
/** The cluster creation timestamp. */
|
|
107
|
+
timestamp: string;
|
|
108
|
+
|
|
109
|
+
/** The cluster configuration hash. */
|
|
110
|
+
config_hash: string;
|
|
111
|
+
|
|
112
|
+
/** The distributed validator threshold. */
|
|
113
|
+
threshold: number;
|
|
114
|
+
|
|
115
|
+
/** The number of distributed validators in the cluster. */
|
|
116
|
+
num_validators: number;
|
|
117
|
+
|
|
118
|
+
/** The hash of the cluster definition. */
|
|
119
|
+
definition_hash?: string;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Unsigned DV Builder Registration Message
|
|
124
|
+
*/
|
|
125
|
+
export type BuilderRegistrationMessage = {
|
|
126
|
+
|
|
127
|
+
/** The DV fee recipient. */
|
|
128
|
+
fee_recipient: string;
|
|
129
|
+
|
|
130
|
+
/** Default is 30000000. */
|
|
131
|
+
gas_limit: number;
|
|
132
|
+
|
|
133
|
+
/** Timestamp when generating cluster lock file. */
|
|
134
|
+
timestamp: number;
|
|
135
|
+
|
|
136
|
+
/** The public key of the DV. */
|
|
137
|
+
pubkey: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Pre-generated Signed Validator Builder Registration
|
|
142
|
+
*/
|
|
143
|
+
export type BuilderRegistration = {
|
|
144
|
+
|
|
145
|
+
/** Builder registration message. */
|
|
146
|
+
message: BuilderRegistrationMessage;
|
|
147
|
+
|
|
148
|
+
/** BLS signature of the builder registration message. */
|
|
149
|
+
signature: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Required deposit data for validator activation
|
|
154
|
+
*/
|
|
155
|
+
export type DepositData = {
|
|
156
|
+
|
|
157
|
+
/** The public key of the distributed validator. */
|
|
158
|
+
pubkey: string;
|
|
159
|
+
|
|
160
|
+
/** The 0x01 withdrawal address of the DV. */
|
|
161
|
+
withdrawal_credentials: string;
|
|
162
|
+
|
|
163
|
+
/** 32 ethers. */
|
|
164
|
+
amount: string;
|
|
165
|
+
|
|
166
|
+
/** A checksum for DepositData fields . */
|
|
167
|
+
deposit_data_root: string;
|
|
168
|
+
|
|
169
|
+
/** BLS signature of the deposit message. */
|
|
170
|
+
signature: string;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Required deposit data for validator activation
|
|
175
|
+
*/
|
|
176
|
+
export type DistributedValidator = {
|
|
177
|
+
|
|
178
|
+
/** The public key of the distributed validator. */
|
|
179
|
+
distributed_public_key: string;
|
|
180
|
+
|
|
181
|
+
/** The public key of the node distributed validator share. */
|
|
182
|
+
public_shares: string[];
|
|
183
|
+
|
|
184
|
+
/** The required deposit data for activating the DV. */
|
|
185
|
+
deposit_data: Partial<DepositData>;
|
|
186
|
+
|
|
187
|
+
/** pre-generated signed validator builder registration to be sent to builder network. */
|
|
188
|
+
builder_registration: BuilderRegistration;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Cluster Details after DKG is complete
|
|
193
|
+
*/
|
|
194
|
+
export type ClusterLock = {
|
|
195
|
+
|
|
196
|
+
/** The cluster definition. */
|
|
197
|
+
cluster_definition: ClusterDefintion;
|
|
198
|
+
|
|
199
|
+
/** The cluster distributed validators. */
|
|
200
|
+
distributed_validators: DistributedValidator[];
|
|
201
|
+
|
|
202
|
+
/** The cluster bls signature aggregate. */
|
|
203
|
+
signature_aggregate: string;
|
|
204
|
+
|
|
205
|
+
/** The hash of the cluster lock. */
|
|
206
|
+
lock_hash: string;
|
|
207
|
+
|
|
208
|
+
/** Node Signature for the lock hash by the node secp256k1 key. */
|
|
209
|
+
node_signatures: string[];
|
|
210
|
+
};
|
|
211
|
+
|
package/src/utils.ts
ADDED
package/src/verify.ts
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import * as semver from 'semver';
|
|
2
|
+
import { init, aggregateSignatures, verifyMultiple, verifyAggregate } from "@chainsafe/bls";
|
|
3
|
+
import { ENR } from '@chainsafe/discv5';
|
|
4
|
+
import { ByteVectorType, ContainerType, fromHexString } from '@chainsafe/ssz';
|
|
5
|
+
import { UintNumberByteLen, UintNumberType } from '@chainsafe/ssz/lib/type/uint.js';
|
|
6
|
+
import {
|
|
7
|
+
SignTypedDataVersion,
|
|
8
|
+
TypedDataUtils,
|
|
9
|
+
} from '@metamask/eth-sig-util';
|
|
10
|
+
import elliptic from 'elliptic';
|
|
11
|
+
import { ethers } from 'ethers';
|
|
12
|
+
|
|
13
|
+
import { BuilderRegistrationMessage, ClusterDefintion, ClusterLock, DepositData, DistributedValidator, FORK_MAPPING } from "./types.js";
|
|
14
|
+
import { clusterConfigOrDefinitionHash, clusterLockHash } from './hash.js';
|
|
15
|
+
import { DOMAIN_APPLICATION_BUILDER, DOMAIN_DEPOSIT, DefinitionFlow, GENESIS_VALIDATOR_ROOT, signCreatorConfigHashPayload, signEnrPayload, signOperatorConfigHashPayload } from './constants.js';
|
|
16
|
+
import { hexWithout0x } from './utils.js';
|
|
17
|
+
|
|
18
|
+
const builderRegistrationMessageType = new ContainerType({
|
|
19
|
+
fee_recipient: new ByteVectorType(20),
|
|
20
|
+
gas_limit: new UintNumberType(8 as UintNumberByteLen),
|
|
21
|
+
timestamp: new UintNumberType(8 as UintNumberByteLen),
|
|
22
|
+
pubkey: new ByteVectorType(48),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
//For domain computation that is used in deposit data and builder registration verification for dv
|
|
26
|
+
const forkDataType = new ContainerType({
|
|
27
|
+
currentVersion: new ByteVectorType(4),
|
|
28
|
+
genesisValidatorsRoot: new ByteVectorType(32),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const depositMessageType = new ContainerType({
|
|
32
|
+
pubkey: new ByteVectorType(48),
|
|
33
|
+
withdrawal_credentials: new ByteVectorType(32),
|
|
34
|
+
amount: new UintNumberType(8),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const signingRootType = new ContainerType({
|
|
38
|
+
objectRoot: new ByteVectorType(32),
|
|
39
|
+
domain: new ByteVectorType(32),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const definitionFlow = (
|
|
43
|
+
clusterDefinition: ClusterDefintion,
|
|
44
|
+
): DefinitionFlow | null => {
|
|
45
|
+
if (
|
|
46
|
+
clusterDefinition.creator.address &&
|
|
47
|
+
clusterDefinition.creator.config_signature &&
|
|
48
|
+
clusterDefinition.operators.every(operator => {
|
|
49
|
+
return (
|
|
50
|
+
operator.address &&
|
|
51
|
+
operator.config_signature &&
|
|
52
|
+
operator.enr &&
|
|
53
|
+
operator.enr_signature
|
|
54
|
+
);
|
|
55
|
+
})
|
|
56
|
+
) {
|
|
57
|
+
return DefinitionFlow.Group;
|
|
58
|
+
} else if (
|
|
59
|
+
clusterDefinition.creator.address &&
|
|
60
|
+
clusterDefinition.creator.config_signature &&
|
|
61
|
+
clusterDefinition.operators.every(operator => {
|
|
62
|
+
return (
|
|
63
|
+
!operator.address &&
|
|
64
|
+
!operator.config_signature &&
|
|
65
|
+
operator.enr &&
|
|
66
|
+
!operator.enr_signature
|
|
67
|
+
);
|
|
68
|
+
})
|
|
69
|
+
) {
|
|
70
|
+
return DefinitionFlow.Solo;
|
|
71
|
+
} else if (
|
|
72
|
+
!clusterDefinition.creator.address &&
|
|
73
|
+
!clusterDefinition.creator.config_signature &&
|
|
74
|
+
clusterDefinition.operators.every(operator => {
|
|
75
|
+
return (
|
|
76
|
+
!operator.address &&
|
|
77
|
+
!operator.config_signature &&
|
|
78
|
+
operator.enr &&
|
|
79
|
+
!operator.enr_signature
|
|
80
|
+
);
|
|
81
|
+
})
|
|
82
|
+
) {
|
|
83
|
+
return DefinitionFlow.Charon;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
//cluster-definition signatures verificatin
|
|
90
|
+
|
|
91
|
+
const getPOSTConfigHashSigner = (
|
|
92
|
+
signature: string,
|
|
93
|
+
configHash: string,
|
|
94
|
+
chainId: FORK_MAPPING,): string => {
|
|
95
|
+
try {
|
|
96
|
+
const sig = ethers.Signature.from(signature);
|
|
97
|
+
const data = signCreatorConfigHashPayload(
|
|
98
|
+
{ creator_config_hash: configHash },
|
|
99
|
+
chainId,
|
|
100
|
+
);
|
|
101
|
+
const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
|
|
102
|
+
|
|
103
|
+
return ethers.recoverAddress(digest, sig).toLowerCase();
|
|
104
|
+
} catch (err) {
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const getPUTConfigHashSigner = (
|
|
110
|
+
signature: string,
|
|
111
|
+
configHash: string,
|
|
112
|
+
chainId: number,
|
|
113
|
+
): string => {
|
|
114
|
+
try {
|
|
115
|
+
const sig = ethers.Signature.from(signature);
|
|
116
|
+
const data = signOperatorConfigHashPayload(
|
|
117
|
+
{ operator_config_hash: configHash },
|
|
118
|
+
chainId,
|
|
119
|
+
);
|
|
120
|
+
const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
|
|
121
|
+
|
|
122
|
+
return ethers.recoverAddress(digest, sig).toLowerCase();
|
|
123
|
+
} catch (err) {
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const getEnrSigner = (
|
|
129
|
+
signature: string,
|
|
130
|
+
payload: string,
|
|
131
|
+
chainId: number,
|
|
132
|
+
): string => {
|
|
133
|
+
try {
|
|
134
|
+
const sig = ethers.Signature.from(signature);
|
|
135
|
+
|
|
136
|
+
const data = signEnrPayload({ enr: payload }, chainId);
|
|
137
|
+
const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
|
|
138
|
+
|
|
139
|
+
return ethers.recoverAddress(digest, sig).toLowerCase();
|
|
140
|
+
} catch (err) {
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const verifyDefinitionSignatures = (
|
|
146
|
+
clusterDefinition: ClusterDefintion,
|
|
147
|
+
definitionType: DefinitionFlow,
|
|
148
|
+
): boolean => {
|
|
149
|
+
if (definitionType === 'Charon-Command') {
|
|
150
|
+
return true;
|
|
151
|
+
} else {
|
|
152
|
+
const configSigner = getPOSTConfigHashSigner(
|
|
153
|
+
clusterDefinition.creator.config_signature as string,
|
|
154
|
+
clusterDefinition.config_hash,
|
|
155
|
+
FORK_MAPPING[clusterDefinition.fork_version as keyof typeof FORK_MAPPING],
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (configSigner !== clusterDefinition.creator.address.toLowerCase()) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (definitionType === 'LP-Solo') {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
return clusterDefinition.operators.every(operator => {
|
|
165
|
+
const configSigner = getPUTConfigHashSigner(
|
|
166
|
+
operator.config_signature as string,
|
|
167
|
+
clusterDefinition.config_hash,
|
|
168
|
+
FORK_MAPPING[clusterDefinition.fork_version as keyof typeof FORK_MAPPING],
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const enrSigner = getEnrSigner(
|
|
172
|
+
operator.enr_signature as string,
|
|
173
|
+
operator.enr as string,
|
|
174
|
+
FORK_MAPPING[clusterDefinition.fork_version as keyof typeof FORK_MAPPING],
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
if (
|
|
178
|
+
configSigner !== operator.address.toLowerCase() ||
|
|
179
|
+
enrSigner !== operator.address.toLowerCase()
|
|
180
|
+
) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
//cluster-lock data verification
|
|
189
|
+
const computeSigningRoot = (
|
|
190
|
+
sszObjectRoot: Uint8Array,
|
|
191
|
+
domain: Uint8Array,
|
|
192
|
+
): Uint8Array => {
|
|
193
|
+
const val1 = signingRootType.defaultValue();
|
|
194
|
+
val1.objectRoot = sszObjectRoot;
|
|
195
|
+
val1.domain = domain;
|
|
196
|
+
return Buffer.from(signingRootType.hashTreeRoot(val1).buffer);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const computeDepositMsgRoot = (msg: Partial<DepositData>): Buffer => {
|
|
200
|
+
const depositMsgVal = depositMessageType.defaultValue();
|
|
201
|
+
|
|
202
|
+
depositMsgVal.pubkey = fromHexString(msg.pubkey as string);
|
|
203
|
+
depositMsgVal.withdrawal_credentials = fromHexString(
|
|
204
|
+
msg.withdrawal_credentials as string,
|
|
205
|
+
);
|
|
206
|
+
depositMsgVal.amount = parseInt(msg.amount as string);
|
|
207
|
+
return Buffer.from(depositMessageType.hashTreeRoot(depositMsgVal).buffer);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const computeForkDataRoot = (
|
|
211
|
+
currentVersion: Uint8Array,
|
|
212
|
+
genesisValidatorsRoot: Uint8Array,
|
|
213
|
+
): Uint8Array => {
|
|
214
|
+
const forkDataVal = forkDataType.defaultValue();
|
|
215
|
+
forkDataVal.currentVersion = currentVersion;
|
|
216
|
+
forkDataVal.genesisValidatorsRoot = genesisValidatorsRoot;
|
|
217
|
+
return Buffer.from(forkDataType.hashTreeRoot(forkDataVal).buffer);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const computebuilderRegistrationMsgRoot = (
|
|
221
|
+
msg: BuilderRegistrationMessage,
|
|
222
|
+
): Buffer => {
|
|
223
|
+
const builderRegistrationMsgVal =
|
|
224
|
+
builderRegistrationMessageType.defaultValue();
|
|
225
|
+
|
|
226
|
+
builderRegistrationMsgVal.fee_recipient = fromHexString(msg.fee_recipient);
|
|
227
|
+
builderRegistrationMsgVal.gas_limit = msg.gas_limit;
|
|
228
|
+
builderRegistrationMsgVal.timestamp = msg.timestamp;
|
|
229
|
+
builderRegistrationMsgVal.pubkey = fromHexString(msg.pubkey);
|
|
230
|
+
return Buffer.from(
|
|
231
|
+
builderRegistrationMessageType.hashTreeRoot(builderRegistrationMsgVal)
|
|
232
|
+
.buffer,
|
|
233
|
+
);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
// DV data verification
|
|
238
|
+
const computeDomain = (
|
|
239
|
+
domainType: Uint8Array,
|
|
240
|
+
lockForkVersion: string,
|
|
241
|
+
genesisValidatorsRoot: Uint8Array = fromHexString(GENESIS_VALIDATOR_ROOT),
|
|
242
|
+
): Uint8Array => {
|
|
243
|
+
const forkVersion = fromHexString(
|
|
244
|
+
lockForkVersion.substring(2, lockForkVersion.length),
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const forkDataRoot = computeForkDataRoot(forkVersion, genesisValidatorsRoot);
|
|
248
|
+
const domain = new Uint8Array(32);
|
|
249
|
+
domain.set(domainType);
|
|
250
|
+
domain.set(forkDataRoot.subarray(0, 28), 4);
|
|
251
|
+
return domain;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const verifyDepositData = (
|
|
255
|
+
validator: DistributedValidator,
|
|
256
|
+
withdrawalAddress: string,
|
|
257
|
+
): boolean => {
|
|
258
|
+
const eth1AddressWithdrawalPrefix = '0x01';
|
|
259
|
+
if (
|
|
260
|
+
eth1AddressWithdrawalPrefix +
|
|
261
|
+
'0'.repeat(22) +
|
|
262
|
+
withdrawalAddress.toLowerCase().slice(2) !==
|
|
263
|
+
validator.deposit_data.withdrawal_credentials
|
|
264
|
+
) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (validator.distributed_public_key !== validator.deposit_data.pubkey) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return true;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const verifyBuilderRegistration = (
|
|
276
|
+
validator: DistributedValidator,
|
|
277
|
+
feeRecipientAddress: string,
|
|
278
|
+
): boolean => {
|
|
279
|
+
if (
|
|
280
|
+
validator.distributed_public_key !==
|
|
281
|
+
validator.builder_registration.message.pubkey
|
|
282
|
+
) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
if (
|
|
286
|
+
feeRecipientAddress.toLowerCase() !==
|
|
287
|
+
validator.builder_registration.message.fee_recipient.toLowerCase()
|
|
288
|
+
) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return true;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export const signingRoot = (
|
|
296
|
+
domain: Uint8Array,
|
|
297
|
+
messageBuffer: Buffer,
|
|
298
|
+
): Uint8Array => {
|
|
299
|
+
return computeSigningRoot(messageBuffer, domain);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const verifyLockData = async (
|
|
303
|
+
clusterLock: ClusterLock,
|
|
304
|
+
): Promise<boolean> => {
|
|
305
|
+
const ec = new elliptic.ec('secp256k1');
|
|
306
|
+
const validators = clusterLock.distributed_validators;
|
|
307
|
+
const nodeSignatures = clusterLock.node_signatures;
|
|
308
|
+
const depositDomain = computeDomain(
|
|
309
|
+
fromHexString(DOMAIN_DEPOSIT),
|
|
310
|
+
clusterLock.cluster_definition.fork_version,
|
|
311
|
+
);
|
|
312
|
+
const builderDomain = computeDomain(
|
|
313
|
+
fromHexString(DOMAIN_APPLICATION_BUILDER),
|
|
314
|
+
clusterLock.cluster_definition.fork_version,
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
const pubShares = [];
|
|
318
|
+
const pubKeys = [];
|
|
319
|
+
const builderRegistrationAndDepositDataMessages = [];
|
|
320
|
+
const blsSignatures = [];
|
|
321
|
+
|
|
322
|
+
await init('herumi');
|
|
323
|
+
|
|
324
|
+
for (let i = 0; i < validators.length; i++) {
|
|
325
|
+
const validator = validators[i];
|
|
326
|
+
const validatorPublicShares = validator['public_shares'];
|
|
327
|
+
|
|
328
|
+
for (const element of validatorPublicShares) {
|
|
329
|
+
pubShares.push(fromHexString(element));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
//Deposit data signature
|
|
333
|
+
if (semver.gte(clusterLock.cluster_definition.version, 'v1.6.0')) {
|
|
334
|
+
if (
|
|
335
|
+
!verifyDepositData(
|
|
336
|
+
validator,
|
|
337
|
+
clusterLock.cluster_definition.validators[i].withdrawal_address,
|
|
338
|
+
)
|
|
339
|
+
) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const depositMessageBuffer = computeDepositMsgRoot(
|
|
344
|
+
validator.deposit_data,
|
|
345
|
+
);
|
|
346
|
+
const depositDataMessage = signingRoot(
|
|
347
|
+
depositDomain,
|
|
348
|
+
depositMessageBuffer,
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
pubKeys.push(fromHexString(validator.distributed_public_key));
|
|
352
|
+
builderRegistrationAndDepositDataMessages.push(depositDataMessage);
|
|
353
|
+
blsSignatures.push(fromHexString(validator.deposit_data.signature));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
//Builder registration signature
|
|
357
|
+
if (semver.gte(clusterLock.cluster_definition.version, 'v1.7.0')) {
|
|
358
|
+
if (
|
|
359
|
+
!verifyBuilderRegistration(
|
|
360
|
+
validator,
|
|
361
|
+
clusterLock.cluster_definition.validators[i].fee_recipient_address,
|
|
362
|
+
)
|
|
363
|
+
) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const builderRegistrationMessageBuffer =
|
|
368
|
+
computebuilderRegistrationMsgRoot(
|
|
369
|
+
validator.builder_registration.message,
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
const builderRegistrationMessage = signingRoot(
|
|
373
|
+
builderDomain,
|
|
374
|
+
builderRegistrationMessageBuffer,
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
pubKeys.push(fromHexString(validator.distributed_public_key));
|
|
378
|
+
builderRegistrationAndDepositDataMessages.push(
|
|
379
|
+
builderRegistrationMessage,
|
|
380
|
+
);
|
|
381
|
+
blsSignatures.push(
|
|
382
|
+
fromHexString(validator.builder_registration.signature),
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (
|
|
388
|
+
blsSignatures.length &&
|
|
389
|
+
pubKeys.length &&
|
|
390
|
+
builderRegistrationAndDepositDataMessages.length
|
|
391
|
+
) {
|
|
392
|
+
// verify all deposit data and builder registration bls signatures
|
|
393
|
+
const aggregateBLSSignature = aggregateSignatures(blsSignatures);
|
|
394
|
+
|
|
395
|
+
if (
|
|
396
|
+
!verifyMultiple(
|
|
397
|
+
pubKeys,
|
|
398
|
+
builderRegistrationAndDepositDataMessages,
|
|
399
|
+
aggregateBLSSignature,
|
|
400
|
+
)
|
|
401
|
+
) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (semver.gte(clusterLock.cluster_definition.version, 'v1.7.0')) {
|
|
407
|
+
const lockHashWithout0x = hexWithout0x(clusterLock.lock_hash);
|
|
408
|
+
//node(ENR) signatures
|
|
409
|
+
for (let i = 0; i < nodeSignatures.length; i++) {
|
|
410
|
+
const pubkey = ENR.decodeTxt(
|
|
411
|
+
clusterLock.cluster_definition.operators[i].enr as string,
|
|
412
|
+
).publicKey.toString('hex');
|
|
413
|
+
|
|
414
|
+
const ENRsignature = {
|
|
415
|
+
r: nodeSignatures[i].slice(2, 66),
|
|
416
|
+
s: nodeSignatures[i].slice(66, 130),
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const nodeSignatureVerification = ec
|
|
420
|
+
.keyFromPublic(pubkey, 'hex')
|
|
421
|
+
.verify(lockHashWithout0x, ENRsignature);
|
|
422
|
+
|
|
423
|
+
if (!nodeSignatureVerification) {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
//signature aggregate
|
|
430
|
+
if (
|
|
431
|
+
!verifyAggregate(
|
|
432
|
+
pubShares,
|
|
433
|
+
fromHexString(clusterLock.lock_hash),
|
|
434
|
+
fromHexString(clusterLock.signature_aggregate),
|
|
435
|
+
)
|
|
436
|
+
) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return true;
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
export const isValidClusterLock = async (
|
|
444
|
+
clusterLock: ClusterLock,
|
|
445
|
+
): Promise<boolean> => {
|
|
446
|
+
try {
|
|
447
|
+
const definitionType = definitionFlow(clusterLock.cluster_definition);
|
|
448
|
+
if (!definitionType) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
const isValidDefinitionData = verifyDefinitionSignatures(
|
|
452
|
+
clusterLock.cluster_definition,
|
|
453
|
+
definitionType,
|
|
454
|
+
);
|
|
455
|
+
if (!isValidDefinitionData) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (
|
|
460
|
+
clusterConfigOrDefinitionHash(clusterLock.cluster_definition, false) !==
|
|
461
|
+
clusterLock.cluster_definition.definition_hash
|
|
462
|
+
) {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
if (clusterLockHash(clusterLock) !== clusterLock.lock_hash) {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const isValidLockData = await verifyLockData(clusterLock);
|
|
470
|
+
if (!isValidLockData) {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
return true;
|
|
474
|
+
} catch (err) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
|
package/dist/ajv.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ajv.d.ts","sourceRoot":"","sources":["../src/ajv.ts"],"names":[],"mappings":"AAAA,OAAY,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEvC,wBAAgB,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,WAAW,EAAE,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAQlG"}
|
package/dist/base.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../src/base.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,aAAK,MAAM,GAAG;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB,CAAC;AAEF,8BAAsB,IAAI;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;gBAIT,EAAE,OAA0B,EAAE,OAA0B,EAAE,EAAE,MAAM;cAM9D,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;CAqBhF"}
|
package/dist/cluster.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cluster.test.d.ts","sourceRoot":"","sources":["../src/cluster.test.ts"],"names":[],"mappings":""}
|