working rss feed to nostr publish
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 Paul Miller (https://paulmillr.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the “Software”), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
# noble-secp256k1
|
||||
|
||||
[Fastest](#speed) 4KB JS implementation of [secp256k1](https://www.secg.org/sec2-v2.pdf)
|
||||
elliptic curve. Auditable, high-security, 0-dependency ECDH & ECDSA signatures compliant with RFC6979.
|
||||
|
||||
The library is a tiny single-feature version of
|
||||
[noble-curves](https://github.com/paulmillr/noble-curves), with some features
|
||||
removed. Check out curves as a drop-in replacement with
|
||||
Schnorr signatures, DER encoding and support for different hash functions.
|
||||
|
||||
Take a look at: [Upgrading](#upgrading) section for v1 to v2 transition instructions,
|
||||
[the online demo](https://paulmillr.com/noble/) and blog post
|
||||
[Learning fast elliptic-curve cryptography in JS](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/).
|
||||
|
||||
### This library belongs to _noble_ crypto
|
||||
|
||||
> **noble-crypto** — high-security, easily auditable set of contained cryptographic libraries and tools.
|
||||
|
||||
- No dependencies, protection against supply chain attacks
|
||||
- Auditable TypeScript / JS code
|
||||
- Supported in all major browsers and stable node.js versions
|
||||
- All releases are signed with PGP keys
|
||||
- Check out [homepage](https://paulmillr.com/noble/) & all libraries:
|
||||
[curves](https://github.com/paulmillr/noble-curves)
|
||||
(4kb versions [secp256k1](https://github.com/paulmillr/noble-secp256k1),
|
||||
[ed25519](https://github.com/paulmillr/noble-ed25519)),
|
||||
[hashes](https://github.com/paulmillr/noble-hashes)
|
||||
|
||||
## Usage
|
||||
|
||||
Browser, deno, node.js and unpkg are supported:
|
||||
|
||||
> npm install @noble/secp256k1
|
||||
|
||||
```js
|
||||
import * as secp from '@noble/secp256k1'; // ESM-only. Use bundler for common.js
|
||||
// import * as secp from "https://deno.land/x/secp256k1/mod.ts"; // Deno
|
||||
// import * as secp from "https://unpkg.com/@noble/secp256k1"; // Unpkg
|
||||
(async () => {
|
||||
// keys, messages & other inputs can be Uint8Arrays or hex strings
|
||||
// Uint8Array.from([0xde, 0xad, 0xbe, 0xef]) === 'deadbeef'
|
||||
const privKey = secp.utils.randomPrivateKey(); // Secure random private key
|
||||
// sha256 of 'hello world'
|
||||
const msgHash = 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9';
|
||||
const pubKey = secp.getPublicKey(privKey); // Make pubkey from the private key
|
||||
const signature = await secp.signAsync(msgHash, privKey); // sign
|
||||
const isValid = secp.verify(signature, msgHash, pubKey); // verify
|
||||
|
||||
const pubKey2 = getPublicKey(secp.utils.randomPrivateKey()); // Key of user 2
|
||||
secp.getSharedSecret(privKey, alicesPubkey); // Elliptic curve diffie-hellman
|
||||
signature.recoverPublicKey(msgHash); // Public key recovery
|
||||
})();
|
||||
```
|
||||
|
||||
Advanced examples:
|
||||
|
||||
```ts
|
||||
// 1. Use the shim to enable synchronous methods.
|
||||
// Only async methods are available by default to keep library dependency-free.
|
||||
import { hmac } from '@noble/hashes/hmac';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m))
|
||||
const signature2 = secp.sign(msgHash, privKey); // Can be used now
|
||||
|
||||
// 2. Use the shim only for node.js <= 18 BEFORE importing noble-secp256k1.
|
||||
// The library depends on global variable crypto to work. It is available in
|
||||
// all browsers and many environments, but node.js <= 18 don't have it.
|
||||
import { webcrypto } from 'node:crypto';
|
||||
// @ts-ignore
|
||||
if (!globalThis.crypto) globalThis.crypto = webcrypto;
|
||||
|
||||
// Other stuff
|
||||
// Malleable signatures, incompatible with BTC/ETH, but compatible with openssl
|
||||
// `lowS: true` prohibits signatures which have (sig.s >= CURVE.n/2n) because of
|
||||
// malleability
|
||||
const signatureMalleable = secp.sign(msgHash, privKey, { lowS: false });
|
||||
|
||||
// Signatures with improved security: adds additional entropy `k` for
|
||||
// deterministic signature, follows section 3.6 of RFC6979. When `true`, it
|
||||
// would be filled with 32b from CSPRNG. **Strongly recommended** to pass `true`
|
||||
// to improve security:
|
||||
// - No disadvantage: if an entropy generator is broken, sigs would be the same
|
||||
// as they are without the option
|
||||
// - It would help a lot in case there is an error somewhere in `k` gen.
|
||||
// Exposing `k` could leak private keys
|
||||
// - Sigs with extra entropy would have different `r` / `s`, which means they
|
||||
// would still be valid, but may break some test vectors if you're
|
||||
// cross-testing against other libs
|
||||
const signatureImproved = secp.sign(msgHash, privKey, { extraEntropy: true });
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
There are 3 main methods: `getPublicKey(privateKey)`,
|
||||
`sign(messageHash, privateKey)` and
|
||||
`verify(signature, messageHash, publicKey)`.
|
||||
|
||||
```typescript
|
||||
type Hex = Uint8Array | string;
|
||||
|
||||
// Generates public key from 32-byte private key.
|
||||
// isCompressed=true by default, meaning 33-byte output. Set to false for 65b.
|
||||
function getPublicKey(privateKey: Hex, isCompressed?: boolean): Uint8Array;
|
||||
// Use:
|
||||
// - `ProjectivePoint.fromPrivateKey(privateKey)` for Point instance
|
||||
// - `ProjectivePoint.fromHex(publicKey)` to convert hex / bytes into Point.
|
||||
|
||||
// Generates low-s deterministic-k RFC6979 ECDSA signature.
|
||||
// Use with `extraEntropy: true` to improve security.
|
||||
function sign(
|
||||
messageHash: Hex, // message hash (not message) which would be signed
|
||||
privateKey: Hex, // private key which will sign the hash
|
||||
opts?: { lowS: boolean, extraEntropy: boolean | Hex } // optional params
|
||||
): Signature;
|
||||
function signAsync(
|
||||
messageHash: Hex,
|
||||
privateKey: Hex,
|
||||
opts?: { lowS: boolean; extraEntropy: boolean | Hex }
|
||||
): Promise<Signature>;
|
||||
|
||||
// Verifies ECDSA signature.
|
||||
// lowS option Ensures a signature.s is in the lower-half of CURVE.n.
|
||||
// Used in BTC, ETH.
|
||||
// `{ lowS: false }` should only be used if you need OpenSSL-compatible signatures
|
||||
function verify(
|
||||
signature: Hex | Signature, // returned by the `sign` function
|
||||
messageHash: Hex, // message hash (not message) that must be verified
|
||||
publicKey: Hex, // public (not private) key
|
||||
opts?: { lowS: boolean } // optional params; { lowS: true } by default
|
||||
): boolean;
|
||||
|
||||
// Computes ECDH (Elliptic Curve Diffie-Hellman) shared secret between
|
||||
// key A and different key B.
|
||||
function getSharedSecret(
|
||||
privateKeyA: Uint8Array | string, // Alices's private key
|
||||
publicKeyB: Uint8Array | string, // Bob's public key
|
||||
isCompressed = true // optional arg. (default) true=33b key, false=65b.
|
||||
): Uint8Array;
|
||||
// Use `ProjectivePoint.fromHex(publicKeyB).multiply(privateKeyA)` for Point instance
|
||||
|
||||
// Recover public key from Signature instance with `recovery` bit set
|
||||
signature.recoverPublicKey(
|
||||
msgHash: Uint8Array | string
|
||||
): Uint8Array | undefined;
|
||||
```
|
||||
|
||||
A bunch of useful **utilities** are also exposed:
|
||||
|
||||
```typescript
|
||||
type Bytes = Uint8Array;
|
||||
export declare const etc: {
|
||||
hexToBytes: (hex: string) => Bytes;
|
||||
bytesToHex: (b: Bytes) => string;
|
||||
concatBytes: (...arrs: Bytes[]) => Uint8Array;
|
||||
bytesToNumberBE: (b: Bytes) => bigint;
|
||||
numberToBytesBE: (num: bigint) => Bytes;
|
||||
mod: (a: bigint, b?: bigint) => bigint;
|
||||
invert: (num: bigint, md?: bigint) => bigint;
|
||||
hmacSha256Async: (key: Bytes, ...msgs: Bytes[]) => Promise<Bytes>;
|
||||
hmacSha256Sync: HmacFnSync;
|
||||
hashToPrivateKey: (hash: Hex) => Bytes;
|
||||
randomBytes: (len: number) => Bytes;
|
||||
};
|
||||
export declare const utils: {
|
||||
normPrivateKeyToScalar: (p: PrivKey) => bigint;
|
||||
randomPrivateKey: () => Bytes;
|
||||
isValidPrivateKey: (key: Hex) => boolean;
|
||||
precompute(p: Point, windowSize?: number): Point;
|
||||
};
|
||||
class ProjectivePoint {
|
||||
readonly px: bigint;
|
||||
readonly py: bigint;
|
||||
readonly pz: bigint;
|
||||
constructor(px: bigint, py: bigint, pz: bigint);
|
||||
static readonly BASE: Point;
|
||||
static readonly ZERO: Point;
|
||||
static fromHex(hex: Hex): Point;
|
||||
static fromPrivateKey(n: PrivKey): Point;
|
||||
get x(): bigint;
|
||||
get y(): bigint;
|
||||
equals(other: Point): boolean;
|
||||
add(other: Point): Point;
|
||||
multiply(n: bigint): Point;
|
||||
negate(): Point;
|
||||
toAffine(): AffinePoint;
|
||||
assertValidity(): Point;
|
||||
toHex(isCompressed?: boolean): string;
|
||||
toRawBytes(isCompressed?: boolean): Uint8Array;
|
||||
}
|
||||
class Signature {
|
||||
readonly r: bigint;
|
||||
readonly s: bigint;
|
||||
readonly recovery?: number | undefined;
|
||||
constructor(r: bigint, s: bigint, recovery?: number | undefined);
|
||||
ok(): Signature;
|
||||
static fromCompact(hex: Hex): Signature;
|
||||
hasHighS(): boolean;
|
||||
recoverPublicKey(msgh: Hex): Point;
|
||||
toCompactRawBytes(): Uint8Array;
|
||||
toCompactHex(): string;
|
||||
}
|
||||
CURVE // curve prime; order; equation params, generator coordinates
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
The module is production-ready.
|
||||
It is cross-tested against [noble-curves](https://github.com/paulmillr/noble-curves),
|
||||
and has similar security.
|
||||
|
||||
1. The current version is rewrite of v1, which has been audited by cure53:
|
||||
[PDF](https://cure53.de/pentest-report_noble-lib.pdf) (funded by [Umbra.cash](https://umbra.cash) & community).
|
||||
2. It's being fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz):
|
||||
run the fuzzer by yourself to check.
|
||||
|
||||
Our EC multiplication is hardened to be algorithmically constant time.
|
||||
We're using built-in JS `BigInt`, which is potentially vulnerable to
|
||||
[timing attacks](https://en.wikipedia.org/wiki/Timing_attack) as
|
||||
[per MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#cryptography).
|
||||
But, _JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard
|
||||
to achieve in a scripting language. Which means _any other JS library doesn't
|
||||
use constant-time bigints_. Including bn.js or anything else.
|
||||
Even statically typed Rust, a language without GC,
|
||||
[makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security)
|
||||
for some cases. If your goal is absolute security, don't use any JS lib —
|
||||
including bindings to native ones. Use low-level libraries & languages.
|
||||
|
||||
We consider infrastructure attacks like rogue NPM modules very important;
|
||||
that's why it's crucial to minimize the amount of 3rd-party dependencies & native
|
||||
bindings. If your app uses 500 dependencies, any dep could get hacked and you'll
|
||||
be downloading malware with every `npm install`. Our goal is to minimize this attack vector.
|
||||
|
||||
## Speed
|
||||
|
||||
Use [noble-curves](https://github.com/paulmillr/noble-curves) if you need even higher performance.
|
||||
|
||||
Benchmarks measured with Apple M2 on MacOS 13 with node.js 19.
|
||||
|
||||
getPublicKey(utils.randomPrivateKey()) x 5,540 ops/sec @ 180μs/op
|
||||
sign x 3,301 ops/sec @ 302μs/op
|
||||
verify x 517 ops/sec @ 1ms/op
|
||||
getSharedSecret x 433 ops/sec @ 2ms/op
|
||||
recoverPublicKey x 526 ops/sec @ 1ms/op
|
||||
Point.fromHex (decompression) x 8,415 ops/sec @ 118μs/op
|
||||
|
||||
Compare to other libraries on M1 (`openssl` uses native bindings, not JS):
|
||||
|
||||
elliptic#getPublicKey x 1,940 ops/sec
|
||||
sjcl#getPublicKey x 211 ops/sec
|
||||
|
||||
elliptic#sign x 1,808 ops/sec
|
||||
sjcl#sign x 199 ops/sec
|
||||
openssl#sign x 4,243 ops/sec
|
||||
ecdsa#sign x 116 ops/sec
|
||||
bip-schnorr#sign x 60 ops/sec
|
||||
|
||||
elliptic#verify x 812 ops/sec
|
||||
sjcl#verify x 166 ops/sec
|
||||
openssl#verify x 4,452 ops/sec
|
||||
ecdsa#verify x 80 ops/sec
|
||||
bip-schnorr#verify x 56 ops/sec
|
||||
|
||||
elliptic#ecdh x 971 ops/sec
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Clone the repository.
|
||||
2. `npm install` to install build dependencies like TypeScript
|
||||
3. `npm run build` to compile TypeScript code
|
||||
4. `npm test` to run jest on `test/index.ts`
|
||||
|
||||
Special thanks to [Roman Koblov](https://github.com/romankoblov), who have
|
||||
helped to improve scalar multiplication speed.
|
||||
|
||||
## Upgrading
|
||||
|
||||
noble-secp256k1 v2 features improved security and smaller attack surface.
|
||||
The goal of v2 is to provide minimum possible JS library which is safe and fast.
|
||||
|
||||
That means the library was reduced 4x, to just over 400 lines. In order to
|
||||
achieve the goal, **some features were moved** to
|
||||
[noble-curves](https://github.com/paulmillr/noble-curves), which is
|
||||
even safer and faster drop-in replacement library with same API.
|
||||
Switch to curves if you intend to keep using these features:
|
||||
|
||||
- DER encoding: toDERHex, toDERRawBytes, signing / verification of DER sigs
|
||||
- Schnorr signatures
|
||||
- Using `utils.precompute()` for non-base point
|
||||
- Support for environments which don't support bigint literals
|
||||
- Common.js support
|
||||
- Support for node.js 18 and older without [shim](#usage)
|
||||
|
||||
Other changes for upgrading from @noble/secp256k1 1.7 to 2.0:
|
||||
|
||||
- `getPublicKey`
|
||||
- now produce 33-byte compressed signatures by default
|
||||
- to use old behavior, which produced 65-byte uncompressed keys, set
|
||||
argument `isCompressed` to `false`: `getPublicKey(priv, false)`
|
||||
- `sign`
|
||||
- is now sync; use `signAsync` for async version
|
||||
- now returns `Signature` instance with `{ r, s, recovery }` properties
|
||||
- `canonical` option was renamed to `lowS`
|
||||
- `recovered` option has been removed because recovery bit is always returned now
|
||||
- `der` option has been removed. There are 2 options:
|
||||
1. Use compact encoding: `fromCompact`, `toCompactRawBytes`, `toCompactHex`.
|
||||
Compact encoding is simply a concatenation of 32-byte r and 32-byte s.
|
||||
2. If you must use DER encoding, switch to noble-curves (see above).
|
||||
- `verify`
|
||||
- `strict` option was renamed to `lowS`
|
||||
- `getSharedSecret`
|
||||
- now produce 33-byte compressed signatures by default
|
||||
- to use old behavior, which produced 65-byte uncompressed keys, set
|
||||
argument `isCompressed` to `false`: `getSharedSecret(a, b, false)`
|
||||
- `recoverPublicKey(msg, sig, rec)` was changed to `sig.recoverPublicKey(msg)`
|
||||
- `number` type for private keys have been removed: use `bigint` instead
|
||||
- `Point` (2d xy) has been changed to `ProjectivePoint` (3d xyz)
|
||||
- `utils` were split into `utils` (same api as in noble-curves) and
|
||||
`etc` (`hmacSha256Sync` and others)
|
||||
|
||||
## License
|
||||
|
||||
MIT (c) Paul Miller [(https://paulmillr.com)](https://paulmillr.com), see LICENSE file.
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
declare const CURVE: {
|
||||
p: bigint;
|
||||
n: bigint;
|
||||
a: bigint;
|
||||
b: bigint;
|
||||
Gx: bigint;
|
||||
Gy: bigint;
|
||||
};
|
||||
type Bytes = Uint8Array;
|
||||
type Hex = Bytes | string;
|
||||
type PrivKey = Hex | bigint;
|
||||
interface AffinePoint {
|
||||
x: bigint;
|
||||
y: bigint;
|
||||
}
|
||||
declare class Point {
|
||||
readonly px: bigint;
|
||||
readonly py: bigint;
|
||||
readonly pz: bigint;
|
||||
constructor(px: bigint, py: bigint, pz: bigint);
|
||||
static readonly BASE: Point;
|
||||
static readonly ZERO: Point;
|
||||
static fromAffine(p: AffinePoint): Point;
|
||||
static fromHex(hex: Hex): Point;
|
||||
static fromPrivateKey(k: PrivKey): Point;
|
||||
get x(): bigint;
|
||||
get y(): bigint;
|
||||
equals(other: Point): boolean;
|
||||
negate(): Point;
|
||||
double(): Point;
|
||||
add(other: Point): Point;
|
||||
mul(n: bigint, safe?: boolean): Point;
|
||||
mulAddQUns(R: Point, u1: bigint, u2: bigint): Point;
|
||||
toAffine(): AffinePoint;
|
||||
assertValidity(): Point;
|
||||
multiply(n: bigint): Point;
|
||||
aff(): AffinePoint;
|
||||
ok(): Point;
|
||||
toHex(isCompressed?: boolean): string;
|
||||
toRawBytes(isCompressed?: boolean): Uint8Array;
|
||||
}
|
||||
declare function getPublicKey(privKey: PrivKey, isCompressed?: boolean): Uint8Array;
|
||||
declare class Signature {
|
||||
readonly r: bigint;
|
||||
readonly s: bigint;
|
||||
readonly recovery?: number | undefined;
|
||||
constructor(r: bigint, s: bigint, recovery?: number | undefined);
|
||||
static fromCompact(hex: Hex): Signature;
|
||||
assertValidity(): this;
|
||||
addRecoveryBit(rec: number): Signature;
|
||||
hasHighS(): boolean;
|
||||
recoverPublicKey(msgh: Hex): Point;
|
||||
toCompactRawBytes(): Uint8Array;
|
||||
toCompactHex(): string;
|
||||
}
|
||||
type HmacFnSync = undefined | ((key: Bytes, ...msgs: Bytes[]) => Bytes);
|
||||
declare function signAsync(msgh: Hex, priv: Hex, opts?: {
|
||||
lowS?: boolean | undefined;
|
||||
extraEntropy?: boolean | Hex | undefined;
|
||||
}): Promise<Signature>;
|
||||
declare function sign(msgh: Hex, priv: Hex, opts?: {
|
||||
lowS?: boolean | undefined;
|
||||
extraEntropy?: boolean | Hex | undefined;
|
||||
}): Signature;
|
||||
type SigLike = {
|
||||
r: bigint;
|
||||
s: bigint;
|
||||
};
|
||||
declare function verify(sig: Hex | SigLike, msgh: Hex, pub: Hex, opts?: {
|
||||
lowS?: boolean | undefined;
|
||||
}): boolean;
|
||||
declare function getSharedSecret(privA: Hex, pubB: Hex, isCompressed?: boolean): Bytes;
|
||||
declare function hashToPrivateKey(hash: Hex): Bytes;
|
||||
declare const etc: {
|
||||
hexToBytes: (hex: string) => Bytes;
|
||||
bytesToHex: (b: Bytes) => string;
|
||||
concatBytes: (...arrs: Bytes[]) => Uint8Array;
|
||||
bytesToNumberBE: (b: Bytes) => bigint;
|
||||
numberToBytesBE: (num: bigint) => Bytes;
|
||||
mod: (a: bigint, b?: bigint) => bigint;
|
||||
invert: (num: bigint, md?: bigint) => bigint;
|
||||
hmacSha256Async: (key: Bytes, ...msgs: Bytes[]) => Promise<Bytes>;
|
||||
hmacSha256Sync: HmacFnSync;
|
||||
hashToPrivateKey: typeof hashToPrivateKey;
|
||||
randomBytes: (len: number) => Bytes;
|
||||
};
|
||||
declare const utils: {
|
||||
normPrivateKeyToScalar: (p: PrivKey) => bigint;
|
||||
isValidPrivateKey: (key: Hex) => boolean;
|
||||
randomPrivateKey: () => Bytes;
|
||||
precompute(w?: number, p?: Point): Point;
|
||||
};
|
||||
export { getPublicKey, sign, signAsync, verify, CURVE, // Remove the export to easily use in REPL
|
||||
getSharedSecret, etc, utils, Point as ProjectivePoint, Signature };
|
||||
+510
@@ -0,0 +1,510 @@
|
||||
/*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
|
||||
const B256 = 2n ** 256n; // secp256k1 is short weierstrass curve
|
||||
const P = B256 - 0x1000003d1n; // curve's field prime
|
||||
const N = B256 - 0x14551231950b75fc4402da1732fc9bebfn; // curve (group) order
|
||||
const Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n; // base point x
|
||||
const Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n; // base point y
|
||||
const CURVE = { p: P, n: N, a: 0n, b: 7n, Gx, Gy }; // exported variables incl. a, b
|
||||
const fLen = 32; // field / group byte length
|
||||
const crv = (x) => mod(mod(x * x) * x + CURVE.b); // x³ + ax + b weierstrass formula; no a
|
||||
const err = (m = '') => { throw new Error(m); }; // error helper, messes-up stack trace
|
||||
const big = (n) => typeof n === 'bigint'; // is big integer
|
||||
const str = (s) => typeof s === 'string'; // is string
|
||||
const fe = (n) => big(n) && 0n < n && n < P; // is field element (invertible)
|
||||
const ge = (n) => big(n) && 0n < n && n < N; // is group element
|
||||
const au8 = (a, l) => // is Uint8Array (of specific length)
|
||||
!(a instanceof Uint8Array) || (typeof l === 'number' && l > 0 && a.length !== l) ?
|
||||
err('Uint8Array expected') : a;
|
||||
const u8n = (data) => new Uint8Array(data); // creates Uint8Array
|
||||
const toU8 = (a, len) => au8(str(a) ? h2b(a) : u8n(a), len); // norm(hex/u8a) to u8a
|
||||
const mod = (a, b = P) => { let r = a % b; return r >= 0n ? r : b + r; }; // mod division
|
||||
const isPoint = (p) => (p instanceof Point ? p : err('Point expected')); // is 3d point
|
||||
let Gpows = undefined; // precomputes for base point G
|
||||
class Point {
|
||||
constructor(px, py, pz) {
|
||||
this.px = px;
|
||||
this.py = py;
|
||||
this.pz = pz;
|
||||
} //3d=less inversions
|
||||
static fromAffine(p) { return new Point(p.x, p.y, 1n); }
|
||||
static fromHex(hex) {
|
||||
hex = toU8(hex); // convert hex string to Uint8Array
|
||||
let p = undefined;
|
||||
const head = hex[0], tail = hex.subarray(1); // first byte is prefix, rest is data
|
||||
const x = slcNum(tail, 0, fLen), len = hex.length; // next 32 bytes are x coordinate
|
||||
if (len === 33 && [0x02, 0x03].includes(head)) { // compressed points: 33b, start
|
||||
if (!fe(x))
|
||||
err('Point hex invalid: x not FE'); // with byte 0x02 or 0x03. Check if 0<x<P
|
||||
let y = sqrt(crv(x)); // x³ + ax + b is right side of equation
|
||||
const isYOdd = (y & 1n) === 1n; // y² is equivalent left-side. Calculate y²:
|
||||
const headOdd = (head & 1) === 1; // y = √y²; there are two solutions: y, -y
|
||||
if (headOdd !== isYOdd)
|
||||
y = mod(-y); // determine proper solution
|
||||
p = new Point(x, y, 1n); // create point
|
||||
} // Uncompressed points: 65b, start with 0x04
|
||||
if (len === 65 && head === 0x04)
|
||||
p = new Point(x, slcNum(tail, fLen, 2 * fLen), 1n);
|
||||
return p ? p.ok() : err('Point is not on curve'); // Verify the result
|
||||
}
|
||||
static fromPrivateKey(k) { return G.mul(toPriv(k)); } // Create point from a private key.
|
||||
get x() { return this.aff().x; } // .x, .y will call expensive toAffine:
|
||||
get y() { return this.aff().y; } // should be used with care.
|
||||
equals(other) {
|
||||
const { px: X1, py: Y1, pz: Z1 } = this;
|
||||
const { px: X2, py: Y2, pz: Z2 } = isPoint(other); // isPoint() checks class equality
|
||||
const X1Z2 = mod(X1 * Z2), X2Z1 = mod(X2 * Z1);
|
||||
const Y1Z2 = mod(Y1 * Z2), Y2Z1 = mod(Y2 * Z1);
|
||||
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
||||
}
|
||||
negate() { return new Point(this.px, mod(-this.py), this.pz); } // Flip point over y coord
|
||||
double() { return this.add(this); } // Point doubling: P+P, complete formula.
|
||||
add(other) {
|
||||
const { px: X1, py: Y1, pz: Z1 } = this; // free formula from Renes-Costello-Batina
|
||||
const { px: X2, py: Y2, pz: Z2 } = isPoint(other); // https://eprint.iacr.org/2015/1060, algo 1
|
||||
const { a, b } = CURVE; // Cost: 12M + 0S + 3*a + 3*b3 + 23add
|
||||
let X3 = 0n, Y3 = 0n, Z3 = 0n;
|
||||
const b3 = mod(b * 3n);
|
||||
let t0 = mod(X1 * X2), t1 = mod(Y1 * Y2), t2 = mod(Z1 * Z2), t3 = mod(X1 + Y1); // step 1
|
||||
let t4 = mod(X2 + Y2); // step 5
|
||||
t3 = mod(t3 * t4);
|
||||
t4 = mod(t0 + t1);
|
||||
t3 = mod(t3 - t4);
|
||||
t4 = mod(X1 + Z1);
|
||||
let t5 = mod(X2 + Z2); // step 10
|
||||
t4 = mod(t4 * t5);
|
||||
t5 = mod(t0 + t2);
|
||||
t4 = mod(t4 - t5);
|
||||
t5 = mod(Y1 + Z1);
|
||||
X3 = mod(Y2 + Z2); // step 15
|
||||
t5 = mod(t5 * X3);
|
||||
X3 = mod(t1 + t2);
|
||||
t5 = mod(t5 - X3);
|
||||
Z3 = mod(a * t4);
|
||||
X3 = mod(b3 * t2); // step 20
|
||||
Z3 = mod(X3 + Z3);
|
||||
X3 = mod(t1 - Z3);
|
||||
Z3 = mod(t1 + Z3);
|
||||
Y3 = mod(X3 * Z3);
|
||||
t1 = mod(t0 + t0); // step 25
|
||||
t1 = mod(t1 + t0);
|
||||
t2 = mod(a * t2);
|
||||
t4 = mod(b3 * t4);
|
||||
t1 = mod(t1 + t2);
|
||||
t2 = mod(t0 - t2); // step 30
|
||||
t2 = mod(a * t2);
|
||||
t4 = mod(t4 + t2);
|
||||
t0 = mod(t1 * t4);
|
||||
Y3 = mod(Y3 + t0);
|
||||
t0 = mod(t5 * t4); // step 35
|
||||
X3 = mod(t3 * X3);
|
||||
X3 = mod(X3 - t0);
|
||||
t0 = mod(t3 * t1);
|
||||
Z3 = mod(t5 * Z3);
|
||||
Z3 = mod(Z3 + t0); // step 40
|
||||
return new Point(X3, Y3, Z3);
|
||||
}
|
||||
mul(n, safe = true) {
|
||||
if (!safe && n === 0n)
|
||||
return I; // in unsafe mode, allow zero
|
||||
if (!ge(n))
|
||||
err('invalid scalar'); // must be 0 < n < CURVE.n
|
||||
if (this.equals(G))
|
||||
return wNAF(n).p; // use precomputes for base point
|
||||
let p = I, f = G; // init result point & fake point
|
||||
for (let d = this; n > 0n; d = d.double(), n >>= 1n) { // double-and-add ladder
|
||||
if (n & 1n)
|
||||
p = p.add(d); // if bit is present, add to point
|
||||
else if (safe)
|
||||
f = f.add(d); // if not, add to fake for timing safety
|
||||
}
|
||||
return p;
|
||||
}
|
||||
mulAddQUns(R, u1, u2) {
|
||||
return this.mul(u1, false).add(R.mul(u2, false)).ok(); // Unsafe: do NOT use for stuff related
|
||||
} // to private keys. Doesn't use Shamir trick
|
||||
toAffine() {
|
||||
const { px: x, py: y, pz: z } = this; // (x, y, z) ∋ (x=x/z, y=y/z)
|
||||
if (this.equals(I))
|
||||
return { x: 0n, y: 0n }; // fast-path for zero point
|
||||
if (z === 1n)
|
||||
return { x, y }; // if z is 1, pass affine coordinates as-is
|
||||
const iz = inv(z); // z^-1: invert z
|
||||
if (mod(z * iz) !== 1n)
|
||||
err('invalid inverse'); // (z * z^-1) must be 1, otherwise bad math
|
||||
return { x: mod(x * iz), y: mod(y * iz) }; // x = x*z^-1; y = y*z^-1
|
||||
}
|
||||
assertValidity() {
|
||||
const { x, y } = this.aff(); // convert to 2d xy affine point.
|
||||
if (!fe(x) || !fe(y))
|
||||
err('Point invalid: x or y'); // x and y must be in range 0 < n < P
|
||||
return mod(y * y) === crv(x) ? // y² = x³ + ax + b, must be equal
|
||||
this : err('Point invalid: not on curve');
|
||||
}
|
||||
multiply(n) { return this.mul(n); } // Aliases to compress code
|
||||
aff() { return this.toAffine(); }
|
||||
ok() { return this.assertValidity(); }
|
||||
toHex(isCompressed = true) {
|
||||
const { x, y } = this.aff(); // convert to 2d xy affine point
|
||||
const head = isCompressed ? ((y & 1n) === 0n ? '02' : '03') : '04'; // 0x02, 0x03, 0x04 prefix
|
||||
return head + n2h(x) + (isCompressed ? '' : n2h(y)); // prefix||x and ||y
|
||||
}
|
||||
toRawBytes(isCompressed = true) {
|
||||
return h2b(this.toHex(isCompressed)); // re-use toHex(), convert hex to bytes
|
||||
}
|
||||
}
|
||||
Point.BASE = new Point(Gx, Gy, 1n); // Generator / base point
|
||||
Point.ZERO = new Point(0n, 1n, 0n); // Identity / zero point
|
||||
const { BASE: G, ZERO: I } = Point; // Generator, identity points
|
||||
const padh = (n, pad) => n.toString(16).padStart(pad, '0');
|
||||
const b2h = (b) => Array.from(b).map(e => padh(e, 2)).join(''); // bytes to hex
|
||||
const h2b = (hex) => {
|
||||
const l = hex.length; // error if not string,
|
||||
if (!str(hex) || l % 2)
|
||||
err('hex invalid 1'); // or has odd length like 3, 5.
|
||||
const arr = u8n(l / 2); // create result array
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const j = i * 2;
|
||||
const h = hex.slice(j, j + 2); // hexByte. slice is faster than substr
|
||||
const b = Number.parseInt(h, 16); // byte, created from string part
|
||||
if (Number.isNaN(b) || b < 0)
|
||||
err('hex invalid 2'); // byte must be valid 0 <= byte < 256
|
||||
arr[i] = b;
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
const b2n = (b) => BigInt('0x' + (b2h(b) || '0')); // bytes to number
|
||||
const slcNum = (b, from, to) => b2n(b.slice(from, to)); // slice bytes num
|
||||
const n2b = (num) => {
|
||||
return big(num) && num >= 0n && num < B256 ? h2b(padh(num, 2 * fLen)) : err('bigint expected');
|
||||
};
|
||||
const n2h = (num) => b2h(n2b(num)); // number to 32b hex
|
||||
const concatB = (...arrs) => {
|
||||
const r = u8n(arrs.reduce((sum, a) => sum + au8(a).length, 0)); // create u8a of summed length
|
||||
let pad = 0; // walk through each array,
|
||||
arrs.forEach(a => { r.set(a, pad); pad += a.length; }); // ensure they have proper type
|
||||
return r;
|
||||
};
|
||||
const inv = (num, md = P) => {
|
||||
if (num === 0n || md <= 0n)
|
||||
err('no inverse n=' + num + ' mod=' + md); // no neg exponent for now
|
||||
let a = mod(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
|
||||
while (a !== 0n) { // uses euclidean gcd algorithm
|
||||
const q = b / a, r = b % a; // not constant-time
|
||||
const m = x - u * q, n = y - v * q;
|
||||
b = a, a = r, x = u, y = v, u = m, v = n;
|
||||
}
|
||||
return b === 1n ? mod(x, md) : err('no inverse'); // b is gcd at this point
|
||||
};
|
||||
const sqrt = (n) => {
|
||||
let r = 1n; // So, a special, fast case. Paper: "Square Roots from 1;24,51,10 to Dan Shanks".
|
||||
for (let num = n, e = (P + 1n) / 4n; e > 0n; e >>= 1n) { // powMod: modular exponentiation.
|
||||
if (e & 1n)
|
||||
r = (r * num) % P; // Uses exponentiation by squaring.
|
||||
num = (num * num) % P; // Not constant-time.
|
||||
}
|
||||
return mod(r * r) === n ? r : err('sqrt invalid'); // check if result is valid
|
||||
};
|
||||
const toPriv = (p) => {
|
||||
if (!big(p))
|
||||
p = b2n(toU8(p, fLen)); // convert to bigint when bytes
|
||||
return ge(p) ? p : err('private key out of range'); // check if bigint is in range
|
||||
};
|
||||
const moreThanHalfN = (n) => n > (N >> 1n); // if a number is bigger than CURVE.n/2
|
||||
function getPublicKey(privKey, isCompressed = true) {
|
||||
return Point.fromPrivateKey(privKey).toRawBytes(isCompressed); // 33b or 65b output
|
||||
}
|
||||
class Signature {
|
||||
constructor(r, s, recovery) {
|
||||
this.r = r;
|
||||
this.s = s;
|
||||
this.recovery = recovery;
|
||||
this.assertValidity(); // recovery bit is optional when
|
||||
} // constructed outside.
|
||||
static fromCompact(hex) {
|
||||
hex = toU8(hex, 64); // compact repr is (32b r)||(32b s)
|
||||
return new Signature(slcNum(hex, 0, fLen), slcNum(hex, fLen, 2 * fLen));
|
||||
}
|
||||
assertValidity() { return ge(this.r) && ge(this.s) ? this : err(); } // 0 < r or s < CURVE.n
|
||||
addRecoveryBit(rec) { return new Signature(this.r, this.s, rec); }
|
||||
hasHighS() { return moreThanHalfN(this.s); }
|
||||
recoverPublicKey(msgh) {
|
||||
const { r, s, recovery: rec } = this; // secg.org/sec1-v2.pdf 4.1.6
|
||||
if (![0, 1, 2, 3].includes(rec))
|
||||
err('recovery id invalid'); // check recovery id
|
||||
const h = bits2int_modN(toU8(msgh, 32)); // Truncate hash
|
||||
const radj = rec === 2 || rec === 3 ? r + N : r; // If rec was 2 or 3, q.x is bigger than n
|
||||
if (radj >= P)
|
||||
err('q.x invalid'); // ensure q.x is still a field element
|
||||
const head = (rec & 1) === 0 ? '02' : '03'; // head is 0x02 or 0x03
|
||||
const R = Point.fromHex(head + n2h(radj)); // concat head + hex repr of r
|
||||
const ir = inv(radj, N); // r^-1
|
||||
const u1 = mod(-h * ir, N); // -hr^-1
|
||||
const u2 = mod(s * ir, N); // sr^-1
|
||||
return G.mulAddQUns(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1)
|
||||
}
|
||||
toCompactRawBytes() { return h2b(this.toCompactHex()); } // Uint8Array 64b compact repr
|
||||
toCompactHex() { return n2h(this.r) + n2h(this.s); } // hex 64b compact repr
|
||||
}
|
||||
const bits2int = (bytes) => {
|
||||
const delta = bytes.length * 8 - 256; // RFC suggests optional truncating via bits2octets
|
||||
const num = b2n(bytes); // FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which
|
||||
return delta > 0 ? num >> BigInt(delta) : num; // matches bits2int. bits2int can produce res>N.
|
||||
};
|
||||
const bits2int_modN = (bytes) => {
|
||||
return mod(bits2int(bytes), N); // with 0: BAD for trunc as per RFC vectors
|
||||
};
|
||||
const i2o = (num) => n2b(num); // int to octets
|
||||
const cr = () => // We support: 1) browsers 2) node.js 19+ 3) deno, other envs with crypto
|
||||
typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;
|
||||
let _hmacSync; // Can be redefined by use in utils; built-ins don't provide it
|
||||
const optS = { lowS: true }; // opts for sign()
|
||||
const optV = { lowS: true }; // standard opts for verify()
|
||||
function prepSig(msgh, priv, opts = optS) {
|
||||
if (['der', 'recovered', 'canonical'].some(k => k in opts)) // Ban legacy options
|
||||
err('sign() legacy options not supported');
|
||||
let { lowS } = opts; // generates low-s sigs by default
|
||||
if (lowS == null)
|
||||
lowS = true; // RFC6979 3.2: we skip step A
|
||||
const h1i = bits2int_modN(toU8(msgh)); // msg bigint
|
||||
const h1o = i2o(h1i); // msg octets
|
||||
const d = toPriv(priv); // validate private key, convert to bigint
|
||||
const seed = [i2o(d), h1o]; // Step D of RFC6979 3.2
|
||||
let ent = opts.extraEntropy; // RFC6979 3.6: additional k' (optional)
|
||||
if (ent) { // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
|
||||
if (ent === true)
|
||||
ent = etc.randomBytes(fLen); // if true, use CSPRNG to generate data
|
||||
const e = toU8(ent); // convert Hex|Bytes to Bytes
|
||||
if (e.length !== fLen)
|
||||
err(); // Expected 32 bytes of extra data
|
||||
seed.push(e);
|
||||
}
|
||||
const m = h1i; // convert msg to bigint
|
||||
const k2sig = (kBytes) => {
|
||||
const k = bits2int(kBytes); // RFC6979 method.
|
||||
if (!ge(k))
|
||||
return; // Check 0 < k < CURVE.n
|
||||
const ik = inv(k, N); // k^-1 mod n, NOT mod P
|
||||
const q = G.mul(k).aff(); // q = Gk
|
||||
const r = mod(q.x, N); // r = q.x mod n
|
||||
if (r === 0n)
|
||||
return; // r=0 invalid
|
||||
const s = mod(ik * mod(m + mod(d * r, N), N), N); // s = k^-1(m + rd) mod n
|
||||
if (s === 0n)
|
||||
return; // s=0 invalid
|
||||
let normS = s; // normalized S
|
||||
let rec = (q.x === r ? 0 : 2) | Number(q.y & 1n); // recovery bit
|
||||
if (lowS && moreThanHalfN(s)) { // if lowS was passed, ensure s is always
|
||||
normS = mod(-s, N); // in the bottom half of CURVE.n
|
||||
rec ^= 1;
|
||||
}
|
||||
return new Signature(r, normS, rec); // use normS, not s
|
||||
};
|
||||
return { seed: concatB(...seed), k2sig };
|
||||
}
|
||||
function hmacDrbg(asynchronous) {
|
||||
let v = u8n(fLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
|
||||
let k = u8n(fLen); // Steps B, C of RFC6979 3.2: set hashLen, in our case always same
|
||||
let i = 0; // Iterations counter, will throw when over 1000
|
||||
const reset = () => { v.fill(1); k.fill(0); i = 0; };
|
||||
const _e = 'drbg: tried 1000 values';
|
||||
if (asynchronous) { // asynchronous=true
|
||||
const h = (...b) => etc.hmacSha256Async(k, v, ...b); // hmac(k)(v, ...values)
|
||||
const reseed = async (seed = u8n()) => {
|
||||
k = await h(u8n([0x00]), seed); // k = hmac(K || V || 0x00 || seed)
|
||||
v = await h(); // v = hmac(K || V)
|
||||
if (seed.length === 0)
|
||||
return;
|
||||
k = await h(u8n([0x01]), seed); // k = hmac(K || V || 0x01 || seed)
|
||||
v = await h(); // v = hmac(K || V)
|
||||
};
|
||||
const gen = async () => {
|
||||
if (i++ >= 1000)
|
||||
err(_e);
|
||||
v = await h(); // v = hmac(K || V)
|
||||
return v;
|
||||
};
|
||||
return async (seed, pred) => {
|
||||
reset(); // the returned fn, don't, it's: 1. slower (JIT). 2. unsafe (async race conditions)
|
||||
await reseed(seed); // Steps D-G
|
||||
let res = undefined; // Step H: grind until k is in [1..n-1]
|
||||
while (!(res = pred(await gen())))
|
||||
await reseed(); // test predicate until it returns ok
|
||||
reset();
|
||||
return res;
|
||||
};
|
||||
}
|
||||
else {
|
||||
const h = (...b) => {
|
||||
const f = _hmacSync;
|
||||
if (!f)
|
||||
err('etc.hmacSha256Sync not set');
|
||||
return f(k, v, ...b); // hmac(k)(v, ...values)
|
||||
};
|
||||
const reseed = (seed = u8n()) => {
|
||||
k = h(u8n([0x00]), seed); // k = hmac(k || v || 0x00 || seed)
|
||||
v = h(); // v = hmac(k || v)
|
||||
if (seed.length === 0)
|
||||
return;
|
||||
k = h(u8n([0x01]), seed); // k = hmac(k || v || 0x01 || seed)
|
||||
v = h(); // v = hmac(k || v)
|
||||
};
|
||||
const gen = () => {
|
||||
if (i++ >= 1000)
|
||||
err(_e);
|
||||
v = h(); // v = hmac(k || v)
|
||||
return v;
|
||||
};
|
||||
return (seed, pred) => {
|
||||
reset();
|
||||
reseed(seed); // Steps D-G
|
||||
let res = undefined; // Step H: grind until k is in [1..n-1]
|
||||
while (!(res = pred(gen())))
|
||||
reseed(); // test predicate until it returns ok
|
||||
reset();
|
||||
return res;
|
||||
};
|
||||
}
|
||||
}
|
||||
// ECDSA signature generation. via secg.org/sec1-v2.pdf 4.1.2 + RFC6979 deterministic k
|
||||
async function signAsync(msgh, priv, opts = optS) {
|
||||
const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg
|
||||
return hmacDrbg(true)(seed, k2sig); // Re-run hmac-drbg until k2sig returns ok
|
||||
}
|
||||
function sign(msgh, priv, opts = optS) {
|
||||
const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg
|
||||
return hmacDrbg(false)(seed, k2sig); // Re-run hmac-drbg until k2sig returns ok
|
||||
}
|
||||
function verify(sig, msgh, pub, opts = optV) {
|
||||
let { lowS } = opts; // ECDSA signature verification
|
||||
if (lowS == null)
|
||||
lowS = true; // Default lowS=true
|
||||
if ('strict' in opts)
|
||||
err('verify() legacy options not supported'); // legacy param
|
||||
let sig_, h, P; // secg.org/sec1-v2.pdf 4.1.4
|
||||
const rs = sig && typeof sig === 'object' && 'r' in sig; // Previous ver supported DER sigs. We
|
||||
if (!rs && (toU8(sig).length !== 2 * fLen)) // throw error when DER is suspected now.
|
||||
err('signature must be 64 bytes');
|
||||
try {
|
||||
sig_ = rs ? new Signature(sig.r, sig.s).assertValidity() : Signature.fromCompact(sig);
|
||||
h = bits2int_modN(toU8(msgh, fLen)); // Truncate hash
|
||||
P = pub instanceof Point ? pub.ok() : Point.fromHex(pub); // Validate public key
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
} // Check sig for validity in both cases
|
||||
if (!sig_)
|
||||
return false;
|
||||
const { r, s } = sig_;
|
||||
if (lowS && moreThanHalfN(s))
|
||||
return false; // lowS bans sig.s >= CURVE.n/2
|
||||
let R;
|
||||
try {
|
||||
const is = inv(s, N); // s^-1
|
||||
const u1 = mod(h * is, N); // u1 = hs^-1 mod n
|
||||
const u2 = mod(r * is, N); // u2 = rs^-1 mod n
|
||||
R = G.mulAddQUns(P, u1, u2).aff(); // R = u1⋅G + u2⋅P
|
||||
}
|
||||
catch (error) {
|
||||
return false;
|
||||
}
|
||||
if (!R)
|
||||
return false; // stop if R is identity / zero point
|
||||
const v = mod(R.x, N); // <== The weird ECDSA part. R.x must be in N's field, not P's
|
||||
return v === r; // mod(R.x, n) == r
|
||||
}
|
||||
function getSharedSecret(privA, pubB, isCompressed = true) {
|
||||
return Point.fromHex(pubB).mul(toPriv(privA)).toRawBytes(isCompressed); // ECDH
|
||||
}
|
||||
function hashToPrivateKey(hash) {
|
||||
hash = toU8(hash); // produces private keys with modulo bias
|
||||
const minLen = fLen + 8; // being neglible.
|
||||
if (hash.length < minLen || hash.length > 1024)
|
||||
err('expected proper params');
|
||||
const num = mod(b2n(hash), N - 1n) + 1n; // takes at least n+8 bytes
|
||||
return n2b(num);
|
||||
}
|
||||
const etc = {
|
||||
hexToBytes: h2b, bytesToHex: b2h,
|
||||
concatBytes: concatB, bytesToNumberBE: b2n, numberToBytesBE: n2b,
|
||||
mod, invert: inv,
|
||||
hmacSha256Async: async (key, ...msgs) => {
|
||||
const crypto = cr(); // HMAC-SHA256 async. No sync built-in!
|
||||
if (!crypto)
|
||||
return err('etc.hmacSha256Async not set'); // Uses webcrypto: native cryptography.
|
||||
const s = crypto.subtle;
|
||||
const k = await s.importKey('raw', key, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']);
|
||||
return u8n(await s.sign('HMAC', k, concatB(...msgs)));
|
||||
},
|
||||
hmacSha256Sync: _hmacSync,
|
||||
hashToPrivateKey,
|
||||
randomBytes: (len) => {
|
||||
const crypto = cr(); // Can be shimmed in node.js <= 18 to prevent error:
|
||||
// import { webcrypto } from 'node:crypto';
|
||||
// if (!globalThis.crypto) globalThis.crypto = webcrypto;
|
||||
if (!crypto)
|
||||
err('crypto.getRandomValues must be defined');
|
||||
return crypto.getRandomValues(u8n(len));
|
||||
},
|
||||
};
|
||||
const utils = {
|
||||
normPrivateKeyToScalar: toPriv,
|
||||
isValidPrivateKey: (key) => { try {
|
||||
return !!toPriv(key);
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
} },
|
||||
randomPrivateKey: () => hashToPrivateKey(etc.randomBytes(fLen + 8)),
|
||||
precompute(w = 8, p = G) { p.multiply(3n); return p; }, // no-op
|
||||
};
|
||||
Object.defineProperties(etc, { hmacSha256Sync: {
|
||||
configurable: false, get() { return _hmacSync; }, set(f) { if (!_hmacSync)
|
||||
_hmacSync = f; },
|
||||
} });
|
||||
const W = 8; // Precomputes-related code. W = window size
|
||||
const precompute = () => {
|
||||
const points = []; // 10x sign(), 2x verify(). To achieve this,
|
||||
const windows = 256 / W + 1; // app needs to spend 40ms+ to calculate
|
||||
let p = G, b = p; // a lot of points related to base point G.
|
||||
for (let w = 0; w < windows; w++) { // Points are stored in array and used
|
||||
b = p; // any time Gx multiplication is done.
|
||||
points.push(b); // They consume 16-32 MiB of RAM.
|
||||
for (let i = 1; i < 2 ** (W - 1); i++) {
|
||||
b = b.add(p);
|
||||
points.push(b);
|
||||
}
|
||||
p = b.double(); // Precomputes don't speed-up getSharedKey,
|
||||
} // which multiplies user point by scalar,
|
||||
return points; // when precomputes are using base point
|
||||
};
|
||||
const wNAF = (n) => {
|
||||
// Compared to other point mult methods,
|
||||
const comp = Gpows || (Gpows = precompute()); // stores 2x less points using subtraction
|
||||
const neg = (cnd, p) => { let n = p.negate(); return cnd ? n : p; }; // negate
|
||||
let p = I, f = G; // f must be G, or could become I in the end
|
||||
const windows = 1 + 256 / W; // W=8 17 windows
|
||||
const wsize = 2 ** (W - 1); // W=8 128 window size
|
||||
const mask = BigInt(2 ** W - 1); // W=8 will create mask 0b11111111
|
||||
const maxNum = 2 ** W; // W=8 256
|
||||
const shiftBy = BigInt(W); // W=8 8
|
||||
for (let w = 0; w < windows; w++) {
|
||||
const off = w * wsize;
|
||||
let wbits = Number(n & mask); // extract W bits.
|
||||
n >>= shiftBy; // shift number by W bits.
|
||||
if (wbits > wsize) {
|
||||
wbits -= maxNum;
|
||||
n += 1n;
|
||||
} // split if bits > max: +224 => 256-32
|
||||
const off1 = off, off2 = off + Math.abs(wbits) - 1; // offsets, evaluate both
|
||||
const cnd1 = w % 2 !== 0, cnd2 = wbits < 0; // conditions, evaluate both
|
||||
if (wbits === 0) {
|
||||
f = f.add(neg(cnd1, comp[off1])); // bits are 0: add garbage to fake point
|
||||
}
|
||||
else { // ^ can't add off2, off2 = I
|
||||
p = p.add(neg(cnd2, comp[off2])); // bits are 1: add to result point
|
||||
}
|
||||
}
|
||||
return { p, f }; // return both real and fake points for JIT
|
||||
}; // !! you can disable precomputes by commenting-out call of the wNAF() inside Point#mul()
|
||||
export { getPublicKey, sign, signAsync, verify, CURVE, // Remove the export to easily use in REPL
|
||||
getSharedSecret, etc, utils, Point as ProjectivePoint, Signature }; // envs like browser console
|
||||
+431
@@ -0,0 +1,431 @@
|
||||
/*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
|
||||
const B256 = 2n ** 256n; // secp256k1 is short weierstrass curve
|
||||
const P = B256 - 0x1000003d1n; // curve's field prime
|
||||
const N = B256 - 0x14551231950b75fc4402da1732fc9bebfn; // curve (group) order
|
||||
const Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n; // base point x
|
||||
const Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n; // base point y
|
||||
const CURVE = {p: P, n: N, a: 0n, b: 7n, Gx, Gy};// exported variables incl. a, b
|
||||
const fLen = 32; // field / group byte length
|
||||
type Bytes = Uint8Array; type Hex = Bytes | string; type PrivKey = Hex | bigint;
|
||||
const crv = (x: bigint) => mod(mod(x * x) * x + CURVE.b); // x³ + ax + b weierstrass formula; no a
|
||||
const err = (m = ''): never => { throw new Error(m); }; // error helper, messes-up stack trace
|
||||
const big = (n: unknown): n is bigint => typeof n === 'bigint'; // is big integer
|
||||
const str = (s: unknown): s is string => typeof s === 'string'; // is string
|
||||
const fe = (n: bigint) => big(n) && 0n < n && n < P; // is field element (invertible)
|
||||
const ge = (n: bigint) => big(n) && 0n < n && n < N; // is group element
|
||||
const au8 = (a: unknown, l?: number): Bytes => // is Uint8Array (of specific length)
|
||||
!(a instanceof Uint8Array) || (typeof l === 'number' && l > 0 && a.length !== l) ?
|
||||
err('Uint8Array expected') : a;
|
||||
const u8n = (data?: any) => new Uint8Array(data); // creates Uint8Array
|
||||
const toU8 = (a: Hex, len?: number) => au8(str(a) ? h2b(a) : u8n(a), len); // norm(hex/u8a) to u8a
|
||||
const mod = (a: bigint, b = P) => { let r = a % b; return r >= 0n ? r : b + r; }; // mod division
|
||||
const isPoint = (p: unknown) => (p instanceof Point ? p : err('Point expected')); // is 3d point
|
||||
let Gpows: Point[] | undefined = undefined; // precomputes for base point G
|
||||
interface AffinePoint { x: bigint, y: bigint } // Point in 2d xy affine coordinates
|
||||
class Point { // Point in 3d xyz projective coordinates
|
||||
constructor(readonly px: bigint, readonly py: bigint, readonly pz: bigint) {} //3d=less inversions
|
||||
static readonly BASE = new Point(Gx, Gy, 1n); // Generator / base point
|
||||
static readonly ZERO = new Point(0n, 1n, 0n); // Identity / zero point
|
||||
static fromAffine(p: AffinePoint) { return new Point(p.x, p.y, 1n); }
|
||||
static fromHex(hex: Hex): Point { // Convert Uint8Array or hex string to Point
|
||||
hex = toU8(hex); // convert hex string to Uint8Array
|
||||
let p: Point | undefined = undefined;
|
||||
const head = hex[0], tail = hex.subarray(1); // first byte is prefix, rest is data
|
||||
const x = slcNum(tail, 0, fLen), len = hex.length; // next 32 bytes are x coordinate
|
||||
if (len === 33 && [0x02, 0x03].includes(head)) { // compressed points: 33b, start
|
||||
if (!fe(x)) err('Point hex invalid: x not FE'); // with byte 0x02 or 0x03. Check if 0<x<P
|
||||
let y = sqrt(crv(x)); // x³ + ax + b is right side of equation
|
||||
const isYOdd = (y & 1n) === 1n; // y² is equivalent left-side. Calculate y²:
|
||||
const headOdd = (head & 1) === 1; // y = √y²; there are two solutions: y, -y
|
||||
if (headOdd !== isYOdd) y = mod(-y); // determine proper solution
|
||||
p = new Point(x, y, 1n); // create point
|
||||
} // Uncompressed points: 65b, start with 0x04
|
||||
if (len === 65 && head === 0x04) p = new Point(x, slcNum(tail, fLen, 2 * fLen), 1n);
|
||||
return p ? p.ok() : err('Point is not on curve'); // Verify the result
|
||||
}
|
||||
static fromPrivateKey(k: PrivKey) { return G.mul(toPriv(k)); } // Create point from a private key.
|
||||
get x() { return this.aff().x; } // .x, .y will call expensive toAffine:
|
||||
get y() { return this.aff().y; } // should be used with care.
|
||||
equals(other: Point): boolean { // Equality check: compare points
|
||||
const { px: X1, py: Y1, pz: Z1 } = this;
|
||||
const { px: X2, py: Y2, pz: Z2 } = isPoint(other); // isPoint() checks class equality
|
||||
const X1Z2 = mod(X1 * Z2), X2Z1 = mod(X2 * Z1);
|
||||
const Y1Z2 = mod(Y1 * Z2), Y2Z1 = mod(Y2 * Z1);
|
||||
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
||||
}
|
||||
negate() { return new Point(this.px, mod(-this.py), this.pz); } // Flip point over y coord
|
||||
double() { return this.add(this); } // Point doubling: P+P, complete formula.
|
||||
add(other: Point) { // Point addition: P+Q, complete, exception
|
||||
const { px: X1, py: Y1, pz: Z1 } = this; // free formula from Renes-Costello-Batina
|
||||
const { px: X2, py: Y2, pz: Z2 } = isPoint(other); // https://eprint.iacr.org/2015/1060, algo 1
|
||||
const { a, b } = CURVE; // Cost: 12M + 0S + 3*a + 3*b3 + 23add
|
||||
let X3 = 0n, Y3 = 0n, Z3 = 0n;
|
||||
const b3 = mod(b * 3n);
|
||||
let t0 = mod(X1 * X2), t1 = mod(Y1 * Y2), t2 = mod(Z1 * Z2), t3 = mod(X1 + Y1); // step 1
|
||||
let t4 = mod(X2 + Y2); // step 5
|
||||
t3 = mod(t3 * t4); t4 = mod(t0 + t1); t3 = mod(t3 - t4); t4 = mod(X1 + Z1);
|
||||
let t5 = mod(X2 + Z2); // step 10
|
||||
t4 = mod(t4 * t5); t5 = mod(t0 + t2); t4 = mod(t4 - t5); t5 = mod(Y1 + Z1);
|
||||
X3 = mod(Y2 + Z2); // step 15
|
||||
t5 = mod(t5 * X3); X3 = mod(t1 + t2); t5 = mod(t5 - X3); Z3 = mod(a * t4);
|
||||
X3 = mod(b3 * t2); // step 20
|
||||
Z3 = mod(X3 + Z3); X3 = mod(t1 - Z3); Z3 = mod(t1 + Z3); Y3 = mod(X3 * Z3);
|
||||
t1 = mod(t0 + t0); // step 25
|
||||
t1 = mod(t1 + t0); t2 = mod(a * t2); t4 = mod(b3 * t4); t1 = mod(t1 + t2);
|
||||
t2 = mod(t0 - t2); // step 30
|
||||
t2 = mod(a * t2); t4 = mod(t4 + t2); t0 = mod(t1 * t4); Y3 = mod(Y3 + t0);
|
||||
t0 = mod(t5 * t4); // step 35
|
||||
X3 = mod(t3 * X3); X3 = mod(X3 - t0); t0 = mod(t3 * t1); Z3 = mod(t5 * Z3);
|
||||
Z3 = mod(Z3 + t0); // step 40
|
||||
return new Point(X3, Y3, Z3);
|
||||
}
|
||||
mul(n: bigint, safe = true) { // Point scalar multiplication.
|
||||
if (!safe && n === 0n) return I; // in unsafe mode, allow zero
|
||||
if (!ge(n)) err('invalid scalar'); // must be 0 < n < CURVE.n
|
||||
if (this.equals(G)) return wNAF(n).p; // use precomputes for base point
|
||||
let p = I, f = G; // init result point & fake point
|
||||
for (let d: Point = this; n > 0n; d = d.double(), n >>= 1n) { // double-and-add ladder
|
||||
if (n & 1n) p = p.add(d); // if bit is present, add to point
|
||||
else if (safe) f = f.add(d); // if not, add to fake for timing safety
|
||||
}
|
||||
return p;
|
||||
}
|
||||
mulAddQUns(R: Point, u1: bigint, u2: bigint) { // Double scalar mult. Q = u1⋅G + u2⋅R.
|
||||
return this.mul(u1, false).add(R.mul(u2, false)).ok(); // Unsafe: do NOT use for stuff related
|
||||
} // to private keys. Doesn't use Shamir trick
|
||||
toAffine(): AffinePoint { // Convert point to 2d xy affine point.
|
||||
const { px: x, py: y, pz: z } = this; // (x, y, z) ∋ (x=x/z, y=y/z)
|
||||
if (this.equals(I)) return { x: 0n, y: 0n }; // fast-path for zero point
|
||||
if (z === 1n) return { x, y }; // if z is 1, pass affine coordinates as-is
|
||||
const iz = inv(z); // z^-1: invert z
|
||||
if (mod(z * iz) !== 1n) err('invalid inverse'); // (z * z^-1) must be 1, otherwise bad math
|
||||
return { x: mod(x * iz), y: mod(y * iz) }; // x = x*z^-1; y = y*z^-1
|
||||
}
|
||||
assertValidity(): Point { // Checks if the point is valid and on-curve
|
||||
const { x, y } = this.aff(); // convert to 2d xy affine point.
|
||||
if (!fe(x) || !fe(y)) err('Point invalid: x or y'); // x and y must be in range 0 < n < P
|
||||
return mod(y * y) === crv(x) ? // y² = x³ + ax + b, must be equal
|
||||
this : err('Point invalid: not on curve');
|
||||
}
|
||||
multiply(n: bigint) { return this.mul(n); } // Aliases to compress code
|
||||
aff() { return this.toAffine(); }
|
||||
ok() { return this.assertValidity(); }
|
||||
toHex(isCompressed = true) { // Encode point to hex string.
|
||||
const { x, y } = this.aff(); // convert to 2d xy affine point
|
||||
const head = isCompressed ? ((y & 1n) === 0n ? '02' : '03') : '04'; // 0x02, 0x03, 0x04 prefix
|
||||
return head + n2h(x) + (isCompressed ? '' : n2h(y));// prefix||x and ||y
|
||||
}
|
||||
toRawBytes(isCompressed = true) { // Encode point to Uint8Array.
|
||||
return h2b(this.toHex(isCompressed)); // re-use toHex(), convert hex to bytes
|
||||
}
|
||||
}
|
||||
const { BASE: G, ZERO: I } = Point; // Generator, identity points
|
||||
const padh = (n: number | bigint, pad: number) => n.toString(16).padStart(pad, '0');
|
||||
const b2h = (b: Bytes): string => Array.from(b).map(e => padh(e, 2)).join(''); // bytes to hex
|
||||
const h2b = (hex: string): Bytes => { // hex to bytes
|
||||
const l = hex.length; // error if not string,
|
||||
if (!str(hex) || l % 2) err('hex invalid 1'); // or has odd length like 3, 5.
|
||||
const arr = u8n(l / 2); // create result array
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const j = i * 2;
|
||||
const h = hex.slice(j, j + 2); // hexByte. slice is faster than substr
|
||||
const b = Number.parseInt(h, 16); // byte, created from string part
|
||||
if (Number.isNaN(b) || b < 0) err('hex invalid 2'); // byte must be valid 0 <= byte < 256
|
||||
arr[i] = b;
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
const b2n = (b: Bytes): bigint => BigInt('0x' + (b2h(b) || '0')); // bytes to number
|
||||
const slcNum = (b: Bytes, from: number, to: number) => b2n(b.slice(from, to)); // slice bytes num
|
||||
const n2b = (num: bigint): Bytes => { // number to 32bytes. mustbe 0 <= num < B256
|
||||
return big(num) && num >= 0n && num < B256 ? h2b(padh(num, 2 * fLen)) : err('bigint expected');
|
||||
};
|
||||
const n2h = (num: bigint): string => b2h(n2b(num)); // number to 32b hex
|
||||
const concatB = (...arrs: Bytes[]) => { // concatenate Uint8Array-s
|
||||
const r = u8n(arrs.reduce((sum, a) => sum + au8(a).length, 0)); // create u8a of summed length
|
||||
let pad = 0; // walk through each array,
|
||||
arrs.forEach(a => {r.set(a, pad); pad += a.length}); // ensure they have proper type
|
||||
return r;
|
||||
};
|
||||
const inv = (num: bigint, md = P): bigint => { // modular inversion
|
||||
if (num === 0n || md <= 0n) err('no inverse n=' + num + ' mod=' + md); // no neg exponent for now
|
||||
let a = mod(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
|
||||
while (a !== 0n) { // uses euclidean gcd algorithm
|
||||
const q = b / a, r = b % a; // not constant-time
|
||||
const m = x - u * q, n = y - v * q;
|
||||
b = a, a = r, x = u, y = v, u = m, v = n;
|
||||
}
|
||||
return b === 1n ? mod(x, md) : err('no inverse'); // b is gcd at this point
|
||||
};
|
||||
const sqrt = (n: bigint) => { // √n = n^((p+1)/4) for fields p = 3 mod 4
|
||||
let r = 1n; // So, a special, fast case. Paper: "Square Roots from 1;24,51,10 to Dan Shanks".
|
||||
for (let num = n, e = (P + 1n) / 4n; e > 0n; e >>= 1n) { // powMod: modular exponentiation.
|
||||
if (e & 1n) r = (r * num) % P; // Uses exponentiation by squaring.
|
||||
num = (num * num) % P; // Not constant-time.
|
||||
}
|
||||
return mod(r * r) === n ? r : err('sqrt invalid'); // check if result is valid
|
||||
};
|
||||
const toPriv = (p: PrivKey): bigint => { // normalize private key to bigint
|
||||
if (!big(p)) p = b2n(toU8(p, fLen)); // convert to bigint when bytes
|
||||
return ge(p) ? p : err('private key out of range'); // check if bigint is in range
|
||||
};
|
||||
const moreThanHalfN = (n: bigint): boolean => n > (N >> 1n) // if a number is bigger than CURVE.n/2
|
||||
function getPublicKey(privKey: PrivKey, isCompressed = true) { // Make public key from priv
|
||||
return Point.fromPrivateKey(privKey).toRawBytes(isCompressed); // 33b or 65b output
|
||||
}
|
||||
class Signature { // ECDSA Signature class
|
||||
constructor(readonly r: bigint, readonly s: bigint, readonly recovery?: number) {
|
||||
this.assertValidity(); // recovery bit is optional when
|
||||
} // constructed outside.
|
||||
static fromCompact(hex: Hex) { // create signature from 64b compact repr
|
||||
hex = toU8(hex, 64); // compact repr is (32b r)||(32b s)
|
||||
return new Signature(slcNum(hex, 0, fLen), slcNum(hex, fLen, 2 * fLen));
|
||||
}
|
||||
assertValidity() { return ge(this.r) && ge(this.s) ? this : err(); } // 0 < r or s < CURVE.n
|
||||
addRecoveryBit(rec: number) { return new Signature(this.r, this.s, rec); }
|
||||
hasHighS() { return moreThanHalfN(this.s); }
|
||||
recoverPublicKey(msgh: Hex): Point { // ECDSA public key recovery
|
||||
const { r, s, recovery: rec } = this; // secg.org/sec1-v2.pdf 4.1.6
|
||||
if (![0, 1, 2, 3].includes(rec!)) err('recovery id invalid'); // check recovery id
|
||||
const h = bits2int_modN(toU8(msgh, 32)); // Truncate hash
|
||||
const radj = rec === 2 || rec === 3 ? r + N : r; // If rec was 2 or 3, q.x is bigger than n
|
||||
if (radj >= P) err('q.x invalid'); // ensure q.x is still a field element
|
||||
const head = (rec! & 1) === 0 ? '02' : '03'; // head is 0x02 or 0x03
|
||||
const R = Point.fromHex(head + n2h(radj)); // concat head + hex repr of r
|
||||
const ir = inv(radj, N); // r^-1
|
||||
const u1 = mod(-h * ir, N); // -hr^-1
|
||||
const u2 = mod(s * ir, N); // sr^-1
|
||||
return G.mulAddQUns(R, u1, u2); // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1)
|
||||
}
|
||||
toCompactRawBytes() { return h2b(this.toCompactHex()); } // Uint8Array 64b compact repr
|
||||
toCompactHex() { return n2h(this.r) + n2h(this.s); } // hex 64b compact repr
|
||||
}
|
||||
const bits2int = (bytes: Uint8Array): bigint => { // RFC6979: ensure ECDSA msg is X bytes.
|
||||
const delta = bytes.length * 8 - 256; // RFC suggests optional truncating via bits2octets
|
||||
const num = b2n(bytes); // FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which
|
||||
return delta > 0 ? num >> BigInt(delta) : num; // matches bits2int. bits2int can produce res>N.
|
||||
};
|
||||
const bits2int_modN = (bytes: Uint8Array): bigint => { // int2octets can't be used; pads small msgs
|
||||
return mod(bits2int(bytes), N); // with 0: BAD for trunc as per RFC vectors
|
||||
};
|
||||
const i2o = (num: bigint): Bytes => n2b(num); // int to octets
|
||||
declare const globalThis: Record<string, any> | undefined; // Typescript symbol present in browsers
|
||||
const cr = () => // We support: 1) browsers 2) node.js 19+ 3) deno, other envs with crypto
|
||||
typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;
|
||||
type HmacFnSync = undefined | ((key: Bytes, ...msgs: Bytes[]) => Bytes);
|
||||
let _hmacSync: HmacFnSync; // Can be redefined by use in utils; built-ins don't provide it
|
||||
const optS: { lowS?: boolean; extraEntropy?: boolean | Hex; } = { lowS: true }; // opts for sign()
|
||||
const optV: { lowS?: boolean } = { lowS: true }; // standard opts for verify()
|
||||
type BC = { seed: Bytes, k2sig : (kb: Bytes) => Signature | undefined }; // Bytes+predicate checker
|
||||
function prepSig(msgh: Hex, priv: Hex, opts = optS): BC { // prepare for RFC6979 sig generation
|
||||
if (['der', 'recovered', 'canonical'].some(k => k in opts)) // Ban legacy options
|
||||
err('sign() legacy options not supported');
|
||||
let { lowS } = opts; // generates low-s sigs by default
|
||||
if (lowS == null) lowS = true; // RFC6979 3.2: we skip step A
|
||||
const h1i = bits2int_modN(toU8(msgh)); // msg bigint
|
||||
const h1o = i2o(h1i); // msg octets
|
||||
const d = toPriv(priv); // validate private key, convert to bigint
|
||||
const seed = [i2o(d), h1o]; // Step D of RFC6979 3.2
|
||||
let ent = opts.extraEntropy; // RFC6979 3.6: additional k' (optional)
|
||||
if (ent) { // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
|
||||
if (ent === true) ent = etc.randomBytes(fLen); // if true, use CSPRNG to generate data
|
||||
const e = toU8(ent); // convert Hex|Bytes to Bytes
|
||||
if (e.length !== fLen) err(); // Expected 32 bytes of extra data
|
||||
seed.push(e);
|
||||
}
|
||||
const m = h1i; // convert msg to bigint
|
||||
const k2sig = (kBytes: Bytes): Signature | undefined => { // Transform k into Signature.
|
||||
const k = bits2int(kBytes); // RFC6979 method.
|
||||
if (!ge(k)) return; // Check 0 < k < CURVE.n
|
||||
const ik = inv(k, N); // k^-1 mod n, NOT mod P
|
||||
const q = G.mul(k).aff(); // q = Gk
|
||||
const r = mod(q.x, N); // r = q.x mod n
|
||||
if (r === 0n) return; // r=0 invalid
|
||||
const s = mod(ik * mod(m + mod(d * r, N), N), N); // s = k^-1(m + rd) mod n
|
||||
if (s === 0n) return; // s=0 invalid
|
||||
let normS = s; // normalized S
|
||||
let rec = (q.x === r ? 0 : 2) | Number(q.y & 1n); // recovery bit
|
||||
if (lowS && moreThanHalfN(s)) { // if lowS was passed, ensure s is always
|
||||
normS = mod(-s, N); // in the bottom half of CURVE.n
|
||||
rec ^= 1;
|
||||
}
|
||||
return new Signature(r, normS, rec); // use normS, not s
|
||||
};
|
||||
return { seed: concatB(...seed), k2sig }
|
||||
}
|
||||
type Pred<T> = (v: Uint8Array) => T | undefined;
|
||||
function hmacDrbg<T>(asynchronous: true): (seed: Bytes, predicate: Pred<T>) => Promise<T>;
|
||||
function hmacDrbg<T>(asynchronous: false): (seed: Bytes, predicate: Pred<T>) => T;
|
||||
function hmacDrbg<T>(asynchronous: boolean) { // HMAC-DRBG async
|
||||
let v = u8n(fLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
|
||||
let k = u8n(fLen); // Steps B, C of RFC6979 3.2: set hashLen, in our case always same
|
||||
let i = 0; // Iterations counter, will throw when over 1000
|
||||
const reset = () => { v.fill(1); k.fill(0); i = 0; };
|
||||
const _e = 'drbg: tried 1000 values';
|
||||
if (asynchronous) { // asynchronous=true
|
||||
const h = (...b: Bytes[]) => etc.hmacSha256Async(k, v, ...b); // hmac(k)(v, ...values)
|
||||
const reseed = async (seed = u8n()) => { // HMAC-DRBG reseed() function. Steps D-G
|
||||
k = await h(u8n([0x00]), seed); // k = hmac(K || V || 0x00 || seed)
|
||||
v = await h(); // v = hmac(K || V)
|
||||
if (seed.length === 0) return;
|
||||
k = await h(u8n([0x01]), seed); // k = hmac(K || V || 0x01 || seed)
|
||||
v = await h(); // v = hmac(K || V)
|
||||
};
|
||||
const gen = async () => { // HMAC-DRBG generate() function
|
||||
if (i++ >= 1000) err(_e);
|
||||
v = await h(); // v = hmac(K || V)
|
||||
return v;
|
||||
};
|
||||
return async (seed: Bytes, pred: Pred<T>): Promise<T> => { // Even though it feels safe to reuse
|
||||
reset(); // the returned fn, don't, it's: 1. slower (JIT). 2. unsafe (async race conditions)
|
||||
await reseed(seed); // Steps D-G
|
||||
let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1]
|
||||
while (!(res = pred(await gen()))) await reseed();// test predicate until it returns ok
|
||||
reset();
|
||||
return res!;
|
||||
};
|
||||
} else {
|
||||
const h = (...b: Bytes[]) => { // asynchronous=false; same, but synchronous
|
||||
const f = _hmacSync;
|
||||
if (!f) err('etc.hmacSha256Sync not set');
|
||||
return f!(k, v, ...b); // hmac(k)(v, ...values)
|
||||
};
|
||||
const reseed = (seed = u8n()) => { // HMAC-DRBG reseed() function. Steps D-G
|
||||
k = h(u8n([0x00]), seed); // k = hmac(k || v || 0x00 || seed)
|
||||
v = h(); // v = hmac(k || v)
|
||||
if (seed.length === 0) return;
|
||||
k = h(u8n([0x01]), seed); // k = hmac(k || v || 0x01 || seed)
|
||||
v = h(); // v = hmac(k || v)
|
||||
};
|
||||
const gen = () => { // HMAC-DRBG generate() function
|
||||
if (i++ >= 1000) err(_e);
|
||||
v = h(); // v = hmac(k || v)
|
||||
return v;
|
||||
};
|
||||
return (seed: Bytes, pred: Pred<T>): T => {
|
||||
reset();
|
||||
reseed(seed); // Steps D-G
|
||||
let res: T | undefined = undefined; // Step H: grind until k is in [1..n-1]
|
||||
while (!(res = pred(gen()))) reseed(); // test predicate until it returns ok
|
||||
reset();
|
||||
return res!;
|
||||
};
|
||||
}
|
||||
}
|
||||
// ECDSA signature generation. via secg.org/sec1-v2.pdf 4.1.2 + RFC6979 deterministic k
|
||||
async function signAsync(msgh: Hex, priv: Hex, opts = optS): Promise<Signature> {
|
||||
const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg
|
||||
return hmacDrbg<Signature>(true)(seed, k2sig); // Re-run hmac-drbg until k2sig returns ok
|
||||
}
|
||||
function sign(msgh: Hex, priv: Hex, opts = optS): Signature {
|
||||
const { seed, k2sig } = prepSig(msgh, priv, opts); // Extract arguments for hmac-drbg
|
||||
return hmacDrbg<Signature>(false)(seed, k2sig); // Re-run hmac-drbg until k2sig returns ok
|
||||
}
|
||||
type SigLike = { r: bigint, s: bigint };
|
||||
function verify(sig: Hex | SigLike, msgh: Hex, pub: Hex, opts = optV): boolean {
|
||||
let { lowS } = opts; // ECDSA signature verification
|
||||
if (lowS == null) lowS = true; // Default lowS=true
|
||||
if ('strict' in opts) err('verify() legacy options not supported'); // legacy param
|
||||
let sig_: Signature, h: bigint, P: Point; // secg.org/sec1-v2.pdf 4.1.4
|
||||
const rs = sig && typeof sig === 'object' && 'r' in sig; // Previous ver supported DER sigs. We
|
||||
if (!rs && (toU8(sig).length !== 2 * fLen)) // throw error when DER is suspected now.
|
||||
err('signature must be 64 bytes');
|
||||
try {
|
||||
sig_ = rs ? new Signature(sig.r, sig.s).assertValidity() : Signature.fromCompact(sig);
|
||||
h = bits2int_modN(toU8(msgh, fLen)); // Truncate hash
|
||||
P = pub instanceof Point ? pub.ok() : Point.fromHex(pub); // Validate public key
|
||||
} catch (e) { return false; } // Check sig for validity in both cases
|
||||
if (!sig_) return false;
|
||||
const { r, s } = sig_;
|
||||
if (lowS && moreThanHalfN(s)) return false; // lowS bans sig.s >= CURVE.n/2
|
||||
let R: AffinePoint;
|
||||
try {
|
||||
const is = inv(s, N); // s^-1
|
||||
const u1 = mod(h * is, N); // u1 = hs^-1 mod n
|
||||
const u2 = mod(r * is, N); // u2 = rs^-1 mod n
|
||||
R = G.mulAddQUns(P, u1, u2).aff(); // R = u1⋅G + u2⋅P
|
||||
} catch (error) { return false; }
|
||||
if (!R) return false; // stop if R is identity / zero point
|
||||
const v = mod(R.x, N); // <== The weird ECDSA part. R.x must be in N's field, not P's
|
||||
return v === r; // mod(R.x, n) == r
|
||||
}
|
||||
function getSharedSecret(privA: Hex, pubB: Hex, isCompressed = true): Bytes {
|
||||
return Point.fromHex(pubB).mul(toPriv(privA)).toRawBytes(isCompressed); // ECDH
|
||||
}
|
||||
function hashToPrivateKey(hash: Hex): Bytes { // FIPS 186 B.4.1 compliant key generation
|
||||
hash = toU8(hash); // produces private keys with modulo bias
|
||||
const minLen = fLen + 8; // being neglible.
|
||||
if (hash.length < minLen || hash.length > 1024) err('expected proper params');
|
||||
const num = mod(b2n(hash), N - 1n) + 1n; // takes at least n+8 bytes
|
||||
return n2b(num);
|
||||
}
|
||||
const etc = { // Not placed in utils because they
|
||||
hexToBytes: h2b, bytesToHex: b2h, // share API with noble-curves.
|
||||
concatBytes: concatB, bytesToNumberBE: b2n, numberToBytesBE: n2b,
|
||||
mod, invert: inv, // math utilities
|
||||
hmacSha256Async: async (key: Bytes, ...msgs: Bytes[]): Promise<Bytes> => {
|
||||
const crypto = cr(); // HMAC-SHA256 async. No sync built-in!
|
||||
if (!crypto) return err('etc.hmacSha256Async not set'); // Uses webcrypto: native cryptography.
|
||||
const s = crypto.subtle;
|
||||
const k = await s.importKey('raw', key, {name:'HMAC',hash:{name:'SHA-256'}}, false, ['sign']);
|
||||
return u8n(await s.sign('HMAC', k, concatB(...msgs)));
|
||||
},
|
||||
hmacSha256Sync: _hmacSync, // For TypeScript. Actual logic is below
|
||||
hashToPrivateKey,
|
||||
randomBytes: (len: number): Bytes => { // CSPRNG (random number generator)
|
||||
const crypto = cr(); // Can be shimmed in node.js <= 18 to prevent error:
|
||||
// import { webcrypto } from 'node:crypto';
|
||||
// if (!globalThis.crypto) globalThis.crypto = webcrypto;
|
||||
if (!crypto) err('crypto.getRandomValues must be defined');
|
||||
return crypto.getRandomValues(u8n(len));
|
||||
},
|
||||
}
|
||||
const utils = { // utilities
|
||||
normPrivateKeyToScalar: toPriv,
|
||||
isValidPrivateKey: (key: Hex) => { try { return !!toPriv(key); } catch (e) { return false; } },
|
||||
randomPrivateKey: (): Bytes => hashToPrivateKey(etc.randomBytes(fLen + 8)), // FIPS 186 B.4.1.
|
||||
precompute(w=8, p: Point = G) { p.multiply(3n); return p; }, // no-op
|
||||
};
|
||||
Object.defineProperties(etc, { hmacSha256Sync: { // Allow setting it once, ignore then
|
||||
configurable: false, get() { return _hmacSync; }, set(f) { if (!_hmacSync) _hmacSync = f; },
|
||||
} });
|
||||
const W = 8; // Precomputes-related code. W = window size
|
||||
const precompute = () => { // They give 12x faster getPublicKey(),
|
||||
const points: Point[] = []; // 10x sign(), 2x verify(). To achieve this,
|
||||
const windows = 256 / W + 1; // app needs to spend 40ms+ to calculate
|
||||
let p = G, b = p; // a lot of points related to base point G.
|
||||
for (let w = 0; w < windows; w++) { // Points are stored in array and used
|
||||
b = p; // any time Gx multiplication is done.
|
||||
points.push(b); // They consume 16-32 MiB of RAM.
|
||||
for (let i = 1; i < 2 ** (W - 1); i++) { b = b.add(p); points.push(b); }
|
||||
p = b.double(); // Precomputes don't speed-up getSharedKey,
|
||||
} // which multiplies user point by scalar,
|
||||
return points; // when precomputes are using base point
|
||||
}
|
||||
const wNAF = (n: bigint): { p: Point; f: Point } => { // w-ary non-adjacent form (wNAF) method.
|
||||
// Compared to other point mult methods,
|
||||
const comp = Gpows || (Gpows = precompute()); // stores 2x less points using subtraction
|
||||
const neg = (cnd: boolean, p: Point) => { let n = p.negate(); return cnd ? n : p; } // negate
|
||||
let p = I, f = G; // f must be G, or could become I in the end
|
||||
const windows = 1 + 256 / W; // W=8 17 windows
|
||||
const wsize = 2 ** (W - 1); // W=8 128 window size
|
||||
const mask = BigInt(2 ** W - 1); // W=8 will create mask 0b11111111
|
||||
const maxNum = 2 ** W; // W=8 256
|
||||
const shiftBy = BigInt(W); // W=8 8
|
||||
for (let w = 0; w < windows; w++) {
|
||||
const off = w * wsize;
|
||||
let wbits = Number(n & mask); // extract W bits.
|
||||
n >>= shiftBy; // shift number by W bits.
|
||||
if (wbits > wsize) { wbits -= maxNum; n += 1n; } // split if bits > max: +224 => 256-32
|
||||
const off1 = off, off2 = off + Math.abs(wbits) - 1; // offsets, evaluate both
|
||||
const cnd1 = w % 2 !== 0, cnd2 = wbits < 0; // conditions, evaluate both
|
||||
if (wbits === 0) {
|
||||
f = f.add(neg(cnd1, comp[off1])); // bits are 0: add garbage to fake point
|
||||
} else { // ^ can't add off2, off2 = I
|
||||
p = p.add(neg(cnd2, comp[off2])); // bits are 1: add to result point
|
||||
}
|
||||
}
|
||||
return { p, f } // return both real and fake points for JIT
|
||||
}; // !! you can disable precomputes by commenting-out call of the wNAF() inside Point#mul()
|
||||
export { getPublicKey, sign, signAsync, verify, CURVE, // Remove the export to easily use in REPL
|
||||
getSharedSecret, etc, utils, Point as ProjectivePoint, Signature } // envs like browser console
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@noble/secp256k1",
|
||||
"version": "2.0.0",
|
||||
"description": "Fastest 4KB JS implementation of secp256k1 elliptic curve. Auditable, high-security, 0-dependency ECDH & ECDSA signatures compliant with RFC6979",
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"index.ts"
|
||||
],
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"module": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build:release": "rollup -c rollup.config.js",
|
||||
"test": "node test/secp256k1.test.mjs",
|
||||
"bench": "node test/benchmark.js",
|
||||
"min": "cd test/build; npm install; npm run terser",
|
||||
"loc": "echo \"`npm run --silent min | wc -c` symbols `wc -l < index.ts` LOC, `npm run --silent min | gzip -c8 | wc -c`B gzipped\""
|
||||
},
|
||||
"author": "Paul Miller (https://paulmillr.com)",
|
||||
"homepage": "https://paulmillr.com/noble/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paulmillr/noble-secp256k1.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@noble/hashes": "1.3.0",
|
||||
"fast-check": "3.0.0",
|
||||
"micro-bmark": "0.3.0",
|
||||
"micro-should": "0.4.0",
|
||||
"typescript": "5.0.2"
|
||||
},
|
||||
"keywords": [
|
||||
"secp256k1",
|
||||
"rfc6979",
|
||||
"signature",
|
||||
"ecdsa",
|
||||
"noble",
|
||||
"cryptography",
|
||||
"elliptic curve",
|
||||
"ecc",
|
||||
"curve",
|
||||
"schnorr",
|
||||
"bitcoin",
|
||||
"ethereum"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"default": "./index.js"
|
||||
}
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user