@obolnetwork/obol-sdk 1.0.12 → 1.0.13

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.
Files changed (70) hide show
  1. package/LICENCE +21 -0
  2. package/README.md +33 -3
  3. package/dist/cjs/package.json +128 -0
  4. package/dist/cjs/src/ajv.js +2 -1
  5. package/dist/cjs/src/base.js +2 -2
  6. package/dist/cjs/src/constants.js +38 -13
  7. package/dist/cjs/src/errors.js +1 -1
  8. package/dist/cjs/src/index.js +24 -21
  9. package/dist/cjs/src/schema.js +24 -36
  10. package/dist/cjs/src/services.js +2 -2
  11. package/dist/cjs/src/types.js +0 -1
  12. package/dist/cjs/src/utils.js +36 -1
  13. package/dist/cjs/src/{verify.js → verification/common.js} +128 -155
  14. package/dist/cjs/src/verification/sszTypes.js +69 -0
  15. package/dist/cjs/src/verification/v1.6.0.js +149 -0
  16. package/dist/cjs/src/verification/v1.7.0.js +173 -0
  17. package/dist/cjs/src/verification/v1.8.0.js +183 -0
  18. package/dist/cjs/test/fixtures.js +281 -63
  19. package/dist/cjs/test/methods.test.js +58 -35
  20. package/dist/esm/package.json +128 -0
  21. package/dist/esm/src/ajv.js +2 -1
  22. package/dist/esm/src/base.js +2 -2
  23. package/dist/esm/src/constants.js +14 -12
  24. package/dist/esm/src/errors.js +1 -1
  25. package/dist/esm/src/index.js +27 -24
  26. package/dist/esm/src/schema.js +24 -36
  27. package/dist/esm/src/services.js +1 -1
  28. package/dist/esm/src/types.js +0 -1
  29. package/dist/esm/src/utils.js +34 -0
  30. package/dist/esm/src/{verify.js → verification/common.js} +114 -146
  31. package/dist/esm/src/verification/sszTypes.js +64 -0
  32. package/dist/esm/src/verification/v1.6.0.js +142 -0
  33. package/dist/esm/src/verification/v1.7.0.js +166 -0
  34. package/dist/esm/src/verification/v1.8.0.js +176 -0
  35. package/dist/esm/test/fixtures.js +280 -62
  36. package/dist/esm/test/methods.test.js +59 -36
  37. package/dist/types/src/ajv.d.ts +1 -1
  38. package/dist/types/src/base.d.ts +3 -3
  39. package/dist/types/src/constants.d.ts +8 -10
  40. package/dist/types/src/index.d.ts +16 -16
  41. package/dist/types/src/services.d.ts +1 -1
  42. package/dist/types/src/types.d.ts +33 -27
  43. package/dist/types/src/utils.d.ts +3 -0
  44. package/dist/types/src/verification/common.d.ts +32 -0
  45. package/dist/types/src/verification/sszTypes.d.ts +63 -0
  46. package/dist/types/src/verification/v1.6.0.d.ts +34 -0
  47. package/dist/types/src/verification/v1.7.0.d.ts +34 -0
  48. package/dist/types/src/verification/v1.8.0.d.ts +35 -0
  49. package/dist/types/test/fixtures.d.ts +104 -2
  50. package/package.json +42 -8
  51. package/src/ajv.ts +15 -10
  52. package/src/base.ts +25 -21
  53. package/src/constants.ts +80 -78
  54. package/src/errors.ts +6 -9
  55. package/src/index.ts +118 -81
  56. package/src/schema.ts +24 -36
  57. package/src/services.ts +15 -13
  58. package/src/types.ts +72 -74
  59. package/src/utils.ts +53 -4
  60. package/src/verification/common.ts +420 -0
  61. package/src/verification/sszTypes.ts +79 -0
  62. package/src/verification/v1.6.0.ts +226 -0
  63. package/src/verification/v1.7.0.ts +274 -0
  64. package/src/verification/v1.8.0.ts +281 -0
  65. package/dist/cjs/src/hash.js +0 -172
  66. package/dist/esm/src/hash.js +0 -165
  67. package/dist/types/src/hash.d.ts +0 -56
  68. package/dist/types/src/verify.d.ts +0 -4
  69. package/src/hash.ts +0 -250
  70. package/src/verify.ts +0 -479
