@cofhe/sdk 0.8.0 → 1.0.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.
Files changed (87) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/acps/acp.ts +25 -6
  3. package/acps/index.ts +20 -15
  4. package/acps/onchain-utils.ts +15 -10
  5. package/acps/registry.ts +166 -8
  6. package/acps/sealing.ts +31 -5
  7. package/acps/share-metadata/appKey.ts +8 -0
  8. package/acps/share-metadata/decode.ts +120 -0
  9. package/acps/share-metadata/describe.ts +198 -0
  10. package/acps/share-metadata/encode.ts +147 -0
  11. package/acps/share-metadata/erc7984.ts +26 -0
  12. package/acps/share-metadata/index.ts +9 -0
  13. package/acps/share-metadata/layout.ts +69 -0
  14. package/acps/share-metadata/presets.ts +116 -0
  15. package/acps/share-metadata/schema.ts +47 -0
  16. package/acps/share-metadata/test/codec.test.ts +140 -0
  17. package/acps/share-metadata/test/describe.test.ts +112 -0
  18. package/acps/share-metadata/test/fixtures.ts +157 -0
  19. package/acps/share-metadata/test/live.test.ts +56 -0
  20. package/acps/share-metadata/test/presets.test.ts +119 -0
  21. package/acps/share-metadata/test/verify.test.ts +144 -0
  22. package/acps/share-metadata/verify.ts +155 -0
  23. package/acps/store.ts +48 -18
  24. package/acps/test/aclServedAddresses.test.ts +65 -0
  25. package/acps/test/acp.test.ts +102 -7
  26. package/acps/test/localstorage.test.ts +28 -1
  27. package/acps/test/registry.test.ts +144 -0
  28. package/acps/test/sealing.test.ts +74 -0
  29. package/acps/test/store.test.ts +71 -0
  30. package/acps/test/utils.test.ts +63 -0
  31. package/acps/test/validation.test.ts +0 -4
  32. package/acps/types.ts +43 -6
  33. package/acps/utils.ts +15 -2
  34. package/acps/validation.ts +3 -1
  35. package/core/acps.ts +123 -74
  36. package/core/client.ts +33 -7
  37. package/core/clientTypes.ts +25 -7
  38. package/core/config.ts +8 -0
  39. package/core/decrypt/apiError.ts +4 -1
  40. package/core/decrypt/verifyDecryptResult.ts +4 -1
  41. package/core/encrypt/cofheMocksZkVerifySign.ts +19 -2
  42. package/core/test/acpDefaults.test.ts +108 -19
  43. package/core/test/acpStoreAccountCase.test.ts +80 -0
  44. package/core/test/acps.test.ts +38 -3
  45. package/core/test/apiError.test.ts +8 -0
  46. package/core/test/appKeyConfig.test.ts +94 -0
  47. package/core/test/config.test.ts +8 -0
  48. package/core/test/decryptBuilders.test.ts +110 -2
  49. package/core/test/incomingShares.test.ts +84 -0
  50. package/core/test/mocksEncryptRange.test.ts +90 -0
  51. package/core/test/utils.test.ts +128 -0
  52. package/core/test/verifyDecryptResult.test.ts +51 -0
  53. package/core/utils.ts +16 -9
  54. package/dist/acps.cjs +796 -132
  55. package/dist/acps.d.cts +367 -35
  56. package/dist/acps.d.ts +367 -35
  57. package/dist/acps.js +2 -2
  58. package/dist/{chunk-UJYOBHYA.js → chunk-3L2IVPMI.js} +688 -203
  59. package/dist/chunk-J23FYAI2.js +174 -0
  60. package/dist/{chunk-GGI7Y6AW.js → chunk-O2ITEUEU.js} +110 -269
  61. package/dist/chunk-WJGTPW7K.js +228 -0
  62. package/dist/{clientTypes-D3aIXmUp.d.cts → clientTypes-IO6b5Um8.d.cts} +61 -21
  63. package/dist/{clientTypes-5YPPR16m.d.ts → clientTypes-b1pOxOUE.d.ts} +61 -21
  64. package/dist/core.cjs +3115 -2584
  65. package/dist/core.d.cts +6 -3
  66. package/dist/core.d.ts +6 -3
  67. package/dist/core.js +4 -3
  68. package/dist/node.cjs +3001 -2470
  69. package/dist/node.d.cts +2 -2
  70. package/dist/node.d.ts +2 -2
  71. package/dist/node.js +18 -17
  72. package/dist/{onchain-utils-CUAK8glx.d.cts → onchain-utils-CgOslubH.d.cts} +139 -15
  73. package/dist/{onchain-utils-CUAK8glx.d.ts → onchain-utils-CgOslubH.d.ts} +139 -15
  74. package/dist/web.cjs +3011 -2487
  75. package/dist/web.d.cts +2 -2
  76. package/dist/web.d.ts +2 -2
  77. package/dist/web.js +4 -10
  78. package/dist/zkProve.worker.cjs +14 -7
  79. package/dist/zkProve.worker.js +2 -8
  80. package/node/index.ts +1 -10
  81. package/node/storage.ts +18 -7
  82. package/node/test/storage-permissions.test.ts +40 -0
  83. package/package.json +4 -4
  84. package/web/index.ts +1 -10
  85. package/web/test/threadPoolBenchHelper.ts +1 -7
  86. package/web/zkProve.worker.ts +1 -10
  87. package/dist/chunk-NMKMCAI3.js +0 -14
