@coordination-network/identity 0.0.1

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 (39) hide show
  1. package/DERIVATION.md +313 -0
  2. package/LICENSE +21 -0
  3. package/README.md +258 -0
  4. package/dist/index.cjs +957 -0
  5. package/dist/index.js +893 -0
  6. package/package.json +57 -0
  7. package/src/account.ts +61 -0
  8. package/src/auth.ts +20 -0
  9. package/src/binding.ts +298 -0
  10. package/src/bytes.ts +142 -0
  11. package/src/confirmation-key.ts +50 -0
  12. package/src/constants.ts +40 -0
  13. package/src/derive.ts +41 -0
  14. package/src/device-secret.ts +24 -0
  15. package/src/eddsa-peer.ts +32 -0
  16. package/src/eddsa.ts +37 -0
  17. package/src/errors.ts +76 -0
  18. package/src/hash-to-scalar.ts +35 -0
  19. package/src/index.ts +53 -0
  20. package/src/keypair.ts +38 -0
  21. package/src/network-id.ts +26 -0
  22. package/src/stealthAddresses/assertCompressedPublicKey.ts +25 -0
  23. package/src/stealthAddresses/buildAnnouncement.ts +37 -0
  24. package/src/stealthAddresses/checkStealthAddress.ts +37 -0
  25. package/src/stealthAddresses/computeSharedSecret.ts +16 -0
  26. package/src/stealthAddresses/decodeMetaStealthAddress.ts +27 -0
  27. package/src/stealthAddresses/deriveStealthKeys.ts +30 -0
  28. package/src/stealthAddresses/encodeMetaStealthAddress.ts +19 -0
  29. package/src/stealthAddresses/encodeMetaStealthAddressUri.ts +8 -0
  30. package/src/stealthAddresses/generateEphemeralKey.ts +12 -0
  31. package/src/stealthAddresses/generateStealthAddress.ts +27 -0
  32. package/src/stealthAddresses/generateStealthPrivateKey.ts +29 -0
  33. package/src/stealthAddresses/getStealthPublicKey.ts +21 -0
  34. package/src/stealthAddresses/getViewTag.ts +7 -0
  35. package/src/stealthAddresses/metaStealthAddressUri.ts +7 -0
  36. package/src/stealthAddresses/publicKeyToAddress.ts +17 -0
  37. package/src/stealthAddresses/validateMetaStealthAddress.ts +19 -0
  38. package/src/types.ts +51 -0
  39. package/types/index.d.ts +499 -0
