@verbeth/sdk 0.1.10 → 0.1.11

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.
@@ -1,20 +1,12 @@
1
- export interface ChainConfig {
2
- verbethProxy: `0x${string}`;
3
- verbethImpl: `0x${string}`;
4
- creationBlock: number;
5
- moduleSetupHelper?: `0x${string}`;
6
- }
7
- export declare const VERBETH_CONFIG: ChainConfig;
8
- export declare const MODULE_SETUP_HELPERS: Record<number, `0x${string}`>;
1
+ export declare const VERBETH_ADDRESSES: {
2
+ readonly verbethProxy: `0x${string}`;
3
+ readonly verbethImpl: `0x${string}`;
4
+ };
5
+ export declare const MODULE_SETUP_HELPER: `0x${string}`;
6
+ export declare const SESSION_MODULE: `0x${string}`;
7
+ export declare const CREATION_BLOCKS: Record<number, number>;
9
8
  export declare function getVerbethAddress(): `0x${string}`;
10
- export declare function getCreationBlock(): number;
9
+ export declare function getCreationBlock(chainId: number): number;
11
10
  export declare function getModuleSetupHelper(chainId: number): `0x${string}` | undefined;
12
11
  export declare function isModuleSetupSupported(chainId: number): boolean;
13
- export declare const SCAN_DEFAULTS: {
14
- readonly INITIAL_SCAN_BLOCKS: 1000;
15
- readonly MAX_RETRIES: 3;
16
- readonly MAX_RANGE_PROVIDER: 2000;
17
- readonly CHUNK_SIZE: 2000;
18
- readonly REAL_TIME_BUFFER: 3;
19
- };
20
12
  //# sourceMappingURL=addresses.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"addresses.d.ts","sourceRoot":"","sources":["../../../src/addresses.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,KAAK,MAAM,EAAE,CAAC;IAC5B,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;CACnC;AAGD,eAAO,MAAM,cAAc,EAAE,WAInB,CAAC;AAGX,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,MAAM,EAAE,CAGrD,CAAC;AAGX,wBAAgB,iBAAiB,IAAI,KAAK,MAAM,EAAE,CAEjD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,MAAM,EAAE,GAAG,SAAS,CAE/E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE/D;AAGD,eAAO,MAAM,aAAa;;;;;;CAMhB,CAAC"}
