@obolnetwork/obol-sdk 2.1.2 → 2.2.0
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/dist/cjs/package.json +4 -1
- package/dist/cjs/src/constants.js +9 -1
- package/dist/cjs/src/index.js +1 -0
- package/dist/cjs/src/types.js +18 -1
- package/dist/cjs/src/utils.js +11 -1
- package/dist/cjs/src/verification/common.js +35 -28
- package/dist/cjs/src/verification/signature-validator.js +72 -0
- package/dist/cjs/test/fixtures.js +88 -1
- package/dist/cjs/test/methods.test.js +4 -0
- package/dist/esm/package.json +4 -1
- package/dist/esm/src/constants.js +8 -0
- package/dist/esm/src/index.js +1 -0
- package/dist/esm/src/types.js +17 -0
- package/dist/esm/src/utils.js +10 -1
- package/dist/esm/src/verification/common.js +35 -28
- package/dist/esm/src/verification/signature-validator.js +63 -0
- package/dist/esm/test/fixtures.js +87 -0
- package/dist/esm/test/methods.test.js +5 -1
- package/dist/types/src/constants.d.ts +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/types.d.ts +7 -1
- package/dist/types/src/utils.d.ts +2 -1
- package/dist/types/src/verification/signature-validator.d.ts +19 -0
- package/dist/types/test/fixtures.d.ts +51 -0
- package/package.json +4 -1
- package/src/constants.ts +10 -0
- package/src/index.ts +1 -0
- package/src/types.ts +23 -0
- package/src/utils.ts +11 -3
- package/src/verification/common.ts +40 -31
- package/src/verification/signature-validator.ts +96 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
11
|
+
import { ethers } from 'ethers';
|
|
12
|
+
import { SignTypedDataVersion, TypedDataUtils, } from '@metamask/eth-sig-util';
|
|
13
|
+
import Safe from '@safe-global/protocol-kit';
|
|
14
|
+
import { PROVIDER_MAP } from '../constants';
|
|
15
|
+
import { hashTypedData } from '@safe-global/protocol-kit/dist/src/utils';
|
|
16
|
+
import { isContractAvailable, getProvider } from '../utils';
|
|
17
|
+
export const validateAddressSignature = ({ address, token, data, chainId, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
+
try {
|
|
19
|
+
const provider = getProvider(chainId);
|
|
20
|
+
if (provider) {
|
|
21
|
+
const contractAddress = yield isContractAvailable(address, provider);
|
|
22
|
+
if (contractAddress) {
|
|
23
|
+
return yield validateSmartContractSignature({
|
|
24
|
+
token,
|
|
25
|
+
data: data,
|
|
26
|
+
address,
|
|
27
|
+
chainId,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return validateEOASignature({ token, data, address });
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return validateEOASignature({ token, data, address });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
export const validateEOASignature = ({ token, data, address, }) => {
|
|
38
|
+
try {
|
|
39
|
+
const sig = ethers.Signature.from(token);
|
|
40
|
+
const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
|
|
41
|
+
return (ethers.recoverAddress(digest, sig).toLowerCase() ===
|
|
42
|
+
address.toLocaleLowerCase());
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
console.error(`validate EOA Signature error: ${err}`);
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
export const validateSmartContractSignature = ({ token, data, address, chainId, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
+
try {
|
|
51
|
+
const provider = PROVIDER_MAP[chainId];
|
|
52
|
+
const protocolKit = yield Safe.init({
|
|
53
|
+
provider,
|
|
54
|
+
safeAddress: address,
|
|
55
|
+
});
|
|
56
|
+
const messageHash = hashTypedData(data);
|
|
57
|
+
const isValidSignature = yield protocolKit.isValidSignature(messageHash, token);
|
|
58
|
+
return isValidSignature;
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
throw new Error(`Error validating smart contract signature: ${err.message}`);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
@@ -420,3 +420,90 @@ export const nullDepositAmountsClusterLockV1X8 = {
|
|
|
420
420
|
'0xdb6332a4ca9c41592511e7480c835b449fea120430e759735f3c24f1a6d496551a516151d2baae21d0cc9469b28dc1122b672af40ebb0e77045da92e0da6a03500',
|
|
421
421
|
],
|
|
422
422
|
};
|
|
423
|
+
export const clusterLockWithSafe = {
|
|
424
|
+
cluster_definition: {
|
|
425
|
+
name: 'Rainy cluster',
|
|
426
|
+
creator: {
|
|
427
|
+
address: '0xEF43Ff87070247EC9D28B482D6474318e9DAFc98',
|
|
428
|
+
config_signature: '0x0c4aaafcef722cf770b419d47a21571b3c73275185a1b719e5709168af6327eb2ee392134e660d6e4ea113abd184dd4f2bd8ee09a4ae33f434433f7575873b981c',
|
|
429
|
+
},
|
|
430
|
+
operators: [
|
|
431
|
+
{
|
|
432
|
+
address: '0xe5b709A14859EdF820347D78E587b1634B0ec771',
|
|
433
|
+
enr: 'enr:-HW4QGrhMVQGLtKv_KlubzcVZM9AUsSVgDPtq4k_0q344ZtWJ6tD4riMP5jfWXXdHIklAm_pHdQv0MbTE1DrXmuEM7yAgmlkgnY0iXNlY3AyNTZrMaECa_cfUuFYiuFQp_dUEEicCBdFWNfzm8QhrNhBwey8PBg',
|
|
434
|
+
config_signature: '0xad813cd706d7469b560570ef13b0afdf36d0637e9bc5362ec358c218085d9b231b1377f0078e1ed9c2af99fddb3d7608c03bf8aee6edc931c73d7204a7fdee181b',
|
|
435
|
+
enr_signature: '0x304c022e74c13bfd492219efc52fb133f728afa01fefa9fe1d2540ce7c23ca730dd8248b0ad76630dd120975085d77fba20c050620f1e9583590d003110dbca41c',
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
address: '0x83b7CA5be230A50DEE511657CCFF262ba04D0353',
|
|
439
|
+
enr: 'enr:-HW4QNGbmr_0YhIeTUQfljlDGGCnR8RGhtWr_gjMI8a5BwigGur2qFgGZrqSumAvk4gX9ciP2Tu74uOdOZPw6nvTi7aAgmlkgnY0iXNlY3AyNTZrMaEC0nrshQsWtEOphaLrZVTzPFOz8EAJweGbhXDtSsXPiLY',
|
|
440
|
+
config_signature: '0x41dfb02ff13775d53ad3f739151c44952ff98a3283d124b6f69782e7458150e92771e32f965803391818d23ad95c9c7bcba15e896c3a576d1a700a16d8cb29fe1b',
|
|
441
|
+
enr_signature: '0x388460abfdeded56839e4ae1480ea48a70ac44a89963bda24152e4f879a9d22918c3e77733f2ccaed5c2745f503565d651a357c6a50634a597d61e16c384961e1c',
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
address: '0x123C1cD7AeE10b525D09b848D4A62596958f7D9C',
|
|
445
|
+
enr: 'enr:-HW4QMpDdYHJR3TlnWsnuIKfbmg2aRMMcAFlhTuuZa0Grd3dMFC0AEPHvHM2cKboULC4RUl2Dz7-LpwL2EJRGhJzxcyAgmlkgnY0iXNlY3AyNTZrMaEC73iR5dcgO_bJ8tvsHLrFglwspGFhaAbiBtGAHAJPD9c',
|
|
446
|
+
config_signature: '0x91d23aefc02263fdf0edd0dcfdf26eee01c4d673b97a91a099fc10dfc01f55fa690eccc809658ea2ed17383df355f80a62bd96df15e40a2e2f740189fa6537f61b',
|
|
447
|
+
enr_signature: '0x1035b284df3b1d60434bfe2d139f1472ed29663e2af719779bb5da35537f202032ab0f7cdc540c724c8e80bedfba247809d741260201fbb5bcbfc7f0813334241b',
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
address: '0x169212AE1A834C1E0efd8C827Abdbc2b5557B86C', // safe address
|
|
451
|
+
enr: 'enr:-HW4QPwheIq51G9r9k-6GYrhp9-y-hN2SSowqgQemvPyBu9cOKERqPeqy0JuZ0yKyWiji406eK8fFeHX5fKU-6OI3u6AgmlkgnY0iXNlY3AyNTZrMaECaARWVFt5KNkHPno5YYdFBUrkLU5EmOB3K0Ns9hp-s-s',
|
|
452
|
+
config_signature: '0xf97cc2536afe96e43e07aac140254cea37b0345573b12476f8840e571095d72049aa9e03a099ef2965523f83648c4323cd16ec2ee3f5e6efac177da13bcf6eb21c',
|
|
453
|
+
enr_signature: '0x422f471d54eb19fc52e076bb11fd5c8de895734ed111b66708ef805ae124f87b7519e841e59eccdb75c8328223efd8bb324f3fd7d3048f037419bb83e03caae41b',
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
uuid: '3ac43657-b465-49a8-94aa-25b7f7979c2a',
|
|
457
|
+
version: 'v1.8.0',
|
|
458
|
+
timestamp: '2025-01-08T15:18:28.598Z',
|
|
459
|
+
num_validators: 1,
|
|
460
|
+
threshold: 3,
|
|
461
|
+
validators: [
|
|
462
|
+
{
|
|
463
|
+
fee_recipient_address: '0xe5b709A14859EdF820347D78E587b1634B0ec771',
|
|
464
|
+
withdrawal_address: '0xe5b709A14859EdF820347D78E587b1634B0ec771',
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
dkg_algorithm: 'default',
|
|
468
|
+
fork_version: '0x01017000',
|
|
469
|
+
deposit_amounts: ['32000000000'],
|
|
470
|
+
config_hash: '0x1849d9434a5ce7a7d15e1e030a48740a307c80424608c6618386d78a2e597fb3',
|
|
471
|
+
definition_hash: '0x85dabfcebf9bf850521cdb65ea6083c462d9ac57eb62d9a9eb47761ec10bc80e',
|
|
472
|
+
},
|
|
473
|
+
distributed_validators: [
|
|
474
|
+
{
|
|
475
|
+
distributed_public_key: '0xb0427b57f3f31da882a1ca5d9980e6164ba4c4d3e2c35fb6699f4e76d88e1aede881e0bdb87c48ac707da282f0c92e92',
|
|
476
|
+
public_shares: [
|
|
477
|
+
'0x8d150f921a54fe258bb57f568288de87e392946124df65861afbe52d2a3be6a1fa09d19beb62f879b0f18b2e3b26f69f',
|
|
478
|
+
'0x8fa69ec7c31eeda66007bc3d49a3d5654dbe6a3c0890898ea5913ed263a0efc3f6744064dbde3ee0cc1d2f08db33b4d7',
|
|
479
|
+
'0xb862933b7b7adb33cfb3de1052f7dd9e45dfa30accda01dfc4d3c38716747504aade8f832fa5a256dcaf13f80dbbb38f',
|
|
480
|
+
'0xb5a15d86945c7ff38e0b09158a3264033e6dbaebf2ec9f3426988e6d24e38b3dbd6fe413d3253b308404bb691757fb2e',
|
|
481
|
+
],
|
|
482
|
+
builder_registration: {
|
|
483
|
+
message: {
|
|
484
|
+
fee_recipient: '0xe5b709a14859edf820347d78e587b1634b0ec771',
|
|
485
|
+
gas_limit: 30000000,
|
|
486
|
+
timestamp: 1696000704,
|
|
487
|
+
pubkey: '0xb0427b57f3f31da882a1ca5d9980e6164ba4c4d3e2c35fb6699f4e76d88e1aede881e0bdb87c48ac707da282f0c92e92',
|
|
488
|
+
},
|
|
489
|
+
signature: '0xa5e4220e7d8e7fd4430590c70a27104473b15c8ea03b2d1571bc86bbbcb443e2b7467f75a7e77b5d9b1a10d5fbc335cf0d59c3b3bc1b6d036b2e22511abcf41373567a15185f011ce0129741f0ae0e9e49759f9c5c0ce62f2a99f30e8c7df996',
|
|
490
|
+
},
|
|
491
|
+
partial_deposit_data: [
|
|
492
|
+
{
|
|
493
|
+
pubkey: '0xb0427b57f3f31da882a1ca5d9980e6164ba4c4d3e2c35fb6699f4e76d88e1aede881e0bdb87c48ac707da282f0c92e92',
|
|
494
|
+
withdrawal_credentials: '0x010000000000000000000000e5b709a14859edf820347d78e587b1634b0ec771',
|
|
495
|
+
amount: '32000000000',
|
|
496
|
+
signature: '0x90aff3cb35c577644cb5fe3036ae8aeb5ac6cc2ce2a33a4332e2e883dc48f8fb71165531862d3035a4b6e3a60215d661021672a59f9406e3bd04b5f88f4f77c5f54a9ff37167807010123cdcc230040e5a485b5eb36704fd276dbf541f7151a8',
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
},
|
|
500
|
+
],
|
|
501
|
+
signature_aggregate: '0x86fdeae3db45215d5f24fc19645f8f95e91fffd1a1d0a87668d3f27724fa735b85fb15de120106b8463ff1d32823813e0f139ee7cb6d3b026c6b81cc96566819817742a13e24ac44fc6571385d5b0fdbaace7e50e2cfbcd692e36b1ca25436e4',
|
|
502
|
+
lock_hash: '0xe03b0fdf6d6a7d38360b0ceac1f0380b051f710e80d0e166b855ef3148d46733',
|
|
503
|
+
node_signatures: [
|
|
504
|
+
'0x564328bcad4cac498aa51b7e7e041cf264e9c65c95138df1fc00d4bf714bc38419c43b5a32662d5c8b747d646d6e77830b2bfd62fa8402e136147c244acdce0300',
|
|
505
|
+
'0xb0e984efd2874fc4bc97bb9c42dccbc723080cf29a20a3cf0b05867ebe87bc430c29a560936000439fcde610ce91452ced763586d3bcca24fbe4fe83bb39202601',
|
|
506
|
+
'0xf3633a8e59db2e8d253ffe1b284f9edab1c1c1aca6288c3a3b2e9cb4eb7e789e23366fc325274f13b7311b9faf53665c80b5cb1f93810713f7b094cd2bec1d9900',
|
|
507
|
+
'0x4067921a5257efe4ceb103f2129faaa7a502781157b3b54e5efca559c558c2b43b89f30962e87df88fbf62250049a31888fcd62735d54b7553e5dc75c3b6ae0901',
|
|
508
|
+
],
|
|
509
|
+
};
|
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { ethers, JsonRpcProvider } from 'ethers';
|
|
11
11
|
import { Client, validateClusterLock } from '../src/index';
|
|
12
|
-
import { clusterConfigV1X7, clusterConfigV1X8, clusterLockV1X6, clusterLockV1X7, clusterLockV1X8, nullDepositAmountsClusterLockV1X8, } from './fixtures.js';
|
|
12
|
+
import { clusterConfigV1X7, clusterConfigV1X8, clusterLockV1X6, clusterLockV1X7, clusterLockV1X8, clusterLockWithSafe, nullDepositAmountsClusterLockV1X8, } from './fixtures.js';
|
|
13
13
|
import { SDK_VERSION } from '../src/constants';
|
|
14
14
|
import { Base } from '../src/base';
|
|
15
15
|
import { validatePayload } from '../src/ajv';
|
|
@@ -177,6 +177,10 @@ describe('Cluster Client without a signer', () => {
|
|
|
177
177
|
version: 'null deposit_amounts v1.8.0',
|
|
178
178
|
clusterLock: nullDepositAmountsClusterLockV1X8,
|
|
179
179
|
},
|
|
180
|
+
{
|
|
181
|
+
version: 'Cluster with safe address v1.8.0',
|
|
182
|
+
clusterLock: clusterLockWithSafe,
|
|
183
|
+
},
|
|
180
184
|
])("$version: 'should return true on verified cluster lock'", ({ clusterLock }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
181
185
|
const isValidLock = yield validateClusterLock(clusterLock);
|
|
182
186
|
expect(isValidLock).toEqual(true);
|
|
@@ -110,3 +110,4 @@ export declare const CHAIN_CONFIGURATION: {
|
|
|
110
110
|
export declare const DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT = 1;
|
|
111
111
|
export declare const DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT = 0.1;
|
|
112
112
|
export declare const OBOL_SDK_EMAIL = "sdk@dvlabs.tech";
|
|
113
|
+
export declare const PROVIDER_MAP: Record<number, string>;
|
|
@@ -3,6 +3,7 @@ import { Base } from './base.js';
|
|
|
3
3
|
import { type RewardsSplitPayload, type ClusterDefinition, type ClusterLock, type ClusterPayload, type OperatorPayload, type TotalSplitPayload, type ClusterValidator, type ETH_ADDRESS, type OWRTranches } from './types.js';
|
|
4
4
|
export * from './types.js';
|
|
5
5
|
export * from './services.js';
|
|
6
|
+
export * from './verification/signature-validator.js';
|
|
6
7
|
/**
|
|
7
8
|
* Obol sdk Client can be used for creating, managing and activating distributed validators.
|
|
8
9
|
*/
|
|
@@ -9,8 +9,14 @@ export declare enum FORK_MAPPING {
|
|
|
9
9
|
/** Gnosis Chain. */
|
|
10
10
|
'0x00000064' = 100,
|
|
11
11
|
/** Holesky. */
|
|
12
|
-
'0x01017000' = 17000
|
|
12
|
+
'0x01017000' = 17000,
|
|
13
|
+
/** Sepolia. */
|
|
14
|
+
'0x90000069' = 11155111
|
|
13
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Permitted Chain Names
|
|
18
|
+
*/
|
|
19
|
+
export declare const FORK_NAMES: Record<number, string>;
|
|
14
20
|
/**
|
|
15
21
|
* Node operator data
|
|
16
22
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Provider } from 'ethers';
|
|
1
|
+
import { ethers, type Provider } from 'ethers';
|
|
2
2
|
import { DefinitionFlow } from './constants';
|
|
3
3
|
import { type ClusterDefinition } from './types';
|
|
4
4
|
export declare const hexWithout0x: (hex: string) => string;
|
|
@@ -6,3 +6,4 @@ export declare const strToUint8Array: (str: string) => Uint8Array;
|
|
|
6
6
|
export declare const definitionFlow: (clusterDefinition: ClusterDefinition) => DefinitionFlow | null;
|
|
7
7
|
export declare const findDeployedBytecode: (contractAddress: string, provider: Provider) => Promise<string>;
|
|
8
8
|
export declare const isContractAvailable: (contractAddress: string, provider: Provider, bytecode?: string) => Promise<boolean>;
|
|
9
|
+
export declare const getProvider: (chainId: number) => ethers.Provider;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type TypedMessage } from '@metamask/eth-sig-util';
|
|
2
|
+
import { type EIP712TypedData } from '@safe-global/safe-core-sdk-types';
|
|
3
|
+
export declare const validateAddressSignature: ({ address, token, data, chainId, }: {
|
|
4
|
+
address: string;
|
|
5
|
+
token: string;
|
|
6
|
+
data: TypedMessage<any>;
|
|
7
|
+
chainId: number;
|
|
8
|
+
}) => Promise<boolean>;
|
|
9
|
+
export declare const validateEOASignature: ({ token, data, address, }: {
|
|
10
|
+
token: string;
|
|
11
|
+
data: TypedMessage<any>;
|
|
12
|
+
address: string;
|
|
13
|
+
}) => boolean;
|
|
14
|
+
export declare const validateSmartContractSignature: ({ token, data, address, chainId, }: {
|
|
15
|
+
token: string;
|
|
16
|
+
data: EIP712TypedData;
|
|
17
|
+
address: string;
|
|
18
|
+
chainId: number;
|
|
19
|
+
}) => Promise<boolean>;
|
|
@@ -212,3 +212,54 @@ export declare const nullDepositAmountsClusterLockV1X8: {
|
|
|
212
212
|
lock_hash: string;
|
|
213
213
|
node_signatures: string[];
|
|
214
214
|
};
|
|
215
|
+
export declare const clusterLockWithSafe: {
|
|
216
|
+
cluster_definition: {
|
|
217
|
+
name: string;
|
|
218
|
+
creator: {
|
|
219
|
+
address: string;
|
|
220
|
+
config_signature: string;
|
|
221
|
+
};
|
|
222
|
+
operators: {
|
|
223
|
+
address: string;
|
|
224
|
+
enr: string;
|
|
225
|
+
config_signature: string;
|
|
226
|
+
enr_signature: string;
|
|
227
|
+
}[];
|
|
228
|
+
uuid: string;
|
|
229
|
+
version: string;
|
|
230
|
+
timestamp: string;
|
|
231
|
+
num_validators: number;
|
|
232
|
+
threshold: number;
|
|
233
|
+
validators: {
|
|
234
|
+
fee_recipient_address: string;
|
|
235
|
+
withdrawal_address: string;
|
|
236
|
+
}[];
|
|
237
|
+
dkg_algorithm: string;
|
|
238
|
+
fork_version: string;
|
|
239
|
+
deposit_amounts: string[];
|
|
240
|
+
config_hash: string;
|
|
241
|
+
definition_hash: string;
|
|
242
|
+
};
|
|
243
|
+
distributed_validators: {
|
|
244
|
+
distributed_public_key: string;
|
|
245
|
+
public_shares: string[];
|
|
246
|
+
builder_registration: {
|
|
247
|
+
message: {
|
|
248
|
+
fee_recipient: string;
|
|
249
|
+
gas_limit: number;
|
|
250
|
+
timestamp: number;
|
|
251
|
+
pubkey: string;
|
|
252
|
+
};
|
|
253
|
+
signature: string;
|
|
254
|
+
};
|
|
255
|
+
partial_deposit_data: {
|
|
256
|
+
pubkey: string;
|
|
257
|
+
withdrawal_credentials: string;
|
|
258
|
+
amount: string;
|
|
259
|
+
signature: string;
|
|
260
|
+
}[];
|
|
261
|
+
}[];
|
|
262
|
+
signature_aggregate: string;
|
|
263
|
+
lock_hash: string;
|
|
264
|
+
node_signatures: string[];
|
|
265
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obolnetwork/obol-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "A package for creating Distributed Validators using the Obol API.",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/obolnetwork/obol-sdk/issues"
|
|
@@ -44,9 +44,12 @@
|
|
|
44
44
|
"@chainsafe/discv5": "^0.5.1",
|
|
45
45
|
"@chainsafe/ssz": "^0.14.0",
|
|
46
46
|
"@metamask/eth-sig-util": "^7.0.1",
|
|
47
|
+
"@safe-global/protocol-kit": "4.1.1",
|
|
48
|
+
"@safe-global/safe-core-sdk-types": "5.1.0",
|
|
47
49
|
"@types/pdf-parse": "^1.1.4",
|
|
48
50
|
"ajv": "^8.12.0",
|
|
49
51
|
"cross-fetch": "^3.1.5",
|
|
52
|
+
"dotenv": "^16.4.7",
|
|
50
53
|
"elliptic": "^6.5.4",
|
|
51
54
|
"eslint-config-standard-with-typescript": "^43.0.1",
|
|
52
55
|
"eslint-plugin-import": "^2.29.1",
|
package/src/constants.ts
CHANGED
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
MAINNET_SPLITMAIN_BYTECODE,
|
|
12
12
|
} from './bytecodes';
|
|
13
13
|
|
|
14
|
+
import * as dotenv from 'dotenv';
|
|
15
|
+
dotenv.config();
|
|
16
|
+
|
|
14
17
|
export const CONFLICT_ERROR_MSG = 'Conflict';
|
|
15
18
|
|
|
16
19
|
export const EIP712_DOMAIN_NAME = 'Obol';
|
|
@@ -195,3 +198,10 @@ export const DEFAULT_RETROACTIVE_FUNDING_REWARDS_ONLY_SPLIT = 1;
|
|
|
195
198
|
export const DEFAULT_RETROACTIVE_FUNDING_TOTAL_SPLIT = 0.1;
|
|
196
199
|
|
|
197
200
|
export const OBOL_SDK_EMAIL = 'sdk@dvlabs.tech';
|
|
201
|
+
|
|
202
|
+
export const PROVIDER_MAP: Record<number, string> = {
|
|
203
|
+
1: `${process.env.RPC_MAINNET}`, // Mainnet
|
|
204
|
+
17000: `${process.env.RPC_HOLESKY}`, // Holesky
|
|
205
|
+
11155111: `${process.env.RPC_SEPOLIA}`, // Sepolia
|
|
206
|
+
100: `${process.env.RPC_GNOSIS}`, // Gnosis
|
|
207
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
import { isContractAvailable } from './utils.js';
|
|
50
50
|
export * from './types.js';
|
|
51
51
|
export * from './services.js';
|
|
52
|
+
export * from './verification/signature-validator.js';
|
|
52
53
|
|
|
53
54
|
/**
|
|
54
55
|
* Obol sdk Client can be used for creating, managing and activating distributed validators.
|
package/src/types.ts
CHANGED
|
@@ -13,8 +13,31 @@ export enum FORK_MAPPING {
|
|
|
13
13
|
|
|
14
14
|
/** Holesky. */
|
|
15
15
|
'0x01017000' = 17000,
|
|
16
|
+
|
|
17
|
+
/** Sepolia. */
|
|
18
|
+
'0x90000069' = 11155111,
|
|
16
19
|
}
|
|
17
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Permitted Chain Names
|
|
23
|
+
*/
|
|
24
|
+
export const FORK_NAMES: Record<number, string> = {
|
|
25
|
+
/** Mainnet. */
|
|
26
|
+
[FORK_MAPPING['0x00000000']]: 'mainnet',
|
|
27
|
+
|
|
28
|
+
/** Goerli/Prater. */
|
|
29
|
+
[FORK_MAPPING['0x00001020']]: 'goerli',
|
|
30
|
+
|
|
31
|
+
/** Gnosis Chain. */
|
|
32
|
+
[FORK_MAPPING['0x00000064']]: 'gnosis',
|
|
33
|
+
|
|
34
|
+
/** Holesky. */
|
|
35
|
+
[FORK_MAPPING['0x01017000']]: 'holesky',
|
|
36
|
+
|
|
37
|
+
/** Sepolia. */
|
|
38
|
+
[FORK_MAPPING['0x90000069']]: 'sepolia',
|
|
39
|
+
};
|
|
40
|
+
|
|
18
41
|
/**
|
|
19
42
|
* Node operator data
|
|
20
43
|
*/
|
package/src/utils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type Provider } from 'ethers';
|
|
2
|
-
import { DefinitionFlow } from './constants';
|
|
3
|
-
import { type ClusterDefinition } from './types';
|
|
1
|
+
import { ethers, type Provider } from 'ethers';
|
|
2
|
+
import { DefinitionFlow, PROVIDER_MAP } from './constants';
|
|
3
|
+
import { FORK_NAMES, type ClusterDefinition } from './types';
|
|
4
4
|
|
|
5
5
|
export const hexWithout0x = (hex: string): string => {
|
|
6
6
|
return hex.slice(2, hex.length);
|
|
@@ -75,3 +75,11 @@ export const isContractAvailable = async (
|
|
|
75
75
|
}
|
|
76
76
|
return !!code && code !== '0x' && code !== '0x0';
|
|
77
77
|
};
|
|
78
|
+
|
|
79
|
+
export const getProvider = (chainId: number): ethers.Provider => {
|
|
80
|
+
const rpcUrl = PROVIDER_MAP[chainId];
|
|
81
|
+
if (!rpcUrl) {
|
|
82
|
+
throw new Error(`No provider configured for ${FORK_NAMES[chainId]}`);
|
|
83
|
+
}
|
|
84
|
+
return new ethers.JsonRpcProvider(rpcUrl);
|
|
85
|
+
};
|
|
@@ -23,7 +23,6 @@ import {
|
|
|
23
23
|
hashClusterLockV1X7,
|
|
24
24
|
verifyDVV1X7,
|
|
25
25
|
} from './v1.7.0.js';
|
|
26
|
-
import { ethers } from 'ethers';
|
|
27
26
|
import {
|
|
28
27
|
DOMAIN_APPLICATION_BUILDER,
|
|
29
28
|
DOMAIN_DEPOSIT,
|
|
@@ -33,7 +32,6 @@ import {
|
|
|
33
32
|
signEnrPayload,
|
|
34
33
|
signOperatorConfigHashPayload,
|
|
35
34
|
} from '../constants.js';
|
|
36
|
-
import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util';
|
|
37
35
|
import {
|
|
38
36
|
builderRegistrationMessageType,
|
|
39
37
|
depositMessageType,
|
|
@@ -48,6 +46,7 @@ import {
|
|
|
48
46
|
hashClusterLockV1X8,
|
|
49
47
|
verifyDVV1X8,
|
|
50
48
|
} from './v1.8.0.js';
|
|
49
|
+
import { validateAddressSignature } from './signature-validator.js';
|
|
51
50
|
|
|
52
51
|
// cluster-definition hash
|
|
53
52
|
|
|
@@ -132,82 +131,94 @@ export const clusterLockHash = (clusterLock: ClusterLock): string => {
|
|
|
132
131
|
|
|
133
132
|
// cluster-definition signatures verification
|
|
134
133
|
|
|
135
|
-
const
|
|
134
|
+
const validatePOSTConfigHashSigner = async (
|
|
135
|
+
address: string,
|
|
136
136
|
signature: string,
|
|
137
137
|
configHash: string,
|
|
138
138
|
chainId: FORK_MAPPING,
|
|
139
|
-
):
|
|
139
|
+
): Promise<boolean> => {
|
|
140
140
|
try {
|
|
141
|
-
const sig = ethers.Signature.from(signature);
|
|
142
141
|
const data = signCreatorConfigHashPayload(
|
|
143
142
|
{ creator_config_hash: configHash },
|
|
144
143
|
chainId,
|
|
145
144
|
);
|
|
146
|
-
const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
|
|
147
145
|
|
|
148
|
-
return
|
|
146
|
+
return await validateAddressSignature({
|
|
147
|
+
address,
|
|
148
|
+
token: signature,
|
|
149
|
+
data,
|
|
150
|
+
chainId,
|
|
151
|
+
});
|
|
149
152
|
} catch (err) {
|
|
150
153
|
throw err;
|
|
151
154
|
}
|
|
152
155
|
};
|
|
153
156
|
|
|
154
|
-
const
|
|
157
|
+
const validatePUTConfigHashSigner = async (
|
|
158
|
+
address: string,
|
|
155
159
|
signature: string,
|
|
156
160
|
configHash: string,
|
|
157
161
|
chainId: number,
|
|
158
|
-
):
|
|
162
|
+
): Promise<boolean> => {
|
|
159
163
|
try {
|
|
160
|
-
const sig = ethers.Signature.from(signature);
|
|
161
164
|
const data = signOperatorConfigHashPayload(
|
|
162
165
|
{ operator_config_hash: configHash },
|
|
163
166
|
chainId,
|
|
164
167
|
);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
return await validateAddressSignature({
|
|
169
|
+
address,
|
|
170
|
+
token: signature,
|
|
171
|
+
data,
|
|
172
|
+
chainId,
|
|
173
|
+
});
|
|
168
174
|
} catch (err) {
|
|
169
175
|
throw err;
|
|
170
176
|
}
|
|
171
177
|
};
|
|
172
178
|
|
|
173
|
-
const
|
|
179
|
+
const validateEnrSigner = async (
|
|
180
|
+
address: string,
|
|
174
181
|
signature: string,
|
|
175
182
|
payload: string,
|
|
176
183
|
chainId: number,
|
|
177
|
-
):
|
|
184
|
+
): Promise<boolean> => {
|
|
178
185
|
try {
|
|
179
|
-
const sig = ethers.Signature.from(signature);
|
|
180
|
-
|
|
181
186
|
const data = signEnrPayload({ enr: payload }, chainId);
|
|
182
|
-
const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
|
|
183
187
|
|
|
184
|
-
return
|
|
188
|
+
return await validateAddressSignature({
|
|
189
|
+
address,
|
|
190
|
+
token: signature,
|
|
191
|
+
data,
|
|
192
|
+
chainId,
|
|
193
|
+
});
|
|
185
194
|
} catch (err) {
|
|
186
195
|
throw err;
|
|
187
196
|
}
|
|
188
197
|
};
|
|
189
198
|
|
|
190
|
-
const verifyDefinitionSignatures = (
|
|
199
|
+
const verifyDefinitionSignatures = async (
|
|
191
200
|
clusterDefinition: ClusterDefinition,
|
|
192
201
|
definitionType: DefinitionFlow,
|
|
193
|
-
): boolean => {
|
|
202
|
+
): Promise<boolean> => {
|
|
194
203
|
if (definitionType === DefinitionFlow.Charon) {
|
|
195
204
|
return true;
|
|
196
205
|
} else {
|
|
197
|
-
const
|
|
206
|
+
const isPOSTConfigHashSignerValid = await validatePOSTConfigHashSigner(
|
|
207
|
+
clusterDefinition.creator.address,
|
|
198
208
|
clusterDefinition.creator.config_signature as string,
|
|
199
209
|
clusterDefinition.config_hash,
|
|
200
210
|
FORK_MAPPING[clusterDefinition.fork_version as keyof typeof FORK_MAPPING],
|
|
201
211
|
);
|
|
202
212
|
|
|
203
|
-
if (
|
|
213
|
+
if (!isPOSTConfigHashSignerValid) {
|
|
204
214
|
return false;
|
|
205
215
|
}
|
|
206
216
|
if (definitionType === DefinitionFlow.Solo) {
|
|
207
217
|
return true;
|
|
208
218
|
}
|
|
209
|
-
return clusterDefinition.operators.every(operator => {
|
|
210
|
-
const
|
|
219
|
+
return clusterDefinition.operators.every(async operator => {
|
|
220
|
+
const isPUTConfigHashSignerValid = await validatePUTConfigHashSigner(
|
|
221
|
+
operator.address,
|
|
211
222
|
operator.config_signature as string,
|
|
212
223
|
clusterDefinition.config_hash,
|
|
213
224
|
FORK_MAPPING[
|
|
@@ -215,7 +226,8 @@ const verifyDefinitionSignatures = (
|
|
|
215
226
|
],
|
|
216
227
|
);
|
|
217
228
|
|
|
218
|
-
const
|
|
229
|
+
const isENRSignerValid = await validateEnrSigner(
|
|
230
|
+
operator.address,
|
|
219
231
|
operator.enr_signature as string,
|
|
220
232
|
operator.enr as string,
|
|
221
233
|
FORK_MAPPING[
|
|
@@ -223,10 +235,7 @@ const verifyDefinitionSignatures = (
|
|
|
223
235
|
],
|
|
224
236
|
);
|
|
225
237
|
|
|
226
|
-
if (
|
|
227
|
-
configSigner !== operator.address.toLowerCase() ||
|
|
228
|
-
enrSigner !== operator.address.toLowerCase()
|
|
229
|
-
) {
|
|
238
|
+
if (!isPUTConfigHashSignerValid || !isENRSignerValid) {
|
|
230
239
|
return false;
|
|
231
240
|
}
|
|
232
241
|
return true;
|
|
@@ -444,7 +453,7 @@ export const isValidClusterLock = async (
|
|
|
444
453
|
if (definitionType == null) {
|
|
445
454
|
return false;
|
|
446
455
|
}
|
|
447
|
-
const isValidDefinitionData = verifyDefinitionSignatures(
|
|
456
|
+
const isValidDefinitionData = await verifyDefinitionSignatures(
|
|
448
457
|
clusterLock.cluster_definition,
|
|
449
458
|
definitionType,
|
|
450
459
|
);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
2
|
+
import { ethers } from 'ethers';
|
|
3
|
+
import {
|
|
4
|
+
SignTypedDataVersion,
|
|
5
|
+
TypedDataUtils,
|
|
6
|
+
type TypedMessage,
|
|
7
|
+
} from '@metamask/eth-sig-util';
|
|
8
|
+
import Safe from '@safe-global/protocol-kit';
|
|
9
|
+
import { PROVIDER_MAP } from '../constants';
|
|
10
|
+
import { hashTypedData } from '@safe-global/protocol-kit/dist/src/utils';
|
|
11
|
+
import { type EIP712TypedData } from '@safe-global/safe-core-sdk-types';
|
|
12
|
+
import { isContractAvailable, getProvider } from '../utils';
|
|
13
|
+
|
|
14
|
+
export const validateAddressSignature = async ({
|
|
15
|
+
address,
|
|
16
|
+
token,
|
|
17
|
+
data,
|
|
18
|
+
chainId,
|
|
19
|
+
}: {
|
|
20
|
+
address: string;
|
|
21
|
+
token: string;
|
|
22
|
+
data: TypedMessage<any>;
|
|
23
|
+
chainId: number;
|
|
24
|
+
}): Promise<boolean> => {
|
|
25
|
+
try {
|
|
26
|
+
const provider = getProvider(chainId);
|
|
27
|
+
if (provider) {
|
|
28
|
+
const contractAddress = await isContractAvailable(address, provider);
|
|
29
|
+
if (contractAddress) {
|
|
30
|
+
return await validateSmartContractSignature({
|
|
31
|
+
token,
|
|
32
|
+
data: data as unknown as EIP712TypedData,
|
|
33
|
+
address,
|
|
34
|
+
chainId,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return validateEOASignature({ token, data, address });
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return validateEOASignature({ token, data, address });
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const validateEOASignature = ({
|
|
45
|
+
token,
|
|
46
|
+
data,
|
|
47
|
+
address,
|
|
48
|
+
}: {
|
|
49
|
+
token: string;
|
|
50
|
+
data: TypedMessage<any>;
|
|
51
|
+
address: string;
|
|
52
|
+
}): boolean => {
|
|
53
|
+
try {
|
|
54
|
+
const sig = ethers.Signature.from(token);
|
|
55
|
+
const digest = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
ethers.recoverAddress(digest, sig).toLowerCase() ===
|
|
59
|
+
address.toLocaleLowerCase()
|
|
60
|
+
);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error(`validate EOA Signature error: ${err}`);
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const validateSmartContractSignature = async ({
|
|
68
|
+
token,
|
|
69
|
+
data,
|
|
70
|
+
address,
|
|
71
|
+
chainId,
|
|
72
|
+
}: {
|
|
73
|
+
token: string;
|
|
74
|
+
data: EIP712TypedData;
|
|
75
|
+
address: string;
|
|
76
|
+
chainId: number;
|
|
77
|
+
}): Promise<boolean> => {
|
|
78
|
+
try {
|
|
79
|
+
const provider = PROVIDER_MAP[chainId];
|
|
80
|
+
const protocolKit = await Safe.init({
|
|
81
|
+
provider,
|
|
82
|
+
safeAddress: address,
|
|
83
|
+
});
|
|
84
|
+
const messageHash = hashTypedData(data);
|
|
85
|
+
const isValidSignature = await protocolKit.isValidSignature(
|
|
86
|
+
messageHash,
|
|
87
|
+
token,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return isValidSignature;
|
|
91
|
+
} catch (err: any) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Error validating smart contract signature: ${err.message}`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
};
|