Add comprehensive installation and setup documentation

- Add GETTING_STARTED.md with quick start guide and development modes
- Add INSTALL.sh automated installation script
- Add INSTALLATION_CHECKLIST.md, INSTALLATION_SUCCESS.md, and INSTALLATION_SUMMARY.md
- Add QUICK_REFERENCE.md for common commands
- Add SETUP_GUIDE.md with detailed setup instructions
- Update README.md with improved project overview
- Add did-wallet app dependencies and node_modules
This commit is contained in:
Dorian
2026-01-27 17:18:21 +00:00
parent a81f655133
commit 0d073fa89e
22658 changed files with 4494151 additions and 6 deletions
+21
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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/"
}
]
}