1
+ {"version":3,"file":"addresses.d.ts","sourceRoot":"","sources":["../../../src/addresses.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB;2BACkC,KAAK,MAAM,EAAE;0BACd,KAAK,MAAM,EAAE;CAClE,CAAC;AACX,eAAO,MAAM,mBAAmB,EAAmD,KAAK,MAAM,EAAE,CAAC;AACjG,eAAO,MAAM,cAAc,EAAmD,KAAK,MAAM,EAAE,CAAC;AAG5F,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAIzC,CAAC;AAEX,wBAAgB,iBAAiB,IAAI,KAAK,MAAM,EAAE,CAEjD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,MAAM,EAAE,GAAG,SAAS,CAE/E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE/D"}
@@ -1,32 +1,29 @@
1
1
  // packages/sdk/src/addresses.ts
2
- // Deterministic deployment
3
- export const VERBETH_CONFIG = {
2
+ // deterministic CREATE2 deployments
3
+ export const VERBETH_ADDRESSES = {
4
4
  verbethProxy: '0x82C9c5475D63e4C9e959280e9066aBb24973a663',
5
5
  verbethImpl: '0x51670aB6eDE1d1B11C654CCA53b7D42080802326',
6
- creationBlock: 37097547, // *** only base sepolia for now
7
6
  };
8
- // helpers for Safe session module
9
- export const MODULE_SETUP_HELPERS = {
10
- 8453: '0xc022F74924BDB4b62D830234d89b066359bF67c0',
11
- 84532: '0xbd59Fea46D308eDF3b75C22a6f64AC68feFc731A',
7
+ export const MODULE_SETUP_HELPER = '0xbd59Fea46D308eDF3b75C22a6f64AC68feFc731A';
8
+ export const SESSION_MODULE = '0xFDBcE316F66e20Cae78D969b4f6635C703C53805';
9
+ // block at which verbeth was deployed
10
+ export const CREATION_BLOCKS = {
11
+ 8453: 42658728,
12
+ 84532: 37097547,
13
+ 11155111: 10340254,
12
14
  };
13
15
  export function getVerbethAddress() {
14
- return VERBETH_CONFIG.verbethProxy;
16
+ return VERBETH_ADDRESSES.verbethProxy;
15
17
  }
16
- export function getCreationBlock() {
17
- return VERBETH_CONFIG.creationBlock;
18
+ export function getCreationBlock(chainId) {
19
+ const block = CREATION_BLOCKS[chainId];
20
+ if (block === undefined)
21
+ throw new Error(`Unsupported chain: ${chainId}`);
22
+ return block;
18
23
  }
19
24
  export function getModuleSetupHelper(chainId) {
20
- return MODULE_SETUP_HELPERS[chainId];
25
+ return chainId in CREATION_BLOCKS ? MODULE_SETUP_HELPER : undefined;
21
26
  }
22
27
  export function isModuleSetupSupported(chainId) {
23
- return chainId in MODULE_SETUP_HELPERS;
28
+ return chainId in CREATION_BLOCKS;
24
29
  }
25
- // Scanning defaults (chain-agnostic)
26
- export const SCAN_DEFAULTS = {
27
- INITIAL_SCAN_BLOCKS: 1000,
28
- MAX_RETRIES: 3,
29
- MAX_RANGE_PROVIDER: 2000,
30
- CHUNK_SIZE: 2000,
31
- REAL_TIME_BUFFER: 3,
32
- };
@@ -0,0 +1,3 @@
1
+ export declare function bytesToNumberBE(bytes: Uint8Array): bigint;
2
+ export declare function numberToBytesBE(x: bigint, length: number): Uint8Array;
3
+ //# sourceMappingURL=scalars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scalars.d.ts","sourceRoot":"","sources":["../../../../src/internal/scalars.ts"],"names":[],"mappings":"AAGA,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAIzD;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CASrE"}
@@ -0,0 +1,19 @@
1
+ // packages/sdk/src/internal/scalars.ts
2
+ // Shared scalar utilities for secp256k1 big-integer ↔ byte conversions.
3
+ export function bytesToNumberBE(bytes) {
4
+ let hex = "";
5
+ for (const b of bytes)
6
+ hex += b.toString(16).padStart(2, "0");
7
+ return hex.length ? BigInt("0x" + hex) : 0n;
8
+ }
9
+ export function numberToBytesBE(x, length) {
10
+ let hex = x.toString(16);
11
+ if (hex.length > length * 2)
12
+ hex = hex.slice(hex.length - length * 2);
13
+ hex = hex.padStart(length * 2, "0");
14
+ const out = new Uint8Array(length);
15
+ for (let i = 0; i < length; i++) {
16
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
17
+ }
18
+ return out;
19
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Derive blinding factor from session stealth keys.
3
+ * Includes chainId in HKDF info for cross-chain domain separation.
4
+ */
5
+ export declare function deriveBlindingFactor(stealthChainKey: Uint8Array, stealthRootKey: Uint8Array, chainId: number, purpose?: string): Uint8Array;
6
+ /**
7
+ * Derive stealth Ethereum address for a recipient.
8
+ * K_stealth = K_spend + G * blinding (secp256k1 point addition)
9
+ */
10
+ export declare function deriveStealthAddress(recipientSpendPub: Uint8Array, // 33 bytes compressed
11
+ blinding: Uint8Array): string;
12
+ /**
13
+ * Derive stealth private key for spending.
14
+ * stealth_priv = k_spend + blinding (mod n)
15
+ */
16
+ export declare function deriveStealthPrivateKey(spendPriv: Uint8Array, blinding: Uint8Array): Uint8Array;
17
+ //# sourceMappingURL=stealth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stealth.d.ts","sourceRoot":"","sources":["../../../src/stealth.ts"],"names":[],"mappings":"AAYA;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,UAAU,EAC3B,cAAc,EAAE,UAAU,EAC1B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,UAAU,CAOZ;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,iBAAiB,EAAE,UAAU,EAAG,sBAAsB;AACtD,QAAQ,EAAE,UAAU,GACnB,MAAM,CAaR;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,UAAU,EACrB,QAAQ,EAAE,UAAU,GACnB,UAAU,CAUZ"}
@@ -0,0 +1,65 @@
1
+ // packages/sdk/src/stealth.ts
2
+ // Stealth payment derivation — pure functions with strict input validation.
3
+ import { hkdf } from '@noble/hashes/hkdf';
4
+ import { sha256 } from '@noble/hashes/sha2';
5
+ import { secp256k1 } from '@noble/curves/secp256k1';
6
+ import { keccak256 } from 'ethers';
7
+ import { bytesToNumberBE, numberToBytesBE } from './internal/scalars.js';
8
+ const Point = secp256k1.Point;
9
+ const curveN = Point.CURVE().n;
10
+ /**
11
+ * Derive blinding factor from session stealth keys.
12
+ * Includes chainId in HKDF info for cross-chain domain separation.
13
+ */
14
+ export function deriveBlindingFactor(stealthChainKey, stealthRootKey, chainId, purpose) {
15
+ if (stealthChainKey.length !== 32)
16
+ throw new Error('stealthChainKey must be 32 bytes');
17
+ if (stealthRootKey.length !== 32)
18
+ throw new Error('stealthRootKey must be 32 bytes');
19
+ const info = purpose
20
+ ? `verbeth:stealth:v1:${chainId}:${purpose}`
21
+ : `verbeth:stealth:v1:${chainId}`;
22
+ return hkdf(sha256, stealthChainKey, stealthRootKey, info, 32);
23
+ }
24
+ /**
25
+ * Derive stealth Ethereum address for a recipient.
26
+ * K_stealth = K_spend + G * blinding (secp256k1 point addition)
27
+ */
28
+ export function deriveStealthAddress(recipientSpendPub, // 33 bytes compressed
29
+ blinding // 32 bytes
30
+ ) {
31
+ if (recipientSpendPub.length !== 33)
32
+ throw new Error('recipientSpendPub must be 33 bytes compressed');
33
+ if (blinding.length !== 32)
34
+ throw new Error('blinding must be 32 bytes');
35
+ // Validate point is on curve (fromHex throws on invalid)
36
+ const spendPoint = Point.fromHex(recipientSpendPub);
37
+ const blindScalar = bytesToNumberBE(blinding);
38
+ if (blindScalar === 0n || blindScalar >= curveN)
39
+ throw new Error('invalid blinding scalar');
40
+ const blindPoint = Point.BASE.multiply(blindScalar);
41
+ const stealthPoint = spendPoint.add(blindPoint);
42
+ const stealthPubUncompressed = stealthPoint.toBytes(false); // 65 bytes
43
+ const hash = keccak256(stealthPubUncompressed.slice(1));
44
+ return '0x' + hash.slice(-40);
45
+ }
46
+ /**
47
+ * Derive stealth private key for spending.
48
+ * stealth_priv = k_spend + blinding (mod n)
49
+ */
50
+ export function deriveStealthPrivateKey(spendPriv, blinding) {
51
+ if (spendPriv.length !== 32)
52
+ throw new Error('spendPriv must be 32 bytes');
53
+ if (blinding.length !== 32)
54
+ throw new Error('blinding must be 32 bytes');
55
+ const privScalar = bytesToNumberBE(spendPriv);
56
+ const blindScalar = bytesToNumberBE(blinding);
57
+ if (privScalar === 0n || privScalar >= curveN)
58
+ throw new Error('invalid spend private key');
59
+ if (blindScalar === 0n || blindScalar >= curveN)
60
+ throw new Error('invalid blinding scalar');
61
+ const result = (privScalar + blindScalar) % curveN;
62
+ if (result === 0n)
63
+ throw new Error('stealth private key is zero');
64
+ return numberToBytesBE(result, 32);
65
+ }
@@ -1,20 +1,12 @@
1
- export interface ChainConfig {
2
- verbethProxy: `0x${string}`;
3
- verbethImpl: `0x${string}`;
4
- creationBlock: number;
5
- moduleSetupHelper?: `0x${string}`;
6
- }
7
- export declare const VERBETH_CONFIG: ChainConfig;
8
- export declare const MODULE_SETUP_HELPERS: Record<number, `0x${string}`>;
1
+ export declare const VERBETH_ADDRESSES: {
2
+ readonly verbethProxy: `0x${string}`;
3
+ readonly verbethImpl: `0x${string}`;
4
+ };
5
+ export declare const MODULE_SETUP_HELPER: `0x${string}`;
6
+ export declare const SESSION_MODULE: `0x${string}`;
7
+ export declare const CREATION_BLOCKS: Record<number, number>;
9
8
  export declare function getVerbethAddress(): `0x${string}`;
10
- export declare function getCreationBlock(): number;
9
+ export declare function getCreationBlock(chainId: number): number;
11
10
  export declare function getModuleSetupHelper(chainId: number): `0x${string}` | undefined;
12
11
  export declare function isModuleSetupSupported(chainId: number): boolean;
13
- export declare const SCAN_DEFAULTS: {
14
- readonly INITIAL_SCAN_BLOCKS: 1000;
15
- readonly MAX_RETRIES: 3;
16
- readonly MAX_RANGE_PROVIDER: 2000;
17
- readonly CHUNK_SIZE: 2000;
18
- readonly REAL_TIME_BUFFER: 3;
19
- };
20
12
  //# sourceMappingURL=addresses.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"addresses.d.ts","sourceRoot":"","sources":["../../src/addresses.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,KAAK,MAAM,EAAE,CAAC;IAC5B,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;CACnC;AAGD,eAAO,MAAM,cAAc,EAAE,WAInB,CAAC;AAGX,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,MAAM,EAAE,CAGrD,CAAC;AAGX,wBAAgB,iBAAiB,IAAI,KAAK,MAAM,EAAE,CAEjD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,MAAM,EAAE,GAAG,SAAS,CAE/E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE/D;AAGD,eAAO,MAAM,aAAa;;;;;;CAMhB,CAAC"}
1
+ {"version":3,"file":"addresses.d.ts","sourceRoot":"","sources":["../../src/addresses.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB;2BACkC,KAAK,MAAM,EAAE;0BACd,KAAK,MAAM,EAAE;CAClE,CAAC;AACX,eAAO,MAAM,mBAAmB,EAAmD,KAAK,MAAM,EAAE,CAAC;AACjG,eAAO,MAAM,cAAc,EAAmD,KAAK,MAAM,EAAE,CAAC;AAG5F,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAIzC,CAAC;AAEX,wBAAgB,iBAAiB,IAAI,KAAK,MAAM,EAAE,CAEjD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,MAAM,EAAE,GAAG,SAAS,CAE/E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE/D"}
@@ -1,32 +1,29 @@
1
1
  // packages/sdk/src/addresses.ts
2
- // Deterministic deployment
3
- export const VERBETH_CONFIG = {
2
+ // deterministic CREATE2 deployments
3
+ export const VERBETH_ADDRESSES = {
4
4
  verbethProxy: '0x82C9c5475D63e4C9e959280e9066aBb24973a663',
5
5
  verbethImpl: '0x51670aB6eDE1d1B11C654CCA53b7D42080802326',
6
- creationBlock: 37097547, // *** only base sepolia for now
7
6
  };
8
- // helpers for Safe session module
9
- export const MODULE_SETUP_HELPERS = {
10
- 8453: '0xc022F74924BDB4b62D830234d89b066359bF67c0',
11
- 84532: '0xbd59Fea46D308eDF3b75C22a6f64AC68feFc731A',
7
+ export const MODULE_SETUP_HELPER = '0xbd59Fea46D308eDF3b75C22a6f64AC68feFc731A';
8
+ export const SESSION_MODULE = '0xFDBcE316F66e20Cae78D969b4f6635C703C53805';
9
+ // block at which verbeth was deployed
10
+ export const CREATION_BLOCKS = {
11
+ 8453: 42658728,
12
+ 84532: 37097547,
13
+ 11155111: 10340254,
12
14
  };
13
15
  export function getVerbethAddress() {
14
- return VERBETH_CONFIG.verbethProxy;
16
+ return VERBETH_ADDRESSES.verbethProxy;
15
17
  }
16
- export function getCreationBlock() {
17
- return VERBETH_CONFIG.creationBlock;
18
+ export function getCreationBlock(chainId) {
19
+ const block = CREATION_BLOCKS[chainId];
20
+ if (block === undefined)
21
+ throw new Error(`Unsupported chain: ${chainId}`);
22
+ return block;
18
23
  }
19
24
  export function getModuleSetupHelper(chainId) {
20
- return MODULE_SETUP_HELPERS[chainId];
25
+ return chainId in CREATION_BLOCKS ? MODULE_SETUP_HELPER : undefined;
21
26
  }
22
27
  export function isModuleSetupSupported(chainId) {
23
- return chainId in MODULE_SETUP_HELPERS;
28
+ return chainId in CREATION_BLOCKS;
24
29
  }
25
- // Scanning defaults (chain-agnostic)
26
- export const SCAN_DEFAULTS = {
27
- INITIAL_SCAN_BLOCKS: 1000,
28
- MAX_RETRIES: 3,
29
- MAX_RANGE_PROVIDER: 2000,
30
- CHUNK_SIZE: 2000,
31
- REAL_TIME_BUFFER: 3,
32
- };
@@ -0,0 +1,3 @@
1
+ export declare function bytesToNumberBE(bytes: Uint8Array): bigint;
2
+ export declare function numberToBytesBE(x: bigint, length: number): Uint8Array;
3
+ //# sourceMappingURL=scalars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scalars.d.ts","sourceRoot":"","sources":["../../../src/internal/scalars.ts"],"names":[],"mappings":"AAGA,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAIzD;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CASrE"}
@@ -0,0 +1,19 @@
1
+ // packages/sdk/src/internal/scalars.ts
2
+ // Shared scalar utilities for secp256k1 big-integer ↔ byte conversions.
3
+ export function bytesToNumberBE(bytes) {
4
+ let hex = "";
5
+ for (const b of bytes)
6
+ hex += b.toString(16).padStart(2, "0");
7
+ return hex.length ? BigInt("0x" + hex) : 0n;
8
+ }
9
+ export function numberToBytesBE(x, length) {
10
+ let hex = x.toString(16);
11
+ if (hex.length > length * 2)
12
+ hex = hex.slice(hex.length - length * 2);
13
+ hex = hex.padStart(length * 2, "0");
14
+ const out = new Uint8Array(length);
15
+ for (let i = 0; i < length; i++) {
16
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
17
+ }
18
+ return out;
19
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Derive blinding factor from session stealth keys.
3
+ * Includes chainId in HKDF info for cross-chain domain separation.
4
+ */
5
+ export declare function deriveBlindingFactor(stealthChainKey: Uint8Array, stealthRootKey: Uint8Array, chainId: number, purpose?: string): Uint8Array;
6
+ /**
7
+ * Derive stealth Ethereum address for a recipient.
8
+ * K_stealth = K_spend + G * blinding (secp256k1 point addition)
9
+ */
10
+ export declare function deriveStealthAddress(recipientSpendPub: Uint8Array, // 33 bytes compressed
11
+ blinding: Uint8Array): string;
12
+ /**
13
+ * Derive stealth private key for spending.
14
+ * stealth_priv = k_spend + blinding (mod n)
15
+ */
16
+ export declare function deriveStealthPrivateKey(spendPriv: Uint8Array, blinding: Uint8Array): Uint8Array;
17
+ //# sourceMappingURL=stealth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stealth.d.ts","sourceRoot":"","sources":["../../src/stealth.ts"],"names":[],"mappings":"AAYA;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,UAAU,EAC3B,cAAc,EAAE,UAAU,EAC1B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,UAAU,CAOZ;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,iBAAiB,EAAE,UAAU,EAAG,sBAAsB;AACtD,QAAQ,EAAE,UAAU,GACnB,MAAM,CAaR;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,UAAU,EACrB,QAAQ,EAAE,UAAU,GACnB,UAAU,CAUZ"}
@@ -0,0 +1,65 @@
1
+ // packages/sdk/src/stealth.ts
2
+ // Stealth payment derivation — pure functions with strict input validation.
3
+ import { hkdf } from '@noble/hashes/hkdf';
4
+ import { sha256 } from '@noble/hashes/sha2';
5
+ import { secp256k1 } from '@noble/curves/secp256k1';
6
+ import { keccak256 } from 'ethers';
7
+ import { bytesToNumberBE, numberToBytesBE } from './internal/scalars.js';
8
+ const Point = secp256k1.Point;
9
+ const curveN = Point.CURVE().n;
10
+ /**
11
+ * Derive blinding factor from session stealth keys.
12
+ * Includes chainId in HKDF info for cross-chain domain separation.
13
+ */
14
+ export function deriveBlindingFactor(stealthChainKey, stealthRootKey, chainId, purpose) {
15
+ if (stealthChainKey.length !== 32)
16
+ throw new Error('stealthChainKey must be 32 bytes');
17
+ if (stealthRootKey.length !== 32)
18
+ throw new Error('stealthRootKey must be 32 bytes');
19
+ const info = purpose
20
+ ? `verbeth:stealth:v1:${chainId}:${purpose}`
21
+ : `verbeth:stealth:v1:${chainId}`;
22
+ return hkdf(sha256, stealthChainKey, stealthRootKey, info, 32);
23
+ }
24
+ /**
25
+ * Derive stealth Ethereum address for a recipient.
26
+ * K_stealth = K_spend + G * blinding (secp256k1 point addition)
27
+ */
28
+ export function deriveStealthAddress(recipientSpendPub, // 33 bytes compressed
29
+ blinding // 32 bytes
30
+ ) {
31
+ if (recipientSpendPub.length !== 33)
32
+ throw new Error('recipientSpendPub must be 33 bytes compressed');
33
+ if (blinding.length !== 32)
34
+ throw new Error('blinding must be 32 bytes');
35
+ // Validate point is on curve (fromHex throws on invalid)
36
+ const spendPoint = Point.fromHex(recipientSpendPub);
37
+ const blindScalar = bytesToNumberBE(blinding);
38
+ if (blindScalar === 0n || blindScalar >= curveN)
39
+ throw new Error('invalid blinding scalar');
40
+ const blindPoint = Point.BASE.multiply(blindScalar);
41
+ const stealthPoint = spendPoint.add(blindPoint);
42
+ const stealthPubUncompressed = stealthPoint.toBytes(false); // 65 bytes
43
+ const hash = keccak256(stealthPubUncompressed.slice(1));
44
+ return '0x' + hash.slice(-40);
45
+ }
46
+ /**
47
+ * Derive stealth private key for spending.
48
+ * stealth_priv = k_spend + blinding (mod n)
49
+ */
50
+ export function deriveStealthPrivateKey(spendPriv, blinding) {
51
+ if (spendPriv.length !== 32)
52
+ throw new Error('spendPriv must be 32 bytes');
53
+ if (blinding.length !== 32)
54
+ throw new Error('blinding must be 32 bytes');
55
+ const privScalar = bytesToNumberBE(spendPriv);
56
+ const blindScalar = bytesToNumberBE(blinding);
57
+ if (privScalar === 0n || privScalar >= curveN)
58
+ throw new Error('invalid spend private key');
59
+ if (blindScalar === 0n || blindScalar >= curveN)
60
+ throw new Error('invalid blinding scalar');
61
+ const result = (privScalar + blindScalar) % curveN;
62
+ if (result === 0n)
63
+ throw new Error('stealth private key is zero');
64
+ return numberToBytesBE(result, 32);
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verbeth/sdk",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "private": false,
5
5
  "main": "dist/src/index.js",
6
6
  "module": "dist/esm/src/index.js",