@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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -0
  3. package/dist/cjs/compute-key.d.ts +24 -0
  4. package/dist/cjs/compute-key.d.ts.map +1 -0
  5. package/dist/cjs/compute-key.js +72 -0
  6. package/dist/cjs/compute-key.js.map +1 -0
  7. package/dist/cjs/constants.d.ts +20 -0
  8. package/dist/cjs/constants.d.ts.map +1 -0
  9. package/dist/cjs/constants.js +23 -0
  10. package/dist/cjs/constants.js.map +1 -0
  11. package/dist/cjs/errors.d.ts +35 -0
  12. package/dist/cjs/errors.d.ts.map +1 -0
  13. package/dist/cjs/errors.js +48 -0
  14. package/dist/cjs/errors.js.map +1 -0
  15. package/dist/cjs/identity.d.ts +20 -0
  16. package/dist/cjs/identity.d.ts.map +1 -0
  17. package/dist/cjs/identity.js +38 -0
  18. package/dist/cjs/identity.js.map +1 -0
  19. package/dist/cjs/index.d.ts +12 -0
  20. package/dist/cjs/index.d.ts.map +1 -0
  21. package/dist/cjs/index.js +36 -0
  22. package/dist/cjs/index.js.map +1 -0
  23. package/dist/cjs/keys.d.ts +17 -0
  24. package/dist/cjs/keys.d.ts.map +1 -0
  25. package/dist/cjs/keys.js +51 -0
  26. package/dist/cjs/keys.js.map +1 -0
  27. package/dist/cjs/meta-address.d.ts +26 -0
  28. package/dist/cjs/meta-address.d.ts.map +1 -0
  29. package/dist/cjs/meta-address.js +86 -0
  30. package/dist/cjs/meta-address.js.map +1 -0
  31. package/dist/cjs/package.json +1 -0
  32. package/dist/cjs/stealth.d.ts +22 -0
  33. package/dist/cjs/stealth.d.ts.map +1 -0
  34. package/dist/cjs/stealth.js +100 -0
  35. package/dist/cjs/stealth.js.map +1 -0
  36. package/dist/cjs/types.d.ts +46 -0
  37. package/dist/cjs/types.d.ts.map +1 -0
  38. package/dist/cjs/types.js +9 -0
  39. package/dist/cjs/types.js.map +1 -0
  40. package/dist/cjs/view-tag.d.ts +29 -0
  41. package/dist/cjs/view-tag.d.ts.map +1 -0
  42. package/dist/cjs/view-tag.js +62 -0
  43. package/dist/cjs/view-tag.js.map +1 -0
  44. package/dist/esm/compute-key.d.ts +24 -0
  45. package/dist/esm/compute-key.d.ts.map +1 -0
  46. package/dist/esm/compute-key.js +69 -0
  47. package/dist/esm/compute-key.js.map +1 -0
  48. package/dist/esm/constants.d.ts +20 -0
  49. package/dist/esm/constants.d.ts.map +1 -0
  50. package/dist/esm/constants.js +20 -0
  51. package/dist/esm/constants.js.map +1 -0
  52. package/dist/esm/errors.d.ts +35 -0
  53. package/dist/esm/errors.d.ts.map +1 -0
  54. package/dist/esm/errors.js +41 -0
  55. package/dist/esm/errors.js.map +1 -0
  56. package/dist/esm/identity.d.ts +20 -0
  57. package/dist/esm/identity.d.ts.map +1 -0
  58. package/dist/esm/identity.js +35 -0
  59. package/dist/esm/identity.js.map +1 -0
  60. package/dist/esm/index.d.ts +12 -0
  61. package/dist/esm/index.d.ts.map +1 -0
  62. package/dist/esm/index.js +17 -0
  63. package/dist/esm/index.js.map +1 -0
  64. package/dist/esm/keys.d.ts +17 -0
  65. package/dist/esm/keys.d.ts.map +1 -0
  66. package/dist/esm/keys.js +48 -0
  67. package/dist/esm/keys.js.map +1 -0
  68. package/dist/esm/meta-address.d.ts +26 -0
  69. package/dist/esm/meta-address.d.ts.map +1 -0
  70. package/dist/esm/meta-address.js +82 -0
  71. package/dist/esm/meta-address.js.map +1 -0
  72. package/dist/esm/stealth.d.ts +22 -0
  73. package/dist/esm/stealth.d.ts.map +1 -0
  74. package/dist/esm/stealth.js +97 -0
  75. package/dist/esm/stealth.js.map +1 -0
  76. package/dist/esm/types.d.ts +46 -0
  77. package/dist/esm/types.d.ts.map +1 -0
  78. package/dist/esm/types.js +8 -0
  79. package/dist/esm/types.js.map +1 -0
  80. package/dist/esm/view-tag.d.ts +29 -0
  81. package/dist/esm/view-tag.d.ts.map +1 -0
  82. package/dist/esm/view-tag.js +58 -0
  83. package/dist/esm/view-tag.js.map +1 -0
  84. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  85. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  86. package/dist/tsconfig.tsbuildinfo +1 -0
  87. 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"}
@@ -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":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnE;;;GAGG;AACH,SAAS,eAAe,CAAC,cAA0B;IACjD,MAAM,IAAI,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACjC,OAAO,KAAK,UAAU,CAAC,SAAS,CAAC,EAAmB,CAAC;AACvD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAwB;IAExB,0EAA0E;IAC1E,IAAI,YAA4D,CAAC;IACjE,IAAI,aAA6D,CAAC;IAClE,IAAI,CAAC;QACH,YAAY,GAAG,SAAS,CAAC,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,eAAe,CAAC,4CAA4C,CAAC,CAAC;IAC1E,CAAC;IACD,IAAI,CAAC;QACH,aAAa,GAAG,SAAS,CAAC,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,eAAe,CAAC,6CAA6C,CAAC,CAAC;IAC3E,CAAC;IAED,qCAAqC;IACrC,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC;IACzD,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC,aAAa;IAE/E,IAAI,CAAC;QACH,6CAA6C;QAC7C,MAAM,eAAe,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;QACrD,MAAM,iBAAiB,GAAG,YAAY,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;QAEjE,4CAA4C;QAC5C,MAAM,sBAAsB,GAAG,iBAAiB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW;QAC9E,MAAM,YAAY,GAAG,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC,WAAW;QAEpE,gCAAgC;QAChC,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAE,CAAC;QAEjC,0DAA0D;QAC1D,MAAM,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACrE,IAAI,YAAY,KAAK,EAAE,EAAE,CAAC;YACxB,MAAM,IAAI,mBAAmB,CAAC,+BAA+B,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,WAAW,GAAG,SAAS,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QAC1E,MAAM,eAAe,GAAG,aAAa,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAEvD,+BAA+B;QAC/B,IAAI,CAAC;YACH,eAAe,CAAC,cAAc,EAAE,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,mBAAmB,CAAC,6CAA6C,CAAC,CAAC;QAC/E,CAAC;QAED,kEAAkE;QAClE,MAAM,sBAAsB,GAAG,eAAe,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,0BAA0B;QAC5F,MAAM,cAAc,GAAG,eAAe,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,oBAAoB;QAE7F,OAAO;YACL,cAAc;YACd,eAAe,EAAE,YAAY;YAC7B,OAAO;SACR,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,6BAA6B;QAC7B,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;AACH,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,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,8 @@
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
+ export {};
8
+ //# 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,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"}