@coordination-network/identity 0.0.2 → 0.1.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.
- package/DERIVATION.md +46 -30
- package/README.md +84 -72
- package/dist/index.cjs +85 -47
- package/dist/index.js +82 -48
- package/package.json +2 -2
- package/src/binding.ts +31 -13
- package/src/constants.ts +9 -3
- package/src/derive.ts +7 -4
- package/src/eddsa.ts +22 -13
- package/src/index.ts +2 -2
- package/src/network-id.ts +31 -14
- package/src/types.ts +5 -3
- package/types/index.d.ts +47 -23
package/src/binding.ts
CHANGED
|
@@ -3,10 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* NORMATIVE layouts:
|
|
5
5
|
* ```
|
|
6
|
-
* deviceBindingMessage = nonce(32) || networkId(32) || holderKeyCommitment(32)
|
|
7
|
-
* accountBindingMessage = nonce(32) || confirmationKeyHash(32) || networkId(32)
|
|
6
|
+
* deviceBindingMessage = nonce(32) || networkId(32) || holderKeyCommitment(32) || requestCommitment(32)
|
|
7
|
+
* accountBindingMessage = nonce(32) || confirmationKeyHash(32) || networkId(32)
|
|
8
8
|
* ```
|
|
9
9
|
*
|
|
10
|
+
* B (P-256) signs the 128-byte device message; A (secp256k1) signs the 96-byte account message.
|
|
11
|
+
*
|
|
12
|
+
* `requestCommitment` is opaque here: the credential layer defines it and
|
|
13
|
+
* binds the issuer and scope shown at consent into it.
|
|
14
|
+
*
|
|
10
15
|
* NORMATIVE MUST: the device signs a locally-derived `networkId`, never one
|
|
11
16
|
* supplied by the issuance request. Every function here except the two
|
|
12
17
|
* low-level builders and {@link verifyDeviceBindingSignature} enforces this
|
|
@@ -31,6 +36,12 @@ function toBindingBytes(value: Uint8Array | Hex, label: string): Uint8Array {
|
|
|
31
36
|
}
|
|
32
37
|
}
|
|
33
38
|
|
|
39
|
+
function toBindingHash32(value: Uint8Array | Hex, label: string): Uint8Array {
|
|
40
|
+
const bytes = toBindingBytes(value, label)
|
|
41
|
+
if (bytes.length !== 32) throw new InvalidBindingInputError(`${label}: expected exactly 32 bytes`)
|
|
42
|
+
return bytes
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
function decodeAndValidateNonceAndNetworkId(
|
|
35
46
|
nonce: Uint8Array | Hex,
|
|
36
47
|
networkId: Uint8Array | Hex
|
|
@@ -111,7 +122,8 @@ function parseDeviceSignature(signature: Uint8Array | Hex): ReturnType<typeof p2
|
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
/**
|
|
114
|
-
* Builds
|
|
125
|
+
* Builds the 128-byte `deviceBindingMessage`:
|
|
126
|
+
* `nonce(32) || networkId(32) || holderKeyCommitment(32) || requestCommitment(32)`.
|
|
115
127
|
*
|
|
116
128
|
* Low-level, spec-literal builder — prefer {@link buildBindingMessages},
|
|
117
129
|
* which enforces the locally-derived-`networkId` MUST structurally.
|
|
@@ -119,13 +131,13 @@ function parseDeviceSignature(signature: Uint8Array | Hex): ReturnType<typeof p2
|
|
|
119
131
|
export function buildDeviceBindingMessage(
|
|
120
132
|
nonce: Uint8Array | Hex,
|
|
121
133
|
networkId: Uint8Array | Hex,
|
|
122
|
-
holderKeyCommitment
|
|
134
|
+
holderKeyCommitment: Uint8Array | Hex,
|
|
135
|
+
requestCommitment: Uint8Array | Hex
|
|
123
136
|
): Uint8Array {
|
|
124
137
|
const { nonceBytes, networkIdBytes } = decodeAndValidateNonceAndNetworkId(nonce, networkId)
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
return concatBytes(nonceBytes, networkIdBytes, holderBytes)
|
|
138
|
+
const holderBytes = toBindingHash32(holderKeyCommitment, "holderKeyCommitment")
|
|
139
|
+
const requestBytes = toBindingHash32(requestCommitment, "requestCommitment")
|
|
140
|
+
return concatBytes(nonceBytes, networkIdBytes, holderBytes, requestBytes)
|
|
129
141
|
}
|
|
130
142
|
|
|
131
143
|
/**
|
|
@@ -155,7 +167,8 @@ export function buildBindingMessages(
|
|
|
155
167
|
nonce: Uint8Array | Hex,
|
|
156
168
|
accountUncompressedPublicKey: Uint8Array | Hex,
|
|
157
169
|
confirmationKey: Uint8Array | Hex,
|
|
158
|
-
holderKeyCommitment
|
|
170
|
+
holderKeyCommitment: Uint8Array | Hex,
|
|
171
|
+
requestCommitment: Uint8Array | Hex
|
|
159
172
|
): { networkId: Hex; deviceBindingMessage: Uint8Array; accountBindingMessage: Uint8Array } {
|
|
160
173
|
let networkId: Hex
|
|
161
174
|
|
|
@@ -167,7 +180,7 @@ export function buildBindingMessages(
|
|
|
167
180
|
)
|
|
168
181
|
}
|
|
169
182
|
|
|
170
|
-
const deviceBindingMessage = buildDeviceBindingMessage(nonce, networkId, holderKeyCommitment)
|
|
183
|
+
const deviceBindingMessage = buildDeviceBindingMessage(nonce, networkId, holderKeyCommitment, requestCommitment)
|
|
171
184
|
const accountBindingMessage = buildAccountBindingMessage(nonce, confirmationKey, networkId)
|
|
172
185
|
|
|
173
186
|
return { networkId, deviceBindingMessage, accountBindingMessage }
|
|
@@ -237,15 +250,17 @@ export function verifyDeviceBindingSignature({
|
|
|
237
250
|
networkId,
|
|
238
251
|
confirmationKey,
|
|
239
252
|
holderKeyCommitment,
|
|
253
|
+
requestCommitment,
|
|
240
254
|
signature
|
|
241
255
|
}: {
|
|
242
256
|
nonce: Uint8Array | Hex
|
|
243
257
|
networkId: Uint8Array | Hex
|
|
244
258
|
confirmationKey: Uint8Array | Hex
|
|
245
|
-
holderKeyCommitment
|
|
259
|
+
holderKeyCommitment: Uint8Array | Hex
|
|
260
|
+
requestCommitment: Uint8Array | Hex
|
|
246
261
|
signature: Uint8Array | Hex
|
|
247
262
|
}): boolean {
|
|
248
|
-
const message = buildDeviceBindingMessage(nonce, networkId, holderKeyCommitment)
|
|
263
|
+
const message = buildDeviceBindingMessage(nonce, networkId, holderKeyCommitment, requestCommitment)
|
|
249
264
|
const publicKeyBytes = prependUncompressedPrefix(normalizeConfirmationKey(confirmationKey))
|
|
250
265
|
const parsedSignature = parseDeviceSignature(signature)
|
|
251
266
|
|
|
@@ -266,13 +281,15 @@ export function verifyCredentialBinding({
|
|
|
266
281
|
accountPublicKey,
|
|
267
282
|
confirmationKey,
|
|
268
283
|
holderKeyCommitment,
|
|
284
|
+
requestCommitment,
|
|
269
285
|
accountSignature,
|
|
270
286
|
deviceSignature
|
|
271
287
|
}: {
|
|
272
288
|
nonce: Uint8Array | Hex
|
|
273
289
|
accountPublicKey: Uint8Array | Hex
|
|
274
290
|
confirmationKey: Uint8Array | Hex
|
|
275
|
-
holderKeyCommitment
|
|
291
|
+
holderKeyCommitment: Uint8Array | Hex
|
|
292
|
+
requestCommitment: Uint8Array | Hex
|
|
276
293
|
accountSignature: Uint8Array | Hex
|
|
277
294
|
deviceSignature: Uint8Array | Hex
|
|
278
295
|
}): { valid: boolean; networkId: Hex } {
|
|
@@ -291,6 +308,7 @@ export function verifyCredentialBinding({
|
|
|
291
308
|
networkId,
|
|
292
309
|
confirmationKey,
|
|
293
310
|
holderKeyCommitment,
|
|
311
|
+
requestCommitment,
|
|
294
312
|
signature: deviceSignature
|
|
295
313
|
})
|
|
296
314
|
|
package/src/constants.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Normative constants for the `
|
|
2
|
+
* Normative constants for the `v2` key derivation scheme. Changing any of
|
|
3
3
|
* these changes every derived key and `networkId`, so they MUST NOT change
|
|
4
4
|
* without a version bump (new `KEY_DERIVATION_VERSION` and domain strings).
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export const KEY_DERIVATION_VERSION = "
|
|
7
|
+
export const KEY_DERIVATION_VERSION = "v2" as const
|
|
8
8
|
|
|
9
9
|
/** Fixed domain-separation message signed by {@link signDerivationMessage}. */
|
|
10
10
|
export const FIXED_DOMAIN_MSG = "coordination-network/identity/v1" as const
|
|
@@ -18,12 +18,18 @@ export const DOMAIN_AUTH = "coordination-network/identity/v1/auth" as const
|
|
|
18
18
|
/** Domain-separation tag for deriving the Baby Jubjub EdDSA seed (the "fifth key") from the hardware ECDH shared secret. */
|
|
19
19
|
export const DOMAIN_EDDSA = "coordination-network/identity/v1/eddsa" as const
|
|
20
20
|
|
|
21
|
+
/** Distinct Baby Jubjub seed for public paymaster membership; never use the credential holder key as a group leaf. */
|
|
22
|
+
export const DOMAIN_PAYMASTER = "coordination-network/identity/v1/paymaster" as const
|
|
23
|
+
|
|
21
24
|
/** Domain-separation message hashed to the fixed NUMS P-256 peer point `Q` for the fifth-key ECDH (`eddsa-peer.ts`). */
|
|
22
25
|
export const DOMAIN_EDDSA_PEER = "coordination-network/identity/v1/eddsa/peer" as const
|
|
23
26
|
|
|
27
|
+
/** Domain-separation tag prefixed to `X || Y` when hashing a user `networkId`. */
|
|
28
|
+
export const DOMAIN_NETWORK_ID = "coordination-network/identity/v2/network-id" as const
|
|
29
|
+
|
|
24
30
|
/**
|
|
25
31
|
* Byte length of `nonce` and `networkId` in the binding messages — fixed at
|
|
26
|
-
* 32 so the
|
|
32
|
+
* 32 so the 128-byte device and 96-byte account messages are injective.
|
|
27
33
|
*/
|
|
28
34
|
export const BINDING_NONCE_LENGTH = 32 as const
|
|
29
35
|
|
package/src/derive.ts
CHANGED
|
@@ -3,15 +3,16 @@ import { KEY_DERIVATION_VERSION } from "./constants"
|
|
|
3
3
|
import { deriveAccountKeypair, signDerivationMessage } from "./account"
|
|
4
4
|
import { deriveStealthKeys } from "./stealthAddresses/deriveStealthKeys"
|
|
5
5
|
import { deriveAuthKeypair } from "./auth"
|
|
6
|
-
import { computeNetworkId } from "./network-id"
|
|
6
|
+
import { computeMemberNetworkId, computeNetworkId } from "./network-id"
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Derives the full `
|
|
10
|
-
*
|
|
9
|
+
* Derives the full `v2` device identity — `networkId`, `memberNetworkId` and the account,
|
|
10
|
+
* spending, viewing and auth secp256k1 keypairs — from a single device secret.
|
|
11
11
|
*
|
|
12
12
|
* Pipeline (NORMATIVE):
|
|
13
13
|
* 1. `account = deriveAccountKeypair(deviceSecret)`
|
|
14
|
-
* 2. `networkId = computeNetworkId(account.uncompressedPublicKey)`
|
|
14
|
+
* 2. `networkId = computeNetworkId(account.uncompressedPublicKey)` and
|
|
15
|
+
* `memberNetworkId = computeMemberNetworkId(account.uncompressedPublicKey)`
|
|
15
16
|
* 3. `sig = signDerivationMessage(account.privateKey)` (EIP-191, deterministic)
|
|
16
17
|
* 4. `{ spending, viewing } = deriveStealthKeys(sig)` (FluidKey semantics)
|
|
17
18
|
* 5. `auth = deriveAuthKeypair(sig)`
|
|
@@ -19,6 +20,7 @@ import { computeNetworkId } from "./network-id"
|
|
|
19
20
|
export function deriveKeys(deviceSecret: Uint8Array | Hex): DerivedKeys {
|
|
20
21
|
const account = deriveAccountKeypair(deviceSecret)
|
|
21
22
|
const networkId = computeNetworkId(account.uncompressedPublicKey)
|
|
23
|
+
const memberNetworkId = computeMemberNetworkId(account.uncompressedPublicKey)
|
|
22
24
|
const signature = signDerivationMessage(account.privateKey)
|
|
23
25
|
const { spending, viewing } = deriveStealthKeys(signature)
|
|
24
26
|
const auth = deriveAuthKeypair(signature)
|
|
@@ -26,6 +28,7 @@ export function deriveKeys(deviceSecret: Uint8Array | Hex): DerivedKeys {
|
|
|
26
28
|
return {
|
|
27
29
|
version: KEY_DERIVATION_VERSION,
|
|
28
30
|
networkId,
|
|
31
|
+
memberNetworkId,
|
|
29
32
|
account,
|
|
30
33
|
spending,
|
|
31
34
|
viewing,
|
package/src/eddsa.ts
CHANGED
|
@@ -4,9 +4,25 @@ import { keccak_256 } from "@noble/hashes/sha3"
|
|
|
4
4
|
import { deriveSecretScalar, derivePublicKey } from "@zk-kit/eddsa-poseidon"
|
|
5
5
|
import { poseidon2 } from "poseidon-lite/poseidon2"
|
|
6
6
|
import type { EddsaKeypair, Hex } from "./types"
|
|
7
|
-
import { DOMAIN_EDDSA } from "./constants"
|
|
7
|
+
import { DOMAIN_EDDSA, DOMAIN_PAYMASTER } from "./constants"
|
|
8
8
|
import { bytesToHex, concatBytes, normalizeEcdhSharedSecret, utf8ToBytes } from "./bytes"
|
|
9
9
|
|
|
10
|
+
function deriveKeypair(ecdhSharedSecret: Uint8Array | Hex, domain: string): EddsaKeypair {
|
|
11
|
+
const secret = normalizeEcdhSharedSecret(ecdhSharedSecret)
|
|
12
|
+
const seed = keccak_256(concatBytes(utf8ToBytes(domain), secret))
|
|
13
|
+
|
|
14
|
+
const secretScalar = deriveSecretScalar(seed)
|
|
15
|
+
const publicKey = derivePublicKey(seed)
|
|
16
|
+
const commitment = poseidon2(publicKey)
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
seed: bytesToHex(seed),
|
|
20
|
+
secretScalar,
|
|
21
|
+
publicKey,
|
|
22
|
+
commitment
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
10
26
|
/**
|
|
11
27
|
* Derives the Baby Jubjub EdDSA keypair and Semaphore-v4 identity commitment
|
|
12
28
|
* (the "fifth key"), rooted in a hardware ECDH shared secret.
|
|
@@ -21,17 +37,10 @@ import { bytesToHex, concatBytes, normalizeEcdhSharedSecret, utf8ToBytes } from
|
|
|
21
37
|
* @throws {InvalidEcdhSecretError} if `ecdhSharedSecret` is not exactly 32 bytes
|
|
22
38
|
*/
|
|
23
39
|
export function deriveEddsaKeypair(ecdhSharedSecret: Uint8Array | Hex): EddsaKeypair {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const secretScalar = deriveSecretScalar(seed)
|
|
28
|
-
const publicKey = derivePublicKey(seed)
|
|
29
|
-
const commitment = poseidon2(publicKey)
|
|
40
|
+
return deriveKeypair(ecdhSharedSecret, DOMAIN_EDDSA)
|
|
41
|
+
}
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
publicKey,
|
|
35
|
-
commitment
|
|
36
|
-
}
|
|
43
|
+
/** Derives a separate Semaphore identity whose public commitment may join the paymaster group. */
|
|
44
|
+
export function derivePaymasterKeypair(ecdhSharedSecret: Uint8Array | Hex): EddsaKeypair {
|
|
45
|
+
return deriveKeypair(ecdhSharedSecret, DOMAIN_PAYMASTER)
|
|
37
46
|
}
|
package/src/index.ts
CHANGED
|
@@ -37,9 +37,9 @@ export { metaStealthAddressUri } from "./stealthAddresses/metaStealthAddressUri"
|
|
|
37
37
|
export { decodeMetaStealthAddress } from "./stealthAddresses/decodeMetaStealthAddress"
|
|
38
38
|
export { buildAnnouncement } from "./stealthAddresses/buildAnnouncement"
|
|
39
39
|
export { deriveAuthKeypair } from "./auth"
|
|
40
|
-
export { deriveEddsaKeypair } from "./eddsa"
|
|
40
|
+
export { deriveEddsaKeypair, derivePaymasterKeypair } from "./eddsa"
|
|
41
41
|
export { EDDSA_PEER_POINT_HEX, eddsaPeerPointBytes, computeEddsaPeerPoint } from "./eddsa-peer"
|
|
42
|
-
export { computeNetworkId } from "./network-id"
|
|
42
|
+
export { computeNetworkId, computeMemberNetworkId } from "./network-id"
|
|
43
43
|
export { deriveKeys, deriveNetworkId } from "./derive"
|
|
44
44
|
export { normalizeConfirmationKey, computeConfirmationKeyHash } from "./confirmation-key"
|
|
45
45
|
export {
|
package/src/network-id.ts
CHANGED
|
@@ -1,26 +1,43 @@
|
|
|
1
1
|
import { keccak_256 } from "@noble/hashes/sha3"
|
|
2
2
|
import type { Hex } from "./types"
|
|
3
|
-
import { bytesToHex, toBytes } from "./bytes"
|
|
3
|
+
import { bytesToHex, concatBytes, toBytes, utf8ToBytes } from "./bytes"
|
|
4
|
+
import { DOMAIN_NETWORK_ID } from "./constants"
|
|
5
|
+
|
|
6
|
+
function publicKeyCoordinates(uncompressedPublicKey: Uint8Array | Hex, caller: string): Uint8Array {
|
|
7
|
+
const bytes = toBytes(uncompressedPublicKey)
|
|
8
|
+
|
|
9
|
+
if (bytes.length === 65 && bytes[0] === 0x04) {
|
|
10
|
+
return bytes.slice(1)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (bytes.length === 64) {
|
|
14
|
+
return bytes
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
throw new TypeError(`${caller}: expected a 65-byte 0x04-prefixed or 64-byte raw uncompressed public key`)
|
|
18
|
+
}
|
|
4
19
|
|
|
5
20
|
/**
|
|
6
|
-
* Computes
|
|
7
|
-
* (65-byte SEC1 `0x04`-prefixed or 64-byte raw `X || Y`).
|
|
21
|
+
* Computes a user's bytes32 `networkId` from an uncompressed secp256k1 public
|
|
22
|
+
* key (65-byte SEC1 `0x04`-prefixed or 64-byte raw `X || Y`).
|
|
8
23
|
*
|
|
9
|
-
* NORMATIVE: `networkId = keccak256(X || Y)` — the
|
|
10
|
-
*
|
|
24
|
+
* NORMATIVE: `networkId = keccak256(utf8(DOMAIN_NETWORK_ID) || X || Y)` — the
|
|
25
|
+
* SEC1 prefix is stripped.
|
|
11
26
|
*/
|
|
12
27
|
export function computeNetworkId(uncompressedPublicKey: Uint8Array | Hex): Hex {
|
|
13
|
-
const
|
|
28
|
+
const coordinates = publicKeyCoordinates(uncompressedPublicKey, "computeNetworkId")
|
|
14
29
|
|
|
15
|
-
|
|
30
|
+
return bytesToHex(keccak_256(concatBytes(utf8ToBytes(DOMAIN_NETWORK_ID), coordinates)))
|
|
31
|
+
}
|
|
16
32
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Computes a member's bytes32 id from the same public key forms.
|
|
35
|
+
*
|
|
36
|
+
* NORMATIVE: `memberNetworkId = keccak256(X || Y)`. The last 20 bytes are the
|
|
37
|
+
* member's auth address, which `NetworkMemberRegistry` reads.
|
|
38
|
+
*/
|
|
39
|
+
export function computeMemberNetworkId(uncompressedPublicKey: Uint8Array | Hex): Hex {
|
|
40
|
+
const coordinates = publicKeyCoordinates(uncompressedPublicKey, "computeMemberNetworkId")
|
|
24
41
|
|
|
25
42
|
return bytesToHex(keccak_256(coordinates))
|
|
26
43
|
}
|
package/src/types.ts
CHANGED
|
@@ -14,12 +14,14 @@ export interface Secp256k1Keypair {
|
|
|
14
14
|
uncompressedPublicKey: Hex
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/** The full key set derived from one device secret, plus the device's `networkId`. */
|
|
17
|
+
/** The full key set derived from one device secret, plus the device's `networkId` and `memberNetworkId`. */
|
|
18
18
|
export interface DerivedKeys {
|
|
19
19
|
/** Derivation scheme version. See {@link KEY_DERIVATION_VERSION}. */
|
|
20
|
-
version: "
|
|
21
|
-
/** bytes32
|
|
20
|
+
version: "v2"
|
|
21
|
+
/** bytes32 user identifier: `keccak256(utf8(DOMAIN_NETWORK_ID) || X || Y)` of the account pubkey. */
|
|
22
22
|
networkId: Hex
|
|
23
|
+
/** bytes32 member identifier: `keccak256(X || Y)`; its low 20 bytes are the member's auth address. */
|
|
24
|
+
memberNetworkId: Hex
|
|
23
25
|
/** Root secp256k1 keypair the other keys are derived from. */
|
|
24
26
|
account: Secp256k1Keypair
|
|
25
27
|
/** secp256k1 keypair used to authorize spending. */
|
package/types/index.d.ts
CHANGED
|
@@ -11,12 +11,14 @@ interface Secp256k1Keypair {
|
|
|
11
11
|
/** 65-byte SEC1 public key, `0x04`-prefixed (uncompressed form). */
|
|
12
12
|
uncompressedPublicKey: Hex;
|
|
13
13
|
}
|
|
14
|
-
/** The full key set derived from one device secret, plus the device's `networkId`. */
|
|
14
|
+
/** The full key set derived from one device secret, plus the device's `networkId` and `memberNetworkId`. */
|
|
15
15
|
interface DerivedKeys {
|
|
16
16
|
/** Derivation scheme version. See {@link KEY_DERIVATION_VERSION}. */
|
|
17
|
-
version: "
|
|
18
|
-
/** bytes32
|
|
17
|
+
version: "v2";
|
|
18
|
+
/** bytes32 user identifier: `keccak256(utf8(DOMAIN_NETWORK_ID) || X || Y)` of the account pubkey. */
|
|
19
19
|
networkId: Hex;
|
|
20
|
+
/** bytes32 member identifier: `keccak256(X || Y)`; its low 20 bytes are the member's auth address. */
|
|
21
|
+
memberNetworkId: Hex;
|
|
20
22
|
/** Root secp256k1 keypair the other keys are derived from. */
|
|
21
23
|
account: Secp256k1Keypair;
|
|
22
24
|
/** secp256k1 keypair used to authorize spending. */
|
|
@@ -47,11 +49,11 @@ interface EddsaKeypair {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
/**
|
|
50
|
-
* Normative constants for the `
|
|
52
|
+
* Normative constants for the `v2` key derivation scheme. Changing any of
|
|
51
53
|
* these changes every derived key and `networkId`, so they MUST NOT change
|
|
52
54
|
* without a version bump (new `KEY_DERIVATION_VERSION` and domain strings).
|
|
53
55
|
*/
|
|
54
|
-
declare const KEY_DERIVATION_VERSION: "
|
|
56
|
+
declare const KEY_DERIVATION_VERSION: "v2";
|
|
55
57
|
/** Fixed domain-separation message signed by {@link signDerivationMessage}. */
|
|
56
58
|
declare const FIXED_DOMAIN_MSG: "coordination-network/identity/v1";
|
|
57
59
|
/** Domain-separation tag for deriving the account keypair from a device secret. */
|
|
@@ -60,11 +62,15 @@ declare const DOMAIN_ACCOUNT: "coordination-network/identity/v1/account";
|
|
|
60
62
|
declare const DOMAIN_AUTH: "coordination-network/identity/v1/auth";
|
|
61
63
|
/** Domain-separation tag for deriving the Baby Jubjub EdDSA seed (the "fifth key") from the hardware ECDH shared secret. */
|
|
62
64
|
declare const DOMAIN_EDDSA: "coordination-network/identity/v1/eddsa";
|
|
65
|
+
/** Distinct Baby Jubjub seed for public paymaster membership; never use the credential holder key as a group leaf. */
|
|
66
|
+
declare const DOMAIN_PAYMASTER: "coordination-network/identity/v1/paymaster";
|
|
63
67
|
/** Domain-separation message hashed to the fixed NUMS P-256 peer point `Q` for the fifth-key ECDH (`eddsa-peer.ts`). */
|
|
64
68
|
declare const DOMAIN_EDDSA_PEER: "coordination-network/identity/v1/eddsa/peer";
|
|
69
|
+
/** Domain-separation tag prefixed to `X || Y` when hashing a user `networkId`. */
|
|
70
|
+
declare const DOMAIN_NETWORK_ID: "coordination-network/identity/v2/network-id";
|
|
65
71
|
/**
|
|
66
72
|
* Byte length of `nonce` and `networkId` in the binding messages — fixed at
|
|
67
|
-
* 32 so the
|
|
73
|
+
* 32 so the 128-byte device and 96-byte account messages are injective.
|
|
68
74
|
*/
|
|
69
75
|
declare const BINDING_NONCE_LENGTH: 32;
|
|
70
76
|
/** SEC1-compressed secp256k1 public key length. */
|
|
@@ -340,6 +346,8 @@ declare function deriveAuthKeypair(sig64: Uint8Array | Hex): Secp256k1Keypair;
|
|
|
340
346
|
* @throws {InvalidEcdhSecretError} if `ecdhSharedSecret` is not exactly 32 bytes
|
|
341
347
|
*/
|
|
342
348
|
declare function deriveEddsaKeypair(ecdhSharedSecret: Uint8Array | Hex): EddsaKeypair;
|
|
349
|
+
/** Derives a separate Semaphore identity whose public commitment may join the paymaster group. */
|
|
350
|
+
declare function derivePaymasterKeypair(ecdhSharedSecret: Uint8Array | Hex): EddsaKeypair;
|
|
343
351
|
|
|
344
352
|
/** Fixed NUMS P-256 peer point `Q` for the fifth-key ECDH — see `DERIVATION.md` §12. */
|
|
345
353
|
/**
|
|
@@ -361,21 +369,29 @@ declare function eddsaPeerPointBytes(): Uint8Array;
|
|
|
361
369
|
declare function computeEddsaPeerPoint(): Uint8Array;
|
|
362
370
|
|
|
363
371
|
/**
|
|
364
|
-
* Computes
|
|
365
|
-
* (65-byte SEC1 `0x04`-prefixed or 64-byte raw `X || Y`).
|
|
372
|
+
* Computes a user's bytes32 `networkId` from an uncompressed secp256k1 public
|
|
373
|
+
* key (65-byte SEC1 `0x04`-prefixed or 64-byte raw `X || Y`).
|
|
366
374
|
*
|
|
367
|
-
* NORMATIVE: `networkId = keccak256(X || Y)` — the
|
|
368
|
-
*
|
|
375
|
+
* NORMATIVE: `networkId = keccak256(utf8(DOMAIN_NETWORK_ID) || X || Y)` — the
|
|
376
|
+
* SEC1 prefix is stripped.
|
|
369
377
|
*/
|
|
370
378
|
declare function computeNetworkId(uncompressedPublicKey: Uint8Array | Hex): Hex;
|
|
379
|
+
/**
|
|
380
|
+
* Computes a member's bytes32 id from the same public key forms.
|
|
381
|
+
*
|
|
382
|
+
* NORMATIVE: `memberNetworkId = keccak256(X || Y)`. The last 20 bytes are the
|
|
383
|
+
* member's auth address, which `NetworkMemberRegistry` reads.
|
|
384
|
+
*/
|
|
385
|
+
declare function computeMemberNetworkId(uncompressedPublicKey: Uint8Array | Hex): Hex;
|
|
371
386
|
|
|
372
387
|
/**
|
|
373
|
-
* Derives the full `
|
|
374
|
-
*
|
|
388
|
+
* Derives the full `v2` device identity — `networkId`, `memberNetworkId` and the account,
|
|
389
|
+
* spending, viewing and auth secp256k1 keypairs — from a single device secret.
|
|
375
390
|
*
|
|
376
391
|
* Pipeline (NORMATIVE):
|
|
377
392
|
* 1. `account = deriveAccountKeypair(deviceSecret)`
|
|
378
|
-
* 2. `networkId = computeNetworkId(account.uncompressedPublicKey)`
|
|
393
|
+
* 2. `networkId = computeNetworkId(account.uncompressedPublicKey)` and
|
|
394
|
+
* `memberNetworkId = computeMemberNetworkId(account.uncompressedPublicKey)`
|
|
379
395
|
* 3. `sig = signDerivationMessage(account.privateKey)` (EIP-191, deterministic)
|
|
380
396
|
* 4. `{ spending, viewing } = deriveStealthKeys(sig)` (FluidKey semantics)
|
|
381
397
|
* 5. `auth = deriveAuthKeypair(sig)`
|
|
@@ -403,10 +419,15 @@ declare function computeConfirmationKeyHash(confirmationKey: Uint8Array | Hex):
|
|
|
403
419
|
*
|
|
404
420
|
* NORMATIVE layouts:
|
|
405
421
|
* ```
|
|
406
|
-
* deviceBindingMessage = nonce(32) || networkId(32) || holderKeyCommitment(32)
|
|
407
|
-
* accountBindingMessage = nonce(32) || confirmationKeyHash(32) || networkId(32)
|
|
422
|
+
* deviceBindingMessage = nonce(32) || networkId(32) || holderKeyCommitment(32) || requestCommitment(32)
|
|
423
|
+
* accountBindingMessage = nonce(32) || confirmationKeyHash(32) || networkId(32)
|
|
408
424
|
* ```
|
|
409
425
|
*
|
|
426
|
+
* B (P-256) signs the 128-byte device message; A (secp256k1) signs the 96-byte account message.
|
|
427
|
+
*
|
|
428
|
+
* `requestCommitment` is opaque here: the credential layer defines it and
|
|
429
|
+
* binds the issuer and scope shown at consent into it.
|
|
430
|
+
*
|
|
410
431
|
* NORMATIVE MUST: the device signs a locally-derived `networkId`, never one
|
|
411
432
|
* supplied by the issuance request. Every function here except the two
|
|
412
433
|
* low-level builders and {@link verifyDeviceBindingSignature} enforces this
|
|
@@ -414,12 +435,13 @@ declare function computeConfirmationKeyHash(confirmationKey: Uint8Array | Hex):
|
|
|
414
435
|
*/
|
|
415
436
|
|
|
416
437
|
/**
|
|
417
|
-
* Builds
|
|
438
|
+
* Builds the 128-byte `deviceBindingMessage`:
|
|
439
|
+
* `nonce(32) || networkId(32) || holderKeyCommitment(32) || requestCommitment(32)`.
|
|
418
440
|
*
|
|
419
441
|
* Low-level, spec-literal builder — prefer {@link buildBindingMessages},
|
|
420
442
|
* which enforces the locally-derived-`networkId` MUST structurally.
|
|
421
443
|
*/
|
|
422
|
-
declare function buildDeviceBindingMessage(nonce: Uint8Array | Hex, networkId: Uint8Array | Hex, holderKeyCommitment
|
|
444
|
+
declare function buildDeviceBindingMessage(nonce: Uint8Array | Hex, networkId: Uint8Array | Hex, holderKeyCommitment: Uint8Array | Hex, requestCommitment: Uint8Array | Hex): Uint8Array;
|
|
423
445
|
/**
|
|
424
446
|
* Builds the 96-byte `accountBindingMessage`:
|
|
425
447
|
* `nonce(32) || confirmationKeyHash(32) || networkId(32)`.
|
|
@@ -433,7 +455,7 @@ declare function buildAccountBindingMessage(nonce: Uint8Array | Hex, confirmatio
|
|
|
433
455
|
* derived internally from the account's own public key, so a caller cannot
|
|
434
456
|
* supply a `networkId` from elsewhere even by mistake.
|
|
435
457
|
*/
|
|
436
|
-
declare function buildBindingMessages(nonce: Uint8Array | Hex, accountUncompressedPublicKey: Uint8Array | Hex, confirmationKey: Uint8Array | Hex, holderKeyCommitment
|
|
458
|
+
declare function buildBindingMessages(nonce: Uint8Array | Hex, accountUncompressedPublicKey: Uint8Array | Hex, confirmationKey: Uint8Array | Hex, holderKeyCommitment: Uint8Array | Hex, requestCommitment: Uint8Array | Hex): {
|
|
437
459
|
networkId: Hex;
|
|
438
460
|
deviceBindingMessage: Uint8Array;
|
|
439
461
|
accountBindingMessage: Uint8Array;
|
|
@@ -470,11 +492,12 @@ declare function verifyAccountBindingSignature({ nonce, confirmationKey, account
|
|
|
470
492
|
* `prehash: true` (signers hash SHA-256 internally); `lowS: false` (hardware
|
|
471
493
|
* signatures aren't s-normalized — `DERIVATION.md` §11.4).
|
|
472
494
|
*/
|
|
473
|
-
declare function verifyDeviceBindingSignature({ nonce, networkId, confirmationKey, holderKeyCommitment, signature }: {
|
|
495
|
+
declare function verifyDeviceBindingSignature({ nonce, networkId, confirmationKey, holderKeyCommitment, requestCommitment, signature }: {
|
|
474
496
|
nonce: Uint8Array | Hex;
|
|
475
497
|
networkId: Uint8Array | Hex;
|
|
476
498
|
confirmationKey: Uint8Array | Hex;
|
|
477
|
-
holderKeyCommitment
|
|
499
|
+
holderKeyCommitment: Uint8Array | Hex;
|
|
500
|
+
requestCommitment: Uint8Array | Hex;
|
|
478
501
|
signature: Uint8Array | Hex;
|
|
479
502
|
}): boolean;
|
|
480
503
|
/**
|
|
@@ -483,11 +506,12 @@ declare function verifyDeviceBindingSignature({ nonce, networkId, confirmationKe
|
|
|
483
506
|
* with an unrelated account signature in either direction. `networkId` is
|
|
484
507
|
* returned even when `valid` is false, for issuer-side logging/rate-limiting.
|
|
485
508
|
*/
|
|
486
|
-
declare function verifyCredentialBinding({ nonce, accountPublicKey, confirmationKey, holderKeyCommitment, accountSignature, deviceSignature }: {
|
|
509
|
+
declare function verifyCredentialBinding({ nonce, accountPublicKey, confirmationKey, holderKeyCommitment, requestCommitment, accountSignature, deviceSignature }: {
|
|
487
510
|
nonce: Uint8Array | Hex;
|
|
488
511
|
accountPublicKey: Uint8Array | Hex;
|
|
489
512
|
confirmationKey: Uint8Array | Hex;
|
|
490
|
-
holderKeyCommitment
|
|
513
|
+
holderKeyCommitment: Uint8Array | Hex;
|
|
514
|
+
requestCommitment: Uint8Array | Hex;
|
|
491
515
|
accountSignature: Uint8Array | Hex;
|
|
492
516
|
deviceSignature: Uint8Array | Hex;
|
|
493
517
|
}): {
|
|
@@ -495,5 +519,5 @@ declare function verifyCredentialBinding({ nonce, accountPublicKey, confirmation
|
|
|
495
519
|
networkId: Hex;
|
|
496
520
|
};
|
|
497
521
|
|
|
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 };
|
|
522
|
+
export { BINDING_NONCE_LENGTH, COMPRESSED_PUBLIC_KEY_LENGTH, DOMAIN_ACCOUNT, DOMAIN_AUTH, DOMAIN_EDDSA, DOMAIN_EDDSA_PEER, DOMAIN_NETWORK_ID, DOMAIN_PAYMASTER, 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, computeMemberNetworkId, computeNetworkId, computeSharedSecret, decodeMetaStealthAddress, deriveAccountKeypair, deriveAuthKeypair, deriveEddsaKeypair, deriveKeys, deriveNetworkId, derivePaymasterKeypair, 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
523
|
export type { Address, DerivedKeys, EddsaKeypair, HashFn, Hex, KeygenErrorCode, Secp256k1Keypair };
|