@shroud-fi/core 0.1.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/LICENSE +21 -0
- package/README.md +54 -0
- package/dist/cjs/compute-key.d.ts +24 -0
- package/dist/cjs/compute-key.d.ts.map +1 -0
- package/dist/cjs/compute-key.js +72 -0
- package/dist/cjs/compute-key.js.map +1 -0
- package/dist/cjs/constants.d.ts +20 -0
- package/dist/cjs/constants.d.ts.map +1 -0
- package/dist/cjs/constants.js +23 -0
- package/dist/cjs/constants.js.map +1 -0
- package/dist/cjs/errors.d.ts +35 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +48 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/identity.d.ts +20 -0
- package/dist/cjs/identity.d.ts.map +1 -0
- package/dist/cjs/identity.js +38 -0
- package/dist/cjs/identity.js.map +1 -0
- package/dist/cjs/index.d.ts +12 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +36 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/keys.d.ts +17 -0
- package/dist/cjs/keys.d.ts.map +1 -0
- package/dist/cjs/keys.js +51 -0
- package/dist/cjs/keys.js.map +1 -0
- package/dist/cjs/meta-address.d.ts +26 -0
- package/dist/cjs/meta-address.d.ts.map +1 -0
- package/dist/cjs/meta-address.js +86 -0
- package/dist/cjs/meta-address.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/stealth.d.ts +22 -0
- package/dist/cjs/stealth.d.ts.map +1 -0
- package/dist/cjs/stealth.js +100 -0
- package/dist/cjs/stealth.js.map +1 -0
- package/dist/cjs/types.d.ts +46 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +9 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/view-tag.d.ts +29 -0
- package/dist/cjs/view-tag.d.ts.map +1 -0
- package/dist/cjs/view-tag.js +62 -0
- package/dist/cjs/view-tag.js.map +1 -0
- package/dist/esm/compute-key.d.ts +24 -0
- package/dist/esm/compute-key.d.ts.map +1 -0
- package/dist/esm/compute-key.js +69 -0
- package/dist/esm/compute-key.js.map +1 -0
- package/dist/esm/constants.d.ts +20 -0
- package/dist/esm/constants.d.ts.map +1 -0
- package/dist/esm/constants.js +20 -0
- package/dist/esm/constants.js.map +1 -0
- package/dist/esm/errors.d.ts +35 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +41 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/identity.d.ts +20 -0
- package/dist/esm/identity.d.ts.map +1 -0
- package/dist/esm/identity.js +35 -0
- package/dist/esm/identity.js.map +1 -0
- package/dist/esm/index.d.ts +12 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +17 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/keys.d.ts +17 -0
- package/dist/esm/keys.d.ts.map +1 -0
- package/dist/esm/keys.js +48 -0
- package/dist/esm/keys.js.map +1 -0
- package/dist/esm/meta-address.d.ts +26 -0
- package/dist/esm/meta-address.d.ts.map +1 -0
- package/dist/esm/meta-address.js +82 -0
- package/dist/esm/meta-address.js.map +1 -0
- package/dist/esm/stealth.d.ts +22 -0
- package/dist/esm/stealth.d.ts.map +1 -0
- package/dist/esm/stealth.js +97 -0
- package/dist/esm/stealth.js.map +1 -0
- package/dist/esm/types.d.ts +46 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +8 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/view-tag.d.ts +29 -0
- package/dist/esm/view-tag.d.ts.map +1 -0
- package/dist/esm/view-tag.js +58 -0
- package/dist/esm/view-tag.js.map +1 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ERC-6538 Stealth Meta-Address Encode/Decode
|
|
4
|
+
*
|
|
5
|
+
* Format: st:base:0x<spendingPubKey 33 bytes hex><viewingPubKey 33 bytes hex>
|
|
6
|
+
*
|
|
7
|
+
* Both keys are SEC1 compressed secp256k1 public keys (prefix 0x02 or 0x03).
|
|
8
|
+
* Total raw payload: 66 bytes = 132 hex characters.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.encodeMetaAddress = encodeMetaAddress;
|
|
12
|
+
exports.decodeMetaAddress = decodeMetaAddress;
|
|
13
|
+
const secp256k1_1 = require("@noble/curves/secp256k1");
|
|
14
|
+
const utils_1 = require("@noble/hashes/utils");
|
|
15
|
+
const errors_js_1 = require("./errors.js");
|
|
16
|
+
const constants_js_1 = require("./constants.js");
|
|
17
|
+
const META_ADDRESS_PREFIX = 'st:base:0x';
|
|
18
|
+
const COMPRESSED_KEY_LENGTH = 33; // bytes
|
|
19
|
+
const TOTAL_PAYLOAD_LENGTH = COMPRESSED_KEY_LENGTH * 2; // 66 bytes
|
|
20
|
+
/**
|
|
21
|
+
* Validate that a byte array is a valid compressed secp256k1 public key.
|
|
22
|
+
* Must be 33 bytes with 0x02 or 0x03 prefix.
|
|
23
|
+
*/
|
|
24
|
+
function assertValidCompressedKey(key, label) {
|
|
25
|
+
if (key.length !== COMPRESSED_KEY_LENGTH) {
|
|
26
|
+
throw new errors_js_1.InvalidMetaAddressError(`${label} must be ${COMPRESSED_KEY_LENGTH} bytes, got ${key.length}`);
|
|
27
|
+
}
|
|
28
|
+
const prefix = key[0];
|
|
29
|
+
if (prefix !== 0x02 && prefix !== 0x03) {
|
|
30
|
+
throw new errors_js_1.InvalidMetaAddressError(`${label} must have 0x02 or 0x03 prefix`);
|
|
31
|
+
}
|
|
32
|
+
// Validate it's actually on the curve
|
|
33
|
+
try {
|
|
34
|
+
secp256k1_1.secp256k1.ProjectivePoint.fromHex(key);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new errors_js_1.InvalidMetaAddressError(`${label} is not a valid secp256k1 point`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Encode a StealthMetaAddress into the ERC-6538 URI format.
|
|
42
|
+
*
|
|
43
|
+
* @param meta - Stealth meta-address containing spending and viewing public keys
|
|
44
|
+
* @returns Encoded string: "st:base:0x<spendHex><viewHex>"
|
|
45
|
+
* @throws InvalidMetaAddressError if keys are invalid
|
|
46
|
+
*/
|
|
47
|
+
function encodeMetaAddress(meta) {
|
|
48
|
+
assertValidCompressedKey(meta.spendingPubKey, 'Spending public key');
|
|
49
|
+
assertValidCompressedKey(meta.viewingPubKey, 'Viewing public key');
|
|
50
|
+
const spendHex = (0, utils_1.bytesToHex)(meta.spendingPubKey);
|
|
51
|
+
const viewHex = (0, utils_1.bytesToHex)(meta.viewingPubKey);
|
|
52
|
+
return `${META_ADDRESS_PREFIX}${spendHex}${viewHex}`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Decode an ERC-6538 URI string into a StealthMetaAddress.
|
|
56
|
+
*
|
|
57
|
+
* @param encoded - String in format "st:base:0x<66 bytes hex>"
|
|
58
|
+
* @returns Parsed StealthMetaAddress
|
|
59
|
+
* @throws InvalidMetaAddressError if format is invalid
|
|
60
|
+
*/
|
|
61
|
+
function decodeMetaAddress(encoded) {
|
|
62
|
+
if (!encoded.startsWith(META_ADDRESS_PREFIX)) {
|
|
63
|
+
throw new errors_js_1.InvalidMetaAddressError(`Meta-address must start with "${META_ADDRESS_PREFIX}"`);
|
|
64
|
+
}
|
|
65
|
+
const hexPayload = encoded.slice(META_ADDRESS_PREFIX.length);
|
|
66
|
+
if (hexPayload.length !== TOTAL_PAYLOAD_LENGTH * 2) {
|
|
67
|
+
throw new errors_js_1.InvalidMetaAddressError(`Meta-address hex payload must be ${TOTAL_PAYLOAD_LENGTH * 2} characters, got ${hexPayload.length}`);
|
|
68
|
+
}
|
|
69
|
+
let payloadBytes;
|
|
70
|
+
try {
|
|
71
|
+
payloadBytes = (0, utils_1.hexToBytes)(hexPayload);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
throw new errors_js_1.InvalidMetaAddressError('Meta-address contains invalid hex characters');
|
|
75
|
+
}
|
|
76
|
+
const spendingPubKey = payloadBytes.slice(0, COMPRESSED_KEY_LENGTH);
|
|
77
|
+
const viewingPubKey = payloadBytes.slice(COMPRESSED_KEY_LENGTH);
|
|
78
|
+
assertValidCompressedKey(spendingPubKey, 'Spending public key');
|
|
79
|
+
assertValidCompressedKey(viewingPubKey, 'Viewing public key');
|
|
80
|
+
return {
|
|
81
|
+
spendingPubKey,
|
|
82
|
+
viewingPubKey,
|
|
83
|
+
schemeId: constants_js_1.SCHEME_ID,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=meta-address.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta-address.js","sourceRoot":"","sources":["../../src/meta-address.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AA2CH,8CAQC;AASD,8CAiCC;AA3FD,uDAAoD;AACpD,+CAA6D;AAE7D,2CAAsD;AACtD,iDAA2C;AAE3C,MAAM,mBAAmB,GAAG,YAAY,CAAC;AACzC,MAAM,qBAAqB,GAAG,EAAE,CAAC,CAAC,QAAQ;AAC1C,MAAM,oBAAoB,GAAG,qBAAqB,GAAG,CAAC,CAAC,CAAC,WAAW;AAEnE;;;GAGG;AACH,SAAS,wBAAwB,CAAC,GAAe,EAAE,KAAa;IAC9D,IAAI,GAAG,CAAC,MAAM,KAAK,qBAAqB,EAAE,CAAC;QACzC,MAAM,IAAI,mCAAuB,CAC/B,GAAG,KAAK,YAAY,qBAAqB,eAAe,GAAG,CAAC,MAAM,EAAE,CACrE,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACtB,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACvC,MAAM,IAAI,mCAAuB,CAC/B,GAAG,KAAK,gCAAgC,CACzC,CAAC;IACJ,CAAC;IACD,sCAAsC;IACtC,IAAI,CAAC;QACH,qBAAS,CAAC,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,mCAAuB,CAAC,GAAG,KAAK,iCAAiC,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,iBAAiB,CAAC,IAAwB;IACxD,wBAAwB,CAAC,IAAI,CAAC,cAAc,EAAE,qBAAqB,CAAC,CAAC;IACrE,wBAAwB,CAAC,IAAI,CAAC,aAAa,EAAE,oBAAoB,CAAC,CAAC;IAEnE,MAAM,QAAQ,GAAG,IAAA,kBAAU,EAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,IAAA,kBAAU,EAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAE/C,OAAO,GAAG,mBAAmB,GAAG,QAAQ,GAAG,OAAO,EAAE,CAAC;AACvD,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,iBAAiB,CAAC,OAAe;IAC/C,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,mCAAuB,CAC/B,iCAAiC,mBAAmB,GAAG,CACxD,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAE7D,IAAI,UAAU,CAAC,MAAM,KAAK,oBAAoB,GAAG,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,mCAAuB,CAC/B,oCAAoC,oBAAoB,GAAG,CAAC,oBAAoB,UAAU,CAAC,MAAM,EAAE,CACpG,CAAC;IACJ,CAAC;IAED,IAAI,YAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,YAAY,GAAG,IAAA,kBAAU,EAAC,UAAU,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,mCAAuB,CAAC,8CAA8C,CAAC,CAAC;IACpF,CAAC;IAED,MAAM,cAAc,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAC;IACpE,MAAM,aAAa,GAAG,YAAY,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAEhE,wBAAwB,CAAC,cAAc,EAAE,qBAAqB,CAAC,CAAC;IAChE,wBAAwB,CAAC,aAAa,EAAE,oBAAoB,CAAC,CAAC;IAE9D,OAAO;QACL,cAAc;QACd,aAAa;QACb,QAAQ,EAAE,wBAAS;KACpB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stealth Address Generation (Sender Side)
|
|
3
|
+
*
|
|
4
|
+
* Implements EIP-5564 scheme 1 (secp256k1):
|
|
5
|
+
* 1. Generate random ephemeral key r, compute R = r*G
|
|
6
|
+
* 2. ECDH: sharedSecret = r * viewingPubKey
|
|
7
|
+
* 3. Hash: hashedSecret = keccak256(compress(sharedSecret))
|
|
8
|
+
* 4. View tag: viewTag = hashedSecret[0]
|
|
9
|
+
* 5. Stealth pubkey: P_stealth = spendingPubKey + hashedSecret * G
|
|
10
|
+
* 6. Stealth address: keccak256(uncompressed64(P_stealth))[12:] -> 20-byte address
|
|
11
|
+
*/
|
|
12
|
+
import type { StealthMetaAddress, StealthAddressResult } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Generate a one-time stealth address for a recipient.
|
|
15
|
+
*
|
|
16
|
+
* @param meta - Recipient's stealth meta-address (two compressed public keys)
|
|
17
|
+
* @returns StealthAddressResult with stealth address, ephemeral pubkey, and view tag
|
|
18
|
+
* @throws InvalidKeyError if meta-address public keys are invalid
|
|
19
|
+
* @throws StealthAddressError if the derived stealth point is at infinity
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateStealthAddress(meta: StealthMetaAddress): StealthAddressResult;
|
|
22
|
+
//# sourceMappingURL=stealth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stealth.d.ts","sourceRoot":"","sources":["../../src/stealth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAa3E;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,kBAAkB,GACvB,oBAAoB,CA2DtB"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Stealth Address Generation (Sender Side)
|
|
4
|
+
*
|
|
5
|
+
* Implements EIP-5564 scheme 1 (secp256k1):
|
|
6
|
+
* 1. Generate random ephemeral key r, compute R = r*G
|
|
7
|
+
* 2. ECDH: sharedSecret = r * viewingPubKey
|
|
8
|
+
* 3. Hash: hashedSecret = keccak256(compress(sharedSecret))
|
|
9
|
+
* 4. View tag: viewTag = hashedSecret[0]
|
|
10
|
+
* 5. Stealth pubkey: P_stealth = spendingPubKey + hashedSecret * G
|
|
11
|
+
* 6. Stealth address: keccak256(uncompressed64(P_stealth))[12:] -> 20-byte address
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.generateStealthAddress = generateStealthAddress;
|
|
15
|
+
const secp256k1_1 = require("@noble/curves/secp256k1");
|
|
16
|
+
const sha3_1 = require("@noble/hashes/sha3");
|
|
17
|
+
const utils_1 = require("@noble/hashes/utils");
|
|
18
|
+
const errors_js_1 = require("./errors.js");
|
|
19
|
+
/**
|
|
20
|
+
* Convert a 64-byte uncompressed public key (no 0x04 prefix) to an EVM address.
|
|
21
|
+
* address = last 20 bytes of keccak256(pubkey64)
|
|
22
|
+
*/
|
|
23
|
+
function pubKeyToAddress(uncompressed64) {
|
|
24
|
+
const hash = (0, sha3_1.keccak_256)(uncompressed64);
|
|
25
|
+
const addrBytes = hash.slice(12);
|
|
26
|
+
return `0x${(0, utils_1.bytesToHex)(addrBytes)}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Generate a one-time stealth address for a recipient.
|
|
30
|
+
*
|
|
31
|
+
* @param meta - Recipient's stealth meta-address (two compressed public keys)
|
|
32
|
+
* @returns StealthAddressResult with stealth address, ephemeral pubkey, and view tag
|
|
33
|
+
* @throws InvalidKeyError if meta-address public keys are invalid
|
|
34
|
+
* @throws StealthAddressError if the derived stealth point is at infinity
|
|
35
|
+
*/
|
|
36
|
+
function generateStealthAddress(meta) {
|
|
37
|
+
// Validate input public keys by parsing them (fromHex validates on-curve)
|
|
38
|
+
let viewingPoint;
|
|
39
|
+
let spendingPoint;
|
|
40
|
+
try {
|
|
41
|
+
viewingPoint = secp256k1_1.secp256k1.ProjectivePoint.fromHex(meta.viewingPubKey);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new errors_js_1.InvalidKeyError('Invalid viewing public key in meta-address');
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
spendingPoint = secp256k1_1.secp256k1.ProjectivePoint.fromHex(meta.spendingPubKey);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
throw new errors_js_1.InvalidKeyError('Invalid spending public key in meta-address');
|
|
51
|
+
}
|
|
52
|
+
// Step 1: Generate ephemeral keypair
|
|
53
|
+
const ephemeralPriv = secp256k1_1.secp256k1.utils.randomPrivateKey();
|
|
54
|
+
const ephemeralPub = secp256k1_1.secp256k1.getPublicKey(ephemeralPriv, true); // compressed
|
|
55
|
+
try {
|
|
56
|
+
// Step 2: ECDH shared secret S = r * P_view
|
|
57
|
+
const ephemeralScalar = bytesToBigInt(ephemeralPriv);
|
|
58
|
+
const sharedSecretPoint = viewingPoint.multiply(ephemeralScalar);
|
|
59
|
+
// Step 3: Hash the compressed shared secret
|
|
60
|
+
const sharedSecretCompressed = sharedSecretPoint.toRawBytes(true); // 33 bytes
|
|
61
|
+
const hashedSecret = (0, sha3_1.keccak_256)(sharedSecretCompressed); // 32 bytes
|
|
62
|
+
// Step 4: View tag = first byte
|
|
63
|
+
const viewTag = hashedSecret[0];
|
|
64
|
+
// Step 5: Stealth public key = P_spend + hashedSecret * G
|
|
65
|
+
const hashedScalar = bytesToBigInt(hashedSecret) % secp256k1_1.secp256k1.CURVE.n;
|
|
66
|
+
if (hashedScalar === 0n) {
|
|
67
|
+
throw new errors_js_1.StealthAddressError('Hashed secret reduced to zero');
|
|
68
|
+
}
|
|
69
|
+
const hashedPoint = secp256k1_1.secp256k1.ProjectivePoint.BASE.multiply(hashedScalar);
|
|
70
|
+
const stealthPubPoint = spendingPoint.add(hashedPoint);
|
|
71
|
+
// Verify not point at infinity
|
|
72
|
+
try {
|
|
73
|
+
stealthPubPoint.assertValidity();
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
throw new errors_js_1.StealthAddressError('Stealth public key is the point at infinity');
|
|
77
|
+
}
|
|
78
|
+
// Step 6: Derive EVM address from uncompressed stealth public key
|
|
79
|
+
const stealthPubUncompressed = stealthPubPoint.toRawBytes(false); // 65 bytes (04 || x || y)
|
|
80
|
+
const stealthAddress = pubKeyToAddress(stealthPubUncompressed.slice(1)); // strip 0x04 prefix
|
|
81
|
+
return {
|
|
82
|
+
stealthAddress,
|
|
83
|
+
ephemeralPubKey: ephemeralPub,
|
|
84
|
+
viewTag,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
// Zero ephemeral private key
|
|
89
|
+
ephemeralPriv.fill(0);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Convert a Uint8Array to a BigInt (big-endian). */
|
|
93
|
+
function bytesToBigInt(bytes) {
|
|
94
|
+
let result = 0n;
|
|
95
|
+
for (const byte of bytes) {
|
|
96
|
+
result = (result << 8n) | BigInt(byte);
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=stealth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stealth.js","sourceRoot":"","sources":["../../src/stealth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShroudFi Core Types
|
|
3
|
+
*
|
|
4
|
+
* Branded key types for compile-time safety -- prevents accidentally
|
|
5
|
+
* passing a scanning key where a spending key is expected.
|
|
6
|
+
*/
|
|
7
|
+
/** secp256k1 spending private key (32 bytes). Controls funds. */
|
|
8
|
+
export type SpendingKey = {
|
|
9
|
+
readonly __brand: 'SpendingKey';
|
|
10
|
+
readonly bytes: Uint8Array;
|
|
11
|
+
};
|
|
12
|
+
/** secp256k1 scanning private key (32 bytes). Detects incoming payments. */
|
|
13
|
+
export type ScanningKey = {
|
|
14
|
+
readonly __brand: 'ScanningKey';
|
|
15
|
+
readonly bytes: Uint8Array;
|
|
16
|
+
};
|
|
17
|
+
/** secp256k1 viewing key (32 bytes). Enables selective disclosure. */
|
|
18
|
+
export type ViewingKey = {
|
|
19
|
+
readonly __brand: 'ViewingKey';
|
|
20
|
+
readonly bytes: Uint8Array;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* ERC-6538 stealth meta-address: two compressed secp256k1 public keys.
|
|
24
|
+
* Shareable -- contains NO private material.
|
|
25
|
+
*/
|
|
26
|
+
export interface StealthMetaAddress {
|
|
27
|
+
readonly spendingPubKey: Uint8Array;
|
|
28
|
+
readonly viewingPubKey: Uint8Array;
|
|
29
|
+
readonly schemeId: number;
|
|
30
|
+
}
|
|
31
|
+
/** Result of generating a one-time stealth address for a recipient. */
|
|
32
|
+
export interface StealthAddressResult {
|
|
33
|
+
/** The derived stealth EVM address. */
|
|
34
|
+
readonly stealthAddress: `0x${string}`;
|
|
35
|
+
/** Compressed ephemeral public key (33 bytes) -- must be announced. */
|
|
36
|
+
readonly ephemeralPubKey: Uint8Array;
|
|
37
|
+
/** View tag (0-255) -- first byte of hashed shared secret. */
|
|
38
|
+
readonly viewTag: number;
|
|
39
|
+
}
|
|
40
|
+
/** Full key hierarchy derived from a master seed. */
|
|
41
|
+
export interface KeyHierarchy {
|
|
42
|
+
readonly spendingKey: SpendingKey;
|
|
43
|
+
readonly scanningKey: ScanningKey;
|
|
44
|
+
readonly viewingKey: ViewingKey;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,iEAAiE;AACjE,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE,UAAU,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,uEAAuE;AACvE,MAAM,WAAW,oBAAoB;IACnC,uCAAuC;IACvC,QAAQ,CAAC,cAAc,EAAE,KAAK,MAAM,EAAE,CAAC;IACvC,uEAAuE;IACvE,QAAQ,CAAC,eAAe,EAAE,UAAU,CAAC;IACrC,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CACjC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ShroudFi Core Types
|
|
4
|
+
*
|
|
5
|
+
* Branded key types for compile-time safety -- prevents accidentally
|
|
6
|
+
* passing a scanning key where a spending key is expected.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":";AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View Tag Utilities
|
|
3
|
+
*
|
|
4
|
+
* View tags enable fast filtering of stealth address announcements.
|
|
5
|
+
* The view tag is the first byte of keccak256(compress(ECDH_shared_secret)).
|
|
6
|
+
* This filters ~99.6% of non-matching announcements with a single byte check.
|
|
7
|
+
*/
|
|
8
|
+
import type { ScanningKey } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Extract the view tag from a compressed shared secret point.
|
|
11
|
+
* The view tag is byte 0 of keccak256(compressedPoint).
|
|
12
|
+
*
|
|
13
|
+
* @param sharedSecretCompressed - 33-byte compressed secp256k1 point
|
|
14
|
+
* @returns View tag value (0-255)
|
|
15
|
+
*/
|
|
16
|
+
export declare function extractViewTag(sharedSecretCompressed: Uint8Array): number;
|
|
17
|
+
/**
|
|
18
|
+
* Check whether a view tag matches for a given scanning key and ephemeral public key.
|
|
19
|
+
*
|
|
20
|
+
* Performs the ECDH step and view tag extraction, then compares against
|
|
21
|
+
* the expected tag. This is the fast-path filter for scanning.
|
|
22
|
+
*
|
|
23
|
+
* @param scanningKey - Recipient's scanning private key
|
|
24
|
+
* @param ephemeralPubKey - Ephemeral public key from the announcement (33 bytes)
|
|
25
|
+
* @param expectedTag - The view tag from the announcement metadata
|
|
26
|
+
* @returns true if the view tag matches (candidate for full verification)
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkViewTag(scanningKey: ScanningKey, ephemeralPubKey: Uint8Array, expectedTag: number): boolean;
|
|
29
|
+
//# sourceMappingURL=view-tag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view-tag.d.ts","sourceRoot":"","sources":["../../src/view-tag.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,sBAAsB,EAAE,UAAU,GAAG,MAAM,CAGzE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,WAAW,EACxB,eAAe,EAAE,UAAU,EAC3B,WAAW,EAAE,MAAM,GAClB,OAAO,CAkBT"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* View Tag Utilities
|
|
4
|
+
*
|
|
5
|
+
* View tags enable fast filtering of stealth address announcements.
|
|
6
|
+
* The view tag is the first byte of keccak256(compress(ECDH_shared_secret)).
|
|
7
|
+
* This filters ~99.6% of non-matching announcements with a single byte check.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.extractViewTag = extractViewTag;
|
|
11
|
+
exports.checkViewTag = checkViewTag;
|
|
12
|
+
const secp256k1_1 = require("@noble/curves/secp256k1");
|
|
13
|
+
const sha3_1 = require("@noble/hashes/sha3");
|
|
14
|
+
const errors_js_1 = require("./errors.js");
|
|
15
|
+
/**
|
|
16
|
+
* Extract the view tag from a compressed shared secret point.
|
|
17
|
+
* The view tag is byte 0 of keccak256(compressedPoint).
|
|
18
|
+
*
|
|
19
|
+
* @param sharedSecretCompressed - 33-byte compressed secp256k1 point
|
|
20
|
+
* @returns View tag value (0-255)
|
|
21
|
+
*/
|
|
22
|
+
function extractViewTag(sharedSecretCompressed) {
|
|
23
|
+
const hash = (0, sha3_1.keccak_256)(sharedSecretCompressed);
|
|
24
|
+
return hash[0];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check whether a view tag matches for a given scanning key and ephemeral public key.
|
|
28
|
+
*
|
|
29
|
+
* Performs the ECDH step and view tag extraction, then compares against
|
|
30
|
+
* the expected tag. This is the fast-path filter for scanning.
|
|
31
|
+
*
|
|
32
|
+
* @param scanningKey - Recipient's scanning private key
|
|
33
|
+
* @param ephemeralPubKey - Ephemeral public key from the announcement (33 bytes)
|
|
34
|
+
* @param expectedTag - The view tag from the announcement metadata
|
|
35
|
+
* @returns true if the view tag matches (candidate for full verification)
|
|
36
|
+
*/
|
|
37
|
+
function checkViewTag(scanningKey, ephemeralPubKey, expectedTag) {
|
|
38
|
+
// Validate ephemeral public key
|
|
39
|
+
let ephemeralPoint;
|
|
40
|
+
try {
|
|
41
|
+
ephemeralPoint = secp256k1_1.secp256k1.ProjectivePoint.fromHex(ephemeralPubKey);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new errors_js_1.InvalidKeyError('Invalid ephemeral public key');
|
|
45
|
+
}
|
|
46
|
+
// ECDH: sharedSecret = scanningPriv * P_ephemeral
|
|
47
|
+
const scanScalar = bytesToBigInt(scanningKey.bytes);
|
|
48
|
+
const sharedSecretPoint = ephemeralPoint.multiply(scanScalar);
|
|
49
|
+
// Hash compressed shared secret and extract view tag
|
|
50
|
+
const sharedSecretCompressed = sharedSecretPoint.toRawBytes(true);
|
|
51
|
+
const tag = extractViewTag(sharedSecretCompressed);
|
|
52
|
+
return tag === expectedTag;
|
|
53
|
+
}
|
|
54
|
+
/** Convert a Uint8Array to a BigInt (big-endian). */
|
|
55
|
+
function bytesToBigInt(bytes) {
|
|
56
|
+
let result = 0n;
|
|
57
|
+
for (const byte of bytes) {
|
|
58
|
+
result = (result << 8n) | BigInt(byte);
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=view-tag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view-tag.js","sourceRoot":"","sources":["../../src/view-tag.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAcH,wCAGC;AAaD,oCAsBC;AAlDD,uDAAoD;AACpD,6CAAgD;AAEhD,2CAA8C;AAE9C;;;;;;GAMG;AACH,SAAgB,cAAc,CAAC,sBAAkC;IAC/D,MAAM,IAAI,GAAG,IAAA,iBAAU,EAAC,sBAAsB,CAAC,CAAC;IAChD,OAAO,IAAI,CAAC,CAAC,CAAE,CAAC;AAClB,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,YAAY,CAC1B,WAAwB,EACxB,eAA2B,EAC3B,WAAmB;IAEnB,gCAAgC;IAChC,IAAI,cAA8D,CAAC;IACnE,IAAI,CAAC;QACH,cAAc,GAAG,qBAAS,CAAC,eAAe,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,2BAAe,CAAC,8BAA8B,CAAC,CAAC;IAC5D,CAAC;IAED,kDAAkD;IAClD,MAAM,UAAU,GAAG,aAAa,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,iBAAiB,GAAG,cAAc,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAE9D,qDAAqD;IACrD,MAAM,sBAAsB,GAAG,iBAAiB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,cAAc,CAAC,sBAAsB,CAAC,CAAC;IAEnD,OAAO,GAAG,KAAK,WAAW,CAAC;AAC7B,CAAC;AAED,qDAAqD;AACrD,SAAS,aAAa,CAAC,KAAiB;IACtC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stealth Key Computation (Recipient Side)
|
|
3
|
+
*
|
|
4
|
+
* Given the scanning private key, spending private key, and the
|
|
5
|
+
* ephemeral public key from an announcement, derive the stealth
|
|
6
|
+
* private key that controls the stealth address.
|
|
7
|
+
*
|
|
8
|
+
* 1. ECDH: sharedSecret = scanningPrivKey * ephemeralPubKey
|
|
9
|
+
* 2. Hash: hashedSecret = keccak256(compress(sharedSecret))
|
|
10
|
+
* 3. Stealth private key: p_stealth = (spendingPrivKey + hashedSecret) mod n
|
|
11
|
+
*/
|
|
12
|
+
import type { ScanningKey, SpendingKey } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Compute the stealth private key for a detected payment.
|
|
15
|
+
*
|
|
16
|
+
* @param scanningKey - Recipient's scanning private key
|
|
17
|
+
* @param spendingKey - Recipient's spending private key
|
|
18
|
+
* @param ephemeralPubKey - Ephemeral public key from the announcement (33 bytes compressed)
|
|
19
|
+
* @returns 32-byte stealth private key
|
|
20
|
+
* @throws InvalidKeyError if ephemeral public key is invalid
|
|
21
|
+
* @throws StealthAddressError if the derived stealth key is invalid
|
|
22
|
+
*/
|
|
23
|
+
export declare function computeStealthKey(scanningKey: ScanningKey, spendingKey: SpendingKey, ephemeralPubKey: Uint8Array): Uint8Array;
|
|
24
|
+
//# sourceMappingURL=compute-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute-key.d.ts","sourceRoot":"","sources":["../../src/compute-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,WAAW,EACxB,eAAe,EAAE,UAAU,GAC1B,UAAU,CA6BZ"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stealth Key Computation (Recipient Side)
|
|
3
|
+
*
|
|
4
|
+
* Given the scanning private key, spending private key, and the
|
|
5
|
+
* ephemeral public key from an announcement, derive the stealth
|
|
6
|
+
* private key that controls the stealth address.
|
|
7
|
+
*
|
|
8
|
+
* 1. ECDH: sharedSecret = scanningPrivKey * ephemeralPubKey
|
|
9
|
+
* 2. Hash: hashedSecret = keccak256(compress(sharedSecret))
|
|
10
|
+
* 3. Stealth private key: p_stealth = (spendingPrivKey + hashedSecret) mod n
|
|
11
|
+
*/
|
|
12
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
13
|
+
import { keccak_256 } from '@noble/hashes/sha3';
|
|
14
|
+
import { InvalidKeyError, StealthAddressError } from './errors.js';
|
|
15
|
+
/**
|
|
16
|
+
* Compute the stealth private key for a detected payment.
|
|
17
|
+
*
|
|
18
|
+
* @param scanningKey - Recipient's scanning private key
|
|
19
|
+
* @param spendingKey - Recipient's spending private key
|
|
20
|
+
* @param ephemeralPubKey - Ephemeral public key from the announcement (33 bytes compressed)
|
|
21
|
+
* @returns 32-byte stealth private key
|
|
22
|
+
* @throws InvalidKeyError if ephemeral public key is invalid
|
|
23
|
+
* @throws StealthAddressError if the derived stealth key is invalid
|
|
24
|
+
*/
|
|
25
|
+
export function computeStealthKey(scanningKey, spendingKey, ephemeralPubKey) {
|
|
26
|
+
// Validate ephemeral public key
|
|
27
|
+
let ephemeralPoint;
|
|
28
|
+
try {
|
|
29
|
+
ephemeralPoint = secp256k1.ProjectivePoint.fromHex(ephemeralPubKey);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new InvalidKeyError('Invalid ephemeral public key');
|
|
33
|
+
}
|
|
34
|
+
// Step 1: ECDH shared secret S = scanningPriv * P_ephemeral
|
|
35
|
+
const scanScalar = bytesToBigInt(scanningKey.bytes);
|
|
36
|
+
const sharedSecretPoint = ephemeralPoint.multiply(scanScalar);
|
|
37
|
+
// Step 2: Hash the compressed shared secret
|
|
38
|
+
const sharedSecretCompressed = sharedSecretPoint.toRawBytes(true); // 33 bytes
|
|
39
|
+
const hashedSecret = keccak_256(sharedSecretCompressed); // 32 bytes
|
|
40
|
+
// Step 3: Stealth private key = (spendingPriv + hashedSecret) mod n
|
|
41
|
+
const n = secp256k1.CURVE.n;
|
|
42
|
+
const spendScalar = bytesToBigInt(spendingKey.bytes);
|
|
43
|
+
const hashedScalar = bytesToBigInt(hashedSecret) % n;
|
|
44
|
+
const stealthScalar = (spendScalar + hashedScalar) % n;
|
|
45
|
+
if (stealthScalar === 0n) {
|
|
46
|
+
throw new StealthAddressError('Derived stealth key is zero');
|
|
47
|
+
}
|
|
48
|
+
// Convert BigInt back to 32-byte Uint8Array (big-endian)
|
|
49
|
+
return bigIntToBytes(stealthScalar, 32);
|
|
50
|
+
}
|
|
51
|
+
/** Convert a Uint8Array to a BigInt (big-endian). */
|
|
52
|
+
function bytesToBigInt(bytes) {
|
|
53
|
+
let result = 0n;
|
|
54
|
+
for (const byte of bytes) {
|
|
55
|
+
result = (result << 8n) | BigInt(byte);
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/** Convert a BigInt to a fixed-length Uint8Array (big-endian). */
|
|
60
|
+
function bigIntToBytes(value, length) {
|
|
61
|
+
const result = new Uint8Array(length);
|
|
62
|
+
let remaining = value;
|
|
63
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
64
|
+
result[i] = Number(remaining & 0xffn);
|
|
65
|
+
remaining >>= 8n;
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=compute-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute-key.js","sourceRoot":"","sources":["../../src/compute-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEnE;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,WAAwB,EACxB,WAAwB,EACxB,eAA2B;IAE3B,gCAAgC;IAChC,IAAI,cAA8D,CAAC;IACnE,IAAI,CAAC;QACH,cAAc,GAAG,SAAS,CAAC,eAAe,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,eAAe,CAAC,8BAA8B,CAAC,CAAC;IAC5D,CAAC;IAED,6DAA6D;IAC7D,MAAM,UAAU,GAAG,aAAa,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,iBAAiB,GAAG,cAAc,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAE9D,4CAA4C;IAC5C,MAAM,sBAAsB,GAAG,iBAAiB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW;IAC9E,MAAM,YAAY,GAAG,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,WAAW;IAEpE,oEAAoE;IACpE,MAAM,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5B,MAAM,WAAW,GAAG,aAAa,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACrD,MAAM,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IACrD,MAAM,aAAa,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAEvD,IAAI,aAAa,KAAK,EAAE,EAAE,CAAC;QACzB,MAAM,IAAI,mBAAmB,CAAC,6BAA6B,CAAC,CAAC;IAC/D,CAAC;IAED,yDAAyD;IACzD,OAAO,aAAa,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED,qDAAqD;AACrD,SAAS,aAAa,CAAC,KAAiB;IACtC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,kEAAkE;AAClE,SAAS,aAAa,CAAC,KAAa,EAAE,MAAc;IAClD,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC;QACtC,SAAS,KAAK,EAAE,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShroudFi Core Constants
|
|
3
|
+
*
|
|
4
|
+
* EIP-5564 / ERC-6538 scheme configuration for Base (EVM).
|
|
5
|
+
*/
|
|
6
|
+
/** EIP-5564 scheme ID for secp256k1 + view tags. */
|
|
7
|
+
export declare const SCHEME_ID = 1;
|
|
8
|
+
/** HKDF salt used for all key derivation from master seed. */
|
|
9
|
+
export declare const HKDF_SALT = "ShroudFi-EVM-v1";
|
|
10
|
+
/**
|
|
11
|
+
* ERC-5564 Announcer singleton address.
|
|
12
|
+
* Deterministic CREATE2 deployment -- same address on every EVM chain including Base.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ERC5564_ANNOUNCER_ADDRESS: "0x55649E01B5Df198D18D95b5cc5051630cfD45564";
|
|
15
|
+
/**
|
|
16
|
+
* ERC-6538 Stealth Meta-Address Registry singleton address.
|
|
17
|
+
* Deterministic CREATE2 deployment -- same address on every EVM chain including Base.
|
|
18
|
+
*/
|
|
19
|
+
export declare const ERC6538_REGISTRY_ADDRESS: "0x6538E6bf4B0eBd30A8Ea093027Ac2422ce5d6538";
|
|
20
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oDAAoD;AACpD,eAAO,MAAM,SAAS,IAAI,CAAC;AAE3B,8DAA8D;AAC9D,eAAO,MAAM,SAAS,oBAAoB,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,yBAAyB,EACpC,4CAAqD,CAAC;AAExD;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EACnC,4CAAqD,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShroudFi Core Constants
|
|
3
|
+
*
|
|
4
|
+
* EIP-5564 / ERC-6538 scheme configuration for Base (EVM).
|
|
5
|
+
*/
|
|
6
|
+
/** EIP-5564 scheme ID for secp256k1 + view tags. */
|
|
7
|
+
export const SCHEME_ID = 1;
|
|
8
|
+
/** HKDF salt used for all key derivation from master seed. */
|
|
9
|
+
export const HKDF_SALT = 'ShroudFi-EVM-v1';
|
|
10
|
+
/**
|
|
11
|
+
* ERC-5564 Announcer singleton address.
|
|
12
|
+
* Deterministic CREATE2 deployment -- same address on every EVM chain including Base.
|
|
13
|
+
*/
|
|
14
|
+
export const ERC5564_ANNOUNCER_ADDRESS = '0x55649E01B5Df198D18D95b5cc5051630cfD45564';
|
|
15
|
+
/**
|
|
16
|
+
* ERC-6538 Stealth Meta-Address Registry singleton address.
|
|
17
|
+
* Deterministic CREATE2 deployment -- same address on every EVM chain including Base.
|
|
18
|
+
*/
|
|
19
|
+
export const ERC6538_REGISTRY_ADDRESS = '0x6538E6bf4B0eBd30A8Ea093027Ac2422ce5d6538';
|
|
20
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oDAAoD;AACpD,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;AAE3B,8DAA8D;AAC9D,MAAM,CAAC,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAE3C;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GACpC,4CAAqD,CAAC;AAExD;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GACnC,4CAAqD,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShroudFi Privacy-Safe Error Hierarchy
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* - NEVER include private keys, seeds, or scalars in error messages.
|
|
6
|
+
* - NEVER include amounts or balances.
|
|
7
|
+
* - NEVER include stealth addresses or ephemeral keys.
|
|
8
|
+
* - Safe to include: error codes, generic descriptions.
|
|
9
|
+
*/
|
|
10
|
+
/** Base error for all ShroudFi operations. */
|
|
11
|
+
export declare class ShroudFiError extends Error {
|
|
12
|
+
readonly code: string;
|
|
13
|
+
constructor(code: string, message: string);
|
|
14
|
+
}
|
|
15
|
+
/** Thrown when a private or public key fails validation. */
|
|
16
|
+
export declare class InvalidKeyError extends ShroudFiError {
|
|
17
|
+
constructor(message?: string);
|
|
18
|
+
}
|
|
19
|
+
/** Thrown when a stealth meta-address cannot be parsed or is malformed. */
|
|
20
|
+
export declare class InvalidMetaAddressError extends ShroudFiError {
|
|
21
|
+
constructor(message?: string);
|
|
22
|
+
}
|
|
23
|
+
/** Thrown when stealth address generation or verification fails. */
|
|
24
|
+
export declare class StealthAddressError extends ShroudFiError {
|
|
25
|
+
constructor(message?: string);
|
|
26
|
+
}
|
|
27
|
+
/** Discriminated union result type -- avoids throwing for sensitive operations. */
|
|
28
|
+
export type Result<T> = {
|
|
29
|
+
readonly ok: true;
|
|
30
|
+
readonly value: T;
|
|
31
|
+
} | {
|
|
32
|
+
readonly ok: false;
|
|
33
|
+
readonly error: ShroudFiError;
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,8CAA8C;AAC9C,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAM1C;AAED,4DAA4D;AAC5D,qBAAa,eAAgB,SAAQ,aAAa;gBACpC,OAAO,GAAE,MAA+B;CAIrD;AAED,2EAA2E;AAC3E,qBAAa,uBAAwB,SAAQ,aAAa;gBAC5C,OAAO,GAAE,MAAuC;CAI7D;AAED,oEAAoE;AACpE,qBAAa,mBAAoB,SAAQ,aAAa;gBACxC,OAAO,GAAE,MAA2C;CAIjE;AAED,mFAAmF;AACnF,MAAM,MAAM,MAAM,CAAC,CAAC,IAChB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACxC;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShroudFi Privacy-Safe Error Hierarchy
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* - NEVER include private keys, seeds, or scalars in error messages.
|
|
6
|
+
* - NEVER include amounts or balances.
|
|
7
|
+
* - NEVER include stealth addresses or ephemeral keys.
|
|
8
|
+
* - Safe to include: error codes, generic descriptions.
|
|
9
|
+
*/
|
|
10
|
+
/** Base error for all ShroudFi operations. */
|
|
11
|
+
export class ShroudFiError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
constructor(code, message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.name = 'ShroudFiError';
|
|
17
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Thrown when a private or public key fails validation. */
|
|
21
|
+
export class InvalidKeyError extends ShroudFiError {
|
|
22
|
+
constructor(message = 'Invalid key material') {
|
|
23
|
+
super('INVALID_KEY', message);
|
|
24
|
+
this.name = 'InvalidKeyError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Thrown when a stealth meta-address cannot be parsed or is malformed. */
|
|
28
|
+
export class InvalidMetaAddressError extends ShroudFiError {
|
|
29
|
+
constructor(message = 'Invalid stealth meta-address') {
|
|
30
|
+
super('INVALID_META_ADDRESS', message);
|
|
31
|
+
this.name = 'InvalidMetaAddressError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Thrown when stealth address generation or verification fails. */
|
|
35
|
+
export class StealthAddressError extends ShroudFiError {
|
|
36
|
+
constructor(message = 'Stealth address operation failed') {
|
|
37
|
+
super('STEALTH_ADDRESS_ERROR', message);
|
|
38
|
+
this.name = 'StealthAddressError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,8CAA8C;AAC9C,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC7B,IAAI,CAAS;IAEtB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAED,4DAA4D;AAC5D,MAAM,OAAO,eAAgB,SAAQ,aAAa;IAChD,YAAY,UAAkB,sBAAsB;QAClD,KAAK,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,2EAA2E;AAC3E,MAAM,OAAO,uBAAwB,SAAQ,aAAa;IACxD,YAAY,UAAkB,8BAA8B;QAC1D,KAAK,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,oEAAoE;AACpE,MAAM,OAAO,mBAAoB,SAAQ,aAAa;IACpD,YAAY,UAAkB,kCAAkC;QAC9D,KAAK,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF"}
|