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