@@ -0,0 +1,84 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import type { PublicClient } from 'viem';
3
+ import { acps } from '../acps';
4
+ import { UNLABELLED, encodeShareMetadata, shareAppKey } from '../../acps/share-metadata';
5
+ import { shareIdOfChainShare, type ChainShare, type ShareHeader } from '../../acps/registry';
6
+
7
+ const REGISTRY = '0x00000000000000000000000000000000000000e0';
8
+ const ISSUER = '0x00000000000000000000000000000000000000b0';
9
+ const RECIPIENT = '0x00000000000000000000000000000000000000a1';
10
+ const ZERO = '0x0000000000000000000000000000000000000000';
11
+
12
+ const chainShare = (handle: number): ChainShare => ({
13
+ issuer: ISSUER,
14
+ expiration: 2_000_000_000n,
15
+ recipient: RECIPIENT,
16
+ revokerData: 0n,
17
+ revokerContract: ZERO,
18
+ scope: 2,
19
+ contracts: [],
20
+ handles: [`0x${handle.toString(16).padStart(64, '0')}`],
21
+ sealingKey: `0x${'0'.repeat(64)}`,
22
+ issuerSignature: '0x11',
23
+ recipientSignature: '0x',
24
+ });
25
+
26
+ const header: ShareHeader = {
27
+ issuer: ISSUER,
28
+ expiration: 2_000_000_000n,
29
+ recipient: RECIPIENT,
30
+ blockNumber: 10n,
31
+ revokerContract: ZERO,
32
+ revokerData: 0n,
33
+ };
34
+
35
+ const EXPLORER = shareAppKey('explorer.fhenix.io');
36
+ const WALLET = shareAppKey('wallet.example');
37
+
38
+ /** Three shares in one block: one for the explorer, one for a wallet app, one for no app. */
39
+ const shares = [chainShare(1), chainShare(2), chainShare(3)];
40
+ const ids = shares.map(shareIdOfChainShare);
41
+ const metadata = [
42
+ encodeShareMetadata([UNLABELLED], { appKey: EXPLORER }),
43
+ encodeShareMetadata([UNLABELLED], { appKey: WALLET }),
44
+ '0x',
45
+ ] as const;
46
+
47
+ const client = {
48
+ readContract: vi.fn(async () => [ids, shares.map(() => header)]),
49
+ getContractEvents: vi.fn(async ({ args }: { args: { shareId: string[] } }) =>
50
+ shares
51
+ .map((acp, i) => ({ args: { shareId: ids[i], acp, metadata: metadata[i] } }))
52
+ .filter((e) => args.shareId.includes(e.args.shareId))
53
+ ),
54
+ } as unknown as PublicClient;
55
+
56
+ describe('getIncomingShares and the app key', () => {
57
+ it('lists every share without an app, each with the key it carries', async () => {
58
+ const incoming = await acps.getIncomingShares(client, REGISTRY, RECIPIENT);
59
+ expect(incoming.map((s) => s.shareId)).toEqual(ids);
60
+ expect(incoming.map((s) => s.appKey)).toEqual([EXPLORER, WALLET, undefined]);
61
+ });
62
+
63
+ it('lists only the shares posted for the app', async () => {
64
+ const incoming = await acps.getIncomingShares(client, REGISTRY, RECIPIENT, { appKey: EXPLORER });
65
+ expect(incoming.map((s) => s.shareId)).toEqual([ids[0]]);
66
+ });
67
+
68
+ it('leaves out a share whose event the node cannot serve yet, and keeps the rest', async () => {
69
+ const lagging = {
70
+ readContract: vi.fn(async () => [ids, shares.map(() => header)]),
71
+ getContractEvents: vi.fn(async ({ args }: { args: { shareId: string[] } }) =>
72
+ shares
73
+ .map((acp, i) => ({ args: { shareId: ids[i], acp, metadata: metadata[i] } }))
74
+ .filter((e) => args.shareId.includes(e.args.shareId) && e.args.shareId !== ids[1])
75
+ ),
76
+ } as unknown as PublicClient;
77
+ const incoming = await acps.getIncomingShares(lagging, REGISTRY, RECIPIENT);
78
+ expect(incoming.map((s) => s.shareId)).toEqual([ids[0], ids[2]]);
79
+ });
80
+
81
+ it('lists nothing for an app no share names', async () => {
82
+ expect(await acps.getIncomingShares(client, REGISTRY, RECIPIENT, { appKey: shareAppKey('other') })).toEqual([]);
83
+ });
84
+ });
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { type PublicClient, type WalletClient } from 'viem';
3
+ import { hardhat } from 'viem/chains';
4
+ import { EncryptInputsBuilder } from '../encrypt/encryptInputsBuilder.js';
5
+ import { Encryptable, FheTypes, type EncryptableItem } from '../types.js';
6
+ import { createCofheConfigBase } from '../config.js';
7
+ import { zkPack, type ZkCiphertextListBuilder } from '../encrypt/zkPackProveVerify.js';
8
+
9
+ // The hardhat (mocks) encrypt path must reject the same out-of-range plaintexts that the
10
+ // production path rejects in zkPack. Otherwise MockCoFHE._set masks the value with the utype
11
+ // mask (e.g. uint8(256) -> 0) and tests pass on hardhat while the same call throws on a real chain.
12
+
13
+ const account = '0x1234567890123456789012345678901234567890';
14
+ const consumingContract = '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef';
15
+
16
+ const fakeClients = () => {
17
+ const readContract = vi.fn(async ({ args }: any) => (args[0] as bigint[]).map((_, i) => BigInt(i + 1)));
18
+ const writeContract = vi.fn(async () => '0x' as `0x${string}`);
19
+ const publicClient = { readContract } as unknown as PublicClient;
20
+ const walletClient = { account: { address: account }, writeContract } as unknown as WalletClient;
21
+ return { publicClient, walletClient, readContract, writeContract };
22
+ };
23
+
24
+ const mocksBuilder = (inputs: EncryptableItem[]) => {
25
+ const { publicClient, walletClient, readContract, writeContract } = fakeClients();
26
+ const builder = new EncryptInputsBuilder({
27
+ inputs: inputs as any,
28
+ account,
29
+ chainId: hardhat.id,
30
+ config: createCofheConfigBase({ supportedChains: [], mocks: { encryptDelay: 0 } }),
31
+ publicClient,
32
+ walletClient,
33
+ zkvWalletClient: walletClient,
34
+ tfhePublicKeyDeserializer: vi.fn(),
35
+ compactPkeCrsDeserializer: vi.fn(),
36
+ zkBuilderAndCrsGenerator: vi.fn() as any,
37
+ initTfhe: undefined,
38
+ zkProveWorkerFn: undefined,
39
+ keysStorage: undefined,
40
+ requireConnected: vi.fn(),
41
+ });
42
+ builder.setConsumingContract(consumingContract);
43
+ return { builder, readContract, writeContract };
44
+ };
45
+
46
+ const noop = () => undefined;
47
+ const noopZkBuilder = {
48
+ push_boolean: noop,
49
+ push_u8: noop,
50
+ push_u16: noop,
51
+ push_u32: noop,
52
+ push_u64: noop,
53
+ push_u128: noop,
54
+ push_u160: noop,
55
+ build_with_proof_packed: noop,
56
+ } as unknown as ZkCiphertextListBuilder;
57
+
58
+ const outOfRange: [string, EncryptableItem][] = [
59
+ ['uint8(256)', Encryptable.uint8(256n)],
60
+ ['uint16(65536)', Encryptable.uint16(65536n)],
61
+ ['uint32(2^32)', Encryptable.uint32(2n ** 32n)],
62
+ ['uint64(2^64)', Encryptable.uint64(2n ** 64n)],
63
+ ['uint128(2^128)', Encryptable.uint128(2n ** 128n)],
64
+ ['address(2^160)', { data: 2n ** 160n, securityZone: 0, utype: FheTypes.Uint160 } as EncryptableItem],
65
+ ];
66
+
67
+ describe('mocks encrypt path value range parity', () => {
68
+ it.each(outOfRange)('production zkPack rejects %s', (_, item) => {
69
+ expect(() => zkPack([item], noopZkBuilder)).toThrow(/out of range/);
70
+ });
71
+
72
+ it.each(outOfRange)('hardhat mocks encrypt rejects %s before touching the chain', async (_, item) => {
73
+ const { builder, readContract, writeContract } = mocksBuilder([item]);
74
+ await expect(builder.execute()).rejects.toThrow(/out of range/);
75
+ expect(readContract).not.toHaveBeenCalled();
76
+ expect(writeContract).not.toHaveBeenCalled();
77
+ });
78
+
79
+ it('hardhat mocks encrypt still accepts the max value of each type', async () => {
80
+ const { builder, writeContract } = mocksBuilder([
81
+ Encryptable.uint8(255n),
82
+ Encryptable.uint16(65535n),
83
+ Encryptable.uint32(2n ** 32n - 1n),
84
+ Encryptable.uint64(2n ** 64n - 1n),
85
+ Encryptable.bool(true),
86
+ ]);
87
+ await expect(builder.execute()).resolves.toBeDefined();
88
+ expect(writeContract).toHaveBeenCalledTimes(1);
89
+ });
90
+ });
@@ -0,0 +1,128 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ fromHexString,
4
+ toHexString,
5
+ hexToBytes,
6
+ toBigIntOrThrow,
7
+ validateBigIntInRange,
8
+ fheTypeToString,
9
+ } from '../utils.js';
10
+ import { FheTypes } from '../types.js';
11
+
12
+ describe('fromHexString', () => {
13
+ it('strips the 0x prefix before padding an odd-length string', () => {
14
+ // Regression: padding first produced `00x123`, which decoded to [0x00, NaN->0, 0x23].
15
+ expect(Array.from(fromHexString('0x123'))).toEqual([0x01, 0x23]);
16
+ });
17
+
18
+ it('pads odd-length strings without a prefix', () => {
19
+ expect(Array.from(fromHexString('123'))).toEqual([0x01, 0x23]);
20
+ });
21
+
22
+ it('decodes even-length strings with and without the 0x prefix identically', () => {
23
+ expect(Array.from(fromHexString('0x1234'))).toEqual([0x12, 0x34]);
24
+ expect(Array.from(fromHexString('1234'))).toEqual([0x12, 0x34]);
25
+ });
26
+
27
+ it('decodes uppercase hex', () => {
28
+ expect(Array.from(fromHexString('0xDEADBEEF'))).toEqual([0xde, 0xad, 0xbe, 0xef]);
29
+ });
30
+
31
+ it('returns an empty array for empty input', () => {
32
+ expect(fromHexString('')).toEqual(new Uint8Array());
33
+ expect(fromHexString('0x')).toEqual(new Uint8Array());
34
+ });
35
+
36
+ it('round-trips through toHexString', () => {
37
+ const hex = 'a3'.repeat(32);
38
+ const bytes = fromHexString(hex);
39
+
40
+ expect(bytes.length).toBe(32);
41
+ expect(toHexString(bytes)).toBe(hex);
42
+ });
43
+
44
+ it('throws instead of silently producing zero bytes for non-hex characters', () => {
45
+ expect(() => fromHexString('zzzz')).toThrow('Invalid hex string');
46
+ });
47
+
48
+ it('throws for hex strings with a single invalid character', () => {
49
+ expect(() => fromHexString('0x12g4')).toThrow('Invalid hex string');
50
+ });
51
+
52
+ it('only strips a lowercase 0x prefix', () => {
53
+ expect(() => fromHexString('0X12')).toThrow('Invalid hex string');
54
+ });
55
+
56
+ it('does not leak sensitive input in the error message', () => {
57
+ const secret = 'f'.repeat(63) + 'z';
58
+
59
+ expect(() => fromHexString(secret)).toThrow(
60
+ expect.objectContaining({ message: expect.not.stringContaining(secret) })
61
+ );
62
+ });
63
+ });
64
+
65
+ describe('hexToBytes', () => {
66
+ it('decodes even-length hex with and without prefix', () => {
67
+ expect(Array.from(hexToBytes('0x1234'))).toEqual([0x12, 0x34]);
68
+ expect(Array.from(hexToBytes('1234'))).toEqual([0x12, 0x34]);
69
+ });
70
+
71
+ it('handles odd-length hex correctly with prefix', () => {
72
+ expect(Array.from(hexToBytes('0x123'))).toEqual([0x01, 0x23]);
73
+ expect(Array.from(hexToBytes('123'))).toEqual([0x01, 0x23]);
74
+ });
75
+
76
+ it('throws for invalid hex characters', () => {
77
+ expect(() => hexToBytes('0x12z4')).toThrow('Invalid hex string');
78
+ });
79
+ });
80
+
81
+ describe('toHexString', () => {
82
+ it('pads each byte to 2 hex characters', () => {
83
+ const bytes = new Uint8Array([0x00, 0x0f, 0x10, 0xff]);
84
+ expect(toHexString(bytes)).toBe('000f10ff');
85
+ });
86
+ });
87
+
88
+ describe('toBigIntOrThrow', () => {
89
+ it('returns bigint unchanged if passed bigint', () => {
90
+ expect(toBigIntOrThrow(123n)).toBe(123n);
91
+ });
92
+
93
+ it('converts valid string to bigint', () => {
94
+ expect(toBigIntOrThrow('456')).toBe(456n);
95
+ expect(toBigIntOrThrow('0x10')).toBe(16n);
96
+ });
97
+
98
+ it('throws for invalid string', () => {
99
+ expect(() => toBigIntOrThrow('not-a-number')).toThrow('Invalid input: Unable to convert to bigint');
100
+ });
101
+ });
102
+
103
+ describe('validateBigIntInRange', () => {
104
+ it('accepts values within range', () => {
105
+ expect(() => validateBigIntInRange(10n, 20n, 0n)).not.toThrow();
106
+ });
107
+
108
+ it('throws when value exceeds max', () => {
109
+ expect(() => validateBigIntInRange(25n, 20n, 0n)).toThrow('Value out of range');
110
+ });
111
+
112
+ it('throws when value is below min', () => {
113
+ expect(() => validateBigIntInRange(5n, 20n, 10n)).toThrow('Value out of range');
114
+ });
115
+ });
116
+
117
+ describe('fheTypeToString', () => {
118
+ it('maps known types to string identifiers', () => {
119
+ expect(fheTypeToString(FheTypes.Bool)).toBe('bool');
120
+ expect(fheTypeToString(FheTypes.Uint8)).toBe('uint8');
121
+ expect(fheTypeToString(FheTypes.Uint16)).toBe('uint16');
122
+ expect(fheTypeToString(FheTypes.Uint32)).toBe('uint32');
123
+ expect(fheTypeToString(FheTypes.Uint64)).toBe('uint64');
124
+ expect(fheTypeToString(FheTypes.Uint128)).toBe('uint128');
125
+ expect(fheTypeToString(FheTypes.Uint160)).toBe('uint160');
126
+ expect(fheTypeToString(FheTypes.Uint256)).toBe('uint256');
127
+ });
128
+ });
@@ -0,0 +1,51 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { encodePacked, keccak256, zeroAddress, type Address, type PublicClient } from 'viem';
3
+ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
4
+
5
+ import { verifyDecryptResult } from '../decrypt/verifyDecryptResult.js';
6
+ import { clearTaskManagerAddressCache } from '../taskManager.js';
7
+
8
+ const CHAIN_ID = 31337;
9
+ const TM = '0x0000000000000000000000000000000000005000';
10
+ // euint32 (utype 4) in the type byte
11
+ const CT_HASH = (0xabcdefn << 16n) | (4n << 8n);
12
+ const CLEARTEXT = 42n;
13
+
14
+ const signer = privateKeyToAccount(generatePrivateKey());
15
+
16
+ const signDecryptResult = (ctHash: bigint, cleartext: bigint) =>
17
+ signer.sign({
18
+ hash: keccak256(encodePacked(['uint256', 'uint32', 'uint64', 'uint256'], [cleartext, 4, BigInt(CHAIN_ID), ctHash])),
19
+ });
20
+
21
+ const fakeClient = (decryptResultSigner: Address) =>
22
+ ({
23
+ chain: { id: CHAIN_ID },
24
+ readContract: vi.fn(async ({ functionName }: { functionName: string }) =>
25
+ functionName === 'getTm' ? TM : decryptResultSigner
26
+ ),
27
+ }) as unknown as PublicClient;
28
+
29
+ describe('verifyDecryptResult', () => {
30
+ beforeEach(() => clearTaskManagerAddressCache());
31
+
32
+ it('accepts a result signed by the configured decryptResultSigner', async () => {
33
+ const signature = await signDecryptResult(CT_HASH, CLEARTEXT);
34
+
35
+ expect(await verifyDecryptResult(CT_HASH, CLEARTEXT, signature, fakeClient(signer.address))).to.equal(true);
36
+ });
37
+
38
+ it('rejects a result signed by a different key', async () => {
39
+ const signature = await signDecryptResult(CT_HASH, CLEARTEXT);
40
+ const other = privateKeyToAccount(generatePrivateKey());
41
+
42
+ expect(await verifyDecryptResult(CT_HASH, CLEARTEXT, signature, fakeClient(other.address))).to.equal(false);
43
+ });
44
+
45
+ it('fails closed when the decryptResultSigner is unset (zero address)', async () => {
46
+ const signature = await signDecryptResult(CT_HASH, CLEARTEXT);
47
+
48
+ expect(await verifyDecryptResult(CT_HASH, CLEARTEXT, signature, fakeClient(zeroAddress))).to.equal(false);
49
+ expect(await verifyDecryptResult(CT_HASH, CLEARTEXT, '0x', fakeClient(zeroAddress))).to.equal(false);
50
+ });
51
+ });
package/core/utils.ts CHANGED
@@ -5,9 +5,22 @@ import { FheTypes } from './types.js';
5
5
  export const toHexString = (bytes: Uint8Array) =>
