@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,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Identity Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates a complete ShroudFi agent identity (key hierarchy + meta-address)
|
|
5
|
+
* from an optional master seed. If no seed is provided, generates 32
|
|
6
|
+
* cryptographically random bytes via crypto.getRandomValues.
|
|
7
|
+
*/
|
|
8
|
+
import type { KeyHierarchy, StealthMetaAddress } from './types.js';
|
|
9
|
+
export interface AgentIdentity {
|
|
10
|
+
readonly keys: KeyHierarchy;
|
|
11
|
+
readonly metaAddress: StealthMetaAddress;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a full agent identity with key hierarchy and meta-address.
|
|
15
|
+
*
|
|
16
|
+
* @param masterSeed - Optional 32-byte seed. If omitted, generates random bytes.
|
|
17
|
+
* @returns AgentIdentity with derived keys and meta-address
|
|
18
|
+
*/
|
|
19
|
+
export declare function createAgentIdentity(masterSeed?: Uint8Array): AgentIdentity;
|
|
20
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/identity.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAInE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,kBAAkB,CAAC;CAC1C;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,CAAC,EAAE,UAAU,GAAG,aAAa,CAmB1E"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Identity Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates a complete ShroudFi agent identity (key hierarchy + meta-address)
|
|
5
|
+
* from an optional master seed. If no seed is provided, generates 32
|
|
6
|
+
* cryptographically random bytes via crypto.getRandomValues.
|
|
7
|
+
*/
|
|
8
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
9
|
+
import { randomBytes } from '@noble/hashes/utils';
|
|
10
|
+
import { deriveKeys } from './keys.js';
|
|
11
|
+
import { SCHEME_ID } from './constants.js';
|
|
12
|
+
/**
|
|
13
|
+
* Create a full agent identity with key hierarchy and meta-address.
|
|
14
|
+
*
|
|
15
|
+
* @param masterSeed - Optional 32-byte seed. If omitted, generates random bytes.
|
|
16
|
+
* @returns AgentIdentity with derived keys and meta-address
|
|
17
|
+
*/
|
|
18
|
+
export function createAgentIdentity(masterSeed) {
|
|
19
|
+
const seed = masterSeed ?? randomBytes(32);
|
|
20
|
+
const keys = deriveKeys(seed);
|
|
21
|
+
// Derive compressed public keys from the private keys.
|
|
22
|
+
// EIP-5564 scheme 1 has only spending + viewing keys, where "viewing" is the
|
|
23
|
+
// key used for ECDH scanning. In the @shroud-fi/core hierarchy we name that
|
|
24
|
+
// ECDH-scan key `scanningKey` (its semantic role). The separate `viewingKey`
|
|
25
|
+
// is reserved for future compliance/delegated-disclosure flows.
|
|
26
|
+
const spendingPubKey = secp256k1.getPublicKey(keys.spendingKey.bytes, true);
|
|
27
|
+
const viewingPubKey = secp256k1.getPublicKey(keys.scanningKey.bytes, true);
|
|
28
|
+
const metaAddress = {
|
|
29
|
+
spendingPubKey,
|
|
30
|
+
viewingPubKey,
|
|
31
|
+
schemeId: SCHEME_ID,
|
|
32
|
+
};
|
|
33
|
+
return { keys, metaAddress };
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/identity.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAO3C;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAAuB;IACzD,MAAM,IAAI,GAAG,UAAU,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAE9B,uDAAuD;IACvD,6EAA6E;IAC7E,4EAA4E;IAC5E,6EAA6E;IAC7E,gEAAgE;IAChE,MAAM,cAAc,GAAG,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC5E,MAAM,aAAa,GAAG,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAE3E,MAAM,WAAW,GAAuB;QACtC,cAAc;QACd,aAAa;QACb,QAAQ,EAAE,SAAS;KACpB,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type { SpendingKey, ScanningKey, ViewingKey, StealthMetaAddress, StealthAddressResult, KeyHierarchy, } from './types.js';
|
|
2
|
+
export { ShroudFiError, InvalidKeyError, InvalidMetaAddressError, StealthAddressError, } from './errors.js';
|
|
3
|
+
export type { Result } from './errors.js';
|
|
4
|
+
export { SCHEME_ID, HKDF_SALT, ERC5564_ANNOUNCER_ADDRESS, ERC6538_REGISTRY_ADDRESS, } from './constants.js';
|
|
5
|
+
export { deriveKeys } from './keys.js';
|
|
6
|
+
export { generateStealthAddress } from './stealth.js';
|
|
7
|
+
export { computeStealthKey } from './compute-key.js';
|
|
8
|
+
export { extractViewTag, checkViewTag } from './view-tag.js';
|
|
9
|
+
export { encodeMetaAddress, decodeMetaAddress } from './meta-address.js';
|
|
10
|
+
export { createAgentIdentity } from './identity.js';
|
|
11
|
+
export type { AgentIdentity } from './identity.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,aAAa,EACb,eAAe,EACf,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,EACL,SAAS,EACT,SAAS,EACT,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAGvC,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAGtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG7D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Errors
|
|
2
|
+
export { ShroudFiError, InvalidKeyError, InvalidMetaAddressError, StealthAddressError, } from './errors.js';
|
|
3
|
+
// Constants
|
|
4
|
+
export { SCHEME_ID, HKDF_SALT, ERC5564_ANNOUNCER_ADDRESS, ERC6538_REGISTRY_ADDRESS, } from './constants.js';
|
|
5
|
+
// Key derivation
|
|
6
|
+
export { deriveKeys } from './keys.js';
|
|
7
|
+
// Stealth address generation (sender side)
|
|
8
|
+
export { generateStealthAddress } from './stealth.js';
|
|
9
|
+
// Stealth key computation (recipient side)
|
|
10
|
+
export { computeStealthKey } from './compute-key.js';
|
|
11
|
+
// View tag utilities
|
|
12
|
+
export { extractViewTag, checkViewTag } from './view-tag.js';
|
|
13
|
+
// Meta-address encode/decode
|
|
14
|
+
export { encodeMetaAddress, decodeMetaAddress } from './meta-address.js';
|
|
15
|
+
// Agent identity factory
|
|
16
|
+
export { createAgentIdentity } from './identity.js';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAUA,SAAS;AACT,OAAO,EACL,aAAa,EACb,eAAe,EACf,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAGrB,YAAY;AACZ,OAAO,EACL,SAAS,EACT,SAAS,EACT,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AAExB,iBAAiB;AACjB,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,2CAA2C;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAEtD,2CAA2C;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,qBAAqB;AACrB,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7D,6BAA6B;AAC7B,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEzE,yBAAyB;AACzB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key Derivation -- HKDF-SHA256
|
|
3
|
+
*
|
|
4
|
+
* Derives a full KeyHierarchy from a 32-byte master seed using
|
|
5
|
+
* HKDF with domain-separated info labels. Each derived key is
|
|
6
|
+
* validated as a valid secp256k1 scalar (1 < k < n).
|
|
7
|
+
*/
|
|
8
|
+
import type { KeyHierarchy } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Derive the full ShroudFi key hierarchy from a master seed.
|
|
11
|
+
*
|
|
12
|
+
* @param masterSeed - 32-byte cryptographically random seed
|
|
13
|
+
* @returns KeyHierarchy with spending, scanning, and viewing keys
|
|
14
|
+
* @throws InvalidKeyError if seed length is wrong or a derived key is invalid
|
|
15
|
+
*/
|
|
16
|
+
export declare function deriveKeys(masterSeed: Uint8Array): KeyHierarchy;
|
|
17
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAwC,MAAM,YAAY,CAAC;AAiBrF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,UAAU,GAAG,YAAY,CAmB/D"}
|
package/dist/esm/keys.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key Derivation -- HKDF-SHA256
|
|
3
|
+
*
|
|
4
|
+
* Derives a full KeyHierarchy from a 32-byte master seed using
|
|
5
|
+
* HKDF with domain-separated info labels. Each derived key is
|
|
6
|
+
* validated as a valid secp256k1 scalar (1 < k < n).
|
|
7
|
+
*/
|
|
8
|
+
import { hkdf } from '@noble/hashes/hkdf';
|
|
9
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
10
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
11
|
+
import { InvalidKeyError } from './errors.js';
|
|
12
|
+
import { HKDF_SALT } from './constants.js';
|
|
13
|
+
/**
|
|
14
|
+
* Validate that a 32-byte array is a valid secp256k1 private key scalar.
|
|
15
|
+
* Must satisfy: 1 <= k < n (curve order).
|
|
16
|
+
*/
|
|
17
|
+
function assertValidScalar(bytes, label) {
|
|
18
|
+
if (bytes.length !== 32) {
|
|
19
|
+
throw new InvalidKeyError(`Derived ${label} has invalid length`);
|
|
20
|
+
}
|
|
21
|
+
if (!secp256k1.utils.isValidPrivateKey(bytes)) {
|
|
22
|
+
throw new InvalidKeyError(`Derived ${label} is not a valid secp256k1 scalar`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Derive the full ShroudFi key hierarchy from a master seed.
|
|
27
|
+
*
|
|
28
|
+
* @param masterSeed - 32-byte cryptographically random seed
|
|
29
|
+
* @returns KeyHierarchy with spending, scanning, and viewing keys
|
|
30
|
+
* @throws InvalidKeyError if seed length is wrong or a derived key is invalid
|
|
31
|
+
*/
|
|
32
|
+
export function deriveKeys(masterSeed) {
|
|
33
|
+
if (masterSeed.length !== 32) {
|
|
34
|
+
throw new InvalidKeyError('Master seed must be exactly 32 bytes');
|
|
35
|
+
}
|
|
36
|
+
const spendingBytes = hkdf(sha256, masterSeed, HKDF_SALT, 'spending-key', 32);
|
|
37
|
+
assertValidScalar(spendingBytes, 'spending key');
|
|
38
|
+
const scanningBytes = hkdf(sha256, masterSeed, HKDF_SALT, 'scanning-key', 32);
|
|
39
|
+
assertValidScalar(scanningBytes, 'scanning key');
|
|
40
|
+
const viewingBytes = hkdf(sha256, masterSeed, HKDF_SALT, 'viewing-key', 32);
|
|
41
|
+
assertValidScalar(viewingBytes, 'viewing key');
|
|
42
|
+
return {
|
|
43
|
+
spendingKey: { __brand: 'SpendingKey', bytes: spendingBytes },
|
|
44
|
+
scanningKey: { __brand: 'ScanningKey', bytes: scanningBytes },
|
|
45
|
+
viewingKey: { __brand: 'ViewingKey', bytes: viewingBytes },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C;;;GAGG;AACH,SAAS,iBAAiB,CAAC,KAAiB,EAAE,KAAa;IACzD,IAAI,KAAK,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QACxB,MAAM,IAAI,eAAe,CAAC,WAAW,KAAK,qBAAqB,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,eAAe,CAAC,WAAW,KAAK,kCAAkC,CAAC,CAAC;IAChF,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,UAAsB;IAC/C,IAAI,UAAU,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QAC7B,MAAM,IAAI,eAAe,CAAC,sCAAsC,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;IAC9E,iBAAiB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IAEjD,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;IAC9E,iBAAiB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IAEjD,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;IAC5E,iBAAiB,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;IAE/C,OAAO;QACL,WAAW,EAAE,EAAE,OAAO,EAAE,aAAsB,EAAE,KAAK,EAAE,aAAa,EAAiB;QACrF,WAAW,EAAE,EAAE,OAAO,EAAE,aAAsB,EAAE,KAAK,EAAE,aAAa,EAAiB;QACrF,UAAU,EAAE,EAAE,OAAO,EAAE,YAAqB,EAAE,KAAK,EAAE,YAAY,EAAgB;KAClF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ERC-6538 Stealth Meta-Address Encode/Decode
|
|
3
|
+
*
|
|
4
|
+
* Format: st:base:0x<spendingPubKey 33 bytes hex><viewingPubKey 33 bytes hex>
|
|
5
|
+
*
|
|
6
|
+
* Both keys are SEC1 compressed secp256k1 public keys (prefix 0x02 or 0x03).
|
|
7
|
+
* Total raw payload: 66 bytes = 132 hex characters.
|
|
8
|
+
*/
|
|
9
|
+
import type { StealthMetaAddress } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Encode a StealthMetaAddress into the ERC-6538 URI format.
|
|
12
|
+
*
|
|
13
|
+
* @param meta - Stealth meta-address containing spending and viewing public keys
|
|
14
|
+
* @returns Encoded string: "st:base:0x<spendHex><viewHex>"
|
|
15
|
+
* @throws InvalidMetaAddressError if keys are invalid
|
|
16
|
+
*/
|
|
17
|
+
export declare function encodeMetaAddress(meta: StealthMetaAddress): string;
|
|
18
|
+
/**
|
|
19
|
+
* Decode an ERC-6538 URI string into a StealthMetaAddress.
|
|
20
|
+
*
|
|
21
|
+
* @param encoded - String in format "st:base:0x<66 bytes hex>"
|
|
22
|
+
* @returns Parsed StealthMetaAddress
|
|
23
|
+
* @throws InvalidMetaAddressError if format is invalid
|
|
24
|
+
*/
|
|
25
|
+
export declare function decodeMetaAddress(encoded: string): StealthMetaAddress;
|
|
26
|
+
//# sourceMappingURL=meta-address.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta-address.d.ts","sourceRoot":"","sources":["../../src/meta-address.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAgCrD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,CAQlE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAiCrE"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ERC-6538 Stealth Meta-Address Encode/Decode
|
|
3
|
+
*
|
|
4
|
+
* Format: st:base:0x<spendingPubKey 33 bytes hex><viewingPubKey 33 bytes hex>
|
|
5
|
+
*
|
|
6
|
+
* Both keys are SEC1 compressed secp256k1 public keys (prefix 0x02 or 0x03).
|
|
7
|
+
* Total raw payload: 66 bytes = 132 hex characters.
|
|
8
|
+
*/
|
|
9
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
10
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
|
11
|
+
import { InvalidMetaAddressError } from './errors.js';
|
|
12
|
+
import { SCHEME_ID } from './constants.js';
|
|
13
|
+
const META_ADDRESS_PREFIX = 'st:base:0x';
|
|
14
|
+
const COMPRESSED_KEY_LENGTH = 33; // bytes
|
|
15
|
+
const TOTAL_PAYLOAD_LENGTH = COMPRESSED_KEY_LENGTH * 2; // 66 bytes
|
|
16
|
+
/**
|
|
17
|
+
* Validate that a byte array is a valid compressed secp256k1 public key.
|
|
18
|
+
* Must be 33 bytes with 0x02 or 0x03 prefix.
|
|
19
|
+
*/
|
|
20
|
+
function assertValidCompressedKey(key, label) {
|
|
21
|
+
if (key.length !== COMPRESSED_KEY_LENGTH) {
|
|
22
|
+
throw new InvalidMetaAddressError(`${label} must be ${COMPRESSED_KEY_LENGTH} bytes, got ${key.length}`);
|
|
23
|
+
}
|
|
24
|
+
const prefix = key[0];
|
|
25
|
+
if (prefix !== 0x02 && prefix !== 0x03) {
|
|
26
|
+
throw new InvalidMetaAddressError(`${label} must have 0x02 or 0x03 prefix`);
|
|
27
|
+
}
|
|
28
|
+
// Validate it's actually on the curve
|
|
29
|
+
try {
|
|
30
|
+
secp256k1.ProjectivePoint.fromHex(key);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new InvalidMetaAddressError(`${label} is not a valid secp256k1 point`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Encode a StealthMetaAddress into the ERC-6538 URI format.
|
|
38
|
+
*
|
|
39
|
+
* @param meta - Stealth meta-address containing spending and viewing public keys
|
|
40
|
+
* @returns Encoded string: "st:base:0x<spendHex><viewHex>"
|
|
41
|
+
* @throws InvalidMetaAddressError if keys are invalid
|
|
42
|
+
*/
|
|
43
|
+
export function encodeMetaAddress(meta) {
|
|
44
|
+
assertValidCompressedKey(meta.spendingPubKey, 'Spending public key');
|
|
45
|
+
assertValidCompressedKey(meta.viewingPubKey, 'Viewing public key');
|
|
46
|
+
const spendHex = bytesToHex(meta.spendingPubKey);
|
|
47
|
+
const viewHex = bytesToHex(meta.viewingPubKey);
|
|
48
|
+
return `${META_ADDRESS_PREFIX}${spendHex}${viewHex}`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Decode an ERC-6538 URI string into a StealthMetaAddress.
|
|
52
|
+
*
|
|
53
|
+
* @param encoded - String in format "st:base:0x<66 bytes hex>"
|
|
54
|
+
* @returns Parsed StealthMetaAddress
|
|
55
|
+
* @throws InvalidMetaAddressError if format is invalid
|
|
56
|
+
*/
|
|
57
|
+
export function decodeMetaAddress(encoded) {
|
|
58
|
+
if (!encoded.startsWith(META_ADDRESS_PREFIX)) {
|
|
59
|
+
throw new InvalidMetaAddressError(`Meta-address must start with "${META_ADDRESS_PREFIX}"`);
|
|
60
|
+
}
|
|
61
|
+
const hexPayload = encoded.slice(META_ADDRESS_PREFIX.length);
|
|
62
|
+
if (hexPayload.length !== TOTAL_PAYLOAD_LENGTH * 2) {
|
|
63
|
+
throw new InvalidMetaAddressError(`Meta-address hex payload must be ${TOTAL_PAYLOAD_LENGTH * 2} characters, got ${hexPayload.length}`);
|
|
64
|
+
}
|
|
65
|
+
let payloadBytes;
|
|
66
|
+
try {
|
|
67
|
+
payloadBytes = hexToBytes(hexPayload);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
throw new InvalidMetaAddressError('Meta-address contains invalid hex characters');
|
|
71
|
+
}
|
|
72
|
+
const spendingPubKey = payloadBytes.slice(0, COMPRESSED_KEY_LENGTH);
|
|
73
|
+
const viewingPubKey = payloadBytes.slice(COMPRESSED_KEY_LENGTH);
|
|
74
|
+
assertValidCompressedKey(spendingPubKey, 'Spending public key');
|
|
75
|
+
assertValidCompressedKey(viewingPubKey, 'Viewing public key');
|
|
76
|
+
return {
|
|
77
|
+
spendingPubKey,
|
|
78
|
+
viewingPubKey,
|
|
79
|
+
schemeId: SCHEME_ID,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=meta-address.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta-address.js","sourceRoot":"","sources":["../../src/meta-address.ts"],"names":[],"mappings":"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"}
|
|
@@ -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,97 @@
|
|
|
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 { secp256k1 } from '@noble/curves/secp256k1';
|
|
13
|
+
import { keccak_256 } from '@noble/hashes/sha3';
|
|
14
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
15
|
+
import { StealthAddressError, InvalidKeyError } from './errors.js';
|
|
16
|
+
/**
|
|
17
|
+
* Convert a 64-byte uncompressed public key (no 0x04 prefix) to an EVM address.
|
|
18
|
+
* address = last 20 bytes of keccak256(pubkey64)
|
|
19
|
+
*/
|
|
20
|
+
function pubKeyToAddress(uncompressed64) {
|
|
21
|
+
const hash = keccak_256(uncompressed64);
|
|
22
|
+
const addrBytes = hash.slice(12);
|
|
23
|
+
return `0x${bytesToHex(addrBytes)}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generate a one-time stealth address for a recipient.
|
|
27
|
+
*
|
|
28
|
+
* @param meta - Recipient's stealth meta-address (two compressed public keys)
|
|
29
|
+
* @returns StealthAddressResult with stealth address, ephemeral pubkey, and view tag
|
|
30
|
+
* @throws InvalidKeyError if meta-address public keys are invalid
|
|
31
|
+
* @throws StealthAddressError if the derived stealth point is at infinity
|
|
32
|
+
*/
|
|
33
|
+
export function generateStealthAddress(meta) {
|
|
34
|
+
// Validate input public keys by parsing them (fromHex validates on-curve)
|
|
35
|
+
let viewingPoint;
|
|
36
|
+
let spendingPoint;
|
|
37
|
+
try {
|
|
38
|
+
viewingPoint = secp256k1.ProjectivePoint.fromHex(meta.viewingPubKey);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
throw new InvalidKeyError('Invalid viewing public key in meta-address');
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
spendingPoint = secp256k1.ProjectivePoint.fromHex(meta.spendingPubKey);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new InvalidKeyError('Invalid spending public key in meta-address');
|
|
48
|
+
}
|
|
49
|
+
// Step 1: Generate ephemeral keypair
|
|
50
|
+
const ephemeralPriv = secp256k1.utils.randomPrivateKey();
|
|
51
|
+
const ephemeralPub = secp256k1.getPublicKey(ephemeralPriv, true); // compressed
|
|
52
|
+
try {
|
|
53
|
+
// Step 2: ECDH shared secret S = r * P_view
|
|
54
|
+
const ephemeralScalar = bytesToBigInt(ephemeralPriv);
|
|
55
|
+
const sharedSecretPoint = viewingPoint.multiply(ephemeralScalar);
|
|
56
|
+
// Step 3: Hash the compressed shared secret
|
|
57
|
+
const sharedSecretCompressed = sharedSecretPoint.toRawBytes(true); // 33 bytes
|
|
58
|
+
const hashedSecret = keccak_256(sharedSecretCompressed); // 32 bytes
|
|
59
|
+
// Step 4: View tag = first byte
|
|
60
|
+
const viewTag = hashedSecret[0];
|
|
61
|
+
// Step 5: Stealth public key = P_spend + hashedSecret * G
|
|
62
|
+
const hashedScalar = bytesToBigInt(hashedSecret) % secp256k1.CURVE.n;
|
|
63
|
+
if (hashedScalar === 0n) {
|
|
64
|
+
throw new StealthAddressError('Hashed secret reduced to zero');
|
|
65
|
+
}
|
|
66
|
+
const hashedPoint = secp256k1.ProjectivePoint.BASE.multiply(hashedScalar);
|
|
67
|
+
const stealthPubPoint = spendingPoint.add(hashedPoint);
|
|
68
|
+
// Verify not point at infinity
|
|
69
|
+
try {
|
|
70
|
+
stealthPubPoint.assertValidity();
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
throw new StealthAddressError('Stealth public key is the point at infinity');
|
|
74
|
+
}
|
|
75
|
+
// Step 6: Derive EVM address from uncompressed stealth public key
|
|
76
|
+
const stealthPubUncompressed = stealthPubPoint.toRawBytes(false); // 65 bytes (04 || x || y)
|
|
77
|
+
const stealthAddress = pubKeyToAddress(stealthPubUncompressed.slice(1)); // strip 0x04 prefix
|
|
78
|
+
return {
|
|
79
|
+
stealthAddress,
|
|
80
|
+
ephemeralPubKey: ephemeralPub,
|
|
81
|
+
viewTag,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
// Zero ephemeral private key
|
|
86
|
+
ephemeralPriv.fill(0);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Convert a Uint8Array to a BigInt (big-endian). */
|
|
90
|
+
function bytesToBigInt(bytes) {
|
|
91
|
+
let result = 0n;
|
|
92
|
+
for (const byte of bytes) {
|
|
93
|
+
result = (result << 8n) | BigInt(byte);
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
//# 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 @@
|
|
|
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,58 @@
|
|
|
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 { secp256k1 } from '@noble/curves/secp256k1';
|
|
9
|
+
import { keccak_256 } from '@noble/hashes/sha3';
|
|
10
|
+
import { InvalidKeyError } from './errors.js';
|
|
11
|
+
/**
|
|
12
|
+
* Extract the view tag from a compressed shared secret point.
|
|
13
|
+
* The view tag is byte 0 of keccak256(compressedPoint).
|
|
14
|
+
*
|
|
15
|
+
* @param sharedSecretCompressed - 33-byte compressed secp256k1 point
|
|
16
|
+
* @returns View tag value (0-255)
|
|
17
|
+
*/
|
|
18
|
+
export function extractViewTag(sharedSecretCompressed) {
|
|
19
|
+
const hash = keccak_256(sharedSecretCompressed);
|
|
20
|
+
return hash[0];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check whether a view tag matches for a given scanning key and ephemeral public key.
|
|
24
|
+
*
|
|
25
|
+
* Performs the ECDH step and view tag extraction, then compares against
|
|
26
|
+
* the expected tag. This is the fast-path filter for scanning.
|
|
27
|
+
*
|
|
28
|
+
* @param scanningKey - Recipient's scanning private key
|
|
29
|
+
* @param ephemeralPubKey - Ephemeral public key from the announcement (33 bytes)
|
|
30
|
+
* @param expectedTag - The view tag from the announcement metadata
|
|
31
|
+
* @returns true if the view tag matches (candidate for full verification)
|
|
32
|
+
*/
|
|
33
|
+
export function checkViewTag(scanningKey, ephemeralPubKey, expectedTag) {
|
|
34
|
+
// Validate ephemeral public key
|
|
35
|
+
let ephemeralPoint;
|
|
36
|
+
try {
|
|
37
|
+
ephemeralPoint = secp256k1.ProjectivePoint.fromHex(ephemeralPubKey);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
throw new InvalidKeyError('Invalid ephemeral public key');
|
|
41
|
+
}
|
|
42
|
+
// ECDH: sharedSecret = scanningPriv * P_ephemeral
|
|
43
|
+
const scanScalar = bytesToBigInt(scanningKey.bytes);
|
|
44
|
+
const sharedSecretPoint = ephemeralPoint.multiply(scanScalar);
|
|
45
|
+
// Hash compressed shared secret and extract view tag
|
|
46
|
+
const sharedSecretCompressed = sharedSecretPoint.toRawBytes(true);
|
|
47
|
+
const tag = extractViewTag(sharedSecretCompressed);
|
|
48
|
+
return tag === expectedTag;
|
|
49
|
+
}
|
|
50
|
+
/** Convert a Uint8Array to a BigInt (big-endian). */
|
|
51
|
+
function bytesToBigInt(bytes) {
|
|
52
|
+
let result = 0n;
|
|
53
|
+
for (const byte of bytes) {
|
|
54
|
+
result = (result << 8n) | BigInt(byte);
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
//# 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;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,sBAAkC;IAC/D,MAAM,IAAI,GAAG,UAAU,CAAC,sBAAsB,CAAC,CAAC;IAChD,OAAO,IAAI,CAAC,CAAC,CAAE,CAAC;AAClB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAC1B,WAAwB,EACxB,eAA2B,EAC3B,WAAmB;IAEnB,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,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"}
|