@taceo/oprf-client 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/dist/index.cjs +550 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +207 -0
- package/dist/index.d.ts +207 -0
- package/dist/index.js +521 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TACEO
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# @taceo/oprf-client
|
|
2
|
+
|
|
3
|
+
WebSocket client for distributed threshold OPRF over a network of TACEO service nodes.
|
|
4
|
+
|
|
5
|
+
This package provides high-level APIs for interacting with threshold OPRF services, handling the full protocol flow including session management, challenge generation, proof verification, and output finalization.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @taceo/oprf-client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Full Distributed OPRF
|
|
16
|
+
|
|
17
|
+
Services must be pre-built WebSocket URLs. Use `toOprfUri` to construct them from base URLs.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { distributedOprf, toOprfUri } from '@taceo/oprf-client';
|
|
21
|
+
|
|
22
|
+
const bases = [
|
|
23
|
+
'http://node1.example.com',
|
|
24
|
+
'http://node2.example.com',
|
|
25
|
+
'http://node3.example.com',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const services = bases.map((s) => toOprfUri(s, 'my-module'));
|
|
29
|
+
|
|
30
|
+
const result = await distributedOprf(
|
|
31
|
+
services,
|
|
32
|
+
2, // threshold
|
|
33
|
+
12345n, // query
|
|
34
|
+
0n, // domain separator
|
|
35
|
+
{ auth: { api_key: 'secret' } }
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
console.log(result.output); // OPRF output (bigint)
|
|
39
|
+
console.log(result.dlogProof); // Chaum-Pedersen proof
|
|
40
|
+
console.log(result.epoch); // Key epoch from servers
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Building Service URLs
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { toOprfUri } from '@taceo/oprf-client';
|
|
47
|
+
|
|
48
|
+
// http → ws, https → wss; appends /api/{module}/oprf?version={protocolVersion}
|
|
49
|
+
const url = toOprfUri('https://node1.example.com', 'my-module');
|
|
50
|
+
// → 'wss://node1.example.com/api/my-module/oprf?version=1.0.0'
|
|
51
|
+
|
|
52
|
+
// Custom protocol version
|
|
53
|
+
const urlV2 = toOprfUri('https://node1.example.com', 'my-module', '2.0.0');
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Step-by-Step Protocol
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import {
|
|
60
|
+
initSessions,
|
|
61
|
+
finishSessions,
|
|
62
|
+
generateChallengeRequest,
|
|
63
|
+
verifyDlogEquality,
|
|
64
|
+
toOprfUri,
|
|
65
|
+
} from '@taceo/oprf-client';
|
|
66
|
+
import {
|
|
67
|
+
blindQuery,
|
|
68
|
+
unblindResponse,
|
|
69
|
+
finalizeOutput,
|
|
70
|
+
randomBlindingFactor,
|
|
71
|
+
prepareBlindingFactor,
|
|
72
|
+
} from '@taceo/oprf-core';
|
|
73
|
+
|
|
74
|
+
const services = bases.map((s) => toOprfUri(s, module));
|
|
75
|
+
|
|
76
|
+
// 1. Blind the query
|
|
77
|
+
const beta = randomBlindingFactor();
|
|
78
|
+
const blindedRequest = blindQuery(query, beta);
|
|
79
|
+
|
|
80
|
+
// 2. Initialize sessions with service nodes
|
|
81
|
+
const sessions = await initSessions(services, threshold, {
|
|
82
|
+
request_id: crypto.randomUUID(),
|
|
83
|
+
blinded_query: blindedRequest,
|
|
84
|
+
auth: {},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// 3. Generate challenge from commitments
|
|
88
|
+
const challenge = generateChallengeRequest(sessions);
|
|
89
|
+
|
|
90
|
+
// 4. Finish sessions to get proof shares
|
|
91
|
+
const proofShares = await finishSessions(sessions, challenge);
|
|
92
|
+
|
|
93
|
+
// 5. Verify the combined DLog proof
|
|
94
|
+
const proof = verifyDlogEquality(
|
|
95
|
+
requestId,
|
|
96
|
+
sessions.oprfPublicKeys[0],
|
|
97
|
+
blindedRequest,
|
|
98
|
+
proofShares,
|
|
99
|
+
challenge
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// 6. Unblind and finalize
|
|
103
|
+
const blindedResponse = challenge.blindedResponse();
|
|
104
|
+
const unblinded = unblindResponse(blindedResponse, prepareBlindingFactor(beta));
|
|
105
|
+
const output = finalizeOutput(domainSeparator, query, unblinded);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## API
|
|
109
|
+
|
|
110
|
+
### Main Functions
|
|
111
|
+
|
|
112
|
+
- **`distributedOprf(services, threshold, query, domainSeparator, options?): Promise<VerifiableOprfOutput>`**
|
|
113
|
+
|
|
114
|
+
End-to-end distributed OPRF: blind → init sessions → challenge → finish → verify → unblind → finalize. Services must be pre-built WS URLs (use `toOprfUri`).
|
|
115
|
+
|
|
116
|
+
- **`toOprfUri(service, auth, protocolVersion?): string`**
|
|
117
|
+
|
|
118
|
+
Build a WebSocket URL for a single OPRF service. Converts `http://` → `ws://`, `https://` → `wss://`. Appends `/api/{auth}/oprf?version={protocolVersion}`.
|
|
119
|
+
|
|
120
|
+
- **`initSessions(services, threshold, request): Promise<OprfSessions>`**
|
|
121
|
+
|
|
122
|
+
Connect to service nodes via WebSocket (pre-built WS URLs), send blinded query, receive commitments. On threshold failure throws `NodeError[]` (use `aggregateError` to convert).
|
|
123
|
+
|
|
124
|
+
- **`finishSessions(sessions, challenge): Promise<DLogProofShareShamir[]>`**
|
|
125
|
+
|
|
126
|
+
Send challenge to nodes, receive proof shares.
|
|
127
|
+
|
|
128
|
+
- **`generateChallengeRequest(sessions): DLogCommitmentsShamir`**
|
|
129
|
+
|
|
130
|
+
Combine commitments from sessions into a challenge request.
|
|
131
|
+
|
|
132
|
+
- **`verifyDlogEquality(requestId, publicKey, blindedRequest, proofShares, challenge): DLogEqualityProof`**
|
|
133
|
+
|
|
134
|
+
Combine proof shares and verify the DLog equality proof.
|
|
135
|
+
|
|
136
|
+
- **`aggregateError(threshold, errors): OprfClientError`**
|
|
137
|
+
|
|
138
|
+
Aggregate an array of `NodeError` into a single protocol-level `OprfClientError`.
|
|
139
|
+
|
|
140
|
+
### Types
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
interface VerifiableOprfOutput {
|
|
144
|
+
output: bigint; // Final OPRF output
|
|
145
|
+
dlogProof: DLogEqualityProof; // Combined Chaum-Pedersen proof
|
|
146
|
+
blindedRequest: AffinePoint<bigint>; // Client's blinded query
|
|
147
|
+
blindedResponse: AffinePoint<bigint>; // Combined blinded response
|
|
148
|
+
unblindedResponse: AffinePoint<bigint>;
|
|
149
|
+
oprfPublicKey: AffinePoint<bigint>; // Service public key
|
|
150
|
+
epoch: number; // Key epoch
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface DistributedOprfOptions<Auth = unknown> {
|
|
154
|
+
auth?: Auth; // Auth payload for OprfRequest
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Error Handling
|
|
159
|
+
|
|
160
|
+
The library uses a two-tier error model:
|
|
161
|
+
|
|
162
|
+
- **`NodeError`** — per-node error (WebSocket / service level)
|
|
163
|
+
- **`OprfClientError`** — protocol-level error (aggregated or logical)
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import {
|
|
167
|
+
OprfClientError,
|
|
168
|
+
isOprfClientError,
|
|
169
|
+
NodeError,
|
|
170
|
+
isNodeError,
|
|
171
|
+
ServiceError,
|
|
172
|
+
} from '@taceo/oprf-client';
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const result = await distributedOprf(...);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if (isOprfClientError(err)) {
|
|
178
|
+
switch (err.code) {
|
|
179
|
+
case 'NonUniqueServices':
|
|
180
|
+
// Duplicate service URLs provided
|
|
181
|
+
break;
|
|
182
|
+
case 'ThresholdServiceError':
|
|
183
|
+
// >= threshold nodes returned the same application-level error
|
|
184
|
+
console.log(err.details?.serviceError?.errorCode);
|
|
185
|
+
break;
|
|
186
|
+
case 'Networking':
|
|
187
|
+
// >= threshold nodes had WebSocket / networking errors
|
|
188
|
+
console.log(err.details?.networkingErrors);
|
|
189
|
+
break;
|
|
190
|
+
case 'UnexpectedMessage':
|
|
191
|
+
// >= threshold nodes reported unexpected message format
|
|
192
|
+
break;
|
|
193
|
+
case 'InvalidDLogProof':
|
|
194
|
+
// Proof verification failed
|
|
195
|
+
break;
|
|
196
|
+
case 'InconsistentOprfPublicKeys':
|
|
197
|
+
// Nodes returned different public keys
|
|
198
|
+
break;
|
|
199
|
+
case 'CannotFinishSession':
|
|
200
|
+
// Failed to finish a session with a node
|
|
201
|
+
break;
|
|
202
|
+
case 'NodeErrorDisagreement':
|
|
203
|
+
// Nodes returned differing errors — no consensus reached
|
|
204
|
+
console.log(err.details?.nodeErrors);
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Error Codes
|
|
212
|
+
|
|
213
|
+
#### `OprfClientErrorCode` (protocol-level)
|
|
214
|
+
|
|
215
|
+
| Code | Description |
|
|
216
|
+
| ---------------------------- | ----------------------------------------------------- |
|
|
217
|
+
| `NonUniqueServices` | Duplicate service URLs provided |
|
|
218
|
+
| `ThresholdServiceError` | ≥ threshold nodes returned the same application error |
|
|
219
|
+
| `Networking` | ≥ threshold nodes had WebSocket / networking errors |
|
|
220
|
+
| `UnexpectedMessage` | ≥ threshold nodes reported unexpected message format |
|
|
221
|
+
| `InvalidDLogProof` | DLog proof verification failed |
|
|
222
|
+
| `InconsistentOprfPublicKeys` | Nodes returned different public keys |
|
|
223
|
+
| `CannotFinishSession` | Failed to finish session after init |
|
|
224
|
+
| `NodeErrorDisagreement` | Nodes returned differing errors, no consensus |
|
|
225
|
+
| `Unknown` | Unexpected error |
|
|
226
|
+
|
|
227
|
+
#### `NodeErrorCode` (per-node)
|
|
228
|
+
|
|
229
|
+
| Code | Description |
|
|
230
|
+
| ------------------- | ------------------------------------------------ |
|
|
231
|
+
| `ServiceError` | Application-level error in WebSocket close frame |
|
|
232
|
+
| `WsError` | WebSocket connection or transport error |
|
|
233
|
+
| `UnexpectedMessage` | Unexpected message format from a node |
|
|
234
|
+
| `Unknown` | Unclassified per-node error |
|
|
235
|
+
|
|
236
|
+
## Wire Protocol
|
|
237
|
+
|
|
238
|
+
- WebSocket endpoint: `/api/{module}/oprf?version={protocolVersion}`
|
|
239
|
+
- Use `toOprfUri` to build URLs — `http://` → `ws://`, `https://` → `wss://`
|
|
240
|
+
- Messages use JSON with string-serialized BigInts for affine points
|
|
241
|
+
|
|
242
|
+
## License
|
|
243
|
+
|
|
244
|
+
MIT
|