6
6
  bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
7
7
 
8
+ const HEX_BODY_REGEX = /^[0-9a-fA-F]*$/;
9
+
8
10
  export const fromHexString = (hexString: string): Uint8Array => {
9
- const cleanString = hexString.length % 2 === 1 ? `0${hexString}` : hexString;
10
- const arr = cleanString.replace(/^0x/, '').match(/.{1,2}/g);
11
+ // Strip `0x` before padding: padding first turns `0x123` into `00x123`, which no
12
+ // longer matches the prefix and decodes to garbage bytes.
13
+ const body = hexString.replace(/^0x/, '');
14
+
15
+ // `parseInt` maps a non-hex pair to NaN, which `Uint8Array` stores as 0. Without this check a
16
+ // malformed hex string silently becomes zero bytes instead of failing.
17
+ if (!HEX_BODY_REGEX.test(body)) {
18
+ // The value is omitted on purpose: callers may pass private keys or sensitive payloads.
19
+ throw new Error('Invalid hex string: contains non-hexadecimal characters');
20
+ }
21
+
22
+ const padded = body.length % 2 === 1 ? `0${body}` : body;
23
+ const arr = padded.match(/.{1,2}/g);
11
24
  if (!arr) return new Uint8Array();
12
25
  return new Uint8Array(arr.map((byte) => parseInt(byte, 16)));
13
26
  };
@@ -35,13 +48,7 @@ export const validateBigIntInRange = (value: bigint, max: bigint, min: bigint =
35
48
  };
36
49
 
37
50
  // Helper function to convert hex string to bytes
38
- export const hexToBytes = (hex: string): Uint8Array => {
39
- const bytes = new Uint8Array(hex.length / 2);
40
- for (let i = 0; i < hex.length; i += 2) {
41
- bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
42
- }
43
- return bytes;
44
- };
51
+ export const hexToBytes = (hex: string): Uint8Array => fromHexString(hex);
45
52
 
46
53
  export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
47
54