@@ -0,0 +1,226 @@
1
+ import { type UintNumberByteLen, UintNumberType } from '@chainsafe/ssz/lib/type/uint'
2
+ import { strToUint8Array } from '../utils'
3
+ import { type creatorAddressWrapperType, type creatorContainerType, newCreatorContainerType, newOperatorContainerType, type operatorAddressWrapperType, type operatorContainerType, validatorsContainerType } from './sszTypes'
4
+ import { ByteListType, ByteVectorType, ContainerType, ListCompositeType, fromHexString } from '@chainsafe/ssz'
5
+ import { type ValueOfFields } from '@chainsafe/ssz/lib/view/container'
6
+ import { type ClusterDefintion, type ClusterLock, type DepositData } from '../types'
7
+ import { verifyDepositData } from './common'
8
+ import { aggregateSignatures, verifyAggregate, verifyMultiple } from '@chainsafe/bls'
9
+
10
+ // cluster defintion
11
+ type DefinitionFieldsV1X6 = {
12
+ uuid: ByteListType
13
+ name: ByteListType
14
+ version: ByteListType
15
+ timestamp: ByteListType
16
+ num_validators: UintNumberType
17
+ threshold: UintNumberType
18
+ dkg_algorithm: ByteListType
19
+ fork_version: ByteVectorType
20
+ operators: ListCompositeType<
21
+ typeof operatorContainerType | typeof operatorAddressWrapperType
22
+ >
23
+ creator: typeof creatorContainerType | typeof creatorAddressWrapperType
24
+ validators: ListCompositeType<typeof validatorsContainerType>
25
+ config_hash?: ByteVectorType
26
+ }
27
+
28
+ type DefinitionContainerTypeV1X6 =
29
+ ContainerType<DefinitionFieldsV1X6>
30
+
31
+ /**
32
+ * Returns the containerized cluster definition
33
+ * @param cluster ClusterDefinition to calculate the type from
34
+ * @returns SSZ Containerized type of cluster input
35
+ */
36
+ export const clusterDefinitionContainerTypeV1X6 = (
37
+ configOnly: boolean,
38
+ ): DefinitionContainerTypeV1X6 => {
39
+ let returnedContainerType: any = {
40
+ uuid: new ByteListType(64),
41
+ name: new ByteListType(256),
42
+ version: new ByteListType(16),
43
+ timestamp: new ByteListType(32),
44
+ num_validators: new UintNumberType(8 as UintNumberByteLen),
45
+ threshold: new UintNumberType(8 as UintNumberByteLen),
46
+ dkg_algorithm: new ByteListType(32),
47
+ fork_version: new ByteVectorType(4),
48
+ operators: new ListCompositeType(newOperatorContainerType(configOnly), 256),
49
+ creator: newCreatorContainerType(configOnly),
50
+ validators: new ListCompositeType(validatorsContainerType, 65536),
51
+ }
52
+
53
+ if (!configOnly) {
54
+ returnedContainerType = {
55
+ ...returnedContainerType,
56
+ config_hash: new ByteVectorType(32),
57
+ }
58
+ }
59
+
60
+ return new ContainerType(returnedContainerType)
61
+ }
62
+
63
+ export const hashClusterDefinitionV1X6 = (
64
+ cluster: ClusterDefintion,
65
+ configOnly: boolean,
66
+ ): ValueOfFields<DefinitionFieldsV1X6> => {
67
+ const definitionType = clusterDefinitionContainerTypeV1X6(configOnly)
68
+
69
+ const val = definitionType.defaultValue()
70
+
71
+ // order should be same as charon https://github.com/ObolNetwork/charon/blob/main/cluster/ssz.go#L276
72
+ val.uuid = strToUint8Array(cluster.uuid)
73
+ val.name = strToUint8Array(cluster.name)
74
+ val.version = strToUint8Array(cluster.version)
75
+ val.timestamp = strToUint8Array(cluster.timestamp)
76
+ val.num_validators = cluster.num_validators
77
+ val.threshold = cluster.threshold
78
+ val.dkg_algorithm = strToUint8Array(cluster.dkg_algorithm)
79
+ val.fork_version = fromHexString(cluster.fork_version)
80
+ val.operators = cluster.operators.map(operator => {
81
+ return configOnly
82
+ ? { address: fromHexString(operator.address) }
83
+ : {
84
+ address: fromHexString(operator.address),
85
+ enr: strToUint8Array(operator.enr as string),
86
+ config_signature: fromHexString(operator.config_signature as string),
87
+ enr_signature: fromHexString(operator.enr_signature as string),
88
+ }
89
+ })
90
+ val.creator = configOnly
91
+ ? { address: fromHexString(cluster.creator.address) }
92
+ : {
93
+ address: fromHexString(cluster.creator.address),
94
+ config_signature: fromHexString(cluster.creator.config_signature as string),
95
+ }
96
+ val.validators = cluster.validators.map((validator) => {
97
+ return {
98
+ fee_recipient_address: fromHexString(validator.fee_recipient_address),
99
+ withdrawal_address: fromHexString(validator.withdrawal_address),
100
+ }
101
+ })
102
+
103
+ if (!configOnly) {
104
+ val.config_hash = fromHexString(cluster.config_hash)
105
+ }
106
+ return val
107
+ }
108
+
109
+ // cluster lock
110
+
111
+ const dvContainerTypeV1X6 = new ContainerType({
112
+ distributed_public_key: new ByteVectorType(48),
113
+ public_shares: new ListCompositeType(new ByteVectorType(48), 256),
114
+ pubkey: new ByteVectorType(48),
115
+ withdrawal_credentials: new ByteVectorType(32),
116
+ amount: new UintNumberType(8 as UintNumberByteLen),
117
+ signature: new ByteVectorType(96),
118
+ })
119
+
120
+ type LockContainerTypeV1X6 = ContainerType<{
121
+ cluster_definition: DefinitionContainerTypeV1X6
122
+ distributed_validators: ListCompositeType<typeof dvContainerTypeV1X6>
123
+ }>
124
+
125
+ /**
126
+ * @returns SSZ Containerized type of cluster lock
127
+ */
128
+ const clusterLockContainerTypeV1X6 = (): LockContainerTypeV1X6 => {
129
+ return new ContainerType({
130
+ cluster_definition: clusterDefinitionContainerTypeV1X6(false),
131
+ distributed_validators: new ListCompositeType(dvContainerTypeV1X6, 65536),
132
+ })
133
+ }
134
+
135
+ /**
136
+ * @param cluster The published cluster lock
137
+ * @returns The lock hash in of the corresponding cluster
138
+ */
139
+ export const hashClusterLockV1X6 = (cluster: ClusterLock): string => {
140
+ const lockType = clusterLockContainerTypeV1X6()
141
+
142
+ const val = lockType.defaultValue()
143
+
144
+ // Check if we can replace with definition_hash
145
+ val.cluster_definition = hashClusterDefinitionV1X6(
146
+ cluster.cluster_definition,
147
+ false,
148
+ )
149
+ val.distributed_validators = cluster.distributed_validators.map(dValidator => {
150
+ return {
151
+ distributed_public_key: fromHexString(dValidator.distributed_public_key),
152
+ public_shares: dValidator.public_shares.map(publicShare =>
153
+ fromHexString(publicShare),
154
+ ),
155
+ pubkey: fromHexString(dValidator.deposit_data?.pubkey as string),
156
+ withdrawal_credentials: fromHexString(
157
+ dValidator.deposit_data?.withdrawal_credentials as string,
158
+ ),
159
+ amount: parseInt(dValidator.deposit_data?.amount as string),
160
+ signature: fromHexString(dValidator.deposit_data?.signature as string),
161
+ }
162
+ })
163
+
164
+ return '0x' + Buffer.from(lockType.hashTreeRoot(val).buffer).toString('hex')
165
+ }
166
+
167
+ // DV verification
168
+ export const verifyDVV1X6 = (clusterLock: ClusterLock): boolean => {
169
+ const validators = clusterLock.distributed_validators
170
+ const pubShares = []
171
+ const pubKeys = []
172
+ const builderRegistrationAndDepositDataMessages = []
173
+ const blsSignatures = []
174
+
175
+ for (let i = 0; i < validators.length; i++) {
176
+ const validator = validators[i]
177
+ const validatorPublicShares = validator.public_shares
178
+ const distributedPublicKey = validator.distributed_public_key
179
+
180
+ // Needed in signature_aggregate verification
181
+ for (const element of validatorPublicShares) {
182
+ pubShares.push(fromHexString(element))
183
+ }
184
+
185
+ const { isValidDepositData, depositDataMsg } = verifyDepositData(
186
+ distributedPublicKey,
187
+ validator.deposit_data as Partial<DepositData>,
188
+ clusterLock.cluster_definition.validators[i].withdrawal_address,
189
+ clusterLock.cluster_definition.fork_version
190
+ )
191
+
192
+ if (
193
+ !isValidDepositData
194
+ ) {
195
+ return false
196
+ }
197
+
198
+ pubKeys.push(fromHexString(validator.distributed_public_key))
199
+ builderRegistrationAndDepositDataMessages.push(depositDataMsg)
200
+ blsSignatures.push(fromHexString(validator.deposit_data?.signature as string))
201
+ }
202
+
203
+ const aggregateBLSSignature = aggregateSignatures(blsSignatures)
204
+
205
+ if (
206
+ !verifyMultiple(
207
+ pubKeys,
208
+ builderRegistrationAndDepositDataMessages,
209
+ aggregateBLSSignature,
210
+ )
211
+ ) {
212
+ return false
213
+ }
214
+
215
+ if (
216
+ !verifyAggregate(
217
+ pubShares,
218
+ fromHexString(clusterLock.lock_hash),
219
+ fromHexString(clusterLock.signature_aggregate),
220
+ )
221
+ ) {
222
+ return false
223
+ }
224
+
225
+ return true
226
+ }
@@ -0,0 +1,274 @@
1
+ import { type UintNumberByteLen, UintNumberType } from '@chainsafe/ssz/lib/type/uint'
2
+ import { strToUint8Array } from '../utils'
3
+ import { builderRegistrationContainer, type creatorAddressWrapperType, type creatorContainerType, depositDataContainer, newCreatorContainerType, newOperatorContainerType, type operatorAddressWrapperType, type operatorContainerType, validatorsContainerType } from './sszTypes'
4
+ import { ByteListType, ByteVectorType, ContainerType, ListCompositeType, fromHexString } from '@chainsafe/ssz'
5
+ import { type ValueOfFields } from '@chainsafe/ssz/lib/view/container'
6
+ import { type ClusterDefintion, type ClusterLock, type DepositData } from '../types'
7
+ import { verifyBuilderRegistration, verifyDepositData, verifyNodeSignatures } from './common'
8
+ import { aggregateSignatures, verifyAggregate, verifyMultiple } from '@chainsafe/bls'
9
+
10
+ // cluster defintion
11
+ type DefinitionFieldsV1X7 = {
12
+ uuid: ByteListType
13
+ name: ByteListType
14
+ version: ByteListType
15
+ timestamp: ByteListType
16
+ num_validators: UintNumberType
17
+ threshold: UintNumberType
18
+ dkg_algorithm: ByteListType
19
+ fork_version: ByteVectorType
20
+ operators: ListCompositeType<
21
+ typeof operatorContainerType | typeof operatorAddressWrapperType
22
+ >
23
+ creator: typeof creatorContainerType | typeof creatorAddressWrapperType
24
+ validators: ListCompositeType<typeof validatorsContainerType>
25
+ config_hash?: ByteVectorType
26
+ }
27
+
28
+ type DefinitionContainerTypeV1X7 =
29
+ ContainerType<DefinitionFieldsV1X7>
30
+
31
+ /**
32
+ * Returns the containerized cluster definition
33
+ * @param cluster ClusterDefinition to calculate the type from
34
+ * @returns SSZ Containerized type of cluster input
35
+ */
36
+ export const clusterDefinitionContainerTypeV1X7 = (
37
+ configOnly: boolean,
38
+ ): DefinitionContainerTypeV1X7 => {
39
+ let returnedContainerType: any = {
40
+ uuid: new ByteListType(64),
41
+ name: new ByteListType(256),
42
+ version: new ByteListType(16),
43
+ timestamp: new ByteListType(32),
44
+ num_validators: new UintNumberType(8 as UintNumberByteLen),
45
+ threshold: new UintNumberType(8 as UintNumberByteLen),
46
+ dkg_algorithm: new ByteListType(32),
47
+ fork_version: new ByteVectorType(4),
48
+ operators: new ListCompositeType(newOperatorContainerType(configOnly), 256),
49
+ creator: newCreatorContainerType(configOnly),
50
+ validators: new ListCompositeType(validatorsContainerType, 65536),
51
+ }
52
+
53
+ if (!configOnly) {
54
+ returnedContainerType = {
55
+ ...returnedContainerType,
56
+ config_hash: new ByteVectorType(32),
57
+ }
58
+ }
59
+
60
+ return new ContainerType(returnedContainerType)
61
+ }
62
+
63
+ export const hashClusterDefinitionV1X7 = (
64
+ cluster: ClusterDefintion,
65
+ configOnly: boolean,
66
+ ): ValueOfFields<DefinitionFieldsV1X7> => {
67
+ const definitionType = clusterDefinitionContainerTypeV1X7(configOnly)
68
+
69
+ const val = definitionType.defaultValue()
70
+
71
+ // order should be same as charon https://github.com/ObolNetwork/charon/blob/main/cluster/ssz.go#L276
72
+ val.uuid = strToUint8Array(cluster.uuid)
73
+ val.name = strToUint8Array(cluster.name)
74
+ val.version = strToUint8Array(cluster.version)
75
+ val.timestamp = strToUint8Array(cluster.timestamp)
76
+ val.num_validators = cluster.num_validators
77
+ val.threshold = cluster.threshold
78
+ val.dkg_algorithm = strToUint8Array(cluster.dkg_algorithm)
79
+ val.fork_version = fromHexString(cluster.fork_version)
80
+ val.operators = cluster.operators.map(operator => {
81
+ return configOnly
82
+ ? { address: fromHexString(operator.address) }
83
+ : {
84
+ address: fromHexString(operator.address),
85
+ enr: strToUint8Array(operator.enr as string),
86
+ config_signature: fromHexString(operator.config_signature as string),
87
+ enr_signature: fromHexString(operator.enr_signature as string),
88
+ }
89
+ })
90
+ val.creator = configOnly
91
+ ? { address: fromHexString(cluster.creator.address) }
92
+ : {
93
+ address: fromHexString(cluster.creator.address),
94
+ config_signature: fromHexString(cluster.creator.config_signature as string),
95
+ }
96
+ val.validators = cluster.validators.map((validator) => {
97
+ return {
98
+ fee_recipient_address: fromHexString(validator.fee_recipient_address),
99
+ withdrawal_address: fromHexString(validator.withdrawal_address),
100
+ }
101
+ })
102
+
103
+ if (!configOnly) {
104
+ val.config_hash = fromHexString(cluster.config_hash)
105
+ }
106
+ return val
107
+ }
108
+
109
+ // cluster lock
110
+
111
+ const dvContainerTypeV1X7 = new ContainerType({
112
+ distributed_public_key: new ByteVectorType(48),
113
+ public_shares: new ListCompositeType(new ByteVectorType(48), 256),
114
+ deposit_data: depositDataContainer,
115
+ builder_registration: builderRegistrationContainer,
116
+ })
117
+
118
+ type LockContainerTypeV1X7 = ContainerType<{
119
+ cluster_definition: DefinitionContainerTypeV1X7
120
+ distributed_validators: ListCompositeType<typeof dvContainerTypeV1X7>
121
+ }>
122
+
123
+ /**
124
+ * @returns SSZ Containerized type of cluster lock
125
+ */
126
+ const clusterLockContainerTypeV1X7 = (): LockContainerTypeV1X7 => {
127
+ return new ContainerType({
128
+ cluster_definition: clusterDefinitionContainerTypeV1X7(false),
129
+ distributed_validators: new ListCompositeType(dvContainerTypeV1X7, 65536),
130
+ })
131
+ }
132
+
133
+ /**
134
+ * @param cluster The published cluster lock
135
+ * @returns The lock hash in of the corresponding cluster
136
+ */
137
+ export const hashClusterLockV1X7 = (cluster: ClusterLock): string => {
138
+ const lockType = clusterLockContainerTypeV1X7()
139
+
140
+ const val = lockType.defaultValue()
141
+
142
+ // Check if we can replace with definition_hash
143
+ val.cluster_definition = hashClusterDefinitionV1X7(
144
+ cluster.cluster_definition,
145
+ false,
146
+ )
147
+ val.distributed_validators = cluster.distributed_validators.map(
148
+ (dValidator) => {
149
+ return {
150
+ distributed_public_key: fromHexString(dValidator.distributed_public_key),
151
+ public_shares: dValidator.public_shares.map((publicShare) =>
152
+ fromHexString(publicShare),
153
+ ),
154
+ deposit_data: {
155
+ pubkey: fromHexString(dValidator.deposit_data?.pubkey as string),
156
+ withdrawal_credentials: fromHexString(
157
+ dValidator.deposit_data?.withdrawal_credentials as string,
158
+ ),
159
+ amount: parseInt(dValidator.deposit_data?.amount as string),
160
+ signature: fromHexString(dValidator.deposit_data?.signature as string),
161
+ },
162
+ builder_registration: {
163
+ message: {
164
+ fee_recipient: fromHexString(
165
+ dValidator.builder_registration?.message.fee_recipient as string,
166
+ ),
167
+ gas_limit: dValidator.builder_registration?.message.gas_limit as number,
168
+ timestamp: dValidator.builder_registration?.message.timestamp as number,
169
+ pubkey: fromHexString(
170
+ dValidator.builder_registration?.message.pubkey as string,
171
+ ),
172
+ },
173
+ signature: fromHexString(dValidator.builder_registration?.signature as string),
174
+ },
175
+ }
176
+ },
177
+ )
178
+
179
+ return '0x' + Buffer.from(lockType.hashTreeRoot(val).buffer).toString('hex')
180
+ }
181
+
182
+ // DV verification
183
+ export const verifyDVV1X7 = (clusterLock: ClusterLock): boolean => {
184
+ const validators = clusterLock.distributed_validators
185
+
186
+ const pubShares = []
187
+
188
+ const pubKeys = []
189
+ const builderRegistrationAndDepositDataMessages = []
190
+ const blsSignatures = []
191
+
192
+ for (let i = 0; i < validators.length; i++) {
193
+ const validator = validators[i]
194
+ const validatorPublicShares = validator.public_shares
195
+ const distributedPublicKey = validator.distributed_public_key
196
+
197
+ // Needed in signature_aggregate verification
198
+ for (const element of validatorPublicShares) {
199
+ pubShares.push(fromHexString(element))
200
+ }
201
+
202
+ // Deposit Data Verification
203
+ const { isValidDepositData, depositDataMsg } = verifyDepositData(
204
+ distributedPublicKey,
205
+ validator.deposit_data as Partial<DepositData>,
206
+ clusterLock.cluster_definition.validators[i].withdrawal_address,
207
+ clusterLock.cluster_definition.fork_version
208
+ )
209
+
210
+ if (
211
+ !isValidDepositData
212
+ ) {
213
+ return false
214
+ }
215
+
216
+ pubKeys.push(fromHexString(distributedPublicKey))
217
+ builderRegistrationAndDepositDataMessages.push(depositDataMsg)
218
+ blsSignatures.push(fromHexString(validator.deposit_data?.signature as string))
219
+
220
+ // Builder Registration Verification
221
+ const { isValidBuilderRegistration, builderRegistrationMsg } = verifyBuilderRegistration(
222
+ validator,
223
+ clusterLock.cluster_definition.validators[i].fee_recipient_address,
224
+ clusterLock.cluster_definition.fork_version
225
+ )
226
+
227
+ if (
228
+ !isValidBuilderRegistration
229
+ ) {
230
+ return false
231
+ }
232
+
233
+ pubKeys.push(fromHexString(distributedPublicKey))
234
+ builderRegistrationAndDepositDataMessages.push(builderRegistrationMsg)
235
+ blsSignatures.push(
236
+ fromHexString(validator.builder_registration?.signature as string),
237
+ )
238
+ }
239
+
240
+ // BLS signatures verification
241
+ const aggregateBLSSignature = aggregateSignatures(blsSignatures)
242
+
243
+ if (
244
+ !verifyMultiple(
245
+ pubKeys,
246
+ builderRegistrationAndDepositDataMessages,
247
+ aggregateBLSSignature,
248
+ )
249
+ ) {
250
+ return false
251
+ }
252
+
253
+ // Node Signatures verification
254
+ if (
255
+ !verifyNodeSignatures(
256
+ clusterLock
257
+ )
258
+ ) {
259
+ return false
260
+ }
261
+
262
+ // signature_aggregate verification
263
+ if (
264
+ !verifyAggregate(
265
+ pubShares,
266
+ fromHexString(clusterLock.lock_hash),
267
+ fromHexString(clusterLock.signature_aggregate),
268
+ )
269
+ ) {
270
+ return false
271
+ }
272
+
273
+ return true
274
+ }