@@ -0,0 +1,21 @@
1
+ import { secp256k1 } from "@noble/curves/secp256k1"
2
+ import { bytesToNumberBE } from "@noble/curves/utils"
3
+ import { Hex } from "../types"
4
+
5
+ /**
6
+ * Generates stealth public key. ERC-5564 scheme 1: P_stealth = P_spend + (s_h mod n)·G,
7
+ * returned as a 65-byte uncompressed public key.
8
+ */
9
+ export function getStealthPublicKey({
10
+ spendingPublicKey,
11
+ hashedSharedSecret
12
+ }: {
13
+ spendingPublicKey: Hex
14
+ hashedSharedSecret: Uint8Array
15
+ }): Uint8Array {
16
+ const sharedSecretScalar = bytesToNumberBE(hashedSharedSecret) % secp256k1.Point.CURVE().n
17
+
18
+ return secp256k1.Point.fromHex(spendingPublicKey.slice(2))
19
+ .add(secp256k1.Point.BASE.multiply(sharedSecretScalar))
20
+ .toBytes(false)
21
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * First byte of the hashed shared secret, published in announcements so
3
+ * scanners can skip 255/256 non-matching shared secrets.
4
+ */
5
+ export function getViewTag(hashedSharedSecret: Uint8Array): number {
6
+ return hashedSharedSecret[0]
7
+ }
@@ -0,0 +1,7 @@
1
+ import { Hex } from "../types"
2
+ import { META_STEALTH_URI_PREFIX } from "../constants"
3
+
4
+ /** Takes the encoded address, so it also formats one read back from a registry. */
5
+ export function metaStealthAddressUri(metaStealthAddress: Hex): string {
6
+ return `${META_STEALTH_URI_PREFIX}${metaStealthAddress}`
7
+ }
@@ -0,0 +1,17 @@
1
+ import { keccak_256 } from "@noble/hashes/sha3"
2
+ import { Address } from "../types"
3
+ import { bytesToHex } from "../bytes"
4
+ import { InvalidPublicKeyLengthError } from "../errors"
5
+
6
+ /**
7
+ * Derive Ethereum address from public key: keccak256 of the 64-byte X || Y
8
+ * coordinates (65 bytes without 0x04 prefix stripped), then last 20 bytes.
9
+ */
10
+ export function publicKeyToAddress(publicKey: Uint8Array): Address {
11
+ if (publicKey.length !== 65) {
12
+ throw new InvalidPublicKeyLengthError()
13
+ }
14
+ const publicKeyHash = keccak_256(publicKey.slice(1))
15
+
16
+ return `0x${bytesToHex(publicKeyHash).slice(-40)}`
17
+ }
@@ -0,0 +1,19 @@
1
+ import { assertCompressedPublicKey } from "./assertCompressedPublicKey"
2
+
3
+ /** Validates 66-byte meta stealth address: shape and curve checks */
4
+ export function validateMetaStealthAddress(metaStealthAddress: string): boolean {
5
+ if (!metaStealthAddress.startsWith("0x")) {
6
+ return false
7
+ }
8
+
9
+ const cleanedMetaStealthAddress = metaStealthAddress.slice(2)
10
+
11
+ try {
12
+ assertCompressedPublicKey(`0x${cleanedMetaStealthAddress.slice(0, 66)}`, "spending")
13
+ assertCompressedPublicKey(`0x${cleanedMetaStealthAddress.slice(66)}`, "viewing")
14
+ } catch {
15
+ return false
16
+ }
17
+
18
+ return true
19
+ }
package/src/types.ts ADDED
@@ -0,0 +1,51 @@
1
+ /** A `0x`-prefixed lowercase hexadecimal string. */
2
+ export type Hex = `0x${string}`
3
+
4
+ /** An Ethereum address. */
5
+ export type Address = Hex
6
+
7
+ /** A secp256k1 keypair, all fields normalized lowercase hex. */
8
+ export interface Secp256k1Keypair {
9
+ /** 32-byte scalar, `0x` + 64 hex chars, lowercase. */
10
+ privateKey: Hex
11
+ /** 33-byte compressed SEC1 public key. */
12
+ publicKey: Hex
13
+ /** 65-byte SEC1 public key, `0x04`-prefixed (uncompressed form). */
14
+ uncompressedPublicKey: Hex
15
+ }
16
+
17
+ /** The full key set derived from one device secret, plus the device's `networkId`. */
18
+ export interface DerivedKeys {
19
+ /** Derivation scheme version. See {@link KEY_DERIVATION_VERSION}. */
20
+ version: "v1"
21
+ /** bytes32 device identifier: `keccak256(uncompressed account pubkey, sans 04 prefix)`. */
22
+ networkId: Hex
23
+ /** Root secp256k1 keypair the other keys are derived from. */
24
+ account: Secp256k1Keypair
25
+ /** secp256k1 keypair used to authorize spending. */
26
+ spending: Secp256k1Keypair
27
+ /** secp256k1 keypair used to derive/scan stealth viewing tags. */
28
+ viewing: Secp256k1Keypair
29
+ /** secp256k1 keypair used to authenticate as the device. */
30
+ auth: Secp256k1Keypair
31
+ }
32
+
33
+ /**
34
+ * The Baby Jubjub EdDSA keypair and Semaphore-v4 identity commitment (the
35
+ * "fifth key") — a stable device-level ZK-identity key, rooted in a hardware
36
+ * ECDH shared secret rather than the device secret. See `DERIVATION.md` §12.
37
+ */
38
+ export interface EddsaKeypair {
39
+ /**
40
+ * 32-byte keccak256 EdDSA seed as `0x` hex. Decode to raw bytes before
41
+ * re-feeding to zk-kit/Semaphore — the hex string derives a different key
42
+ * (`DERIVATION.md` §12.4).
43
+ */
44
+ seed: Hex
45
+ /** The Baby Jubjub EdDSA private scalar, pruned per `deriveSecretScalar`. */
46
+ secretScalar: bigint
47
+ /** The Baby Jubjub public key point, `[x, y]`. */
48
+ publicKey: [bigint, bigint]
49
+ /** The Semaphore v4 identity commitment: `poseidon2([x, y])`. */
50
+ commitment: bigint
51
+ }
@@ -0,0 +1,499 @@
1
+ /** A `0x`-prefixed lowercase hexadecimal string. */
2
+ type Hex = `0x${string}`;
3
+ /** An Ethereum address. */
4
+ type Address = Hex;
5
+ /** A secp256k1 keypair, all fields normalized lowercase hex. */
6
+ interface Secp256k1Keypair {
7
+ /** 32-byte scalar, `0x` + 64 hex chars, lowercase. */
8
+ privateKey: Hex;
9
+ /** 33-byte compressed SEC1 public key. */
10
+ publicKey: Hex;
11
+ /** 65-byte SEC1 public key, `0x04`-prefixed (uncompressed form). */
12
+ uncompressedPublicKey: Hex;
13
+ }
14
+ /** The full key set derived from one device secret, plus the device's `networkId`. */
15
+ interface DerivedKeys {
16
+ /** Derivation scheme version. See {@link KEY_DERIVATION_VERSION}. */
17
+ version: "v1";
18
+ /** bytes32 device identifier: `keccak256(uncompressed account pubkey, sans 04 prefix)`. */
19
+ networkId: Hex;
20
+ /** Root secp256k1 keypair the other keys are derived from. */
21
+ account: Secp256k1Keypair;
22
+ /** secp256k1 keypair used to authorize spending. */
23
+ spending: Secp256k1Keypair;
24
+ /** secp256k1 keypair used to derive/scan stealth viewing tags. */
25
+ viewing: Secp256k1Keypair;
26
+ /** secp256k1 keypair used to authenticate as the device. */
27
+ auth: Secp256k1Keypair;
28
+ }
29
+ /**
30
+ * The Baby Jubjub EdDSA keypair and Semaphore-v4 identity commitment (the
31
+ * "fifth key") — a stable device-level ZK-identity key, rooted in a hardware
32
+ * ECDH shared secret rather than the device secret. See `DERIVATION.md` §12.
33
+ */
34
+ interface EddsaKeypair {
35
+ /**
36
+ * 32-byte keccak256 EdDSA seed as `0x` hex. Decode to raw bytes before
37
+ * re-feeding to zk-kit/Semaphore — the hex string derives a different key
38
+ * (`DERIVATION.md` §12.4).
39
+ */
40
+ seed: Hex;
41
+ /** The Baby Jubjub EdDSA private scalar, pruned per `deriveSecretScalar`. */
42
+ secretScalar: bigint;
43
+ /** The Baby Jubjub public key point, `[x, y]`. */
44
+ publicKey: [bigint, bigint];
45
+ /** The Semaphore v4 identity commitment: `poseidon2([x, y])`. */
46
+ commitment: bigint;
47
+ }
48
+
49
+ /**
50
+ * Normative constants for the `v1` key derivation scheme. Changing any of
51
+ * these changes every derived key and `networkId`, so they MUST NOT change
52
+ * without a version bump (new `KEY_DERIVATION_VERSION` and domain strings).
53
+ */
54
+ declare const KEY_DERIVATION_VERSION: "v1";
55
+ /** Fixed domain-separation message signed by {@link signDerivationMessage}. */
56
+ declare const FIXED_DOMAIN_MSG: "coordination-network/identity/v1";
57
+ /** Domain-separation tag for deriving the account keypair from a device secret. */
58
+ declare const DOMAIN_ACCOUNT: "coordination-network/identity/v1/account";
59
+ /** Domain-separation tag for deriving the auth keypair. */
60
+ declare const DOMAIN_AUTH: "coordination-network/identity/v1/auth";
61
+ /** Domain-separation tag for deriving the Baby Jubjub EdDSA seed (the "fifth key") from the hardware ECDH shared secret. */
62
+ declare const DOMAIN_EDDSA: "coordination-network/identity/v1/eddsa";
63
+ /** Domain-separation message hashed to the fixed NUMS P-256 peer point `Q` for the fifth-key ECDH (`eddsa-peer.ts`). */
64
+ declare const DOMAIN_EDDSA_PEER: "coordination-network/identity/v1/eddsa/peer";
65
+ /**
66
+ * Byte length of `nonce` and `networkId` in the binding messages — fixed at
67
+ * 32 so the 64- vs 96-byte messages are injective.
68
+ */
69
+ declare const BINDING_NONCE_LENGTH: 32;
70
+ /** SEC1-compressed secp256k1 public key length. */
71
+ declare const COMPRESSED_PUBLIC_KEY_LENGTH: 33;
72
+ /** ERC-6538 scheme 1 meta-stealth address: compressed spending ‖ viewing public keys. */
73
+ declare const META_STEALTH_ADDRESS_LENGTH: 66;
74
+ /** ERC-5564 offchain display prefix — never stored onchain. */
75
+ declare const META_STEALTH_URI_PREFIX: "st:eth:";
76
+ /** ERC-5564 scheme id for secp256k1 with view tags. */
77
+ declare const ERC_5564_SCHEME_ID: 1;
78
+
79
+ type KeygenErrorCode = "INVALID_DEVICE_SECRET" | "DERIVATION_FAILURE" | "INVALID_BINDING_INPUT" | "INVALID_ECDH_SECRET" | "INVALID_META_STEALTH_ADDRESS" | "INVALID_PUBLIC_KEY_LENGTH";
80
+ /**
81
+ * Base error class for all errors thrown by this package, discriminated by
82
+ * `code` so callers can branch without `instanceof` across bundle boundaries.
83
+ */
84
+ declare class KeygenError extends Error {
85
+ readonly code: KeygenErrorCode;
86
+ constructor(code: KeygenErrorCode, message: string);
87
+ }
88
+ /** Thrown when a device secret does not satisfy the exactly-32-bytes contract. */
89
+ declare class InvalidDeviceSecretError extends KeygenError {
90
+ constructor(message?: string);
91
+ }
92
+ /** Thrown when key derivation fails for a reason unrelated to input shape. */
93
+ declare class DerivationFailureError extends KeygenError {
94
+ constructor(message?: string);
95
+ }
96
+ /**
97
+ * Thrown for malformed credential-binding inputs; a well-formed signature
98
+ * that merely fails to verify returns `false` instead.
99
+ */
100
+ declare class InvalidBindingInputError extends KeygenError {
101
+ constructor(message?: string);
102
+ }
103
+ /** Thrown when a meta-stealth address fails to decode (bad prefix, length, hex, or curve point). */
104
+ declare class InvalidMetaStealthAddressError extends KeygenError {
105
+ constructor(message?: string);
106
+ }
107
+ /** Thrown when a hardware ECDH shared secret does not satisfy the exactly-32-bytes contract. */
108
+ declare class InvalidEcdhSecretError extends KeygenError {
109
+ constructor(message?: string);
110
+ }
111
+ /** Thrown when a public key is not the correct length */
112
+ declare class InvalidPublicKeyLengthError extends KeygenError {
113
+ constructor(message?: string);
114
+ }
115
+
116
+ declare function hexToBytes(hex: string): Uint8Array;
117
+ declare function bytesToHex(bytes: Uint8Array): Hex;
118
+ /** Unpadded base64url (RFC 4648 §5) */
119
+ declare function bytesToBase64url(bytes: Uint8Array): string;
120
+ /** Decodes unpadded base64url (RFC 4648 §5). */
121
+ declare function base64urlToBytes(encoded: string): Uint8Array;
122
+ /**
123
+ * Normalizes a device secret to a defensive copy of exactly 32 bytes.
124
+ * Accepts a 32-byte `Uint8Array` or a `0x`-prefixed 64-hex-char string;
125
+ * anything else throws {@link InvalidDeviceSecretError}.
126
+ */
127
+ declare function normalizeDeviceSecret(secret: Uint8Array | Hex | string): Uint8Array;
128
+ /**
129
+ * Normalizes a hardware ECDH shared secret to a defensive copy of exactly 32
130
+ * bytes. Same exactly-32-bytes contract as {@link normalizeDeviceSecret};
131
+ * rethrown as {@link InvalidEcdhSecretError} so callers don't conflate the
132
+ * two secrets by error code.
133
+ */
134
+ declare function normalizeEcdhSharedSecret(secret: Uint8Array | Hex | string): Uint8Array;
135
+
136
+ /**
137
+ * Generates a fresh 32-byte device secret from the platform CSPRNG
138
+ * (`globalThis.crypto.getRandomValues`). Throws if no secure RNG is available
139
+ * (e.g. React Native without the `react-native-get-random-values` polyfill).
140
+ */
141
+ declare function generateDeviceSecret(): Hex;
142
+
143
+ /** A 32-byte-output hash function, e.g. `keccak_256`. */
144
+ type HashFn = (input: Uint8Array) => Uint8Array;
145
+ /** {@link hashToScalar} with an injectable hash, letting tests drive the reject/retry branch. */
146
+ declare function hashToScalarWith(hashFn: HashFn, tag: string, input: Uint8Array): Uint8Array;
147
+ /**
148
+ * Deterministically derives a secp256k1 private scalar from a domain tag and
149
+ * input bytes.
150
+ *
151
+ * NORMATIVE: for `counter = 0..255`, return the first
152
+ * `keccak256(utf8(tag) || input || [counter])` whose big-endian value lies in
153
+ * `[1, n)`. Out-of-range candidates have probability ~`2^-128`, so exhaustion
154
+ * is unreachable.
155
+ */
156
+ declare function hashToScalar(tag: string, input: Uint8Array): Uint8Array;
157
+
158
+ /**
159
+ * Tests whether `scalar` is a valid secp256k1 private key: exactly 32 bytes
160
+ * encoding an integer in `[1, n)`.
161
+ */
162
+ declare function isValidScalar(scalar: Uint8Array): boolean;
163
+ /**
164
+ * Expands a 32-byte private scalar into a full {@link Secp256k1Keypair}, with
165
+ * all fields as lowercase `0x` hex.
166
+ *
167
+ * @throws {DerivationFailureError} if `scalar` is not in `[1, n)`
168
+ */
169
+ declare function scalarToKeypair(scalar: Uint8Array): Secp256k1Keypair;
170
+
171
+ /**
172
+ * Derives the root secp256k1 account keypair from a 32-byte device secret.
173
+ *
174
+ * NORMATIVE: `accountPrivKey = hashToScalar(DOMAIN_ACCOUNT, deviceSecret)`.
175
+ */
176
+ declare function deriveAccountKeypair(deviceSecret: Uint8Array | Hex): Secp256k1Keypair;
177
+ /**
178
+ * Signs the fixed derivation message with the account private key.
179
+ *
180
+ * NORMATIVE: the EIP-191 digest of {@link FIXED_DOMAIN_MSG}, signed with
181
+ * RFC-6979 deterministic ECDSA (`lowS: true`, no `extraEntropy`), returned as
182
+ * the 64-byte compact `r || s` encoding.
183
+ */
184
+ declare function signDerivationMessage(accountPrivateKey: Hex | Uint8Array): Hex;
185
+
186
+ /**
187
+ * Derives the spending and viewing secp256k1 keypairs from the 64-byte
188
+ * compact derivation-message signature.
189
+ *
190
+ * NORMATIVE (FluidKey `generateKeysFromSignature` semantics): with
191
+ * `r = sig[0..32]` and `s = sig[32..64]`, `spendingPrivKey = keccak256(r)`
192
+ * and `viewingPrivKey = keccak256(s)`. No reject/retry — the hash output is
193
+ * used directly and validated to be in `[1, n)` by {@link scalarToKeypair}.
194
+ *
195
+ * `sig64` is unauthenticated — prefer {@link deriveKeys} (see `DERIVATION.md`).
196
+ */
197
+ declare function deriveStealthKeys(sig64: Uint8Array | Hex): {
198
+ spending: Secp256k1Keypair;
199
+ viewing: Secp256k1Keypair;
200
+ };
201
+
202
+ /**
203
+ * The ERC-5564 hashed shared secret: keccak256 of the 33-byte compressed
204
+ * ECDH point.
205
+ *
206
+ * The sender can pass (ephemeralPrivateKey, viewingPublicKey).
207
+ * The recipient can pass (viewingPrivateKey, ephemeralPublicKey).
208
+ */
209
+ declare function computeSharedSecret({ privateKey, publicKey }: {
210
+ privateKey: Hex;
211
+ publicKey: Hex;
212
+ }): Uint8Array;
213
+
214
+ /**
215
+ * First byte of the hashed shared secret, published in announcements so
216
+ * scanners can skip 255/256 non-matching shared secrets.
217
+ */
218
+ declare function getViewTag(hashedSharedSecret: Uint8Array): number;
219
+
220
+ /**
221
+ * Generates stealth public key. ERC-5564 scheme 1: P_stealth = P_spend + (s_h mod n)·G,
222
+ * returned as a 65-byte uncompressed public key.
223
+ */
224
+ declare function getStealthPublicKey({ spendingPublicKey, hashedSharedSecret }: {
225
+ spendingPublicKey: Hex;
226
+ hashedSharedSecret: Uint8Array;
227
+ }): Uint8Array;
228
+
229
+ /**
230
+ * Derive Ethereum address from public key: keccak256 of the 64-byte X || Y
231
+ * coordinates (65 bytes without 0x04 prefix stripped), then last 20 bytes.
232
+ */
233
+ declare function publicKeyToAddress(publicKey: Uint8Array): Address;
234
+
235
+ /**
236
+ * A fresh CSPRNG keypair for one load. MUST be single-use — reusing an
237
+ * ephemeral key derives the same stealth address twice.
238
+ */
239
+ declare function generateEphemeralKey(): Secp256k1Keypair;
240
+
241
+ /**
242
+ * Derives a fresh stealth address from the
243
+ * recipient's spending and viewing public keys.
244
+ */
245
+ declare function generateStealthAddress({ viewingPublicKey, spendingPublicKey, ephemeralPrivateKey }: {
246
+ viewingPublicKey: Hex;
247
+ spendingPublicKey: Hex;
248
+ ephemeralPrivateKey: Hex;
249
+ }): Address;
250
+
251
+ /**
252
+ * Recovers the private key controlling a stealth address,
253
+ * (p_spend + s_h) mod n.
254
+ */
255
+ declare function generateStealthPrivateKey({ ephemeralPublicKey, viewingPrivateKey, spendingPrivateKey }: {
256
+ ephemeralPublicKey: Hex;
257
+ viewingPrivateKey: Hex;
258
+ spendingPrivateKey: Hex;
259
+ }): Hex;
260
+
261
+ /**
262
+ * Checks whether an announcement is addressed to a recipient: filters by
263
+ * view-tag first, then full address recompute.
264
+ */
265
+ declare function checkStealthAddress({ stealthAddress, ephemeralPublicKey, spendingPublicKey, viewingPrivateKey, viewTag }: {
266
+ stealthAddress: Address;
267
+ ephemeralPublicKey: Hex;
268
+ spendingPublicKey: Hex;
269
+ viewingPrivateKey: Hex;
270
+ viewTag: number;
271
+ }): boolean;
272
+
273
+ /** Validates 66-byte meta stealth address: shape and curve checks */
274
+ declare function validateMetaStealthAddress(metaStealthAddress: string): boolean;
275
+
276
+ /**
277
+ * The 66-byte compressed spending || viewing concatenation the onchain registry stores. Throws
278
+ * InvalidMetaStealthAddressError, naming the offending key, so it can be used before registering.
279
+ */
280
+ declare function encodeMetaStealthAddress({ spendingPublicKey, viewingPublicKey }: {
281
+ spendingPublicKey: Hex;
282
+ viewingPublicKey: Hex;
283
+ }): Hex;
284
+
285
+ /** The st:eth:0x… display form (ERC-5564 URI). Not stored onchain. */
286
+ declare function encodeMetaStealthAddressUri(publicKeys: {
287
+ spendingPublicKey: Hex;
288
+ viewingPublicKey: Hex;
289
+ }): string;
290
+
291
+ /** Takes the encoded address, so it also formats one read back from a registry. */
292
+ declare function metaStealthAddressUri(metaStealthAddress: Hex): string;
293
+
294
+ /**
295
+ * Splits a 66-byte meta-stealth address (raw hex or `st:eth:` prefixed) into its
296
+ * spending and viewing public keys.
297
+ */
298
+ declare function decodeMetaStealthAddress(metaStealthAddress: string): {
299
+ spendingPublicKey: Hex;
300
+ viewingPublicKey: Hex;
301
+ };
302
+
303
+ /**
304
+ * Builds an ERC-5564 announcement payload for a fresh stealth address,
305
+ * generates ephemeral key internally. The view tag is the
306
+ * first and only metadata byte.
307
+ */
308
+ declare function buildAnnouncement({ metaStealthAddress }: {
309
+ metaStealthAddress: Hex;
310
+ }): {
311
+ schemeId: typeof ERC_5564_SCHEME_ID;
312
+ stealthAddress: Address;
313
+ ephemeralPublicKey: Hex;
314
+ metadata: Hex;
315
+ };
316
+
317
+ /**
318
+ * Derives the secp256k1 auth keypair from the 64-byte compact
319
+ * derivation-message signature.
320
+ *
321
+ * NORMATIVE: `authPrivKey = hashToScalar(DOMAIN_AUTH, sig64)`.
322
+ *
323
+ * `sig64` is unauthenticated — prefer {@link deriveKeys} (see `DERIVATION.md`).
324
+ */
325
+ declare function deriveAuthKeypair(sig64: Uint8Array | Hex): Secp256k1Keypair;
326
+
327
+ /** Baby Jubjub EdDSA "fifth key" derivation — see `DERIVATION.md` §12. */
328
+
329
+ /**
330
+ * Derives the Baby Jubjub EdDSA keypair and Semaphore-v4 identity commitment
331
+ * (the "fifth key"), rooted in a hardware ECDH shared secret.
332
+ *
333
+ * NORMATIVE (`DERIVATION.md` §12): `ecdhSharedSecret = X(hwPriv · Q)` for the
334
+ * fixed NUMS peer point `Q` (`eddsa-peer.ts`), computed natively by the
335
+ * caller — the library never talks to the Keystore, it only takes the raw
336
+ * 32-byte result. `seed = keccak256(utf8(DOMAIN_EDDSA) || ecdhSharedSecret)`,
337
+ * fed to `@zk-kit/eddsa-poseidon` as raw bytes (MUST NOT be the hex string,
338
+ * see §12.4); `commitment = poseidon2([x, y])`.
339
+ *
340
+ * @throws {InvalidEcdhSecretError} if `ecdhSharedSecret` is not exactly 32 bytes
341
+ */
342
+ declare function deriveEddsaKeypair(ecdhSharedSecret: Uint8Array | Hex): EddsaKeypair;
343
+
344
+ /** Fixed NUMS P-256 peer point `Q` for the fifth-key ECDH — see `DERIVATION.md` §12. */
345
+ /**
346
+ * NORMATIVE: `Q = hashToCurve(utf8(DOMAIN_EDDSA_PEER))` (RFC 9380
347
+ * `P256_XMD:SHA-256_SSWU_RO_`, suite-default DST). Nobody knows `dlog(Q)`,
348
+ * so `hwPriv · Q` is computable only by the device that holds `hwPriv`.
349
+ * 65-byte SEC1 uncompressed encoding, `0x04`-prefixed.
350
+ */
351
+ declare const EDDSA_PEER_POINT_HEX: "0x045db1cabf31539848bf802ad49a94ad8f8e3d52e75bb53aa4552d7cf836eba054213badca0cf4ba6daf8c146d3415d9ae1905440781dc5f08e6e946a471af99e9";
352
+ /** Fresh copy of {@link EDDSA_PEER_POINT_HEX} as raw bytes. */
353
+ declare function eddsaPeerPointBytes(): Uint8Array;
354
+ /**
355
+ * Reproduces `Q` from its RFC 9380 provenance, independent of the pinned
356
+ * constant — the evidence that {@link EDDSA_PEER_POINT_HEX} is a NUMS point.
357
+ *
358
+ * `hashToCurve` returns an `H2CPoint`, which lacks `toRawBytes`; round-trip
359
+ * through `ProjectivePoint.fromAffine` to get the SEC1 encoding.
360
+ */
361
+ declare function computeEddsaPeerPoint(): Uint8Array;
362
+
363
+ /**
364
+ * Computes the bytes32 `networkId` from an uncompressed secp256k1 public key
365
+ * (65-byte SEC1 `0x04`-prefixed or 64-byte raw `X || Y`).
366
+ *
367
+ * NORMATIVE: `networkId = keccak256(X || Y)` — the SEC1 prefix is stripped.
368
+ * The last 20 bytes are the account's Ethereum address.
369
+ */
370
+ declare function computeNetworkId(uncompressedPublicKey: Uint8Array | Hex): Hex;
371
+
372
+ /**
373
+ * Derives the full `v1` device identity — `networkId` plus the four-key
374
+ * all-secp256k1 model — from a single device secret.
375
+ *
376
+ * Pipeline (NORMATIVE):
377
+ * 1. `account = deriveAccountKeypair(deviceSecret)`
378
+ * 2. `networkId = computeNetworkId(account.uncompressedPublicKey)`
379
+ * 3. `sig = signDerivationMessage(account.privateKey)` (EIP-191, deterministic)
380
+ * 4. `{ spending, viewing } = deriveStealthKeys(sig)` (FluidKey semantics)
381
+ * 5. `auth = deriveAuthKeypair(sig)`
382
+ */
383
+ declare function deriveKeys(deviceSecret: Uint8Array | Hex): DerivedKeys;
384
+ /** Derives only the `networkId` — equal to `deriveKeys(deviceSecret).networkId`, minus the signature work. */
385
+ declare function deriveNetworkId(deviceSecret: Uint8Array | Hex): Hex;
386
+
387
+ /**
388
+ * Normalizes a P-256 `confirmationKey` to its raw 64-byte `X || Y`
389
+ * coordinates, validating that the point is on the curve. Accepts a 65-byte
390
+ * SEC1 uncompressed, 33-byte compressed, or 64-byte raw `X || Y` encoding.
391
+ */
392
+ declare function normalizeConfirmationKey(confirmationKey: Uint8Array | Hex): Uint8Array;
393
+ /**
394
+ * Computes the `confirmationKeyHash` embedded in the account binding message.
395
+ *
396
+ * NORMATIVE: the RFC 7638 JWK thumbprint of the P-256 confirmation key, i.e.
397
+ * `sha256(utf8('{"crv":"P-256","kty":"EC","x":<base64url X>,"y":<base64url Y>}'))`
398
+ */
399
+ declare function computeConfirmationKeyHash(confirmationKey: Uint8Array | Hex): Hex;
400
+
401
+ /**
402
+ * Credential dual proof-of-possession binding — see `DERIVATION.md` §11.
403
+ *
404
+ * NORMATIVE layouts:
405
+ * ```
406
+ * deviceBindingMessage = nonce(32) || networkId(32) || holderKeyCommitment(32) // credential issuance
407
+ * accountBindingMessage = nonce(32) || confirmationKeyHash(32) || networkId(32) // 96B, signed by A (secp256k1)
408
+ * ```
409
+ *
410
+ * NORMATIVE MUST: the device signs a locally-derived `networkId`, never one
411
+ * supplied by the issuance request. Every function here except the two
412
+ * low-level builders and {@link verifyDeviceBindingSignature} enforces this
413
+ * by deriving `networkId` internally rather than accepting it as a parameter.
414
+ */
415
+
416
+ /**
417
+ * Builds `nonce(32) || networkId(32)`, optionally followed by a 32-byte holder commitment.
418
+ *
419
+ * Low-level, spec-literal builder — prefer {@link buildBindingMessages},
420
+ * which enforces the locally-derived-`networkId` MUST structurally.
421
+ */
422
+ declare function buildDeviceBindingMessage(nonce: Uint8Array | Hex, networkId: Uint8Array | Hex, holderKeyCommitment?: Uint8Array | Hex): Uint8Array;
423
+ /**
424
+ * Builds the 96-byte `accountBindingMessage`:
425
+ * `nonce(32) || confirmationKeyHash(32) || networkId(32)`.
426
+ *
427
+ * Low-level, spec-literal builder — see {@link buildBindingMessages} for the
428
+ * wallet-side entry point.
429
+ */
430
+ declare function buildAccountBindingMessage(nonce: Uint8Array | Hex, confirmationKey: Uint8Array | Hex, networkId: Uint8Array | Hex): Uint8Array;
431
+ /**
432
+ * Wallet-side entry point: builds both binding messages with `networkId`
433
+ * derived internally from the account's own public key, so a caller cannot
434
+ * supply a `networkId` from elsewhere even by mistake.
435
+ */
436
+ declare function buildBindingMessages(nonce: Uint8Array | Hex, accountUncompressedPublicKey: Uint8Array | Hex, confirmationKey: Uint8Array | Hex, holderKeyCommitment?: Uint8Array | Hex): {
437
+ networkId: Hex;
438
+ deviceBindingMessage: Uint8Array;
439
+ accountBindingMessage: Uint8Array;
440
+ };
441
+ /**
442
+ * Produces `sig_A`: the EIP-191 signature over the `accountBindingMessage`,
443
+ * with `networkId` derived internally from the account private key. Same
444
+ * signing posture as {@link signDerivationMessage} (RFC-6979 deterministic,
445
+ * `lowS: true`, no `extraEntropy`, 64-byte compact `r || s`).
446
+ */
447
+ declare function signAccountBinding(accountPrivateKey: Hex | Uint8Array, nonce: Uint8Array | Hex, confirmationKey: Uint8Array | Hex): {
448
+ signature: Hex;
449
+ networkId: Hex;
450
+ };
451
+ /**
452
+ * Issuer-side verification of `sig_A`, with `networkId` derived internally
453
+ * from `accountPublicKey` — never accepted as a parameter.
454
+ *
455
+ * Malformed inputs throw {@link InvalidBindingInputError}; a well-formed
456
+ * signature that fails to verify returns `false`.
457
+ */
458
+ declare function verifyAccountBindingSignature({ nonce, confirmationKey, accountPublicKey, signature }: {
459
+ nonce: Uint8Array | Hex;
460
+ confirmationKey: Uint8Array | Hex;
461
+ accountPublicKey: Uint8Array | Hex;
462
+ signature: Uint8Array | Hex;
463
+ }): boolean;
464
+ /**
465
+ * Issuer-side verification of `sig_B` (hardware P-256) over the
466
+ * `deviceBindingMessage`. NORMATIVE MUST: `networkId` — not derivable here,
467
+ * since `sig_B` is independent of the account key — must be the device's
468
+ * locally-derived value; prefer {@link verifyCredentialBinding}.
469
+ *
470
+ * `prehash: true` (signers hash SHA-256 internally); `lowS: false` (hardware
471
+ * signatures aren't s-normalized — `DERIVATION.md` §11.4).
472
+ */
473
+ declare function verifyDeviceBindingSignature({ nonce, networkId, confirmationKey, holderKeyCommitment, signature }: {
474
+ nonce: Uint8Array | Hex;
475
+ networkId: Uint8Array | Hex;
476
+ confirmationKey: Uint8Array | Hex;
477
+ holderKeyCommitment?: Uint8Array | Hex;
478
+ signature: Uint8Array | Hex;
479
+ }): boolean;
480
+ /**
481
+ * The full issuer gate: one `networkId` derived from `accountPublicKey` gates
482
+ * both `sig_A` and `sig_B`, so a device signature from one session can't pair
483
+ * with an unrelated account signature in either direction. `networkId` is
484
+ * returned even when `valid` is false, for issuer-side logging/rate-limiting.
485
+ */
486
+ declare function verifyCredentialBinding({ nonce, accountPublicKey, confirmationKey, holderKeyCommitment, accountSignature, deviceSignature }: {
487
+ nonce: Uint8Array | Hex;
488
+ accountPublicKey: Uint8Array | Hex;
489
+ confirmationKey: Uint8Array | Hex;
490
+ holderKeyCommitment?: Uint8Array | Hex;
491
+ accountSignature: Uint8Array | Hex;
492
+ deviceSignature: Uint8Array | Hex;
493
+ }): {
494
+ valid: boolean;
495
+ networkId: Hex;
496
+ };
497
+
498
+ export { BINDING_NONCE_LENGTH, COMPRESSED_PUBLIC_KEY_LENGTH, DOMAIN_ACCOUNT, DOMAIN_AUTH, DOMAIN_EDDSA, DOMAIN_EDDSA_PEER, DerivationFailureError, EDDSA_PEER_POINT_HEX, ERC_5564_SCHEME_ID, FIXED_DOMAIN_MSG, InvalidBindingInputError, InvalidDeviceSecretError, InvalidEcdhSecretError, InvalidMetaStealthAddressError, InvalidPublicKeyLengthError, KEY_DERIVATION_VERSION, KeygenError, META_STEALTH_ADDRESS_LENGTH, META_STEALTH_URI_PREFIX, base64urlToBytes, buildAccountBindingMessage, buildAnnouncement, buildBindingMessages, buildDeviceBindingMessage, bytesToBase64url, bytesToHex, checkStealthAddress, computeConfirmationKeyHash, computeEddsaPeerPoint, computeNetworkId, computeSharedSecret, decodeMetaStealthAddress, deriveAccountKeypair, deriveAuthKeypair, deriveEddsaKeypair, deriveKeys, deriveNetworkId, deriveStealthKeys, eddsaPeerPointBytes, encodeMetaStealthAddress, encodeMetaStealthAddressUri, generateDeviceSecret, generateEphemeralKey, generateStealthAddress, generateStealthPrivateKey, getStealthPublicKey, getViewTag, hashToScalar, hashToScalarWith, hexToBytes, isValidScalar, metaStealthAddressUri, normalizeConfirmationKey, normalizeDeviceSecret, normalizeEcdhSharedSecret, publicKeyToAddress, scalarToKeypair, signAccountBinding, signDerivationMessage, validateMetaStealthAddress, verifyAccountBindingSignature, verifyCredentialBinding, verifyDeviceBindingSignature };
499
+ export type { Address, DerivedKeys, EddsaKeypair, HashFn, Hex, KeygenErrorCode, Secp256k1Keypair };