@noble/curves 2.0.0 → 2.2.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/README.md +214 -122
- package/abstract/bls.d.ts +299 -16
- package/abstract/bls.d.ts.map +1 -1
- package/abstract/bls.js +89 -24
- package/abstract/bls.js.map +1 -1
- package/abstract/curve.d.ts +274 -27
- package/abstract/curve.d.ts.map +1 -1
- package/abstract/curve.js +177 -23
- package/abstract/curve.js.map +1 -1
- package/abstract/edwards.d.ts +166 -30
- package/abstract/edwards.d.ts.map +1 -1
- package/abstract/edwards.js +221 -86
- package/abstract/edwards.js.map +1 -1
- package/abstract/fft.d.ts +327 -10
- package/abstract/fft.d.ts.map +1 -1
- package/abstract/fft.js +155 -12
- package/abstract/fft.js.map +1 -1
- package/abstract/frost.d.ts +293 -0
- package/abstract/frost.d.ts.map +1 -0
- package/abstract/frost.js +704 -0
- package/abstract/frost.js.map +1 -0
- package/abstract/hash-to-curve.d.ts +173 -24
- package/abstract/hash-to-curve.d.ts.map +1 -1
- package/abstract/hash-to-curve.js +170 -31
- package/abstract/hash-to-curve.js.map +1 -1
- package/abstract/modular.d.ts +429 -37
- package/abstract/modular.d.ts.map +1 -1
- package/abstract/modular.js +414 -119
- package/abstract/modular.js.map +1 -1
- package/abstract/montgomery.d.ts +83 -12
- package/abstract/montgomery.d.ts.map +1 -1
- package/abstract/montgomery.js +32 -7
- package/abstract/montgomery.js.map +1 -1
- package/abstract/oprf.d.ts +164 -91
- package/abstract/oprf.d.ts.map +1 -1
- package/abstract/oprf.js +88 -29
- package/abstract/oprf.js.map +1 -1
- package/abstract/poseidon.d.ts +138 -7
- package/abstract/poseidon.d.ts.map +1 -1
- package/abstract/poseidon.js +178 -15
- package/abstract/poseidon.js.map +1 -1
- package/abstract/tower.d.ts +122 -3
- package/abstract/tower.d.ts.map +1 -1
- package/abstract/tower.js +323 -139
- package/abstract/tower.js.map +1 -1
- package/abstract/weierstrass.d.ts +339 -76
- package/abstract/weierstrass.d.ts.map +1 -1
- package/abstract/weierstrass.js +395 -205
- package/abstract/weierstrass.js.map +1 -1
- package/bls12-381.d.ts +16 -2
- package/bls12-381.d.ts.map +1 -1
- package/bls12-381.js +199 -209
- package/bls12-381.js.map +1 -1
- package/bn254.d.ts +11 -2
- package/bn254.d.ts.map +1 -1
- package/bn254.js +93 -38
- package/bn254.js.map +1 -1
- package/ed25519.d.ts +135 -14
- package/ed25519.d.ts.map +1 -1
- package/ed25519.js +207 -41
- package/ed25519.js.map +1 -1
- package/ed448.d.ts +108 -14
- package/ed448.d.ts.map +1 -1
- package/ed448.js +194 -42
- package/ed448.js.map +1 -1
- package/index.js +7 -1
- package/index.js.map +1 -1
- package/misc.d.ts +106 -7
- package/misc.d.ts.map +1 -1
- package/misc.js +141 -32
- package/misc.js.map +1 -1
- package/nist.d.ts +112 -11
- package/nist.d.ts.map +1 -1
- package/nist.js +139 -17
- package/nist.js.map +1 -1
- package/package.json +34 -6
- package/secp256k1.d.ts +92 -15
- package/secp256k1.d.ts.map +1 -1
- package/secp256k1.js +211 -28
- package/secp256k1.js.map +1 -1
- package/src/abstract/bls.ts +356 -69
- package/src/abstract/curve.ts +327 -44
- package/src/abstract/edwards.ts +367 -143
- package/src/abstract/fft.ts +371 -36
- package/src/abstract/frost.ts +1092 -0
- package/src/abstract/hash-to-curve.ts +255 -56
- package/src/abstract/modular.ts +591 -144
- package/src/abstract/montgomery.ts +114 -30
- package/src/abstract/oprf.ts +383 -194
- package/src/abstract/poseidon.ts +235 -35
- package/src/abstract/tower.ts +428 -159
- package/src/abstract/weierstrass.ts +710 -312
- package/src/bls12-381.ts +239 -236
- package/src/bn254.ts +107 -46
- package/src/ed25519.ts +234 -56
- package/src/ed448.ts +227 -57
- package/src/index.ts +7 -1
- package/src/misc.ts +154 -35
- package/src/nist.ts +143 -20
- package/src/secp256k1.ts +284 -41
- package/src/utils.ts +583 -81
- package/src/webcrypto.ts +302 -73
- package/utils.d.ts +457 -24
- package/utils.d.ts.map +1 -1
- package/utils.js +410 -53
- package/utils.js.map +1 -1
- package/webcrypto.d.ts +167 -25
- package/webcrypto.d.ts.map +1 -1
- package/webcrypto.js +165 -58
- package/webcrypto.js.map +1 -1
package/src/secp256k1.ts
CHANGED
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
import { sha256 } from '@noble/hashes/sha2.js';
|
|
10
10
|
import { randomBytes } from '@noble/hashes/utils.js';
|
|
11
11
|
import { createKeygen, type CurveLengths } from './abstract/curve.ts';
|
|
12
|
+
import {
|
|
13
|
+
createFROST,
|
|
14
|
+
type FROST,
|
|
15
|
+
type FrostPublic,
|
|
16
|
+
type FrostSecret,
|
|
17
|
+
type Nonces,
|
|
18
|
+
} from './abstract/frost.ts';
|
|
12
19
|
import { createHasher, type H2CHasher, isogenyMap } from './abstract/hash-to-curve.ts';
|
|
13
20
|
import { Field, mapHashToField, pow2 } from './abstract/modular.ts';
|
|
14
21
|
import {
|
|
@@ -21,7 +28,14 @@ import {
|
|
|
21
28
|
type WeierstrassOpts,
|
|
22
29
|
type WeierstrassPointCons,
|
|
23
30
|
} from './abstract/weierstrass.ts';
|
|
24
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
abytes,
|
|
33
|
+
asciiToBytes,
|
|
34
|
+
bytesToNumberBE,
|
|
35
|
+
concatBytes,
|
|
36
|
+
type TArg,
|
|
37
|
+
type TRet,
|
|
38
|
+
} from './utils.ts';
|
|
25
39
|
|
|
26
40
|
// Seems like generator was produced from some seed:
|
|
27
41
|
// `Pointk1.BASE.multiply(Pointk1.Fn.inv(2n, N)).toAffine().x`
|
|
@@ -88,6 +102,8 @@ const Pointk1 = /* @__PURE__ */ weierstrass(secp256k1_CURVE, {
|
|
|
88
102
|
* pass `{ prehash: false }` to sign / verify.
|
|
89
103
|
*
|
|
90
104
|
* @example
|
|
105
|
+
* Generate one secp256k1 keypair, sign a message, and verify it.
|
|
106
|
+
*
|
|
91
107
|
* ```js
|
|
92
108
|
* import { secp256k1 } from '@noble/curves/secp256k1.js';
|
|
93
109
|
* const { secretKey, publicKey } = secp256k1.keygen();
|
|
@@ -104,22 +120,24 @@ export const secp256k1: ECDSA = /* @__PURE__ */ ecdsa(Pointk1, sha256);
|
|
|
104
120
|
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
|
|
105
121
|
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
|
|
106
122
|
const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};
|
|
107
|
-
|
|
123
|
+
// BIP-340 phrases tags as UTF-8, but all current standardized names here are 7-bit ASCII.
|
|
124
|
+
function taggedHash(tag: string, ...messages: TArg<Uint8Array[]>): TRet<Uint8Array> {
|
|
108
125
|
let tagP = TAGGED_HASH_PREFIXES[tag];
|
|
109
126
|
if (tagP === undefined) {
|
|
110
127
|
const tagH = sha256(asciiToBytes(tag));
|
|
111
128
|
tagP = concatBytes(tagH, tagH);
|
|
112
129
|
TAGGED_HASH_PREFIXES[tag] = tagP;
|
|
113
130
|
}
|
|
114
|
-
return sha256(concatBytes(tagP, ...messages))
|
|
131
|
+
return sha256(concatBytes(tagP, ...messages)) as TRet<Uint8Array>;
|
|
115
132
|
}
|
|
116
133
|
|
|
117
134
|
// ECDSA compact points are 33-byte. Schnorr is 32: we strip first byte 0x02 or 0x03
|
|
118
|
-
const pointToBytes = (point: PointType<bigint
|
|
135
|
+
const pointToBytes = (point: TArg<PointType<bigint>>): TRet<Uint8Array> =>
|
|
136
|
+
point.toBytes(true).slice(1) as TRet<Uint8Array>;
|
|
119
137
|
const hasEven = (y: bigint) => y % _2n === _0n;
|
|
120
138
|
|
|
121
139
|
// Calculate point, scalar and bytes
|
|
122
|
-
function schnorrGetExtPubKey(priv: Uint8Array) {
|
|
140
|
+
function schnorrGetExtPubKey(priv: TArg<Uint8Array>) {
|
|
123
141
|
const { Fn, BASE } = Pointk1;
|
|
124
142
|
const d_ = Fn.fromBytes(priv);
|
|
125
143
|
const p = BASE.multiply(d_); // P = d'⋅G; 0 < d' < n check is done inside
|
|
@@ -143,52 +161,64 @@ function lift_x(x: bigint): PointType<bigint> {
|
|
|
143
161
|
p.assertValidity();
|
|
144
162
|
return p;
|
|
145
163
|
}
|
|
164
|
+
// BIP-340 callers still need to supply canonical 32-byte inputs where required; this alias only
|
|
165
|
+
// parses big-endian bytes and does not enforce the fixed-width contract itself.
|
|
146
166
|
const num = bytesToNumberBE;
|
|
147
|
-
/**
|
|
148
|
-
|
|
149
|
-
*/
|
|
150
|
-
function challenge(...args: Uint8Array[]): bigint {
|
|
167
|
+
/** Create tagged hash, convert it to bigint, reduce modulo-n. */
|
|
168
|
+
function challenge(...args: TArg<Uint8Array[]>): bigint {
|
|
151
169
|
return Pointk1.Fn.create(num(taggedHash('BIP0340/challenge', ...args)));
|
|
152
170
|
}
|
|
153
171
|
|
|
154
|
-
/**
|
|
155
|
-
|
|
156
|
-
*/
|
|
157
|
-
function schnorrGetPublicKey(secretKey: Uint8Array): Uint8Array {
|
|
172
|
+
/** Schnorr public key is just `x` coordinate of Point as per BIP340. */
|
|
173
|
+
function schnorrGetPublicKey(secretKey: TArg<Uint8Array>): TRet<Uint8Array> {
|
|
158
174
|
return schnorrGetExtPubKey(secretKey).bytes; // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)
|
|
159
175
|
}
|
|
160
176
|
|
|
161
177
|
/**
|
|
162
178
|
* Creates Schnorr signature as per BIP340. Verifies itself before returning anything.
|
|
163
|
-
* auxRand is optional and is not the sole source of k generation: bad CSPRNG
|
|
179
|
+
* `auxRand` is optional and is not the sole source of `k` generation: bad CSPRNG output will not
|
|
180
|
+
* be catastrophic, but BIP-340 still recommends fresh auxiliary randomness when available to harden
|
|
181
|
+
* deterministic signing against side-channel and fault-injection attacks.
|
|
164
182
|
*/
|
|
165
183
|
function schnorrSign(
|
|
166
|
-
message: Uint8Array
|
|
167
|
-
secretKey: Uint8Array
|
|
168
|
-
auxRand: Uint8Array = randomBytes(32)
|
|
169
|
-
): Uint8Array {
|
|
170
|
-
const { Fn } = Pointk1;
|
|
184
|
+
message: TArg<Uint8Array>,
|
|
185
|
+
secretKey: TArg<Uint8Array>,
|
|
186
|
+
auxRand: TArg<Uint8Array> = randomBytes(32)
|
|
187
|
+
): TRet<Uint8Array> {
|
|
188
|
+
const { Fn, BASE } = Pointk1;
|
|
171
189
|
const m = abytes(message, undefined, 'message');
|
|
172
190
|
const { bytes: px, scalar: d } = schnorrGetExtPubKey(secretKey); // checks for isWithinCurveOrder
|
|
173
191
|
const a = abytes(auxRand, 32, 'auxRand'); // Auxiliary random data a: a 32-byte array
|
|
174
|
-
|
|
192
|
+
// Let t be the byte-wise xor of bytes(d) and hash/aux(a).
|
|
193
|
+
const t = Fn.toBytes(d ^ num(taggedHash('BIP0340/aux', a)));
|
|
175
194
|
const rand = taggedHash('BIP0340/nonce', t, px, m); // Let rand = hash/nonce(t || bytes(P) || m)
|
|
176
|
-
//
|
|
177
|
-
|
|
195
|
+
// BIP340 defines k' = int(rand) mod n. We can't reuse schnorrGetExtPubKey(rand)
|
|
196
|
+
// here: that helper parses canonical secret keys and rejects rand >= n instead
|
|
197
|
+
// of reducing the nonce hash modulo the group order.
|
|
198
|
+
const k_ = Fn.create(num(rand));
|
|
199
|
+
// BIP-340: "Let k' = int(rand) mod n. Fail if k' = 0. Let R = k'⋅G."
|
|
200
|
+
if (k_ === 0n) throw new Error('sign failed: k is zero');
|
|
201
|
+
const p = BASE.multiply(k_); // Rejects zero; only the raw nonce hash needs reduction.
|
|
202
|
+
const k = hasEven(p.y) ? k_ : Fn.neg(k_);
|
|
203
|
+
const rx = pointToBytes(p);
|
|
178
204
|
const e = challenge(rx, px, m); // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.
|
|
179
205
|
const sig = new Uint8Array(64); // Let sig = bytes(R) || bytes((k + ed) mod n).
|
|
180
206
|
sig.set(rx, 0);
|
|
181
207
|
sig.set(Fn.toBytes(Fn.create(k + e * d)), 32);
|
|
182
208
|
// If Verify(bytes(P), m, sig) (see below) returns failure, abort
|
|
183
209
|
if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
|
|
184
|
-
return sig
|
|
210
|
+
return sig as TRet<Uint8Array>;
|
|
185
211
|
}
|
|
186
212
|
|
|
187
213
|
/**
|
|
188
214
|
* Verifies Schnorr signature.
|
|
189
215
|
* Will swallow errors & return false except for initial type validation of arguments.
|
|
190
216
|
*/
|
|
191
|
-
function schnorrVerify(
|
|
217
|
+
function schnorrVerify(
|
|
218
|
+
signature: TArg<Uint8Array>,
|
|
219
|
+
message: TArg<Uint8Array>,
|
|
220
|
+
publicKey: TArg<Uint8Array>
|
|
221
|
+
): boolean {
|
|
192
222
|
const { Fp, Fn, BASE } = Pointk1;
|
|
193
223
|
const sig = abytes(signature, 64, 'signature');
|
|
194
224
|
const m = abytes(message, undefined, 'message');
|
|
@@ -198,9 +228,13 @@ function schnorrVerify(signature: Uint8Array, message: Uint8Array, publicKey: Ui
|
|
|
198
228
|
const r = num(sig.subarray(0, 32)); // Let r = int(sig[0:32]); fail if r ≥ p.
|
|
199
229
|
if (!Fp.isValidNot0(r)) return false;
|
|
200
230
|
const s = num(sig.subarray(32, 64)); // Let s = int(sig[32:64]); fail if s ≥ n.
|
|
231
|
+
// Stricter than BIP-340/libsecp256k1, which only reject s >= n. Honest signing reaches
|
|
232
|
+
// s = 0 only with negligible probability (k + e*d ≡ 0 mod n), so treat zero-s inputs as
|
|
233
|
+
// crafted edge cases and fail closed instead of carrying that extra verification surface.
|
|
201
234
|
if (!Fn.isValidNot0(s)) return false;
|
|
202
235
|
|
|
203
|
-
|
|
236
|
+
// int(challenge(bytes(r) || bytes(P) || m)) % n
|
|
237
|
+
const e = challenge(Fn.toBytes(r), pointToBytes(P), m);
|
|
204
238
|
// R = s⋅G - e⋅P, where -eP == (n-e)P
|
|
205
239
|
const R = BASE.multiplyUnsafe(s).add(P.multiplyUnsafe(Fn.neg(e)));
|
|
206
240
|
const { x, y } = R.toAffine();
|
|
@@ -212,24 +246,60 @@ function schnorrVerify(signature: Uint8Array, message: Uint8Array, publicKey: Ui
|
|
|
212
246
|
}
|
|
213
247
|
}
|
|
214
248
|
|
|
249
|
+
export const __TEST: { lift_x: typeof lift_x } = /* @__PURE__ */ Object.freeze({ lift_x });
|
|
250
|
+
|
|
251
|
+
/** Schnorr-specific secp256k1 API from BIP340. */
|
|
215
252
|
export type SecpSchnorr = {
|
|
216
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Generate one Schnorr secret/public keypair.
|
|
255
|
+
* @param seed - Optional seed for deterministic testing or custom randomness.
|
|
256
|
+
* @returns Fresh secret/public keypair.
|
|
257
|
+
*/
|
|
258
|
+
keygen: (seed?: TArg<Uint8Array>) => { secretKey: TRet<Uint8Array>; publicKey: TRet<Uint8Array> };
|
|
259
|
+
/**
|
|
260
|
+
* Derive the x-only public key from a secret key.
|
|
261
|
+
* @param secretKey - Secret key bytes.
|
|
262
|
+
* @returns X-only public key bytes.
|
|
263
|
+
*/
|
|
217
264
|
getPublicKey: typeof schnorrGetPublicKey;
|
|
265
|
+
/**
|
|
266
|
+
* Create one BIP340 Schnorr signature.
|
|
267
|
+
* @param message - Message bytes to sign.
|
|
268
|
+
* @param secretKey - Secret key bytes.
|
|
269
|
+
* @param auxRand - Optional auxiliary randomness.
|
|
270
|
+
* @returns Compact Schnorr signature bytes.
|
|
271
|
+
*/
|
|
218
272
|
sign: typeof schnorrSign;
|
|
273
|
+
/**
|
|
274
|
+
* Verify one BIP340 Schnorr signature.
|
|
275
|
+
* @param signature - Compact signature bytes.
|
|
276
|
+
* @param message - Signed message bytes.
|
|
277
|
+
* @param publicKey - X-only public key bytes.
|
|
278
|
+
* @returns `true` when the signature is valid.
|
|
279
|
+
*/
|
|
219
280
|
verify: typeof schnorrVerify;
|
|
281
|
+
/** Underlying secp256k1 point constructor. */
|
|
220
282
|
Point: WeierstrassPointCons<bigint>;
|
|
283
|
+
/** Helper utilities for Schnorr-specific key handling and tagged hashing. */
|
|
221
284
|
utils: {
|
|
222
|
-
|
|
223
|
-
|
|
285
|
+
/** Generate one Schnorr secret key. */
|
|
286
|
+
randomSecretKey: (seed?: TArg<Uint8Array>) => TRet<Uint8Array>;
|
|
287
|
+
/** Convert one point into its x-only BIP340 byte encoding. */
|
|
288
|
+
pointToBytes: (point: TArg<PointType<bigint>>) => TRet<Uint8Array>;
|
|
289
|
+
/** Lift one x coordinate into the unique even-Y point. */
|
|
224
290
|
lift_x: typeof lift_x;
|
|
291
|
+
/** Compute a BIP340 tagged hash. */
|
|
225
292
|
taggedHash: typeof taggedHash;
|
|
226
293
|
};
|
|
294
|
+
/** Public byte lengths for keys, signatures, and seeds. */
|
|
227
295
|
lengths: CurveLengths;
|
|
228
296
|
};
|
|
229
297
|
/**
|
|
230
298
|
* Schnorr signatures over secp256k1.
|
|
231
|
-
* https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
|
|
299
|
+
* See {@link https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki | BIP 340}.
|
|
232
300
|
* @example
|
|
301
|
+
* Generate one BIP340 Schnorr keypair, sign a message, and verify it.
|
|
302
|
+
*
|
|
233
303
|
* ```js
|
|
234
304
|
* import { schnorr } from '@noble/curves/secp256k1.js';
|
|
235
305
|
* const { secretKey, publicKey } = schnorr.keygen();
|
|
@@ -242,31 +312,34 @@ export type SecpSchnorr = {
|
|
|
242
312
|
export const schnorr: SecpSchnorr = /* @__PURE__ */ (() => {
|
|
243
313
|
const size = 32;
|
|
244
314
|
const seedLength = 48;
|
|
245
|
-
const randomSecretKey = (seed
|
|
315
|
+
const randomSecretKey = (seed?: TArg<Uint8Array>): TRet<Uint8Array> => {
|
|
316
|
+
seed = seed === undefined ? randomBytes(seedLength) : seed;
|
|
246
317
|
return mapHashToField(seed, secp256k1_CURVE.n);
|
|
247
318
|
};
|
|
248
|
-
return {
|
|
319
|
+
return Object.freeze({
|
|
249
320
|
keygen: createKeygen(randomSecretKey, schnorrGetPublicKey),
|
|
250
321
|
getPublicKey: schnorrGetPublicKey,
|
|
251
322
|
sign: schnorrSign,
|
|
252
323
|
verify: schnorrVerify,
|
|
253
324
|
Point: Pointk1,
|
|
254
|
-
utils: {
|
|
325
|
+
utils: Object.freeze({
|
|
255
326
|
randomSecretKey,
|
|
256
327
|
taggedHash,
|
|
257
328
|
lift_x,
|
|
258
329
|
pointToBytes,
|
|
259
|
-
},
|
|
260
|
-
lengths: {
|
|
330
|
+
}),
|
|
331
|
+
lengths: Object.freeze({
|
|
261
332
|
secretKey: size,
|
|
262
333
|
publicKey: size,
|
|
263
334
|
publicKeyHasPrefix: false,
|
|
264
335
|
signature: size * 2,
|
|
265
336
|
seed: seedLength,
|
|
266
|
-
},
|
|
267
|
-
};
|
|
337
|
+
}),
|
|
338
|
+
});
|
|
268
339
|
})();
|
|
269
340
|
|
|
341
|
+
// RFC 9380 Appendix E.1 3-isogeny coefficients for secp256k1, stored in ascending degree order.
|
|
342
|
+
// The final `1` in each denominator array is the explicit monic leading term.
|
|
270
343
|
const isoMap = /* @__PURE__ */ (() =>
|
|
271
344
|
isogenyMap(
|
|
272
345
|
Fpk1,
|
|
@@ -300,19 +373,32 @@ const isoMap = /* @__PURE__ */ (() =>
|
|
|
300
373
|
],
|
|
301
374
|
].map((i) => i.map((j) => BigInt(j))) as [bigint[], bigint[], bigint[], bigint[]]
|
|
302
375
|
))();
|
|
303
|
-
|
|
304
|
-
|
|
376
|
+
// RFC 9380 §8.7 secp256k1 E' parameters for the SWU-to-isogeny pipeline below.
|
|
377
|
+
let mapSWU: ((u: bigint) => { x: bigint; y: bigint }) | undefined;
|
|
378
|
+
const getMapSWU = () =>
|
|
379
|
+
mapSWU ||
|
|
380
|
+
(mapSWU = mapToCurveSimpleSWU(Fpk1, {
|
|
381
|
+
// Building the SWU sqrt-ratio helper eagerly adds noticeable `secp256k1.js` import cost, so
|
|
382
|
+
// defer it to first use; after that the cached mapper is reused directly.
|
|
305
383
|
A: BigInt('0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533'),
|
|
306
384
|
B: BigInt('1771'),
|
|
307
385
|
Z: Fpk1.create(BigInt('-11')),
|
|
308
|
-
}))
|
|
386
|
+
}));
|
|
309
387
|
|
|
310
|
-
/**
|
|
388
|
+
/**
|
|
389
|
+
* Hashing / encoding to secp256k1 points / field. RFC 9380 methods.
|
|
390
|
+
* @example
|
|
391
|
+
* Hash one message onto secp256k1.
|
|
392
|
+
*
|
|
393
|
+
* ```ts
|
|
394
|
+
* const point = secp256k1_hasher.hashToCurve(new TextEncoder().encode('hello noble'));
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
311
397
|
export const secp256k1_hasher: H2CHasher<WeierstrassPointCons<bigint>> = /* @__PURE__ */ (() =>
|
|
312
398
|
createHasher(
|
|
313
399
|
Pointk1,
|
|
314
400
|
(scalars: bigint[]) => {
|
|
315
|
-
const { x, y } =
|
|
401
|
+
const { x, y } = getMapSWU()(Fpk1.create(scalars[0]));
|
|
316
402
|
return isoMap(x, y);
|
|
317
403
|
},
|
|
318
404
|
{
|
|
@@ -325,3 +411,160 @@ export const secp256k1_hasher: H2CHasher<WeierstrassPointCons<bigint>> = /* @__P
|
|
|
325
411
|
hash: sha256,
|
|
326
412
|
}
|
|
327
413
|
))();
|
|
414
|
+
/**
|
|
415
|
+
* FROST threshold signatures over secp256k1. RFC 9591.
|
|
416
|
+
* @example
|
|
417
|
+
* Create one trusted-dealer package for 2-of-3 secp256k1 signing.
|
|
418
|
+
*
|
|
419
|
+
* ```ts
|
|
420
|
+
* const alice = secp256k1_FROST.Identifier.derive('alice@example.com');
|
|
421
|
+
* const bob = secp256k1_FROST.Identifier.derive('bob@example.com');
|
|
422
|
+
* const carol = secp256k1_FROST.Identifier.derive('carol@example.com');
|
|
423
|
+
* const deal = secp256k1_FROST.trustedDealer({ min: 2, max: 3 }, [alice, bob, carol]);
|
|
424
|
+
* ```
|
|
425
|
+
*/
|
|
426
|
+
export const secp256k1_FROST: TRet<FROST> = /* @__PURE__ */ (() =>
|
|
427
|
+
createFROST({
|
|
428
|
+
name: 'FROST-secp256k1-SHA256-v1',
|
|
429
|
+
Point: Pointk1,
|
|
430
|
+
hashToScalar: secp256k1_hasher.hashToScalar,
|
|
431
|
+
hash: sha256,
|
|
432
|
+
}))();
|
|
433
|
+
|
|
434
|
+
// Taproot utils
|
|
435
|
+
// `undefined` means "disable TapTweak entirely"; callers that want the BIP-341/BIP-386 empty
|
|
436
|
+
// merkle root must pass `new Uint8Array(0)` explicitly.
|
|
437
|
+
function tweak(point: PointType<bigint>, merkleRoot?: TArg<Uint8Array>): bigint {
|
|
438
|
+
if (merkleRoot === undefined) return _0n;
|
|
439
|
+
const x = pointToBytes(point);
|
|
440
|
+
const t = bytesToNumberBE(taggedHash('TapTweak', x, merkleRoot));
|
|
441
|
+
// BIP-341 taproot_tweak_pubkey/taproot_tweak_seckey: "if t >= SECP256K1_ORDER:
|
|
442
|
+
// raise ValueError". TapTweak must reject overflow instead of reducing modulo n.
|
|
443
|
+
if (!Pointk1.Fn.isValid(t)) throw new Error('invalid TapTweak hash');
|
|
444
|
+
return t;
|
|
445
|
+
}
|
|
446
|
+
function frostPubToEvenY(pub: TArg<FrostPublic>): TRet<FrostPublic> {
|
|
447
|
+
const VK = Pointk1.fromBytes(pub.commitments[0]);
|
|
448
|
+
// Keep aliasing on the already-even path so wrapper callers can skip unnecessary cloning.
|
|
449
|
+
if (hasEven(VK.y)) return pub as TRet<FrostPublic>;
|
|
450
|
+
return {
|
|
451
|
+
signers: { min: pub.signers.min, max: pub.signers.max },
|
|
452
|
+
commitments: pub.commitments.map((i) => Pointk1.fromBytes(i).negate().toBytes()),
|
|
453
|
+
verifyingShares: Object.fromEntries(
|
|
454
|
+
Object.entries(pub.verifyingShares).map(([k, v]) => [
|
|
455
|
+
k,
|
|
456
|
+
Pointk1.fromBytes(v).negate().toBytes(),
|
|
457
|
+
])
|
|
458
|
+
),
|
|
459
|
+
} as TRet<FrostPublic>;
|
|
460
|
+
}
|
|
461
|
+
function frostSecretToEvenY(s: TArg<FrostSecret>, pub: TArg<FrostPublic>): TRet<FrostSecret> {
|
|
462
|
+
const VK = Pointk1.fromBytes(pub.commitments[0]);
|
|
463
|
+
// Keep aliasing on the already-even path so wrapper callers can preserve package identity.
|
|
464
|
+
if (hasEven(VK.y)) return s as TRet<FrostSecret>;
|
|
465
|
+
const Fn = Pointk1.Fn;
|
|
466
|
+
return {
|
|
467
|
+
...s,
|
|
468
|
+
signingShare: Fn.toBytes(Fn.neg(Fn.fromBytes(s.signingShare))),
|
|
469
|
+
} as TRet<FrostSecret>;
|
|
470
|
+
}
|
|
471
|
+
function frostNoncesToEvenY(PK: PointType<bigint>, nonces: TArg<Nonces>): TRet<Nonces> {
|
|
472
|
+
if (hasEven(PK.y)) return nonces as TRet<Nonces>;
|
|
473
|
+
const Fn = Pointk1.Fn;
|
|
474
|
+
return {
|
|
475
|
+
binding: Fn.toBytes(Fn.neg(Fn.fromBytes(nonces.binding))),
|
|
476
|
+
hiding: Fn.toBytes(Fn.neg(Fn.fromBytes(nonces.hiding))),
|
|
477
|
+
} as TRet<Nonces>;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function frostTweakSecret(
|
|
481
|
+
s: TArg<FrostSecret>,
|
|
482
|
+
pub: TArg<FrostPublic>,
|
|
483
|
+
merkleRoot?: TArg<Uint8Array>
|
|
484
|
+
): TRet<FrostSecret> {
|
|
485
|
+
const Fn = Pointk1.Fn;
|
|
486
|
+
const keyPackage = frostSecretToEvenY(s, pub);
|
|
487
|
+
const evenPub = frostPubToEvenY(pub);
|
|
488
|
+
const t = tweak(Pointk1.fromBytes(evenPub.commitments[0]), merkleRoot);
|
|
489
|
+
const signingShare = Fn.toBytes(Fn.add(Fn.fromBytes(keyPackage.signingShare), t));
|
|
490
|
+
return {
|
|
491
|
+
identifier: keyPackage.identifier,
|
|
492
|
+
signingShare,
|
|
493
|
+
} as TRet<FrostSecret>;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function frostTweakPublic(
|
|
497
|
+
pub: TArg<FrostPublic>,
|
|
498
|
+
merkleRoot?: TArg<Uint8Array>
|
|
499
|
+
): TRet<FrostPublic> {
|
|
500
|
+
const PKPackage = frostPubToEvenY(pub);
|
|
501
|
+
const t = tweak(Pointk1.fromBytes(PKPackage.commitments[0]), merkleRoot);
|
|
502
|
+
const tp = Pointk1.BASE.multiply(t);
|
|
503
|
+
const commitments = PKPackage.commitments.map((c, i) =>
|
|
504
|
+
(i === 0 ? Pointk1.fromBytes(c).add(tp) : Pointk1.fromBytes(c)).toBytes()
|
|
505
|
+
);
|
|
506
|
+
const verifyingShares: Record<string, Uint8Array> = {};
|
|
507
|
+
for (const k in PKPackage.verifyingShares) {
|
|
508
|
+
verifyingShares[k] = Pointk1.fromBytes(PKPackage.verifyingShares[k]).add(tp).toBytes();
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
signers: { min: PKPackage.signers.min, max: PKPackage.signers.max },
|
|
512
|
+
commitments,
|
|
513
|
+
verifyingShares,
|
|
514
|
+
} as TRet<FrostPublic>;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* FROST threshold signatures over secp256k1-schnorr-taproot. RFC 9591.
|
|
519
|
+
* DKG outputs are auto-tweaked with the empty Taproot merkle root for compatibility, while
|
|
520
|
+
* `trustedDealer()` outputs stay untweaked unless callers apply the Taproot tweak themselves.
|
|
521
|
+
* @example
|
|
522
|
+
* Create one trusted-dealer package for Taproot-compatible FROST signing.
|
|
523
|
+
*
|
|
524
|
+
* ```ts
|
|
525
|
+
* const alice = schnorr_FROST.Identifier.derive('alice@example.com');
|
|
526
|
+
* const bob = schnorr_FROST.Identifier.derive('bob@example.com');
|
|
527
|
+
* const carol = schnorr_FROST.Identifier.derive('carol@example.com');
|
|
528
|
+
* const deal = schnorr_FROST.trustedDealer({ min: 2, max: 3 }, [alice, bob, carol]);
|
|
529
|
+
* ```
|
|
530
|
+
*/
|
|
531
|
+
export const schnorr_FROST: TRet<FROST> = /* @__PURE__ */ (() =>
|
|
532
|
+
createFROST({
|
|
533
|
+
name: 'FROST-secp256k1-SHA256-TR-v1',
|
|
534
|
+
Point: Pointk1,
|
|
535
|
+
hashToScalar: secp256k1_hasher.hashToScalar,
|
|
536
|
+
hash: sha256,
|
|
537
|
+
// Taproot related hacks
|
|
538
|
+
parsePublicKey(publicKey) {
|
|
539
|
+
// External Taproot keys are x-only, but local key packages still use compressed points.
|
|
540
|
+
if (publicKey.length === 32) return lift_x(bytesToNumberBE(publicKey));
|
|
541
|
+
if (publicKey.length === 33) return Pointk1.fromBytes(publicKey);
|
|
542
|
+
throw new Error(`expected x-only or compressed public key, got length=${publicKey.length}`);
|
|
543
|
+
},
|
|
544
|
+
adjustScalar(n: bigint) {
|
|
545
|
+
const PK = Pointk1.BASE.multiply(n);
|
|
546
|
+
return hasEven(PK.y) ? n : Pointk1.Fn.neg(n);
|
|
547
|
+
},
|
|
548
|
+
adjustPoint: (p) => (hasEven(p.y) ? p : p.negate()),
|
|
549
|
+
challenge(R, PK, msg) {
|
|
550
|
+
return challenge(pointToBytes(R), pointToBytes(PK), msg);
|
|
551
|
+
},
|
|
552
|
+
adjustNonces: frostNoncesToEvenY,
|
|
553
|
+
adjustGroupCommitmentShare: (GC, GCShare) => (!hasEven(GC.y) ? GCShare.negate() : GCShare),
|
|
554
|
+
adjustPublic: frostPubToEvenY,
|
|
555
|
+
adjustSecret: frostSecretToEvenY,
|
|
556
|
+
adjustTx: {
|
|
557
|
+
// Compat with official implementation
|
|
558
|
+
encode: (tx) => tx.subarray(1) as TRet<Uint8Array>,
|
|
559
|
+
decode: (tx) => concatBytes(Uint8Array.of(0x02), tx) as TRet<Uint8Array>,
|
|
560
|
+
},
|
|
561
|
+
adjustDKG: (k) => {
|
|
562
|
+
// Compatibility with frost-secp256k1-tr: DKG output is auto-tweaked with the
|
|
563
|
+
// empty Taproot merkle root, while dealer-generated keys stay untweaked.
|
|
564
|
+
const merkleRoot = new Uint8Array(0);
|
|
565
|
+
return {
|
|
566
|
+
public: frostTweakPublic(k.public, merkleRoot),
|
|
567
|
+
secret: frostTweakSecret(k.secret, k.public, merkleRoot),
|
|
568
|
+
};
|
|
569
|
+
},
|
|
570
|
+
}))();
|