noble-secp256k1
Advanced tools
Comparing version 1.0.6 to 1.1.0
@@ -66,7 +66,20 @@ declare const CURVE: { | ||
export declare function verify(signature: Signature, msgHash: Hex, publicKey: PubKey): boolean; | ||
declare class SchnorrSignResult { | ||
readonly r: bigint; | ||
readonly s: bigint; | ||
constructor(r: bigint, s: bigint); | ||
toHex(): string; | ||
toRawBytes(): Uint8Array; | ||
} | ||
export declare const schnorr: { | ||
SignResult: typeof SchnorrSignResult; | ||
sign(message: Hex, privateKey: PrivKey, auxRand?: Hex): Promise<SchnorrSignResult>; | ||
verify(signature: SchnorrSignResult, message: Hex, publicKey: PubKey): Promise<boolean>; | ||
}; | ||
export declare const utils: { | ||
isValidPrivateKey(privateKey: PrivKey): boolean; | ||
randomPrivateKey: (bytesLength?: number) => Uint8Array; | ||
sha256: (message: Uint8Array) => Promise<Uint8Array>; | ||
hmacSha256: (key: Uint8Array, ...messages: Uint8Array[]) => Promise<Uint8Array>; | ||
precompute(windowSize?: number, point?: Point): Point; | ||
}; |
97
index.js
@@ -273,3 +273,3 @@ 'use strict'; | ||
static fromHex(hex) { | ||
const bytes = hex instanceof Uint8Array ? hex : hexToArray(hex); | ||
const bytes = hex instanceof Uint8Array ? hex : hexToBytes(hex); | ||
const header = bytes[0]; | ||
@@ -301,3 +301,3 @@ if (header === 0x02 || header === 0x03) | ||
toRawBytes(isCompressed = false) { | ||
return hexToArray(this.toHex(isCompressed)); | ||
return hexToBytes(this.toHex(isCompressed)); | ||
} | ||
@@ -377,3 +377,3 @@ toHex(isCompressed = false) { | ||
toRawBytes(isCompressed = false) { | ||
return hexToArray(this.toHex(isCompressed)); | ||
return hexToBytes(this.toHex(isCompressed)); | ||
} | ||
@@ -414,2 +414,5 @@ toHex(isCompressed = false) { | ||
} | ||
function pad32b(num) { | ||
return hexToBytes(pad64(num)); | ||
} | ||
function numberToHex(num) { | ||
@@ -425,3 +428,3 @@ const hex = num.toString(16); | ||
} | ||
function hexToArray(hex) { | ||
function hexToBytes(hex) { | ||
hex = hex.length & 1 ? `0${hex}` : hex; | ||
@@ -528,4 +531,4 @@ const array = new Uint8Array(hex.length / 2); | ||
const num = typeof msgHash === 'string' ? hexToNumber(msgHash) : bytesToNumber(msgHash); | ||
const h1 = hexToArray(pad64(num)); | ||
const x = hexToArray(pad64(privateKey)); | ||
const h1 = pad32b(num); | ||
const x = pad32b(privateKey); | ||
const h1n = bytesToNumber(h1); | ||
@@ -655,2 +658,69 @@ let v = new Uint8Array(32).fill(1); | ||
exports.verify = verify; | ||
function rawX(point) { | ||
return point.toRawBytes(true).slice(1); | ||
} | ||
async function taggedHash(tag, ...messages) { | ||
const tagB = new Uint8Array(tag.split('').map((c) => c.charCodeAt(0))); | ||
const tagH = await exports.utils.sha256(tagB); | ||
const h = await exports.utils.sha256(concatBytes(tagH, tagH, ...messages)); | ||
return bytesToNumber(h); | ||
} | ||
async function createChallenge(x, p, message) { | ||
const rx = pad32b(x); | ||
const t = await taggedHash('BIP0340/challenge', rx, rawX(p), message); | ||
return mod(t, CURVE.n); | ||
} | ||
function hasEvenY(point) { | ||
return mod(point.y, 2n) === 0n; | ||
} | ||
class SchnorrSignResult { | ||
constructor(r, s) { | ||
this.r = r; | ||
this.s = s; | ||
} | ||
toHex() { | ||
return pad64(this.r) + pad64(this.s); | ||
} | ||
toRawBytes() { | ||
return hexToBytes(this.toHex()); | ||
} | ||
} | ||
exports.schnorr = { | ||
SignResult: SchnorrSignResult, | ||
async sign(message, privateKey, auxRand = exports.utils.randomPrivateKey()) { | ||
if (message == null) | ||
throw new TypeError(`Expected valid message, not "${message}"`); | ||
if (privateKey == null) | ||
throw new TypeError('Expected valid private key'); | ||
const msg = typeof message === 'string' ? hexToBytes(message) : message; | ||
const rand = typeof auxRand === 'string' ? hexToBytes(auxRand) : auxRand; | ||
const order = CURVE.n; | ||
const d0 = normalizePrivateKey(privateKey); | ||
const p = Point.fromPrivateKey(d0); | ||
const d = hasEvenY(p) ? d0 : order - d0; | ||
const t0h = await taggedHash('BIP0340/aux', rand); | ||
const t = d ^ t0h; | ||
const k0h = await taggedHash('BIP0340/nonce', pad32b(t), rawX(p), msg); | ||
const k0 = mod(k0h, order); | ||
if (k0 === 0n) | ||
throw new Error('Creation of signature failed. k is zero'); | ||
const r = Point.fromPrivateKey(k0); | ||
const k = hasEvenY(r) ? k0 : order - k0; | ||
const e = await createChallenge(r.x, p, msg); | ||
const sig = new SchnorrSignResult(r.x, mod(k + e * d, order)); | ||
return sig; | ||
}, | ||
async verify(signature, message, publicKey) { | ||
const { r, s } = signature; | ||
const msg = typeof message === 'string' ? hexToBytes(message) : message; | ||
const pub = normalizePublicKey(publicKey); | ||
if (r === 0n || s === 0n || r >= CURVE.P || s >= CURVE.n) | ||
return false; | ||
const e = await createChallenge(r, pub, msg); | ||
const vr = Point.fromPrivateKey(s).add(pub.multiply(CURVE.n - e)); | ||
if (vr.equals(Point.BASE) || !hasEvenY(vr) || vr.x !== r) | ||
return false; | ||
return true; | ||
}, | ||
}; | ||
Point.BASE._setWindowSize(8); | ||
@@ -673,5 +743,18 @@ exports.utils = { | ||
}, | ||
sha256: async (message) => { | ||
if (typeof window == 'object' && 'crypto' in window) { | ||
const buffer = await window.crypto.subtle.digest('SHA-256', message.buffer); | ||
return new Uint8Array(buffer); | ||
} | ||
else if (typeof process === 'object' && 'node' in process.versions) { | ||
const { createHash } = require('crypto'); | ||
return Uint8Array.from(createHash('sha256').update(message).digest()); | ||
} | ||
else { | ||
throw new Error("The environment doesn't have sha256 function"); | ||
} | ||
}, | ||
hmacSha256: async (key, ...messages) => { | ||
if (typeof window == 'object' && 'crypto' in window) { | ||
const ckey = await window.crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign', 'verify']); | ||
const ckey = await window.crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']); | ||
const message = concatBytes(...messages); | ||
@@ -678,0 +761,0 @@ const buffer = await window.crypto.subtle.sign('HMAC', ckey, message); |
{ | ||
"name": "noble-secp256k1", | ||
"version": "1.0.6", | ||
"description": "Noble secp256k1. Very fast, high-security, auditable, 0-dep, 1-file pubkey & ECDSA.", | ||
"version": "1.1.0", | ||
"description": "Noble secp256k1. Very fast, high-security, auditable, 0-dep, 1-file ECDSA & Schnorr.", | ||
"main": "index.js", | ||
@@ -50,2 +50,5 @@ "files": [ | ||
"rfc6979", | ||
"schnorr", | ||
"bip0340", | ||
"bip340", | ||
"ecdsa", | ||
@@ -52,0 +55,0 @@ "endomorphism", |
# 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) | ||
[secp256k1](https://www.secg.org/sec2-v2.pdf), an elliptic curve that could be used for asymmetric encryption, ECDH key agreement protocol and deterministic ECDSA signature scheme from RFC6979. | ||
[Very fast](#speed) JS implementattion of [secp256k1](https://www.secg.org/sec2-v2.pdf), | ||
an elliptic curve that could be used for asymmetric encryption, | ||
ECDH key agreement protocol and deterministic ECDSA signature scheme from RFC6979. Supports **Schnorr** signatures as per BIP0340. | ||
Algorithmically resistant to timing attacks. [Faster](#speed) than indutny/elliptic, ecdsa.js and sjcl. Tested against thousands of vectors from tiny-secp256k1. | ||
Algorithmically resistant to timing attacks. Tested against thousands of vectors from tiny-secp256k1. | ||
@@ -37,2 +39,6 @@ Check out a blog post about this library: [Learning fast elliptic-curve cryptography in JS](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/). | ||
const isSigned = secp.verify(signature, messageHash, publicKey); | ||
// Supports Schnorr signatures. | ||
const signature2 = await secp.schnorr.sign(messageHash, privateKey); | ||
const isSigned2 = await secp.schnorr.verify(signature2, messageHash, privateKey); | ||
})(); | ||
@@ -55,2 +61,4 @@ ``` | ||
- [`recoverPublicKey(hash, signature, recovery)`](#recoverpublickeyhash-signature-recovery) | ||
- [`schnorr.sign(hash, privateKey)`](#schnorrsignhash-privatekey) | ||
- [`schnorr.verify(signature, hash, publicKey)`](#schnorrverifysignature-hash-publickey) | ||
- [Helpers](#helpers) | ||
@@ -126,2 +134,23 @@ | ||
##### `schnorr.sign(hash, privateKey)` | ||
```typescript | ||
function schnorrSign(msgHash: Uint8Array, privateKey: Uint8Array, auxilaryRandom?: Uint8Array): Promise<schnorr.SignResult>; | ||
``` | ||
Generates Schnorr signature as per BIP0340. Asynchronous, so use `await`. | ||
- `msgHash: Uint8Array | string` - message hash which would be signed | ||
- `privateKey: Uint8Array | string | bigint` - private key which will sign the hash | ||
- `auxilaryRandom?: Uint8Array` — optional 32 random bytes. By default, the method gathers cryptogarphically secure random. | ||
- Returns Schnorr signature. | ||
##### `schnorr.verify(signature, hash, publicKey)` | ||
```typescript | ||
function schnorrVerify(signature: Uint8Array, msgHash: Uint8Array, publicKey: Uint8Array): boolean | ||
``` | ||
- `signature: Uint8Array | string | { r: bigint, s: bigint }` - object returned by the `sign` function | ||
- `msgHash: Uint8Array | string` - message 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 | ||
@@ -199,10 +228,12 @@ | ||
Benchmarks measured with 2.9Ghz Coffee Lake. | ||
Benchmarks measured with 2.9Ghz i9-8950HK. | ||
getPublicKey(utils.randomPrivateKey()) x 4017 ops/sec @ 248μs/op | ||
sign x 2620 ops/sec @ 381μs/op | ||
getPublicKey(utils.randomPrivateKey()) x 4,017 ops/sec @ 248μs/op | ||
sign x 2,620 ops/sec @ 381μs/op | ||
verify x 558 ops/sec @ 1ms/op | ||
recoverPublicKey x 301 ops/sec @ 3ms/op | ||
getSharedSecret aka ecdh x 435 ops/sec @ 2ms/op | ||
getSharedSecret (precomputed) x 4079 ops/sec @ 245μs/op | ||
getSharedSecret (precomputed) x 4,079 ops/sec @ 245μs/op | ||
schnorr.sign x 1,643 ops/sec @ 608μs/op | ||
schnorr.verify x 316 ops/sec @ 3ms/op | ||
@@ -209,0 +240,0 @@ Compare to other libraries: |
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
45459
858
268