-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathoprfClient.ts
More file actions
110 lines (93 loc) · 3.82 KB
/
oprfClient.ts
File metadata and controls
110 lines (93 loc) · 3.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
const OPRF = require('oprf');
import axios from 'axios';
import { base64urlToUtf8, hexToUtf8, utf8ToBase64url, utf8ToHex } from './encodingUtils';
const green = '\x1b[32m%s\x1b[0m';
const red = '\x1b[31m%s\x1b[0m';
const yellow = '\x1b[33m%s\x1b[0m';
const blue = '\x1b[34m%s\x1b[0m';
export async function oprfClient(
serverUrl: string,
clientId: string,
password: string,
encoding: 'UTF-8' | 'base64url' | 'hex' = 'UTF-8',
): Promise<Uint8Array | null> {
const oprf = new OPRF();
await oprf.ready;
console.log('Hashing password to curve point...');
const hashPoint = oprf.hashToPoint(password);
if (!oprf.isValidPoint(hashPoint)) {
throw new Error('Hash Point is invalid. This should never happen.');
}
console.log(green, 'Hash Point is valid.');
let maskedSaltedPointIsValid = false;
let saltedPointIsValid = false;
do {
console.log('Masking hash point...');
const maskedPoint = oprf.maskInput(hashPoint);
if (!oprf.isValidPoint(maskedPoint.point)) {
throw new Error('Masked Point is invalid. This should never happen.');
}
console.log(green, 'Masked Point is valid.');
const encodedMaskedPoint = oprf.encodePoint(maskedPoint.point, 'UTF-8');
const input =
encoding === 'base64url'
? utf8ToBase64url(encodedMaskedPoint)
: encoding === 'hex'
? utf8ToHex(encodedMaskedPoint)
: encodedMaskedPoint;
console.log(`Input to OPRF (${encoding}-encoded):`);
console.log(blue, input);
try {
console.log(`Sending input (masked point) to server (${clientId} @ ${serverUrl}) ...`);
const response = await axios.post(serverUrl, { id: clientId, input });
const output = response.data.output as string;
console.log(green, 'Received output (salted masked point) from server.');
console.log(`Salted masked point (${encoding}-encoded):`);
console.log(blue, output);
const encodedMaskedSaltedPoint =
encoding === 'base64url'
? base64urlToUtf8(output)
: encoding === 'hex'
? hexToUtf8(output)
: output;
console.log('Decoding salted masked point...');
const maskedSaltedPoint = oprf.decodePoint(encodedMaskedSaltedPoint, 'UTF-8');
maskedSaltedPointIsValid = oprf.isValidPoint(maskedSaltedPoint);
if (!maskedSaltedPointIsValid) {
console.error(yellow, 'Salted masked point is invalid.');
console.log('This can happen... Trying again with a newly masked point.');
continue;
}
console.log(green, 'Salted masked point is valid.');
try {
console.log('Unmasking salted point...');
const saltedPoint = oprf.unmaskPoint(maskedSaltedPoint, maskedPoint.mask);
saltedPointIsValid = oprf.isValidPoint(saltedPoint);
if (saltedPointIsValid) {
console.log(green, 'Unmasked salted point is valid.');
console.log('Unmasked salted point (hex):');
console.log(blue, oprf.encodePoint(saltedPoint, 'hex'));
console.log('OPRF complete.');
return saltedPoint;
}
console.error(yellow, 'Unmasked salted point is invalid.');
console.log('This can happen... Trying again with a newly masked point.');
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
console.error(red, 'Error:', message, '\nThis should never happen.');
}
} catch (err) {
if (axios.isAxiosError(err)) {
console.error(yellow, 'Error from OPRF server:', err.response?.data?.error ?? err.message);
console.log('This can happen… Trying again with a newly masked point.');
if (err.response?.status !== 400) {
return null;
}
} else {
throw err;
}
}
} while (!(maskedSaltedPointIsValid && saltedPointIsValid));
return null;
}
module.exports = oprfClient;