@noble/curves 1.9.7 → 2.0.0-beta.2
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/README.md +520 -505
- package/abstract/bls.d.ts +58 -120
- package/abstract/bls.d.ts.map +1 -1
- package/abstract/bls.js +108 -152
- package/abstract/bls.js.map +1 -1
- package/abstract/curve.d.ts +18 -54
- package/abstract/curve.d.ts.map +1 -1
- package/abstract/curve.js +30 -49
- package/abstract/curve.js.map +1 -1
- package/abstract/edwards.d.ts +18 -77
- package/abstract/edwards.d.ts.map +1 -1
- package/abstract/edwards.js +68 -144
- package/abstract/edwards.js.map +1 -1
- package/abstract/fft.js +14 -27
- package/abstract/fft.js.map +1 -1
- package/abstract/hash-to-curve.d.ts +35 -47
- package/abstract/hash-to-curve.d.ts.map +1 -1
- package/abstract/hash-to-curve.js +42 -46
- package/abstract/hash-to-curve.js.map +1 -1
- package/abstract/modular.d.ts +5 -17
- package/abstract/modular.d.ts.map +1 -1
- package/abstract/modular.js +170 -169
- package/abstract/modular.js.map +1 -1
- package/abstract/montgomery.d.ts +7 -12
- package/abstract/montgomery.d.ts.map +1 -1
- package/abstract/montgomery.js +22 -29
- package/abstract/montgomery.js.map +1 -1
- package/abstract/oprf.d.ts +282 -0
- package/abstract/oprf.d.ts.map +1 -0
- package/abstract/oprf.js +297 -0
- package/abstract/oprf.js.map +1 -0
- package/abstract/poseidon.d.ts.map +1 -1
- package/abstract/poseidon.js +26 -31
- package/abstract/poseidon.js.map +1 -1
- package/abstract/tower.d.ts.map +1 -1
- package/abstract/tower.js +43 -19
- package/abstract/tower.js.map +1 -1
- package/abstract/weierstrass.d.ts +77 -168
- package/abstract/weierstrass.d.ts.map +1 -1
- package/abstract/weierstrass.js +184 -389
- package/abstract/weierstrass.js.map +1 -1
- package/bls12-381.d.ts +5 -11
- package/bls12-381.d.ts.map +1 -1
- package/bls12-381.js +161 -181
- package/bls12-381.js.map +1 -1
- package/bn254.d.ts +59 -11
- package/bn254.d.ts.map +1 -1
- package/bn254.js +69 -97
- package/bn254.js.map +1 -1
- package/ed25519.d.ts +33 -48
- package/ed25519.d.ts.map +1 -1
- package/ed25519.js +147 -161
- package/ed25519.js.map +1 -1
- package/ed448.d.ts +27 -36
- package/ed448.d.ts.map +1 -1
- package/ed448.js +143 -164
- package/ed448.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.js +20 -4
- package/index.js.map +1 -1
- package/misc.d.ts +10 -14
- package/misc.d.ts.map +1 -1
- package/misc.js +53 -62
- package/misc.js.map +1 -1
- package/nist.d.ts +31 -16
- package/nist.d.ts.map +1 -1
- package/nist.js +75 -64
- package/nist.js.map +1 -1
- package/package.json +20 -234
- package/secp256k1.d.ts +17 -30
- package/secp256k1.d.ts.map +1 -1
- package/secp256k1.js +59 -73
- package/secp256k1.js.map +1 -1
- package/src/abstract/bls.ts +207 -354
- package/src/abstract/curve.ts +25 -84
- package/src/abstract/edwards.ts +68 -193
- package/src/abstract/hash-to-curve.ts +71 -85
- package/src/abstract/modular.ts +150 -134
- package/src/abstract/montgomery.ts +28 -35
- package/src/abstract/oprf.ts +600 -0
- package/src/abstract/poseidon.ts +6 -8
- package/src/abstract/tower.ts +0 -3
- package/src/abstract/weierstrass.ts +203 -525
- package/src/bls12-381.ts +133 -139
- package/src/bn254.ts +69 -93
- package/src/ed25519.ts +106 -133
- package/src/ed448.ts +111 -138
- package/src/index.ts +19 -3
- package/src/misc.ts +68 -51
- package/src/nist.ts +77 -70
- package/src/secp256k1.ts +46 -81
- package/src/utils.ts +67 -137
- package/src/webcrypto.ts +403 -0
- package/utils.d.ts +31 -38
- package/utils.d.ts.map +1 -1
- package/utils.js +66 -185
- package/utils.js.map +1 -1
- package/webcrypto.d.ts +99 -0
- package/webcrypto.d.ts.map +1 -0
- package/webcrypto.js +256 -0
- package/webcrypto.js.map +1 -0
- package/_shortw_utils.d.ts +0 -19
- package/_shortw_utils.d.ts.map +0 -1
- package/_shortw_utils.js +0 -20
- package/_shortw_utils.js.map +0 -1
- package/abstract/utils.d.ts +0 -78
- package/abstract/utils.d.ts.map +0 -1
- package/abstract/utils.js +0 -73
- package/abstract/utils.js.map +0 -1
- package/esm/_shortw_utils.d.ts +0 -19
- package/esm/_shortw_utils.d.ts.map +0 -1
- package/esm/_shortw_utils.js +0 -16
- package/esm/_shortw_utils.js.map +0 -1
- package/esm/abstract/bls.d.ts +0 -190
- package/esm/abstract/bls.d.ts.map +0 -1
- package/esm/abstract/bls.js +0 -408
- package/esm/abstract/bls.js.map +0 -1
- package/esm/abstract/curve.d.ts +0 -231
- package/esm/abstract/curve.d.ts.map +0 -1
- package/esm/abstract/curve.js +0 -465
- package/esm/abstract/curve.js.map +0 -1
- package/esm/abstract/edwards.d.ts +0 -243
- package/esm/abstract/edwards.d.ts.map +0 -1
- package/esm/abstract/edwards.js +0 -627
- package/esm/abstract/edwards.js.map +0 -1
- package/esm/abstract/fft.d.ts +0 -122
- package/esm/abstract/fft.d.ts.map +0 -1
- package/esm/abstract/fft.js +0 -425
- package/esm/abstract/fft.js.map +0 -1
- package/esm/abstract/hash-to-curve.d.ts +0 -102
- package/esm/abstract/hash-to-curve.d.ts.map +0 -1
- package/esm/abstract/hash-to-curve.js +0 -203
- package/esm/abstract/hash-to-curve.js.map +0 -1
- package/esm/abstract/modular.d.ts +0 -171
- package/esm/abstract/modular.d.ts.map +0 -1
- package/esm/abstract/modular.js +0 -530
- package/esm/abstract/modular.js.map +0 -1
- package/esm/abstract/montgomery.d.ts +0 -30
- package/esm/abstract/montgomery.d.ts.map +0 -1
- package/esm/abstract/montgomery.js +0 -157
- package/esm/abstract/montgomery.js.map +0 -1
- package/esm/abstract/poseidon.d.ts +0 -68
- package/esm/abstract/poseidon.d.ts.map +0 -1
- package/esm/abstract/poseidon.js +0 -296
- package/esm/abstract/poseidon.js.map +0 -1
- package/esm/abstract/tower.d.ts +0 -95
- package/esm/abstract/tower.d.ts.map +0 -1
- package/esm/abstract/tower.js +0 -714
- package/esm/abstract/tower.js.map +0 -1
- package/esm/abstract/utils.d.ts +0 -78
- package/esm/abstract/utils.d.ts.map +0 -1
- package/esm/abstract/utils.js +0 -70
- package/esm/abstract/utils.js.map +0 -1
- package/esm/abstract/weierstrass.d.ts +0 -416
- package/esm/abstract/weierstrass.d.ts.map +0 -1
- package/esm/abstract/weierstrass.js +0 -1413
- package/esm/abstract/weierstrass.js.map +0 -1
- package/esm/bls12-381.d.ts +0 -16
- package/esm/bls12-381.d.ts.map +0 -1
- package/esm/bls12-381.js +0 -705
- package/esm/bls12-381.js.map +0 -1
- package/esm/bn254.d.ts +0 -18
- package/esm/bn254.d.ts.map +0 -1
- package/esm/bn254.js +0 -214
- package/esm/bn254.js.map +0 -1
- package/esm/ed25519.d.ts +0 -106
- package/esm/ed25519.d.ts.map +0 -1
- package/esm/ed25519.js +0 -467
- package/esm/ed25519.js.map +0 -1
- package/esm/ed448.d.ts +0 -100
- package/esm/ed448.d.ts.map +0 -1
- package/esm/ed448.js +0 -459
- package/esm/ed448.js.map +0 -1
- package/esm/index.d.ts +0 -2
- package/esm/index.d.ts.map +0 -1
- package/esm/index.js +0 -17
- package/esm/index.js.map +0 -1
- package/esm/jubjub.d.ts +0 -12
- package/esm/jubjub.d.ts.map +0 -1
- package/esm/jubjub.js +0 -12
- package/esm/jubjub.js.map +0 -1
- package/esm/misc.d.ts +0 -19
- package/esm/misc.d.ts.map +0 -1
- package/esm/misc.js +0 -109
- package/esm/misc.js.map +0 -1
- package/esm/nist.d.ts +0 -21
- package/esm/nist.d.ts.map +0 -1
- package/esm/nist.js +0 -132
- package/esm/nist.js.map +0 -1
- package/esm/p256.d.ts +0 -16
- package/esm/p256.d.ts.map +0 -1
- package/esm/p256.js +0 -16
- package/esm/p256.js.map +0 -1
- package/esm/p384.d.ts +0 -16
- package/esm/p384.d.ts.map +0 -1
- package/esm/p384.js +0 -16
- package/esm/p384.js.map +0 -1
- package/esm/p521.d.ts +0 -16
- package/esm/p521.d.ts.map +0 -1
- package/esm/p521.js +0 -16
- package/esm/p521.js.map +0 -1
- package/esm/package.json +0 -4
- package/esm/pasta.d.ts +0 -10
- package/esm/pasta.d.ts.map +0 -1
- package/esm/pasta.js +0 -10
- package/esm/pasta.js.map +0 -1
- package/esm/secp256k1.d.ts +0 -89
- package/esm/secp256k1.d.ts.map +0 -1
- package/esm/secp256k1.js +0 -294
- package/esm/secp256k1.js.map +0 -1
- package/esm/utils.d.ts +0 -110
- package/esm/utils.d.ts.map +0 -1
- package/esm/utils.js +0 -322
- package/esm/utils.js.map +0 -1
- package/jubjub.d.ts +0 -12
- package/jubjub.d.ts.map +0 -1
- package/jubjub.js +0 -15
- package/jubjub.js.map +0 -1
- package/p256.d.ts +0 -16
- package/p256.d.ts.map +0 -1
- package/p256.js +0 -13
- package/p256.js.map +0 -1
- package/p384.d.ts +0 -16
- package/p384.d.ts.map +0 -1
- package/p384.js +0 -13
- package/p384.js.map +0 -1
- package/p521.d.ts +0 -16
- package/p521.d.ts.map +0 -1
- package/p521.js +0 -13
- package/p521.js.map +0 -1
- package/pasta.d.ts +0 -10
- package/pasta.d.ts.map +0 -1
- package/pasta.js +0 -13
- package/pasta.js.map +0 -1
- package/src/_shortw_utils.ts +0 -21
- package/src/abstract/utils.ts +0 -80
- package/src/jubjub.ts +0 -12
- package/src/p256.ts +0 -15
- package/src/p384.ts +0 -15
- package/src/p521.ts +0 -15
- package/src/package.json +0 -3
- package/src/pasta.ts +0 -9
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9497: Oblivious Pseudorandom Functions (OPRFs) Using Prime-Order Groups.
|
|
3
|
+
* https://www.rfc-editor.org/rfc/rfc9497
|
|
4
|
+
*
|
|
5
|
+
|
|
6
|
+
OPRF allows to interactively create an `Output = PRF(Input, serverSecretKey)`:
|
|
7
|
+
|
|
8
|
+
- Server cannot calculate Output by itself: it doesn't know Input
|
|
9
|
+
- Client cannot calculate Output by itself: it doesn't know server secretKey
|
|
10
|
+
- An attacker interception the communication can't restore Input/Output/serverSecretKey and can't
|
|
11
|
+
link Input to some value.
|
|
12
|
+
|
|
13
|
+
## Issues
|
|
14
|
+
|
|
15
|
+
- Low-entropy inputs (e.g. password '123') enable brute-forced dictionary attacks by the server
|
|
16
|
+
(solveable by domain separation in POPRF)
|
|
17
|
+
- High-level protocol needs to be constructed on top, because OPRF is low-level
|
|
18
|
+
|
|
19
|
+
## Use cases
|
|
20
|
+
|
|
21
|
+
1. **Password-Authenticated Key Exchange (PAKE):** Enables secure password login (e.g., OPAQUE)
|
|
22
|
+
without revealing the password to the server.
|
|
23
|
+
2. **Private Set Intersection (PSI):** Allows two parties to compute the intersection of their
|
|
24
|
+
private sets without revealing non-intersecting elements.
|
|
25
|
+
3. **Anonymous Credential Systems:** Supports issuance of anonymous, unlinkable credentials
|
|
26
|
+
(e.g., Privacy Pass) using blind OPRF evaluation.
|
|
27
|
+
4. **Private Information Retrieval (PIR):** Helps users query databases without revealing which
|
|
28
|
+
item they accessed.
|
|
29
|
+
5. **Encrypted Search / Secure Indexing:** Enables keyword search over encrypted data while keeping
|
|
30
|
+
queries private.
|
|
31
|
+
6. **Spam Prevention and Rate-Limiting:** Issues anonymous tokens to prevent abuse
|
|
32
|
+
(e.g., CAPTCHA bypass) without compromising user privacy.
|
|
33
|
+
|
|
34
|
+
## Modes
|
|
35
|
+
|
|
36
|
+
- OPRF: simple mode, client doesn't need to know server public key
|
|
37
|
+
- VOPRF: verifable mode, allows client to verify that server used secret key corresponding to known public key
|
|
38
|
+
- POPRF: partially oblivious mode, VOPRF + domain separation
|
|
39
|
+
|
|
40
|
+
There is also non-interactive mode (Evaluate) that supports creating Output in non-interactive mode with knowledge of secret key.
|
|
41
|
+
|
|
42
|
+
Flow:
|
|
43
|
+
- (once) Server generates secret and public keys, distributes public keys to clients
|
|
44
|
+
- deterministically: `deriveKeyPair` or just random: `generateKeyPair`
|
|
45
|
+
- Client blinds input: `blind(secretInput)`
|
|
46
|
+
- Server evaluates blinded input: `blindEvaluate` generated by client, sends result to client
|
|
47
|
+
- Client creates output using result of evaluation via 'finalize'
|
|
48
|
+
|
|
49
|
+
* @module
|
|
50
|
+
*/
|
|
51
|
+
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
52
|
+
import {
|
|
53
|
+
abytes,
|
|
54
|
+
asciiToBytes,
|
|
55
|
+
bytesToNumberBE,
|
|
56
|
+
bytesToNumberLE,
|
|
57
|
+
concatBytes,
|
|
58
|
+
numberToBytesBE,
|
|
59
|
+
randomBytes,
|
|
60
|
+
validateObject,
|
|
61
|
+
} from '../utils.ts';
|
|
62
|
+
import { pippenger, type CurvePoint, type CurvePointCons } from './curve.ts';
|
|
63
|
+
import { _DST_scalar, type H2CDSTOpts } from './hash-to-curve.ts';
|
|
64
|
+
import { getMinHashLength, mapHashToField } from './modular.ts';
|
|
65
|
+
|
|
66
|
+
// OPRF is designed to be used across network, so we default to serialized values.
|
|
67
|
+
export type PointBytes = Uint8Array;
|
|
68
|
+
export type ScalarBytes = Uint8Array;
|
|
69
|
+
export type Bytes = Uint8Array;
|
|
70
|
+
export type RNG = typeof randomBytes;
|
|
71
|
+
|
|
72
|
+
export type OPRFOpts<P extends CurvePoint<any, P>> = {
|
|
73
|
+
name: string;
|
|
74
|
+
Point: CurvePointCons<P>; // we don't return Point, so we need generic interface only
|
|
75
|
+
// Fn: IField<bigint>;
|
|
76
|
+
hash(msg: Bytes): Bytes;
|
|
77
|
+
hashToScalar(msg: Uint8Array, options: H2CDSTOpts): bigint;
|
|
78
|
+
hashToGroup(msg: Uint8Array, options: H2CDSTOpts): P;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type OPRFKeys = { secretKey: ScalarBytes; publicKey: PointBytes };
|
|
82
|
+
export type OPRFBlind = { blind: Uint8Array; blinded: Uint8Array };
|
|
83
|
+
export type OPRFBlindEval = { evaluated: PointBytes; proof: Bytes };
|
|
84
|
+
export type OPRFBlindEvalBatch = { evaluated: PointBytes[]; proof: Bytes };
|
|
85
|
+
export type OPRFFinalizeItem = {
|
|
86
|
+
input: Bytes;
|
|
87
|
+
blind: ScalarBytes;
|
|
88
|
+
evaluated: PointBytes;
|
|
89
|
+
blinded: PointBytes;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Represents a full OPRF ciphersuite implementation according to RFC 9497.
|
|
94
|
+
* This object bundles the three protocol variants (OPRF, VOPRF, POPRF) for a specific
|
|
95
|
+
* prime-order group and hash function combination.
|
|
96
|
+
*
|
|
97
|
+
* @see https://www.rfc-editor.org/rfc/rfc9497.html
|
|
98
|
+
*/
|
|
99
|
+
export type OPRF = {
|
|
100
|
+
/**
|
|
101
|
+
* The unique identifier for the ciphersuite, e.g., "ristretto255-SHA512".
|
|
102
|
+
* This name is used for domain separation to prevent cross-protocol attacks.
|
|
103
|
+
*/
|
|
104
|
+
readonly name: string;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The base Oblivious Pseudorandom Function (OPRF) mode (mode 0x00).
|
|
108
|
+
* This is a two-party protocol between a client and a server to compute F(k, x)
|
|
109
|
+
* where 'k' is the server's key and 'x' is the client's input.
|
|
110
|
+
*
|
|
111
|
+
* The client learns the output F(k, x) but nothing about 'k'.
|
|
112
|
+
* The server learns nothing about 'x' or F(k, x).
|
|
113
|
+
* This mode is NOT verifiable; the client cannot prove the server used a specific key.
|
|
114
|
+
*/
|
|
115
|
+
readonly oprf: {
|
|
116
|
+
/**
|
|
117
|
+
* (Server-side) Generates a new random private/public key pair for the server.
|
|
118
|
+
* @returns A new key pair.
|
|
119
|
+
*/
|
|
120
|
+
generateKeyPair(): OPRFKeys;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* (Server-side) Deterministically derives a private/public key pair from a seed.
|
|
124
|
+
* @param seed A 32-byte cryptographically secure random seed.
|
|
125
|
+
* @param keyInfo An optional byte string for domain separation.
|
|
126
|
+
* @returns The derived key pair.
|
|
127
|
+
*/
|
|
128
|
+
deriveKeyPair(seed: Bytes, keyInfo: Bytes): OPRFKeys;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* (Client-side) The first step of the protocol. The client blinds its private input.
|
|
132
|
+
* @param input The client's private input bytes.
|
|
133
|
+
* @param rng An optional cryptographically secure random number generator.
|
|
134
|
+
* @returns An object containing the `blind` scalar (which the client MUST keep secret)
|
|
135
|
+
* and the `blinded` element (which the client sends to the server).
|
|
136
|
+
*/
|
|
137
|
+
blind(input: Bytes, rng?: RNG): OPRFBlind;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* (Server-side) The second step. The server evaluates the client's blinded element
|
|
141
|
+
* using its secret key.
|
|
142
|
+
* @param secretKey The server's private key.
|
|
143
|
+
* @param blinded The blinded group element received from the client.
|
|
144
|
+
* @returns The evaluated group element, to be sent back to the client.
|
|
145
|
+
*/
|
|
146
|
+
blindEvaluate(secretKey: ScalarBytes, blinded: PointBytes): PointBytes;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* (Client-side) The final step. The client unblinds the server's response to
|
|
150
|
+
* compute the final OPRF output.
|
|
151
|
+
* @param input The original private input from the `blind` step.
|
|
152
|
+
* @param blind The secret scalar from the `blind` step.
|
|
153
|
+
* @param evaluated The evaluated group element received from the server.
|
|
154
|
+
* @returns The final OPRF output, `Hash(len(input)||input||len(unblinded)||unblinded||"Finalize")`.
|
|
155
|
+
*/
|
|
156
|
+
finalize(input: Bytes, blind: ScalarBytes, evaluated: PointBytes): Bytes;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The Verifiable Oblivious Pseudorandom Function (VOPRF) mode (mode 0x01).
|
|
161
|
+
* This mode extends the base OPRF by providing a proof that the server used the
|
|
162
|
+
* secret key corresponding to its known public key.
|
|
163
|
+
*/
|
|
164
|
+
readonly voprf: {
|
|
165
|
+
/** (Server-side) Generates a key pair for the VOPRF mode. */
|
|
166
|
+
generateKeyPair(): OPRFKeys;
|
|
167
|
+
/** (Server-side) Deterministically derives a key pair for the VOPRF mode. */
|
|
168
|
+
deriveKeyPair(seed: Bytes, keyInfo: Bytes): OPRFKeys;
|
|
169
|
+
/** (Client-side) Blinds the client's private input for the VOPRF protocol. */
|
|
170
|
+
blind(input: Bytes, rng?: RNG): OPRFBlind;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* (Server-side) Evaluates the client's blinded element and generates a DLEQ proof
|
|
174
|
+
* of correctness.
|
|
175
|
+
* @param secretKey The server's private key.
|
|
176
|
+
* @param publicKey The server's public key, used in proof generation.
|
|
177
|
+
* @param blinded The blinded group element received from the client.
|
|
178
|
+
* @param rng An optional cryptographically secure random number generator for the proof.
|
|
179
|
+
* @returns The evaluated element and a proof of correct computation.
|
|
180
|
+
*/
|
|
181
|
+
blindEvaluate(
|
|
182
|
+
secretKey: ScalarBytes,
|
|
183
|
+
publicKey: PointBytes,
|
|
184
|
+
blinded: PointBytes,
|
|
185
|
+
rng?: RNG
|
|
186
|
+
): OPRFBlindEval;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* (Server-side) An optimized batch version of `blindEvaluate`. It evaluates multiple
|
|
190
|
+
* blinded elements and produces a single, constant-size proof for the entire batch,
|
|
191
|
+
* amortizing the cost of proof generation.
|
|
192
|
+
* @param secretKey The server's private key.
|
|
193
|
+
* @param publicKey The server's public key.
|
|
194
|
+
* @param blinded An array of blinded group elements from one or more clients.
|
|
195
|
+
* @param rng An optional cryptographically secure random number generator for the proof.
|
|
196
|
+
* @returns An array of evaluated elements and a single proof for the batch.
|
|
197
|
+
*/
|
|
198
|
+
blindEvaluateBatch(
|
|
199
|
+
secretKey: ScalarBytes,
|
|
200
|
+
publicKey: PointBytes,
|
|
201
|
+
blinded: PointBytes[],
|
|
202
|
+
rng?: RNG
|
|
203
|
+
): OPRFBlindEvalBatch;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* (Client-side) The final step. The client verifies the server's proof, and if valid,
|
|
207
|
+
* unblinds the result to compute the final VOPRF output.
|
|
208
|
+
* @param input The original private input.
|
|
209
|
+
* @param blind The secret scalar from the `blind` step.
|
|
210
|
+
* @param evaluated The evaluated element from the server.
|
|
211
|
+
* @param blinded The blinded element sent to the server (needed for proof verification).
|
|
212
|
+
* @param publicKey The server's public key against which the proof is verified.
|
|
213
|
+
* @param proof The DLEQ proof from the server.
|
|
214
|
+
* @returns The final VOPRF output.
|
|
215
|
+
* @throws If the proof verification fails.
|
|
216
|
+
*/
|
|
217
|
+
finalize(
|
|
218
|
+
input: Bytes,
|
|
219
|
+
blind: ScalarBytes,
|
|
220
|
+
evaluated: PointBytes,
|
|
221
|
+
blinded: PointBytes,
|
|
222
|
+
publicKey: PointBytes,
|
|
223
|
+
proof: Bytes
|
|
224
|
+
): Bytes;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* (Client-side) The batch-aware version of `finalize`. It verifies a single batch proof
|
|
228
|
+
* against a list of corresponding inputs and outputs.
|
|
229
|
+
* @param items An array of objects, each containing the parameters for a single finalization.
|
|
230
|
+
* @param publicKey The server's public key.
|
|
231
|
+
* @param proof The single DLEQ proof for the entire batch.
|
|
232
|
+
* @returns An array of final VOPRF outputs, one for each item in the input.
|
|
233
|
+
* @throws If the proof verification fails.
|
|
234
|
+
*/
|
|
235
|
+
finalizeBatch(items: OPRFFinalizeItem[], publicKey: PointBytes, proof: Bytes): Bytes[];
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* A factory for the Partially Oblivious Pseudorandom Function (POPRF) mode (mode 0x02).
|
|
240
|
+
* This mode extends VOPRF to include a public `info` parameter, known to both client and
|
|
241
|
+
* server, which is cryptographically bound to the final output.
|
|
242
|
+
* This is useful for domain separation at the application level.
|
|
243
|
+
* @param info A public byte string to be mixed into the computation.
|
|
244
|
+
* @returns An object with the POPRF protocol functions.
|
|
245
|
+
*/
|
|
246
|
+
readonly poprf: (info: Bytes) => {
|
|
247
|
+
/** (Server-side) Generates a key pair for the POPRF mode. */
|
|
248
|
+
generateKeyPair(): OPRFKeys;
|
|
249
|
+
/** (Server-side) Deterministically derives a key pair for the POPRF mode. */
|
|
250
|
+
deriveKeyPair(seed: Bytes, keyInfo: Bytes): OPRFKeys;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* (Client-side) Blinds the client's private input and computes the "tweaked key".
|
|
254
|
+
* The tweaked key is a public value derived from the server's public key and the public `info`.
|
|
255
|
+
* @param input The client's private input.
|
|
256
|
+
* @param publicKey The server's public key.
|
|
257
|
+
* @param rng An optional cryptographically secure random number generator.
|
|
258
|
+
* @returns The `blind`, `blinded` element, and the `tweakedKey` which the client uses for verification.
|
|
259
|
+
*/
|
|
260
|
+
blind(input: Bytes, publicKey: PointBytes, rng?: RNG): OPRFBlind & { tweakedKey: PointBytes };
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* (Server-side) Evaluates the blinded element using a key derived from its secret key and the public `info`.
|
|
264
|
+
* It generates a DLEQ proof against the tweaked key.
|
|
265
|
+
* @param secretKey The server's private key.
|
|
266
|
+
* @param blinded The blinded element from the client.
|
|
267
|
+
* @param rng An optional RNG for the proof.
|
|
268
|
+
* @returns The evaluated element and a proof of correct computation.
|
|
269
|
+
*/
|
|
270
|
+
blindEvaluate(secretKey: ScalarBytes, blinded: PointBytes, rng?: RNG): OPRFBlindEval;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* (Server-side) A batch-aware version of `blindEvaluate` for the POPRF mode.
|
|
274
|
+
* @param secretKey The server's private key.
|
|
275
|
+
* @param blinded An array of blinded elements.
|
|
276
|
+
* @param rng An optional RNG for the proof.
|
|
277
|
+
* @returns An array of evaluated elements and a single proof for the batch.
|
|
278
|
+
*/
|
|
279
|
+
blindEvaluateBatch(secretKey: ScalarBytes, blinded: PointBytes[], rng: RNG): OPRFBlindEvalBatch;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* (Client-side) A batch-aware version of `finalize` for the POPRF mode.
|
|
283
|
+
* It verifies the proof against the tweaked key.
|
|
284
|
+
* @param items An array containing the parameters for each finalization.
|
|
285
|
+
* @param proof The single DLEQ proof for the batch.
|
|
286
|
+
* @param tweakedKey The tweaked key corresponding to the proof (all items must share the same `info` and `publicKey`).
|
|
287
|
+
* @returns An array of final POPRF outputs.
|
|
288
|
+
* @throws If proof verification fails.
|
|
289
|
+
*/
|
|
290
|
+
finalizeBatch(items: OPRFFinalizeItem[], proof: Bytes, tweakedKey: PointBytes): Bytes[];
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* (Client-side) Finalizes the POPRF protocol. It verifies the server's proof against the
|
|
294
|
+
* `tweakedKey` computed in the `blind` step. The final output is bound to the public `info`.
|
|
295
|
+
* @param input The original private input.
|
|
296
|
+
* @param blind The secret scalar.
|
|
297
|
+
* @param evaluated The evaluated element from the server.
|
|
298
|
+
* @param blinded The blinded element sent to the server.
|
|
299
|
+
* @param proof The DLEQ proof from the server.
|
|
300
|
+
* @param tweakedKey The public tweaked key computed by the client during the `blind` step.
|
|
301
|
+
* @returns The final POPRF output.
|
|
302
|
+
* @throws If proof verification fails.
|
|
303
|
+
*/
|
|
304
|
+
finalize(
|
|
305
|
+
input: Bytes,
|
|
306
|
+
blind: ScalarBytes,
|
|
307
|
+
evaluated: PointBytes,
|
|
308
|
+
blinded: PointBytes,
|
|
309
|
+
proof: Bytes,
|
|
310
|
+
tweakedKey: PointBytes
|
|
311
|
+
): Bytes;
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* A non-interactive evaluation function for an entity that knows all inputs.
|
|
315
|
+
* Computes the final POPRF output directly. Useful for testing or specific applications
|
|
316
|
+
* where the server needs to compute the output for a known input.
|
|
317
|
+
* @param secretKey The server's private key.
|
|
318
|
+
* @param input The client's private input.
|
|
319
|
+
* @returns The final POPRF output.
|
|
320
|
+
*/
|
|
321
|
+
evaluate(secretKey: ScalarBytes, input: Bytes): Bytes;
|
|
322
|
+
};
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// welcome to generic hell
|
|
326
|
+
export function createORPF<P extends CurvePoint<any, P>>(opts: OPRFOpts<P>): OPRF {
|
|
327
|
+
validateObject(opts, {
|
|
328
|
+
name: 'string',
|
|
329
|
+
hash: 'function',
|
|
330
|
+
hashToScalar: 'function',
|
|
331
|
+
hashToGroup: 'function',
|
|
332
|
+
});
|
|
333
|
+
// TODO
|
|
334
|
+
// Point: 'point',
|
|
335
|
+
const { name, Point, hash } = opts;
|
|
336
|
+
const { Fn } = Point;
|
|
337
|
+
|
|
338
|
+
const hashToGroup = (msg: Uint8Array, ctx: Uint8Array) =>
|
|
339
|
+
opts.hashToGroup(msg, {
|
|
340
|
+
DST: concatBytes(asciiToBytes('HashToGroup-'), ctx),
|
|
341
|
+
}) as P;
|
|
342
|
+
const hashToScalarPrefixed = (msg: Uint8Array, ctx: Uint8Array) =>
|
|
343
|
+
opts.hashToScalar(msg, { DST: concatBytes(_DST_scalar, ctx) });
|
|
344
|
+
const randomScalar = (rng: RNG = randomBytes) => {
|
|
345
|
+
const t = mapHashToField(rng(getMinHashLength(Fn.ORDER)), Fn.ORDER, Fn.isLE);
|
|
346
|
+
// We cannot use Fn.fromBytes here, because field
|
|
347
|
+
// can have different number of bytes (like ed448)
|
|
348
|
+
return Fn.isLE ? bytesToNumberLE(t) : bytesToNumberBE(t);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const msm = (points: P[], scalars: bigint[]) => pippenger(Point, points, scalars);
|
|
352
|
+
|
|
353
|
+
const getCtx = (mode: number) =>
|
|
354
|
+
concatBytes(asciiToBytes('OPRFV1-'), new Uint8Array([mode]), asciiToBytes('-' + name));
|
|
355
|
+
const ctxOPRF = getCtx(0x00);
|
|
356
|
+
const ctxVOPRF = getCtx(0x01);
|
|
357
|
+
const ctxPOPRF = getCtx(0x02);
|
|
358
|
+
|
|
359
|
+
function encode(...args: (Uint8Array | number | string)[]) {
|
|
360
|
+
const res = [];
|
|
361
|
+
for (const a of args) {
|
|
362
|
+
if (typeof a === 'number') res.push(numberToBytesBE(a, 2));
|
|
363
|
+
else if (typeof a === 'string') res.push(asciiToBytes(a));
|
|
364
|
+
else {
|
|
365
|
+
abytes(a);
|
|
366
|
+
res.push(numberToBytesBE(a.length, 2), a);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// No wipe here, since will modify actual bytes
|
|
370
|
+
return concatBytes(...res);
|
|
371
|
+
}
|
|
372
|
+
const hashInput = (...bytes: Uint8Array[]) => hash(encode(...bytes, 'Finalize'));
|
|
373
|
+
|
|
374
|
+
function getTranscripts(B: P, C: P[], D: P[], ctx: Bytes) {
|
|
375
|
+
const Bm = B.toBytes();
|
|
376
|
+
const seed = hash(encode(Bm, concatBytes(asciiToBytes('Seed-'), ctx)));
|
|
377
|
+
const res: bigint[] = [];
|
|
378
|
+
for (let i = 0; i < C.length; i++) {
|
|
379
|
+
const Ci = C[i].toBytes();
|
|
380
|
+
const Di = D[i].toBytes();
|
|
381
|
+
const di = hashToScalarPrefixed(encode(seed, i, Ci, Di, 'Composite'), ctx);
|
|
382
|
+
res.push(di);
|
|
383
|
+
}
|
|
384
|
+
return res;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function computeComposites(B: P, C: P[], D: P[], ctx: Bytes) {
|
|
388
|
+
const T = getTranscripts(B, C, D, ctx);
|
|
389
|
+
const M = msm(C, T);
|
|
390
|
+
const Z = msm(D, T);
|
|
391
|
+
return { M, Z };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function computeCompositesFast(k: bigint, B: P, C: P[], D: P[], ctx: Bytes): { M: P; Z: P } {
|
|
395
|
+
const T = getTranscripts(B, C, D, ctx);
|
|
396
|
+
const M = msm(C, T);
|
|
397
|
+
const Z = M.multiply(k);
|
|
398
|
+
return { M, Z };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function challengeTranscript(B: P, M: P, Z: P, t2: P, t3: P, ctx: Bytes) {
|
|
402
|
+
const [Bm, a0, a1, a2, a3] = [B, M, Z, t2, t3].map((i) => i.toBytes());
|
|
403
|
+
return hashToScalarPrefixed(encode(Bm, a0, a1, a2, a3, 'Challenge'), ctx);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function generateProof(ctx: Bytes, k: bigint, B: P, C: P[], D: P[], rng: RNG) {
|
|
407
|
+
const { M, Z } = computeCompositesFast(k, B, C, D, ctx);
|
|
408
|
+
const r = randomScalar(rng);
|
|
409
|
+
const t2 = Point.BASE.multiply(r);
|
|
410
|
+
const t3 = M.multiply(r);
|
|
411
|
+
const c = challengeTranscript(B, M, Z, t2, t3, ctx);
|
|
412
|
+
const s = Fn.sub(r, Fn.mul(c, k)); // r - c*k
|
|
413
|
+
return concatBytes(...[c, s].map((i) => Fn.toBytes(i)));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function verifyProof(ctx: Bytes, B: P, C: P[], D: P[], proof: Bytes) {
|
|
417
|
+
abytes(proof, 2 * Fn.BYTES);
|
|
418
|
+
const { M, Z } = computeComposites(B, C, D, ctx);
|
|
419
|
+
const [c, s] = [proof.subarray(0, Fn.BYTES), proof.subarray(Fn.BYTES)].map((f) =>
|
|
420
|
+
Fn.fromBytes(f)
|
|
421
|
+
);
|
|
422
|
+
const t2 = Point.BASE.multiply(s).add(B.multiply(c)); // s*G + c*B
|
|
423
|
+
const t3 = M.multiply(s).add(Z.multiply(c)); // s*M + c*Z
|
|
424
|
+
const expectedC = challengeTranscript(B, M, Z, t2, t3, ctx);
|
|
425
|
+
if (!Fn.eql(c, expectedC)) throw new Error('proof verification failed');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function generateKeyPair() {
|
|
429
|
+
const skS = randomScalar();
|
|
430
|
+
const pkS = Point.BASE.multiply(skS);
|
|
431
|
+
return { secretKey: Fn.toBytes(skS), publicKey: pkS.toBytes() };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function deriveKeyPair(ctx: Bytes, seed: Bytes, info: Bytes) {
|
|
435
|
+
const dst = concatBytes(asciiToBytes('DeriveKeyPair'), ctx);
|
|
436
|
+
const msg = concatBytes(seed, encode(info), new Uint8Array([0]));
|
|
437
|
+
for (let counter = 0; counter <= 255; counter++) {
|
|
438
|
+
msg[msg.length - 1] = counter;
|
|
439
|
+
const skS = opts.hashToScalar(msg, { DST: dst });
|
|
440
|
+
if (Fn.is0(skS)) continue; // should not happen
|
|
441
|
+
return { secretKey: Fn.toBytes(skS), publicKey: Point.BASE.multiply(skS).toBytes() };
|
|
442
|
+
}
|
|
443
|
+
throw new Error('Cannot derive key');
|
|
444
|
+
}
|
|
445
|
+
function blind(ctx: Bytes, input: Uint8Array, rng: RNG = randomBytes) {
|
|
446
|
+
const blind = randomScalar(rng);
|
|
447
|
+
const inputPoint = hashToGroup(input, ctx);
|
|
448
|
+
if (inputPoint.equals(Point.ZERO)) throw new Error('Input point at infinity');
|
|
449
|
+
const blinded = inputPoint.multiply(blind);
|
|
450
|
+
return { blind: Fn.toBytes(blind), blinded: blinded.toBytes() };
|
|
451
|
+
}
|
|
452
|
+
function evaluate(ctx: Bytes, secretKey: ScalarBytes, input: Bytes) {
|
|
453
|
+
const skS = Fn.fromBytes(secretKey);
|
|
454
|
+
const inputPoint = hashToGroup(input, ctx);
|
|
455
|
+
if (inputPoint.equals(Point.ZERO)) throw new Error('Input point at infinity');
|
|
456
|
+
const unblinded = inputPoint.multiply(skS).toBytes();
|
|
457
|
+
return hashInput(input, unblinded);
|
|
458
|
+
}
|
|
459
|
+
const oprf = {
|
|
460
|
+
generateKeyPair,
|
|
461
|
+
deriveKeyPair: (seed: Bytes, keyInfo: Bytes) => deriveKeyPair(ctxOPRF, seed, keyInfo),
|
|
462
|
+
blind: (input: Bytes, rng: RNG = randomBytes) => blind(ctxOPRF, input, rng),
|
|
463
|
+
blindEvaluate(secretKey: ScalarBytes, blindedPoint: PointBytes) {
|
|
464
|
+
const skS = Fn.fromBytes(secretKey);
|
|
465
|
+
const elm = Point.fromBytes(blindedPoint);
|
|
466
|
+
return elm.multiply(skS).toBytes();
|
|
467
|
+
},
|
|
468
|
+
finalize(input: Bytes, blindBytes: ScalarBytes, evaluatedBytes: PointBytes) {
|
|
469
|
+
const blind = Fn.fromBytes(blindBytes);
|
|
470
|
+
const evalPoint = Point.fromBytes(evaluatedBytes);
|
|
471
|
+
const unblinded = evalPoint.multiply(Fn.inv(blind)).toBytes();
|
|
472
|
+
return hashInput(input, unblinded);
|
|
473
|
+
},
|
|
474
|
+
evaluate: (secretKey: ScalarBytes, input: Bytes) => evaluate(ctxOPRF, secretKey, input),
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const voprf = {
|
|
478
|
+
generateKeyPair,
|
|
479
|
+
deriveKeyPair: (seed: Bytes, keyInfo: Bytes) => deriveKeyPair(ctxVOPRF, seed, keyInfo),
|
|
480
|
+
blind: (input: Bytes, rng: RNG = randomBytes) => blind(ctxVOPRF, input, rng),
|
|
481
|
+
blindEvaluateBatch(
|
|
482
|
+
secretKey: ScalarBytes,
|
|
483
|
+
publicKey: PointBytes,
|
|
484
|
+
blinded: PointBytes[],
|
|
485
|
+
rng: RNG = randomBytes
|
|
486
|
+
) {
|
|
487
|
+
if (!Array.isArray(blinded)) throw new Error('expected array');
|
|
488
|
+
const skS = Fn.fromBytes(secretKey);
|
|
489
|
+
const pkS = Point.fromBytes(publicKey);
|
|
490
|
+
const blindedPoints = blinded.map(Point.fromBytes);
|
|
491
|
+
const evaluated = blindedPoints.map((i) => i.multiply(skS));
|
|
492
|
+
const proof = generateProof(ctxVOPRF, skS, pkS, blindedPoints, evaluated, rng);
|
|
493
|
+
return { evaluated: evaluated.map((i) => i.toBytes()), proof };
|
|
494
|
+
},
|
|
495
|
+
blindEvaluate(
|
|
496
|
+
secretKey: ScalarBytes,
|
|
497
|
+
publicKey: PointBytes,
|
|
498
|
+
blinded: PointBytes,
|
|
499
|
+
rng: RNG = randomBytes
|
|
500
|
+
) {
|
|
501
|
+
const res = this.blindEvaluateBatch(secretKey, publicKey, [blinded], rng);
|
|
502
|
+
return { evaluated: res.evaluated[0], proof: res.proof };
|
|
503
|
+
},
|
|
504
|
+
finalizeBatch(items: OPRFFinalizeItem[], publicKey: PointBytes, proof: Bytes) {
|
|
505
|
+
if (!Array.isArray(items)) throw new Error('expected array');
|
|
506
|
+
const pkS = Point.fromBytes(publicKey);
|
|
507
|
+
const blindedPoints = items.map((i) => i.blinded).map(Point.fromBytes);
|
|
508
|
+
const evalPoints = items.map((i) => i.evaluated).map(Point.fromBytes);
|
|
509
|
+
verifyProof(ctxVOPRF, pkS, blindedPoints, evalPoints, proof);
|
|
510
|
+
return items.map((i) => oprf.finalize(i.input, i.blind, i.evaluated));
|
|
511
|
+
},
|
|
512
|
+
finalize(
|
|
513
|
+
input: Bytes,
|
|
514
|
+
blind: ScalarBytes,
|
|
515
|
+
evaluated: PointBytes,
|
|
516
|
+
blinded: PointBytes,
|
|
517
|
+
publicKey: PointBytes,
|
|
518
|
+
proof: Bytes
|
|
519
|
+
) {
|
|
520
|
+
return this.finalizeBatch([{ input, blind, evaluated, blinded }], publicKey, proof)[0];
|
|
521
|
+
},
|
|
522
|
+
evaluate: (secretKey: ScalarBytes, input: Bytes) => evaluate(ctxVOPRF, secretKey, input),
|
|
523
|
+
};
|
|
524
|
+
// NOTE: info is domain separation
|
|
525
|
+
const poprf = (info: Bytes) => {
|
|
526
|
+
const m = hashToScalarPrefixed(encode('Info', info), ctxPOPRF);
|
|
527
|
+
const T = Point.BASE.multiply(m);
|
|
528
|
+
return {
|
|
529
|
+
generateKeyPair,
|
|
530
|
+
deriveKeyPair: (seed: Bytes, keyInfo: Bytes) => deriveKeyPair(ctxPOPRF, seed, keyInfo),
|
|
531
|
+
blind(input: Bytes, publicKey: PointBytes, rng: RNG = randomBytes) {
|
|
532
|
+
const pkS = Point.fromBytes(publicKey);
|
|
533
|
+
const tweakedKey = T.add(pkS);
|
|
534
|
+
if (tweakedKey.equals(Point.ZERO)) throw new Error('tweakedKey point at infinity');
|
|
535
|
+
const blind = randomScalar(rng);
|
|
536
|
+
const inputPoint = hashToGroup(input, ctxPOPRF);
|
|
537
|
+
if (inputPoint.equals(Point.ZERO)) throw new Error('Input point at infinity');
|
|
538
|
+
const blindedPoint = inputPoint.multiply(blind);
|
|
539
|
+
return {
|
|
540
|
+
blind: Fn.toBytes(blind),
|
|
541
|
+
blinded: blindedPoint.toBytes(),
|
|
542
|
+
tweakedKey: tweakedKey.toBytes(),
|
|
543
|
+
};
|
|
544
|
+
},
|
|
545
|
+
blindEvaluateBatch(secretKey: ScalarBytes, blinded: PointBytes[], rng: RNG = randomBytes) {
|
|
546
|
+
if (!Array.isArray(blinded)) throw new Error('expected array');
|
|
547
|
+
const skS = Fn.fromBytes(secretKey);
|
|
548
|
+
const t = Fn.add(skS, m);
|
|
549
|
+
// "Hence, this error can be a signal for the server to replace its private key". We throw inside,
|
|
550
|
+
// should be impossible.
|
|
551
|
+
const invT = Fn.inv(t);
|
|
552
|
+
const blindedPoints = blinded.map(Point.fromBytes);
|
|
553
|
+
const evalPoints = blindedPoints.map((i) => i.multiply(invT));
|
|
554
|
+
const tweakedKey = Point.BASE.multiply(t);
|
|
555
|
+
const proof = generateProof(ctxPOPRF, t, tweakedKey, evalPoints, blindedPoints, rng);
|
|
556
|
+
return { evaluated: evalPoints.map((i) => i.toBytes()), proof };
|
|
557
|
+
},
|
|
558
|
+
blindEvaluate(secretKey: ScalarBytes, blinded: PointBytes, rng: RNG = randomBytes) {
|
|
559
|
+
const res = this.blindEvaluateBatch(secretKey, [blinded], rng);
|
|
560
|
+
return { evaluated: res.evaluated[0], proof: res.proof };
|
|
561
|
+
},
|
|
562
|
+
finalizeBatch(items: OPRFFinalizeItem[], proof: Bytes, tweakedKey: PointBytes) {
|
|
563
|
+
if (!Array.isArray(items)) throw new Error('expected array');
|
|
564
|
+
const evalPoints = items.map((i) => i.evaluated).map(Point.fromBytes);
|
|
565
|
+
verifyProof(
|
|
566
|
+
ctxPOPRF,
|
|
567
|
+
Point.fromBytes(tweakedKey),
|
|
568
|
+
evalPoints,
|
|
569
|
+
items.map((i) => i.blinded).map(Point.fromBytes),
|
|
570
|
+
proof
|
|
571
|
+
);
|
|
572
|
+
return items.map((i, j) => {
|
|
573
|
+
const blind = Fn.fromBytes(i.blind);
|
|
574
|
+
const point = evalPoints[j].multiply(Fn.inv(blind)).toBytes();
|
|
575
|
+
return hashInput(i.input, info, point);
|
|
576
|
+
});
|
|
577
|
+
},
|
|
578
|
+
finalize(
|
|
579
|
+
input: Bytes,
|
|
580
|
+
blind: ScalarBytes,
|
|
581
|
+
evaluated: PointBytes,
|
|
582
|
+
blinded: PointBytes,
|
|
583
|
+
proof: Bytes,
|
|
584
|
+
tweakedKey: PointBytes
|
|
585
|
+
) {
|
|
586
|
+
return this.finalizeBatch([{ input, blind, evaluated, blinded }], proof, tweakedKey)[0];
|
|
587
|
+
},
|
|
588
|
+
evaluate(secretKey: ScalarBytes, input: Bytes) {
|
|
589
|
+
const skS = Fn.fromBytes(secretKey);
|
|
590
|
+
const inputPoint = hashToGroup(input, ctxPOPRF);
|
|
591
|
+
if (inputPoint.equals(Point.ZERO)) throw new Error('Input point at infinity');
|
|
592
|
+
const t = Fn.add(skS, m);
|
|
593
|
+
const invT = Fn.inv(t);
|
|
594
|
+
const unblinded = inputPoint.multiply(invT).toBytes();
|
|
595
|
+
return hashInput(input, info, unblinded);
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
};
|
|
599
|
+
return Object.freeze({ name, oprf, voprf, poprf, __tests: { Fn } });
|
|
600
|
+
}
|
package/src/abstract/poseidon.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
10
|
-
import {
|
|
10
|
+
import { asafenumber, bitGet, validateObject } from '../utils.ts';
|
|
11
11
|
import { FpInvertBatch, FpPow, type IField, validateField } from './modular.ts';
|
|
12
12
|
|
|
13
13
|
// Grain LFSR (Linear-Feedback Shift Register): https://eprint.iacr.org/2009/109.pdf
|
|
@@ -44,7 +44,7 @@ export type PoseidonBasicOpts = {
|
|
|
44
44
|
function assertValidPosOpts(opts: PoseidonBasicOpts) {
|
|
45
45
|
const { Fp, roundsFull } = opts;
|
|
46
46
|
validateField(Fp);
|
|
47
|
-
|
|
47
|
+
validateObject(
|
|
48
48
|
opts,
|
|
49
49
|
{
|
|
50
50
|
t: 'number',
|
|
@@ -55,8 +55,9 @@ function assertValidPosOpts(opts: PoseidonBasicOpts) {
|
|
|
55
55
|
isSboxInverse: 'boolean',
|
|
56
56
|
}
|
|
57
57
|
);
|
|
58
|
-
for (const
|
|
59
|
-
|
|
58
|
+
for (const k of ['t', 'roundsFull', 'roundsPartial'] as const) {
|
|
59
|
+
asafenumber(opts[k], k);
|
|
60
|
+
if (opts[k] < 1) throw new Error('invalid number ' + k);
|
|
60
61
|
}
|
|
61
62
|
if (roundsFull & 1) throw new Error('roundsFull is not even' + roundsFull);
|
|
62
63
|
}
|
|
@@ -322,10 +323,7 @@ export type PoseidonSpongeOpts = Omit<PoseidonOpts, 't'> & {
|
|
|
322
323
|
* - https://github.com/arkworks-rs/crypto-primitives/tree/main
|
|
323
324
|
*/
|
|
324
325
|
export function poseidonSponge(opts: PoseidonSpongeOpts): () => PoseidonSponge {
|
|
325
|
-
for (const
|
|
326
|
-
if (typeof opts[i] !== 'number' || !Number.isSafeInteger(opts[i]))
|
|
327
|
-
throw new Error('invalid number ' + i);
|
|
328
|
-
}
|
|
326
|
+
for (const k of ['rate', 'capacity'] as const) asafenumber(opts[k], k);
|
|
329
327
|
const { rate, capacity } = opts;
|
|
330
328
|
const t = opts.rate + opts.capacity;
|
|
331
329
|
// Re-use hash instance between multiple instances
|
package/src/abstract/tower.ts
CHANGED
|
@@ -158,7 +158,6 @@ class _Field2 implements mod.IField<Fp2> {
|
|
|
158
158
|
readonly BITS: number;
|
|
159
159
|
readonly BYTES: number;
|
|
160
160
|
readonly isLE: boolean;
|
|
161
|
-
readonly MASK = _1n;
|
|
162
161
|
|
|
163
162
|
readonly ZERO: Fp2;
|
|
164
163
|
readonly ONE: Fp2;
|
|
@@ -375,7 +374,6 @@ class _Field6 implements Fp6Bls {
|
|
|
375
374
|
readonly BITS: number;
|
|
376
375
|
readonly BYTES: number;
|
|
377
376
|
readonly isLE: boolean;
|
|
378
|
-
readonly MASK = _1n;
|
|
379
377
|
|
|
380
378
|
readonly ZERO: Fp6;
|
|
381
379
|
readonly ONE: Fp6;
|
|
@@ -599,7 +597,6 @@ class _Field12 implements Fp12Bls {
|
|
|
599
597
|
readonly BITS: number;
|
|
600
598
|
readonly BYTES: number;
|
|
601
599
|
readonly isLE: boolean;
|
|
602
|
-
readonly MASK = _1n;
|
|
603
600
|
|
|
604
601
|
readonly ZERO: Fp12;
|
|
605
602
|
readonly ONE: Fp12;
|