Skip to content

Commit 0d4ec4b

Browse files
committed
Add docs on Schnorr
1 parent 8e0bfec commit 0d4ec4b

File tree

4 files changed

+88
-50
lines changed

4 files changed

+88
-50
lines changed

README.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,23 @@ const publicKey2 = secp.recoverPublicKey(sigR, keccak256(msg), { prehash: false
206206

207207
Recover public key from Signature instance with `recovery` bit set.
208208

209+
### schnorr
210+
211+
```ts
212+
import { schnorr } from '@noble/secp256k1';
213+
const { secretKey, publicKey } = schnorr.keygen();
214+
const msg = new TextEncoder().encode('hello noble');
215+
const sig = schnorr.sign(msg, secretKey);
216+
const isValid = schnorr.verify(sig, msg, publicKey);
217+
218+
const sig = await schnorr.signAsync(msg, secretKey);
219+
const isValid = await schnorr.verifyAsync(sig, msg, publicKey);
220+
```
221+
222+
Schnorr
223+
signatures compliant with [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki)
224+
are supported.
225+
209226
### utils
210227

211228
A bunch of useful **utilities** are also exposed:
@@ -326,9 +343,39 @@ Point.fromBytes x 13,656 ops/sec @ 73μs/op
326343

327344
## Upgrading
328345

346+
### v2 to v3
347+
348+
v3 brings the package closer to noble-curves v2.
349+
350+
- Add Schnorr signatures
351+
- Most methods now expect Uint8Array, string hex inputs are prohibited
352+
- Add `keygen`, `keygenAsync` method
353+
- sign, verify: Switch to **prehashed messages**. Instead of
354+
messageHash, the methods now expect unhashed message.
355+
To bring back old behavior, use option `{prehash: false}`
356+
- sign, verify: Switch to **Uint8Array signatures** (format: 'compact') by default.
357+
- verify: **der format must be explicitly specified** in `{format: 'der'}`.
358+
This reduces malleability
359+
- verify: **prohibit Signature-instance** signature. User must now always do
360+
`signature.toBytes()`
361+
- Node v20.19 is now the minimum required version
362+
- Various small changes for types
363+
- etc: hashes are now set in `hashes` object. Also sha256 needs to be set now for `prehash: true`:
364+
365+
```js
366+
// before
367+
// etc.hmacSha256Sync = (key, ...messages) => hmac(sha256, key, etc.concatBytes(...messages));
368+
// etc.hmacSha256Async = (key, ...messages) => Promise.resolve(etc.hmacSha256Sync(key, ...messages));
369+
// after
370+
hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg);
371+
hashes.sha256 = sha256;
372+
hashes.hmacSha256Async = async (key, msg) => hmac(sha256, key, msg);
373+
hashes.sha256Async = async (msg) => sha256(msg);
374+
```
375+
329376
### v1 to v2
330377

331-
noble-secp256k1 v2 improves security and reduces attack surface.
378+
v2 improves security and reduces attack surface.
332379
The goal of v2 is to provide minimum possible JS library which is safe and fast.
333380

334381
- Disable some features to ensure 4x smaller than v1, 5KB bundle size:

index.d.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,8 @@ type KeysSecPub = {
230230
secretKey: Bytes;
231231
publicKey: Bytes;
232232
};
233-
declare const keygen: (seed?: Bytes) => KeysSecPub;
233+
type KeygenFn = (seed?: Bytes) => KeysSecPub;
234+
declare const keygen: KeygenFn;
234235
/** Math, hex, byte helpers. Not in `utils` because utils share API with noble-curves. */
235236
declare const etc: {
236237
hexToBytes: (hex: string) => Bytes;
@@ -254,26 +255,25 @@ declare const utils: {
254255
* Schnorr public key is just `x` coordinate of Point as per BIP340.
255256
*/
256257
declare const pubSchnorr: (secretKey: Bytes) => Bytes;
258+
declare const keygenSchnorr: KeygenFn;
257259
/**
258260
* Creates Schnorr signature as per BIP340. Verifies itself before returning anything.
259261
* auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous.
260262
*/
261263
declare const signSchnorr: (message: Bytes, secretKey: Bytes, auxRand?: Bytes) => Bytes;
262-
declare const signAsyncSchnorr: (message: Bytes, secretKey: Bytes, auxRand?: Bytes) => Promise<Bytes>;
264+
declare const signSchnorrAsync: (message: Bytes, secretKey: Bytes, auxRand?: Bytes) => Promise<Bytes>;
263265
/**
264266
* Verifies Schnorr signature.
265267
* Will swallow errors & return false except for initial type validation of arguments.
266268
*/
267269
declare const verifySchnorr: (s: Bytes, m: Bytes, p: Bytes) => boolean;
268-
declare const verifyAsyncSchnorr: (s: Bytes, m: Bytes, p: Bytes) => Promise<boolean>;
270+
declare const verifySchnorrAsync: (s: Bytes, m: Bytes, p: Bytes) => Promise<boolean>;
269271
declare const schnorr: {
272+
keygen: typeof keygenSchnorr;
270273
getPublicKey: typeof pubSchnorr;
271274
sign: typeof signSchnorr;
272275
verify: typeof verifySchnorr;
276+
signAsync: typeof signSchnorrAsync;
277+
verifyAsync: typeof verifySchnorrAsync;
273278
};
274-
declare const schnorrAsync: {
275-
getPublicKey: typeof pubSchnorr;
276-
signAsync: typeof signAsyncSchnorr;
277-
verifyAsync: typeof verifyAsyncSchnorr;
278-
};
279-
export { etc, getPublicKey, getSharedSecret, hash, hashes, keygen, Point, recoverPublicKey, recoverPublicKeyAsync, schnorr, schnorrAsync, sign, signAsync, Signature, utils, verify, verifyAsync, };
279+
export { etc, getPublicKey, getSharedSecret, hash, hashes, keygen, Point, recoverPublicKey, recoverPublicKeyAsync, schnorr, sign, signAsync, Signature, utils, verify, verifyAsync };

index.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -805,10 +805,11 @@ const randomSecretKey = (seed = randomBytes(lengths.seed)) => {
805805
const num = M(bytesToNumBE(seed), N - 1n);
806806
return numTo32b(num + 1n);
807807
};
808-
const keygen = (seed) => {
808+
const createKeygen = (getPublicKey) => (seed) => {
809809
const secretKey = randomSecretKey(seed);
810810
return { secretKey, publicKey: getPublicKey(secretKey) };
811811
};
812+
const keygen = createKeygen(getPublicKey);
812813
/** Math, hex, byte helpers. Not in `utils` because utils share API with noble-curves. */
813814
const etc = {
814815
hexToBytes: hexToBytes,
@@ -863,6 +864,7 @@ const challengeAsync = async (...args) => bytesModN(await taggedHashAsync(T_CHAL
863864
const pubSchnorr = (secretKey) => {
864865
return extpubSchnorr(secretKey).px; // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)
865866
};
867+
const keygenSchnorr = createKeygen(pubSchnorr);
866868
// Common preparation fn for both sync and async signing
867869
const prepSigSchnorr = (message, secretKey, auxRand) => {
868870
const { px, d } = extpubSchnorr(secretKey);
@@ -900,7 +902,7 @@ const signSchnorr = (message, secretKey, auxRand = randomBytes(L)) => {
900902
err(E_INVSIG);
901903
return sig;
902904
};
903-
const signAsyncSchnorr = async (message, secretKey, auxRand = randomBytes(L)) => {
905+
const signSchnorrAsync = async (message, secretKey, auxRand = randomBytes(L)) => {
904906
const { m, px, d, a } = prepSigSchnorr(message, secretKey, auxRand);
905907
const aux = await taggedHashAsync(T_AUX, a);
906908
// Let t be the byte-wise xor of bytes(d) and hash/aux(a)
@@ -912,7 +914,7 @@ const signAsyncSchnorr = async (message, secretKey, auxRand = randomBytes(L)) =>
912914
const e = await challengeAsync(rx, px, m);
913915
const sig = createSigSchnorr(k, rx, e, d);
914916
// If Verify(bytes(P), m, sig) (see below) returns failure, abort
915-
if (!(await verifyAsyncSchnorr(sig, m, px)))
917+
if (!(await verifySchnorrAsync(sig, m, px)))
916918
err(E_INVSIG);
917919
return sig;
918920
};
@@ -956,16 +958,14 @@ const _verifSchnorr = (signature, message, publicKey, challengeFn) => {
956958
* Will swallow errors & return false except for initial type validation of arguments.
957959
*/
958960
const verifySchnorr = (s, m, p) => _verifSchnorr(s, m, p, challenge);
959-
const verifyAsyncSchnorr = async (s, m, p) => _verifSchnorr(s, m, p, challengeAsync);
961+
const verifySchnorrAsync = async (s, m, p) => _verifSchnorr(s, m, p, challengeAsync);
960962
const schnorr = {
963+
keygen: keygenSchnorr,
961964
getPublicKey: pubSchnorr,
962965
sign: signSchnorr,
963966
verify: verifySchnorr,
964-
};
965-
const schnorrAsync = {
966-
getPublicKey: pubSchnorr,
967-
signAsync: signAsyncSchnorr,
968-
verifyAsync: verifyAsyncSchnorr,
967+
signAsync: signSchnorrAsync,
968+
verifyAsync: verifySchnorrAsync,
969969
};
970970
// ## Precomputes
971971
// --------------
@@ -1043,4 +1043,4 @@ const wNAF = (n) => {
10431043
return { p, f }; // return both real and fake points for JIT
10441044
};
10451045
// !! Remove the export below to easily use in REPL / browser console
1046-
export { etc, getPublicKey, getSharedSecret, hash, hashes, keygen, Point, recoverPublicKey, recoverPublicKeyAsync, schnorr, schnorrAsync, sign, signAsync, Signature, utils, verify, verifyAsync, };
1046+
export { etc, getPublicKey, getSharedSecret, hash, hashes, keygen, Point, recoverPublicKey, recoverPublicKeyAsync, schnorr, sign, signAsync, Signature, utils, verify, verifyAsync };

index.ts

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -876,10 +876,12 @@ const randomSecretKey = (seed = randomBytes(lengths.seed)) => {
876876
};
877877

878878
type KeysSecPub = { secretKey: Bytes; publicKey: Bytes };
879-
const keygen = (seed?: Bytes): KeysSecPub => {
879+
type KeygenFn = (seed?: Bytes) => KeysSecPub;
880+
const createKeygen = (getPublicKey: (secretKey: Bytes) => Bytes) => (seed?: Bytes): KeysSecPub => {
880881
const secretKey = randomSecretKey(seed);
881882
return { secretKey, publicKey: getPublicKey(secretKey) };
882-
};
883+
}
884+
const keygen: KeygenFn = createKeygen(getPublicKey);
883885

884886
/** Math, hex, byte helpers. Not in `utils` because utils share API with noble-curves. */
885887
const etc = {
@@ -942,6 +944,8 @@ const pubSchnorr = (secretKey: Bytes): Bytes => {
942944
return extpubSchnorr(secretKey).px; // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)
943945
};
944946

947+
const keygenSchnorr: KeygenFn = createKeygen(pubSchnorr);
948+
945949
// Common preparation fn for both sync and async signing
946950
const prepSigSchnorr = (message: Bytes, secretKey: Bytes, auxRand: Bytes) => {
947951
const { px, d } = extpubSchnorr(secretKey);
@@ -981,7 +985,7 @@ const signSchnorr = (message: Bytes, secretKey: Bytes, auxRand: Bytes = randomBy
981985
return sig;
982986
};
983987

984-
const signAsyncSchnorr = async (
988+
const signSchnorrAsync = async (
985989
message: Bytes,
986990
secretKey: Bytes,
987991
auxRand: Bytes = randomBytes(L)
@@ -997,7 +1001,7 @@ const signAsyncSchnorr = async (
9971001
const e = await challengeAsync(rx, px, m);
9981002
const sig = createSigSchnorr(k, rx, e, d);
9991003
// If Verify(bytes(P), m, sig) (see below) returns failure, abort
1000-
if (!(await verifyAsyncSchnorr(sig, m, px))) err(E_INVSIG);
1004+
if (!(await verifySchnorrAsync(sig, m, px))) err(E_INVSIG);
10011005
return sig;
10021006
};
10031007

@@ -1050,27 +1054,23 @@ const _verifSchnorr = (
10501054
*/
10511055
const verifySchnorr = (s: Bytes, m: Bytes, p: Bytes): boolean =>
10521056
_verifSchnorr(s, m, p, challenge) as boolean;
1053-
const verifyAsyncSchnorr = async (s: Bytes, m: Bytes, p: Bytes): Promise<boolean> =>
1057+
const verifySchnorrAsync = async (s: Bytes, m: Bytes, p: Bytes): Promise<boolean> =>
10541058
_verifSchnorr(s, m, p, challengeAsync) as Promise<boolean>;
10551059

10561060
const schnorr: {
1061+
keygen: typeof keygenSchnorr,
10571062
getPublicKey: typeof pubSchnorr;
10581063
sign: typeof signSchnorr;
10591064
verify: typeof verifySchnorr;
1065+
signAsync: typeof signSchnorrAsync,
1066+
verifyAsync: typeof verifySchnorrAsync
10601067
} = {
1068+
keygen: keygenSchnorr,
10611069
getPublicKey: pubSchnorr,
10621070
sign: signSchnorr,
10631071
verify: verifySchnorr,
1064-
};
1065-
1066-
const schnorrAsync: {
1067-
getPublicKey: typeof pubSchnorr;
1068-
signAsync: typeof signAsyncSchnorr;
1069-
verifyAsync: typeof verifyAsyncSchnorr;
1070-
} = {
1071-
getPublicKey: pubSchnorr,
1072-
signAsync: signAsyncSchnorr,
1073-
verifyAsync: verifyAsyncSchnorr,
1072+
signAsync: signSchnorrAsync,
1073+
verifyAsync: verifySchnorrAsync,
10741074
};
10751075

10761076
// ## Precomputes
@@ -1152,20 +1152,11 @@ const wNAF = (n: bigint): { p: Point; f: Point } => {
11521152
// !! Remove the export below to easily use in REPL / browser console
11531153
export {
11541154
etc,
1155-
getPublicKey,
1156-
getSharedSecret,
1157-
hash,
1158-
hashes,
1155+
getPublicKey, getSharedSecret,
1156+
hash, hashes,
11591157
keygen,
1160-
Point,
1161-
recoverPublicKey,
1162-
recoverPublicKeyAsync,
1163-
schnorr,
1164-
schnorrAsync,
1165-
sign,
1166-
signAsync,
1167-
Signature,
1168-
utils,
1169-
verify,
1170-
verifyAsync,
1158+
Point, recoverPublicKey, recoverPublicKeyAsync, schnorr, sign, signAsync,
1159+
1160+
Signature, utils, verify, verifyAsync
11711161
};
1162+

0 commit comments

Comments
 (0)