@noble/secp256k1
Advanced tools
Comparing version 1.5.2 to 1.5.3
@@ -47,9 +47,13 @@ /*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ | ||
equals(other) { | ||
const a = this; | ||
const b = other; | ||
const az2 = mod(a.z * a.z); | ||
const az3 = mod(a.z * az2); | ||
const bz2 = mod(b.z * b.z); | ||
const bz3 = mod(b.z * bz2); | ||
return mod(a.x * bz2) === mod(az2 * b.x) && mod(a.y * bz3) === mod(az3 * b.y); | ||
if (!(other instanceof JacobianPoint)) | ||
throw new TypeError('JacobianPoint expected'); | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { x: X2, y: Y2, z: Z2 } = other; | ||
const Z1Z1 = mod(Z1 ** _2n); | ||
const Z2Z2 = mod(Z2 ** _2n); | ||
const U1 = mod(X1 * Z2Z2); | ||
const U2 = mod(X2 * Z1Z1); | ||
const S1 = mod(mod(Y1 * Z2) * Z2Z2); | ||
const S2 = mod(mod(Y2 * Z1) * Z1Z1); | ||
return U1 === U2 && S1 === S2; | ||
} | ||
@@ -60,9 +64,7 @@ negate() { | ||
double() { | ||
const X1 = this.x; | ||
const Y1 = this.y; | ||
const Z1 = this.z; | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const A = mod(X1 ** _2n); | ||
const B = mod(Y1 ** _2n); | ||
const C = mod(B ** _2n); | ||
const D = mod(_2n * (mod(mod((X1 + B) ** _2n)) - A - C)); | ||
const D = mod(_2n * (mod((X1 + B) ** _2n) - A - C)); | ||
const E = mod(_3n * A); | ||
@@ -76,11 +78,6 @@ const F = mod(E ** _2n); | ||
add(other) { | ||
if (!(other instanceof JacobianPoint)) { | ||
throw new TypeError('JacobianPoint#add: expected JacobianPoint'); | ||
} | ||
const X1 = this.x; | ||
const Y1 = this.y; | ||
const Z1 = this.z; | ||
const X2 = other.x; | ||
const Y2 = other.y; | ||
const Z2 = other.z; | ||
if (!(other instanceof JacobianPoint)) | ||
throw new TypeError('JacobianPoint expected'); | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { x: X2, y: Y2, z: Z2 } = other; | ||
if (X2 === _0n || Y2 === _0n) | ||
@@ -94,3 +91,3 @@ return this; | ||
const U2 = mod(X2 * Z1Z1); | ||
const S1 = mod(Y1 * Z2 * Z2Z2); | ||
const S1 = mod(mod(Y1 * Z2) * Z2Z2); | ||
const S2 = mod(mod(Y2 * Z1) * Z1Z1); | ||
@@ -120,4 +117,12 @@ const H = mod(U2 - U1); | ||
let n = normalizeScalar(scalar); | ||
const G = JacobianPoint.BASE; | ||
const P0 = JacobianPoint.ZERO; | ||
if (n === _0n) | ||
return P0; | ||
if (this.equals(P0) || n === _1n) | ||
return this; | ||
if (this.equals(G)) | ||
return this.wNAF(n).p; | ||
if (!USE_ENDOMORPHISM) { | ||
let p = JacobianPoint.ZERO; | ||
let p = P0; | ||
let d = this; | ||
@@ -133,4 +138,4 @@ while (n > _0n) { | ||
let { k1neg, k1, k2neg, k2 } = splitScalarEndo(n); | ||
let k1p = JacobianPoint.ZERO; | ||
let k2p = JacobianPoint.ZERO; | ||
let k1p = P0; | ||
let k2p = P0; | ||
let d = this; | ||
@@ -186,3 +191,3 @@ while (k1 > _0n || k2 > _0n) { | ||
let f = JacobianPoint.ZERO; | ||
const windows = USE_ENDOMORPHISM ? 128 / W + 1 : 256 / W + 1; | ||
const windows = 1 + (USE_ENDOMORPHISM ? 128 / W : 256 / W); | ||
const windowSize = 2 ** (W - 1); | ||
@@ -220,3 +225,3 @@ const mask = BigInt(2 ** W - 1); | ||
if (USE_ENDOMORPHISM) { | ||
let { k1neg, k1, k2neg, k2 } = splitScalarEndo(n); | ||
const { k1neg, k1, k2neg, k2 } = splitScalarEndo(n); | ||
let { p: k1p, f: f1p } = this.wNAF(k1, affinePoint); | ||
@@ -233,3 +238,3 @@ let { p: k2p, f: f2p } = this.wNAF(k2, affinePoint); | ||
else { | ||
let { p, f } = this.wNAF(n, affinePoint); | ||
const { p, f } = this.wNAF(n, affinePoint); | ||
point = p; | ||
@@ -241,6 +246,12 @@ fake = f; | ||
toAffine(invZ = invert(this.z)) { | ||
const invZ2 = invZ ** _2n; | ||
const x = mod(this.x * invZ2); | ||
const y = mod(this.y * invZ2 * invZ); | ||
return new Point(x, y); | ||
const { x, y, z } = this; | ||
const iz1 = invZ; | ||
const iz2 = mod(iz1 * iz1); | ||
const iz3 = mod(iz2 * iz1); | ||
const ax = mod(x * iz2); | ||
const ay = mod(y * iz3); | ||
const zz = mod(z * iz1); | ||
if (zz !== _1n) | ||
throw new Error('invZ was invalid'); | ||
return new Point(ax, ay); | ||
} | ||
@@ -444,9 +455,9 @@ } | ||
toDERHex(isCompressed = false) { | ||
const sHex = sliceDER(numberToHex(this.s)); | ||
const sHex = sliceDER(numberToHexUnpadded(this.s)); | ||
if (isCompressed) | ||
return sHex; | ||
const rHex = sliceDER(numberToHex(this.r)); | ||
const rLen = numberToHex(rHex.length / 2); | ||
const sLen = numberToHex(sHex.length / 2); | ||
const length = numberToHex(rHex.length / 2 + sHex.length / 2 + 4); | ||
const rHex = sliceDER(numberToHexUnpadded(this.r)); | ||
const rLen = numberToHexUnpadded(rHex.length / 2); | ||
const sLen = numberToHexUnpadded(sHex.length / 2); | ||
const length = numberToHexUnpadded(rHex.length / 2 + sHex.length / 2 + 4); | ||
return `30${length}02${rLen}${rHex}02${sLen}${sHex}`; | ||
@@ -502,3 +513,3 @@ } | ||
} | ||
function numberToHex(num) { | ||
function numberToHexUnpadded(num) { | ||
const hex = num.toString(16); | ||
@@ -545,3 +556,3 @@ return hex.length & 1 ? `0${hex}` : hex; | ||
const result = a % b; | ||
return result >= 0 ? result : b + result; | ||
return result >= _0n ? result : b + result; | ||
} | ||
@@ -599,21 +610,18 @@ function pow2(x, power) { | ||
} | ||
function invertBatch(nums, n = CURVE.P) { | ||
const len = nums.length; | ||
const scratch = new Array(len); | ||
let acc = _1n; | ||
for (let i = 0; i < len; i++) { | ||
if (nums[i] === _0n) | ||
continue; | ||
function invertBatch(nums, p = CURVE.P) { | ||
const scratch = new Array(nums.length); | ||
const lastMultiplied = nums.reduce((acc, num, i) => { | ||
if (num === _0n) | ||
return acc; | ||
scratch[i] = acc; | ||
acc = mod(acc * nums[i], n); | ||
} | ||
acc = invert(acc, n); | ||
for (let i = len - 1; i >= 0; i--) { | ||
if (nums[i] === _0n) | ||
continue; | ||
const tmp = mod(acc * nums[i], n); | ||
nums[i] = mod(acc * scratch[i], n); | ||
acc = tmp; | ||
} | ||
return nums; | ||
return mod(acc * num, p); | ||
}, _1n); | ||
const inverted = invert(lastMultiplied, p); | ||
nums.reduceRight((acc, num, i) => { | ||
if (num === _0n) | ||
return acc; | ||
scratch[i] = mod(acc * scratch[i], p); | ||
return mod(acc * num, p); | ||
}, inverted); | ||
return scratch; | ||
} | ||
@@ -638,4 +646,5 @@ const divNearest = (a, b) => (a + b / _2n) / b; | ||
k2 = n - k2; | ||
if (k1 > POW_2_128 || k2 > POW_2_128) | ||
throw new Error('splitScalarEndo: Endomorphism failed'); | ||
if (k1 > POW_2_128 || k2 > POW_2_128) { | ||
throw new Error('splitScalarEndo: Endomorphism failed, k=' + k); | ||
} | ||
return { k1neg, k1, k2neg, k2 }; | ||
@@ -992,2 +1001,11 @@ } | ||
}, | ||
hashToPrivateKey: (hash) => { | ||
hash = ensureBytes(hash); | ||
if (hash.length < 40 || hash.length > 1024) | ||
throw new Error('Expected 40-1024 bytes of private key as per FIPS 186'); | ||
const num = mod(bytesToNumber(hash), CURVE.n); | ||
if (num === _0n || num === _1n) | ||
throw new Error('Invalid private key'); | ||
return numTo32b(num); | ||
}, | ||
randomBytes: (bytesLength = 32) => { | ||
@@ -1006,10 +1024,3 @@ if (crypto.web) { | ||
randomPrivateKey: () => { | ||
let i = 8; | ||
while (i--) { | ||
const b32 = utils.randomBytes(32); | ||
const num = bytesToNumber(b32); | ||
if (isWithinCurveOrder(num) && num !== _1n) | ||
return b32; | ||
} | ||
throw new Error('Valid private key was not found in 8 iterations. PRNG is broken'); | ||
return utils.hashToPrivateKey(utils.randomBytes(40)); | ||
}, | ||
@@ -1016,0 +1027,0 @@ bytesToHex, |
@@ -108,2 +108,3 @@ /*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ | ||
isValidPrivateKey(privateKey: PrivKey): boolean; | ||
hashToPrivateKey: (hash: Hex) => Uint8Array; | ||
randomBytes: (bytesLength?: number) => Uint8Array; | ||
@@ -110,0 +111,0 @@ randomPrivateKey: () => Uint8Array; |
143
lib/index.js
@@ -53,9 +53,13 @@ "use strict"; | ||
equals(other) { | ||
const a = this; | ||
const b = other; | ||
const az2 = mod(a.z * a.z); | ||
const az3 = mod(a.z * az2); | ||
const bz2 = mod(b.z * b.z); | ||
const bz3 = mod(b.z * bz2); | ||
return mod(a.x * bz2) === mod(az2 * b.x) && mod(a.y * bz3) === mod(az3 * b.y); | ||
if (!(other instanceof JacobianPoint)) | ||
throw new TypeError('JacobianPoint expected'); | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { x: X2, y: Y2, z: Z2 } = other; | ||
const Z1Z1 = mod(Z1 ** _2n); | ||
const Z2Z2 = mod(Z2 ** _2n); | ||
const U1 = mod(X1 * Z2Z2); | ||
const U2 = mod(X2 * Z1Z1); | ||
const S1 = mod(mod(Y1 * Z2) * Z2Z2); | ||
const S2 = mod(mod(Y2 * Z1) * Z1Z1); | ||
return U1 === U2 && S1 === S2; | ||
} | ||
@@ -66,9 +70,7 @@ negate() { | ||
double() { | ||
const X1 = this.x; | ||
const Y1 = this.y; | ||
const Z1 = this.z; | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const A = mod(X1 ** _2n); | ||
const B = mod(Y1 ** _2n); | ||
const C = mod(B ** _2n); | ||
const D = mod(_2n * (mod(mod((X1 + B) ** _2n)) - A - C)); | ||
const D = mod(_2n * (mod((X1 + B) ** _2n) - A - C)); | ||
const E = mod(_3n * A); | ||
@@ -82,11 +84,6 @@ const F = mod(E ** _2n); | ||
add(other) { | ||
if (!(other instanceof JacobianPoint)) { | ||
throw new TypeError('JacobianPoint#add: expected JacobianPoint'); | ||
} | ||
const X1 = this.x; | ||
const Y1 = this.y; | ||
const Z1 = this.z; | ||
const X2 = other.x; | ||
const Y2 = other.y; | ||
const Z2 = other.z; | ||
if (!(other instanceof JacobianPoint)) | ||
throw new TypeError('JacobianPoint expected'); | ||
const { x: X1, y: Y1, z: Z1 } = this; | ||
const { x: X2, y: Y2, z: Z2 } = other; | ||
if (X2 === _0n || Y2 === _0n) | ||
@@ -100,3 +97,3 @@ return this; | ||
const U2 = mod(X2 * Z1Z1); | ||
const S1 = mod(Y1 * Z2 * Z2Z2); | ||
const S1 = mod(mod(Y1 * Z2) * Z2Z2); | ||
const S2 = mod(mod(Y2 * Z1) * Z1Z1); | ||
@@ -126,4 +123,12 @@ const H = mod(U2 - U1); | ||
let n = normalizeScalar(scalar); | ||
const G = JacobianPoint.BASE; | ||
const P0 = JacobianPoint.ZERO; | ||
if (n === _0n) | ||
return P0; | ||
if (this.equals(P0) || n === _1n) | ||
return this; | ||
if (this.equals(G)) | ||
return this.wNAF(n).p; | ||
if (!USE_ENDOMORPHISM) { | ||
let p = JacobianPoint.ZERO; | ||
let p = P0; | ||
let d = this; | ||
@@ -139,4 +144,4 @@ while (n > _0n) { | ||
let { k1neg, k1, k2neg, k2 } = splitScalarEndo(n); | ||
let k1p = JacobianPoint.ZERO; | ||
let k2p = JacobianPoint.ZERO; | ||
let k1p = P0; | ||
let k2p = P0; | ||
let d = this; | ||
@@ -192,3 +197,3 @@ while (k1 > _0n || k2 > _0n) { | ||
let f = JacobianPoint.ZERO; | ||
const windows = USE_ENDOMORPHISM ? 128 / W + 1 : 256 / W + 1; | ||
const windows = 1 + (USE_ENDOMORPHISM ? 128 / W : 256 / W); | ||
const windowSize = 2 ** (W - 1); | ||
@@ -226,3 +231,3 @@ const mask = BigInt(2 ** W - 1); | ||
if (USE_ENDOMORPHISM) { | ||
let { k1neg, k1, k2neg, k2 } = splitScalarEndo(n); | ||
const { k1neg, k1, k2neg, k2 } = splitScalarEndo(n); | ||
let { p: k1p, f: f1p } = this.wNAF(k1, affinePoint); | ||
@@ -239,3 +244,3 @@ let { p: k2p, f: f2p } = this.wNAF(k2, affinePoint); | ||
else { | ||
let { p, f } = this.wNAF(n, affinePoint); | ||
const { p, f } = this.wNAF(n, affinePoint); | ||
point = p; | ||
@@ -247,6 +252,12 @@ fake = f; | ||
toAffine(invZ = invert(this.z)) { | ||
const invZ2 = invZ ** _2n; | ||
const x = mod(this.x * invZ2); | ||
const y = mod(this.y * invZ2 * invZ); | ||
return new Point(x, y); | ||
const { x, y, z } = this; | ||
const iz1 = invZ; | ||
const iz2 = mod(iz1 * iz1); | ||
const iz3 = mod(iz2 * iz1); | ||
const ax = mod(x * iz2); | ||
const ay = mod(y * iz3); | ||
const zz = mod(z * iz1); | ||
if (zz !== _1n) | ||
throw new Error('invZ was invalid'); | ||
return new Point(ax, ay); | ||
} | ||
@@ -451,9 +462,9 @@ } | ||
toDERHex(isCompressed = false) { | ||
const sHex = sliceDER(numberToHex(this.s)); | ||
const sHex = sliceDER(numberToHexUnpadded(this.s)); | ||
if (isCompressed) | ||
return sHex; | ||
const rHex = sliceDER(numberToHex(this.r)); | ||
const rLen = numberToHex(rHex.length / 2); | ||
const sLen = numberToHex(sHex.length / 2); | ||
const length = numberToHex(rHex.length / 2 + sHex.length / 2 + 4); | ||
const rHex = sliceDER(numberToHexUnpadded(this.r)); | ||
const rLen = numberToHexUnpadded(rHex.length / 2); | ||
const sLen = numberToHexUnpadded(sHex.length / 2); | ||
const length = numberToHexUnpadded(rHex.length / 2 + sHex.length / 2 + 4); | ||
return `30${length}02${rLen}${rHex}02${sLen}${sHex}`; | ||
@@ -510,3 +521,3 @@ } | ||
} | ||
function numberToHex(num) { | ||
function numberToHexUnpadded(num) { | ||
const hex = num.toString(16); | ||
@@ -553,3 +564,3 @@ return hex.length & 1 ? `0${hex}` : hex; | ||
const result = a % b; | ||
return result >= 0 ? result : b + result; | ||
return result >= _0n ? result : b + result; | ||
} | ||
@@ -607,21 +618,18 @@ function pow2(x, power) { | ||
} | ||
function invertBatch(nums, n = CURVE.P) { | ||
const len = nums.length; | ||
const scratch = new Array(len); | ||
let acc = _1n; | ||
for (let i = 0; i < len; i++) { | ||
if (nums[i] === _0n) | ||
continue; | ||
function invertBatch(nums, p = CURVE.P) { | ||
const scratch = new Array(nums.length); | ||
const lastMultiplied = nums.reduce((acc, num, i) => { | ||
if (num === _0n) | ||
return acc; | ||
scratch[i] = acc; | ||
acc = mod(acc * nums[i], n); | ||
} | ||
acc = invert(acc, n); | ||
for (let i = len - 1; i >= 0; i--) { | ||
if (nums[i] === _0n) | ||
continue; | ||
const tmp = mod(acc * nums[i], n); | ||
nums[i] = mod(acc * scratch[i], n); | ||
acc = tmp; | ||
} | ||
return nums; | ||
return mod(acc * num, p); | ||
}, _1n); | ||
const inverted = invert(lastMultiplied, p); | ||
nums.reduceRight((acc, num, i) => { | ||
if (num === _0n) | ||
return acc; | ||
scratch[i] = mod(acc * scratch[i], p); | ||
return mod(acc * num, p); | ||
}, inverted); | ||
return scratch; | ||
} | ||
@@ -646,4 +654,5 @@ const divNearest = (a, b) => (a + b / _2n) / b; | ||
k2 = n - k2; | ||
if (k1 > POW_2_128 || k2 > POW_2_128) | ||
throw new Error('splitScalarEndo: Endomorphism failed'); | ||
if (k1 > POW_2_128 || k2 > POW_2_128) { | ||
throw new Error('splitScalarEndo: Endomorphism failed, k=' + k); | ||
} | ||
return { k1neg, k1, k2neg, k2 }; | ||
@@ -1005,2 +1014,11 @@ } | ||
}, | ||
hashToPrivateKey: (hash) => { | ||
hash = ensureBytes(hash); | ||
if (hash.length < 40 || hash.length > 1024) | ||
throw new Error('Expected 40-1024 bytes of private key as per FIPS 186'); | ||
const num = mod(bytesToNumber(hash), CURVE.n); | ||
if (num === _0n || num === _1n) | ||
throw new Error('Invalid private key'); | ||
return numTo32b(num); | ||
}, | ||
randomBytes: (bytesLength = 32) => { | ||
@@ -1019,10 +1037,3 @@ if (crypto.web) { | ||
randomPrivateKey: () => { | ||
let i = 8; | ||
while (i--) { | ||
const b32 = exports.utils.randomBytes(32); | ||
const num = bytesToNumber(b32); | ||
if (isWithinCurveOrder(num) && num !== _1n) | ||
return b32; | ||
} | ||
throw new Error('Valid private key was not found in 8 iterations. PRNG is broken'); | ||
return exports.utils.hashToPrivateKey(exports.utils.randomBytes(40)); | ||
}, | ||
@@ -1029,0 +1040,0 @@ bytesToHex, |
{ | ||
"name": "@noble/secp256k1", | ||
"version": "1.5.2", | ||
"version": "1.5.3", | ||
"description": "Fastest JS implementation of secp256k1. Independently audited, high-security, 0-dependency ECDSA & Schnorr signatures", | ||
@@ -5,0 +5,0 @@ "files": [ |
256
README.md
@@ -5,5 +5,5 @@ # noble-secp256k1 ![Node CI](https://github.com/paulmillr/noble-secp256k1/workflows/Node%20CI/badge.svg) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) | ||
an elliptic curve that could be used for asymmetric encryption, | ||
ECDH key agreement protocol and signature schemes. Supports deterministic **ECDSA** from RFC6979 and **Schnorr** signatures from BIP0340. | ||
ECDH key agreement protocol and signature schemes. Supports deterministic **ECDSA** from RFC6979 and **Schnorr** signatures from [BIP0340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). | ||
[**Audited**](#security) with crowdfunding by an independent security firm. Tested against thousands of test vectors from a different library. Check out [the online demo](https://paulmillr.com/ecc) and blog post: [Learning fast elliptic-curve cryptography in JS](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/) | ||
[**Audited**](#security) by an independent security firm. Check out [the online demo](https://paulmillr.com/ecc) and blog post: [Learning fast elliptic-curve cryptography in JS](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/) | ||
@@ -34,28 +34,25 @@ ### This library belongs to *noble* crypto | ||
import * as secp from "@noble/secp256k1"; | ||
// If you're using single file, use global variable instead: | ||
// nobleSecp256k1 | ||
// If you're using single file, use global variable instead: `window.nobleSecp256k1` | ||
(async () => { | ||
// You pass a hex string, or Uint8Array | ||
const privateKey = "6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e"; | ||
const message = "hello world"; | ||
const messageHash = await secp.utils.sha256(message); | ||
// keys, messages & other inputs can be Uint8Arrays or hex strings | ||
// Uint8Array.from([0xde, 0xad, 0xbe, 0xef]) === 'deadbeef' | ||
const privateKey = secp.utils.randomPrivateKey(); | ||
const messageHash = await secp.utils.sha256("hello world"); | ||
const publicKey = secp.getPublicKey(privateKey); | ||
const signature = await secp.sign(messageHash, privateKey); | ||
const isSigned = secp.verify(signature, messageHash, publicKey); | ||
const isValid = secp.verify(signature, messageHash, publicKey); | ||
// Sigs with improved security (see README) | ||
// Signatures with improved security (see extraEntropy docs in README) | ||
const signatureE = await secp.sign(messageHash, privateKey, { extraEntropy: true }); | ||
// Malleable signatures, compatible with openssl | ||
const signatureM = await secp.sign(messageHash, privateKey, { canonical: false }); | ||
// If you need hex strings | ||
const hex = secp.utils.bytesToHex; | ||
console.log(hex(publicKey)); | ||
// Default output is Uint8Array. If you need hex string as an output: | ||
console.log(secp.utils.bytesToHex(publicKey)); | ||
// Supports Schnorr signatures | ||
// Schnorr signatures | ||
const rpub = secp.schnorr.getPublicKey(privateKey); | ||
const rsignature = await secp.schnorr.sign(message, privateKey); | ||
const risSigned = await secp.schnorr.verify(rsignature, message, rpub); | ||
const risValid = await secp.schnorr.verify(rsignature, message, rpub); | ||
})(); | ||
@@ -68,31 +65,16 @@ ``` | ||
- `deno run --import-map=imports.json app.ts` | ||
- app.ts: `import * as secp from "https://deno.land/x/secp256k1/mod.ts";` | ||
- imports.json: `{"imports": {"crypto": "https://deno.land/std@0.119.0/node/crypto.ts"}}` | ||
- `app.ts` | ||
```typescript | ||
import * as secp from "https://deno.land/x/secp256k1/mod.ts"; | ||
const publicKey = secp.getPublicKey(secp.utils.randomPrivateKey()); | ||
console.log(publicKey); | ||
``` | ||
- `imports.json` | ||
```json | ||
{ | ||
"imports": { | ||
"crypto": "https://deno.land/std@0.119.0/node/crypto.ts" | ||
} | ||
} | ||
``` | ||
## API | ||
- [`getPublicKey(privateKey)`](#getpublickeyprivatekey) | ||
- [`getSharedSecret(privateKeyA, publicKeyB)`](#getsharedsecretprivatekeya-publickeyb) | ||
- [`sign(msgHash, privateKey)`](#signmsghash-privatekey) | ||
- [`verify(signature, msgHash, publicKey)`](#verifysignature-msghash-publickey) | ||
- [`getSharedSecret(privateKeyA, publicKeyB)`](#getsharedsecretprivatekeya-publickeyb) | ||
- [`recoverPublicKey(hash, signature, recovery)`](#recoverpublickeyhash-signature-recovery) | ||
- [`schnorr.getPublicKey(privateKey)`](#schnorrgetpublickeyprivatekey) | ||
- [`schnorr.sign(hash, privateKey)`](#schnorrsignhash-privatekey) | ||
- [`schnorr.verify(signature, hash, publicKey)`](#schnorrverifysignature-hash-publickey) | ||
- [Helpers](#helpers) | ||
- [`schnorr.sign(message, privateKey)`](#schnorrsignmessage-privatekey) | ||
- [`schnorr.verify(signature, message, publicKey)`](#schnorrverifysignature-message-publickey) | ||
- [Utilities](#utilities) | ||
@@ -103,22 +85,9 @@ ##### `getPublicKey(privateKey)` | ||
``` | ||
`privateKey` will be used to generate public key. | ||
Public key is generated by doing scalar multiplication of a base Point(x, y) by a fixed | ||
integer. The result is another `Point(x, y)` which we will by default encode to hex Uint8Array. | ||
`isCompressed` (default is `false`) determines whether the output should contain `y` coordinate of the point. | ||
To get Point instance, use `Point.fromPrivateKey(privateKey)`. | ||
Creates public key for the corresponding private key. Could return compressed 33-byte keys, or | ||
uncompressed 65-byte keys. | ||
##### `getSharedSecret(privateKeyA, publicKeyB)` | ||
```typescript | ||
function getSharedSecret(privateKeyA: Uint8Array | string | bigint, publicKeyB: Uint8Array | string | Point): Uint8Array; | ||
``` | ||
Internally, it does `Point.BASE.multiply(privateKey)`. If you need actual `Point` instead of | ||
`Uint8Array`, use `Point.fromPrivateKey(privateKey)`. | ||
Computes ECDH (Elliptic Curve Diffie-Hellman) shared secret between a private key and a different public key. | ||
To get Point instance, use `Point.fromHex(publicKeyB).multiply(privateKeyA)`. | ||
To speed-up the function massively by precomputing EC multiplications, | ||
use `getSharedSecret(privateKeyA, secp.utils.precompute(8, publicKeyB))` | ||
##### `sign(msgHash, privateKey)` | ||
@@ -132,29 +101,28 @@ ```typescript | ||
It's strongly recommended to pass `{extraEntropy: true}` to improve security of signatures: | ||
- In case the entropy generator is broken, signatures would be just like they are without the option | ||
- It would help a lot in case there is an error somewhere in `k` generation. Exposing `k` could leak private keys | ||
- Schnorr signatures are adding extra entropy every time | ||
- The only disadvantage to this is the fact signatures won't be exactly equal to fully-deterministic sigs; | ||
think backwards-compatibility with test vectors. They would still be valid, though | ||
`sign` arguments: | ||
- `msgHash: Uint8Array | string` - message hash which would be signed | ||
- `msgHash: Uint8Array | string` - 32-byte message hash which would be signed | ||
- `privateKey: Uint8Array | string | bigint` - private key which will sign the hash | ||
- `options?: Options` - *optional* object related to signature value and format | ||
- `options?.recovered: boolean = false` - whether the recovered bit should be included in the result. In this case, the result would be an array of two items. | ||
- `options?.canonical: boolean = true` - whether a signature `s` should be no more than 1/2 prime order. | ||
`true` makes signatures compatible with libsecp256k1, | ||
`false` makes signatures compatible with openssl | ||
- `options?.extraEntropy: Uint8Array | string | true` - additional entropy `k'` for deterministic signature, follows section 3.6 of RFC6979. When `true`, it would automatically be filled with 32 bytes of cryptographically secure entropy | ||
- `options?.der: boolean = true` - whether the returned signature should be in DER format. If `false`, it would be in Compact format (32-byte r + 32-byte s) | ||
- `options?: Options` - *optional* object related to signature value and format with following keys: | ||
- `recovered: boolean = false` - whether the recovered bit should be included in the result. In this case, the result would be an array of two items. | ||
- `canonical: boolean = true` - whether a signature `s` should be no more than 1/2 prime order. | ||
`true` (default) makes signatures compatible with libsecp256k1, | ||
`false` makes signatures compatible with openssl | ||
- `der: boolean = true` - whether the returned signature should be in DER format. If `false`, it would be in Compact format (32-byte r + 32-byte s) | ||
- `extraEntropy: Uint8Array | string | true` - additional entropy `k'` for deterministic signature, follows section 3.6 of RFC6979. When `true`, it would automatically be filled with 32 bytes of cryptographically secure entropy. **Strongly recommended** to pass `true` to improve security: | ||
- Schnorr signatures are doing it every time | ||
- It would help a lot in case there is an error somewhere in `k` generation. Exposing `k` could leak private keys | ||
- If the entropy generator is broken, signatures would be the same as they are without the option | ||
- Signatures 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 | ||
The function is asynchronous because we're utilizing built-in HMAC API to not rely on dependencies. | ||
```typescript | ||
function signSync(msgHash: Uint8Array | string, privateKey: Uint8Array | string, opts?: Options): Uint8Array | [Uint8Array, number]; | ||
``` | ||
`signSync` counterpart could also be used, you need to set `utils.hmacSha256Sync` to a function with signature `key: Uint8Array, ...messages: Uint8Array[]) => Uint8Array`. Example with `noble-hashes` package: | ||
```ts | ||
const { hmac } = require('@noble/hashes/hmac'); | ||
const { sha256 } = require('@noble/hashes/sha256'); | ||
import { hmac } = from '@noble/hashes/hmac'; | ||
import { sha256 } from '@noble/hashes/sha256'; | ||
secp256k1.utils.hmacSha256Sync = (key: Uint8Array, ...msgs: Uint8Array[]) => { | ||
@@ -179,7 +147,24 @@ const h = hmac.create(sha256, key); | ||
- `options?: Options` - *optional* object related to signature value and format | ||
- `options?.strict: boolean = true` - whether a signature `s` should be no more than 1/2 prime order. | ||
`true` makes signatures compatible with libsecp256k1, | ||
`false` makes signatures compatible with openssl | ||
- `strict: boolean = true` - whether a signature `s` should be no more than 1/2 prime order. | ||
`true` (default) makes signatures compatible with libsecp256k1, | ||
`false` makes signatures compatible with openssl | ||
- Returns `boolean`: `true` if `signature == hash`; otherwise `false` | ||
##### `getSharedSecret(privateKeyA, publicKeyB)` | ||
```typescript | ||
function getSharedSecret(privateKeyA: Uint8Array | string | bigint, publicKeyB: Uint8Array | string | Point): Uint8Array; | ||
``` | ||
Computes ECDH (Elliptic Curve Diffie-Hellman) shared secret between a private key and a different public key. | ||
- To get Point instance, use `Point.fromHex(publicKeyB).multiply(privateKeyA)`. | ||
- If you have one public key you'll be creating lots of secrets against, | ||
consider massive speed-up by using precomputations: | ||
```js | ||
const pub = secp.utils.precompute(8, publicKeyB); | ||
// Use pub everywhere instead of publicKeyB | ||
getSharedSecret(privKey, pub); // Now 12x faster | ||
``` | ||
##### `recoverPublicKey(hash, signature, recovery)` | ||
@@ -203,9 +188,9 @@ ```typescript | ||
Returns 32-byte public key. *Warning:* it is incompatible with non-schnorr pubkey. | ||
Calculates 32-byte public key from a private key. | ||
Specifically, its *y* coordinate may be flipped. See BIP340 for clarification. | ||
*Warning:* it is incompatible with non-schnorr pubkey. Specifically, its *y* coordinate may be flipped. See [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) for clarification. | ||
##### `schnorr.sign(hash, privateKey)` | ||
##### `schnorr.sign(message, privateKey)` | ||
```typescript | ||
function schnorrSign(msgHash: Uint8Array | string, privateKey: Uint8Array | string, auxilaryRandom?: Uint8Array): Promise<Uint8Array>; | ||
function schnorrSign(message: Uint8Array | string, privateKey: Uint8Array | string, auxilaryRandom?: Uint8Array): Promise<Uint8Array>; | ||
``` | ||
@@ -215,3 +200,3 @@ | ||
- `msgHash: Uint8Array | string` - message hash which would be signed | ||
- `message: Uint8Array | string` - message (not hash) which would be signed | ||
- `privateKey: Uint8Array | string | bigint` - private key which will sign the hash | ||
@@ -221,61 +206,47 @@ - `auxilaryRandom?: Uint8Array` — optional 32 random bytes. By default, the method gathers cryptogarphically secure entropy | ||
##### `schnorr.verify(signature, hash, publicKey)` | ||
##### `schnorr.verify(signature, message, publicKey)` | ||
```typescript | ||
function schnorrVerify(signature: Uint8Array | string, msgHash: Uint8Array | string, publicKey: Uint8Array | string): boolean | ||
function schnorrVerify(signature: Uint8Array | string, message: Uint8Array | string, publicKey: Uint8Array | string): boolean | ||
``` | ||
- `signature: Uint8Array | string | { r: bigint, s: bigint }` - object returned by the `sign` function | ||
- `msgHash: Uint8Array | string` - message hash that needs to be verified | ||
- `message: Uint8Array | string` - message (not hash) that needs to be verified | ||
- `publicKey: Uint8Array | string | Point` - e.g. that was generated from `privateKey` by `getPublicKey` | ||
- Returns `boolean`: `true` if `signature == hash`; otherwise `false` | ||
#### Point methods | ||
#### Utilities | ||
##### Helpers | ||
secp256k1 exposes a few internal utilities for improved developer experience: | ||
###### `utils.randomBytes(): Uint8Array` | ||
```typescript | ||
const utils: { | ||
// Can take 40 or more bytes of uniform input e.g. from CSPRNG or KDF | ||
// and convert them into private key, with the modulo bias being neglible. | ||
// As per FIPS 186 B.1.1. | ||
hashToPrivateKey: (hash: Hex) => Uint8Array; | ||
// Returns `Uint8Array` of 32 cryptographically secure random bytes that can be used as private key | ||
randomPrivateKey: () => Uint8Array; | ||
// Checks private key for validity | ||
isValidPrivateKey(privateKey: PrivKey): boolean; | ||
Returns `Uint8Array` of 32 cryptographically secure random bytes. | ||
// Returns `Uint8Array` of x cryptographically secure random bytes. | ||
randomBytes: (bytesLength?: number) => Uint8Array; | ||
// Converts Uint8Array to hex string | ||
bytesToHex: typeof bytesToHex; | ||
// Modular division over curve prime | ||
mod: (number: number | bigint, modulo = CURVE.P): bigint; | ||
sha256: (message: Uint8Array) => Promise<Uint8Array>; | ||
hmacSha256: (key: Uint8Array, ...messages: Uint8Array[]) => Promise<Uint8Array>; | ||
Uses `crypto.web.getRandomValues` in browser, `require('crypto').randomBytes` in node.js. | ||
// You can set up your synchronous methods for `signSync` to work. The argument order is | ||
// identical to async methods from above | ||
sha256Sync: undefined; | ||
hmacSha256Sync: undefined; | ||
###### `utils.randomPrivateKey(): Uint8Array` | ||
// 1. Returns cached point which you can use to pass to `getSharedSecret` or to `#multiply` by it. | ||
// 2. Precomputes point multiplication table. Is done by default on first `getPublicKey()` call. | ||
// If you want your first getPublicKey to take 0.16ms instead of 20ms, make sure to call | ||
// utils.precompute() somewhere without arguments first. | ||
precompute(windowSize?: number, point?: Point): Point; | ||
}; | ||
Returns `Uint8Array` of 32 cryptographically secure random bytes that can be used as private key. The signature is: | ||
```ts | ||
(key: Uint8Array, ...msgs: Uint8Array[]): Uint8Array; | ||
``` | ||
###### `utils.bytesToHex(bytes: Uint8Array): string` | ||
Converts a byte array to hex string. | ||
###### `utils.mod(number: number | bigint, modulo = CURVE.P): bigint` | ||
Modulo operation. Note: JS `%` operator is remainder, not modulo. | ||
###### `utils.sha256` and `utils.hmacSha256` | ||
Asynchronous methods that calculate `SHA256` and `HMAC-SHA256`. Use browser built-ins by default. | ||
###### `utils.sha256Sync` and `utils.hmacSha256Sync` | ||
The functions are not defined by default, but could be used to implement `signSync` method (see above). | ||
###### `utils.precompute(W = 8, point = BASE_POINT): Point` | ||
Returns cached point which you can use to pass to `getSharedSecret` or to `#multiply` by it. | ||
This is done by default, no need to run it unless you want to | ||
disable precomputation or change window size. | ||
We're doing scalar multiplication (used in getPublicKey etc) with | ||
precomputed BASE_POINT values. | ||
This slows down first getPublicKey() by milliseconds (see Speed section), | ||
but allows to speed-up subsequent getPublicKey() calls up to 20x. | ||
You may want to precompute values for your own point. | ||
```typescript | ||
secp256k1.CURVE.P // Field, 2 ** 256 - 2 ** 32 - 977 | ||
@@ -326,6 +297,7 @@ secp256k1.CURVE.n // Order, 2 ** 256 - 432420386565659656852420866394968145599 | ||
1. The library has been audited by an independent security firm cure53: [PDF](https://cure53.de/pentest-report_noble-lib.pdf). The audit has been [crowdfunded](https://gitcoin.co/grants/2451/audit-of-noble-secp256k1-cryptographic-library) by community with help of [Umbra.cash](https://umbra.cash). | ||
1. The library has been audited by an independent security firm cure53: [PDF](https://cure53.de/pentest-report_noble-lib.pdf). See [changes since audit](https://github.com/paulmillr/noble-secp256k1/compare/1.2.0..main). | ||
- The audit has been [crowdfunded](https://gitcoin.co/grants/2451/audit-of-noble-secp256k1-cryptographic-library) by community with help of [Umbra.cash](https://umbra.cash). | ||
2. The library has also been fuzzed by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz). You can run the fuzzer by yourself to check it. | ||
We're using built-in JS `BigInt`, which is "unsuitable for use in cryptography" as [per official spec](https://github.com/tc39/proposal-bigint#cryptography). This means that the lib is potentially vulnerable to [timing attacks](https://en.wikipedia.org/wiki/Timing_attack). 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. Nonetheless we've hardened implementation of koblitz curve multiplication to be algorithmically constant time. | ||
We're using built-in JS `BigInt`, which is "unsuitable for use in cryptography" as [per official spec](https://github.com/tc39/proposal-bigint#cryptography). This means that the lib is potentially vulnerable to [timing attacks](https://en.wikipedia.org/wiki/Timing_attack). 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. Nonetheless we've hardened implementation of ec curve multiplication to be algorithmically constant time. | ||
@@ -338,11 +310,11 @@ We however 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. | ||
getPublicKey(utils.randomPrivateKey()) x 6,216 ops/sec @ 160μs/op | ||
sign x 4,789 ops/sec @ 208μs/op | ||
verify x 923 ops/sec @ 1ms/op | ||
recoverPublicKey x 491 ops/sec @ 2ms/op | ||
getSharedSecret aka ecdh x 558 ops/sec @ 1790μs/op | ||
getSharedSecret (precomputed) x 7,105 ops/sec @ 140μs/op | ||
Point.fromHex (decompression) x 12,171 ops/sec @ 82μs/op | ||
schnorr.sign x 409 ops/sec @ 2ms/op | ||
schnorr.verify x 504 ops/sec @ 1ms/op | ||
getPublicKey(utils.randomPrivateKey()) x 6,300 ops/sec @ 158μs/op | ||
sign x 4,888 ops/sec @ 204μs/op | ||
verify x 950 ops/sec @ 1ms/op | ||
recoverPublicKey x 499 ops/sec @ 2ms/op | ||
getSharedSecret aka ecdh x 576 ops/sec @ 1ms/op | ||
getSharedSecret (precomputed) x 6,688 ops/sec @ 149μs/op | ||
Point.fromHex (decompression) x 12,553 ops/sec @ 79μs/op | ||
schnorr.sign x 426 ops/sec @ 2ms/op | ||
schnorr.verify x 520 ops/sec @ 1ms/op | ||
@@ -375,4 +347,4 @@ Compare to other libraries (`openssl` uses native bindings, not JS): | ||
2. `npm install` to install build dependencies like TypeScript | ||
3. `npm run compile` to compile TypeScript code | ||
4. `npm run test` to run jest on `test/index.ts` | ||
3. `npm run build` to compile TypeScript code | ||
4. `npm test` to run jest on `test/index.ts` | ||
@@ -379,0 +351,0 @@ Special thanks to [Roman Koblov](https://github.com/romankoblov), who have helped to improve scalar multiplication speed. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
96613
2231
344