@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.
- package/dist/esm/src/addresses.d.ts +8 -16
- package/dist/esm/src/addresses.d.ts.map +1 -1
- package/dist/esm/src/addresses.js +17 -20
- package/dist/esm/src/internal/scalars.d.ts +3 -0
- package/dist/esm/src/internal/scalars.d.ts.map +1 -0
- package/dist/esm/src/internal/scalars.js +19 -0
- package/dist/esm/src/stealth.d.ts +17 -0
- package/dist/esm/src/stealth.d.ts.map +1 -0
- package/dist/esm/src/stealth.js +65 -0
- package/dist/src/addresses.d.ts +8 -16
- package/dist/src/addresses.d.ts.map +1 -1
- package/dist/src/addresses.js +17 -20
- package/dist/src/internal/scalars.d.ts +3 -0
- package/dist/src/internal/scalars.d.ts.map +1 -0
- package/dist/src/internal/scalars.js +19 -0
- package/dist/src/stealth.d.ts +17 -0
- package/dist/src/stealth.d.ts.map +1 -0
- package/dist/src/stealth.js +65 -0
- package/package.json +1 -1
|
@@ -1,20 +1,12 @@
|
|
|
1
|
-
export
|
|
2
|
-
verbethProxy: `0x${string}`;
|
|
3
|
-
verbethImpl: `0x${string}`;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
export declare const
|
|
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":"
|
|
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
|
-
//
|
|
3
|
-
export const
|
|
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
|
-
|
|
9
|
-
export const
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
16
|
+
return VERBETH_ADDRESSES.verbethProxy;
|
|
15
17
|
}
|
|
16
|
-
export function getCreationBlock() {
|
|
17
|
-
|
|
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
|
|
25
|
+
return chainId in CREATION_BLOCKS ? MODULE_SETUP_HELPER : undefined;
|
|
21
26
|
}
|
|
22
27
|
export function isModuleSetupSupported(chainId) {
|
|
23
|
-
return chainId in
|
|
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 @@
|
|
|
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/dist/src/addresses.d.ts
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
|
-
export
|
|
2
|
-
verbethProxy: `0x${string}`;
|
|
3
|
-
verbethImpl: `0x${string}`;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
export declare const
|
|
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":"
|
|
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"}
|
package/dist/src/addresses.js
CHANGED
|
@@ -1,32 +1,29 @@
|
|
|
1
1
|
// packages/sdk/src/addresses.ts
|
|
2
|
-
//
|
|
3
|
-
export const
|
|
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
|
-
|
|
9
|
-
export const
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
16
|
+
return VERBETH_ADDRESSES.verbethProxy;
|
|
15
17
|
}
|
|
16
|
-
export function getCreationBlock() {
|
|
17
|
-
|
|
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
|
|
25
|
+
return chainId in CREATION_BLOCKS ? MODULE_SETUP_HELPER : undefined;
|
|
21
26
|
}
|
|
22
27
|
export function isModuleSetupSupported(chainId) {
|
|
23
|
-
return chainId in
|
|
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 @@
|
|
|
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
|
+
}
|