noble-secp256k1
Advanced tools
Comparing version 1.1.0 to 1.1.1
@@ -15,3 +15,3 @@ declare const CURVE: { | ||
declare type PubKey = Hex | Point; | ||
declare type Signature = Hex | SignResult; | ||
declare type Sig = Hex | Signature; | ||
export declare class Point { | ||
@@ -25,2 +25,3 @@ x: bigint; | ||
_setWindowSize(windowSize: number): void; | ||
private static fromX; | ||
private static fromCompressedHex; | ||
@@ -30,5 +31,7 @@ private static fromUncompressedHex; | ||
static fromPrivateKey(privateKey: PrivKey): Point; | ||
static fromSignature(msgHash: Hex, signature: Signature, recovery: number): Point | undefined; | ||
static fromSignature(msgHash: Hex, signature: Sig, recovery: number): Point | undefined; | ||
toRawBytes(isCompressed?: boolean): Uint8Array; | ||
toHex(isCompressed?: boolean): string; | ||
toHexX(): string; | ||
toRawX(): Uint8Array; | ||
assertValidity(): void; | ||
@@ -42,10 +45,11 @@ equals(other: Point): boolean; | ||
} | ||
export declare class SignResult { | ||
export declare class Signature { | ||
r: bigint; | ||
s: bigint; | ||
constructor(r: bigint, s: bigint); | ||
static fromHex(hex: Hex): SignResult; | ||
static fromHex(hex: Hex): Signature; | ||
toRawBytes(isCompressed?: boolean): Uint8Array; | ||
toHex(isCompressed?: boolean): string; | ||
} | ||
export declare const SignResult: typeof Signature; | ||
export declare function getPublicKey(privateKey: Uint8Array | bigint | number, isCompressed?: boolean): Uint8Array; | ||
@@ -69,14 +73,21 @@ export declare function getPublicKey(privateKey: string, isCompressed?: boolean): string; | ||
export declare function sign(msgHash: string, privateKey: PrivKey, opts?: OptsNoRecovered): Promise<string>; | ||
export declare function verify(signature: Signature, msgHash: Hex, publicKey: PubKey): boolean; | ||
declare class SchnorrSignResult { | ||
export declare function verify(signature: Sig, msgHash: Hex, publicKey: PubKey): boolean; | ||
declare class SchnorrSignature { | ||
readonly r: bigint; | ||
readonly s: bigint; | ||
constructor(r: bigint, s: bigint); | ||
static fromHex(hex: Hex): SchnorrSignature; | ||
toHex(): string; | ||
toRawBytes(): Uint8Array; | ||
} | ||
declare function schnorrGetPublicKey(privateKey: Uint8Array): Uint8Array; | ||
declare function schnorrGetPublicKey(privateKey: string): string; | ||
declare function schnorrSign(msgHash: string, privateKey: string, auxRand?: Hex): Promise<string>; | ||
declare function schnorrSign(msgHash: Uint8Array, privateKey: Uint8Array, auxRand?: Hex): Promise<Uint8Array>; | ||
declare function schnorrVerify(signature: Hex, msgHash: Hex, publicKey: Hex): Promise<boolean>; | ||
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>; | ||
Signature: typeof SchnorrSignature; | ||
getPublicKey: typeof schnorrGetPublicKey; | ||
sign: typeof schnorrSign; | ||
verify: typeof schnorrVerify; | ||
}; | ||
@@ -83,0 +94,0 @@ export declare const utils: { |
145
index.js
@@ -246,5 +246,16 @@ 'use strict'; | ||
} | ||
static fromX(bytes) { | ||
const x = bytesToNumber(bytes); | ||
const sqrY = weistrass(x); | ||
let y = powMod(sqrY, P_DIV4_1, CURVE.P); | ||
const isYOdd = (y & 1n) === 1n; | ||
if (isYOdd) | ||
y = mod(-y); | ||
const point = new Point(x, y); | ||
point.assertValidity(); | ||
return point; | ||
} | ||
static fromCompressedHex(bytes) { | ||
if (bytes.length !== 33) { | ||
throw new TypeError(`Point.fromHex: compressed expects 66 bytes, not ${bytes.length * 2}`); | ||
throw new TypeError(`Point.fromHex: compressed expects 33 bytes, not ${bytes.length * 2}`); | ||
} | ||
@@ -256,5 +267,4 @@ const x = bytesToNumber(bytes.slice(1)); | ||
const isYOdd = (y & 1n) === 1n; | ||
if (isFirstByteOdd !== isYOdd) { | ||
if (isFirstByteOdd !== isYOdd) | ||
y = mod(-y); | ||
} | ||
const point = new Point(x, y); | ||
@@ -266,3 +276,3 @@ point.assertValidity(); | ||
if (bytes.length !== 65) { | ||
throw new TypeError(`Point.fromHex: uncompressed expects 130 bytes, not ${bytes.length * 2}`); | ||
throw new TypeError(`Point.fromHex: uncompressed expects 65 bytes, not ${bytes.length * 2}`); | ||
} | ||
@@ -277,2 +287,4 @@ const x = bytesToNumber(bytes.slice(1, 33)); | ||
const bytes = hex instanceof Uint8Array ? hex : hexToBytes(hex); | ||
if (bytes.length === 32) | ||
return this.fromX(bytes); | ||
const header = bytes[0]; | ||
@@ -315,2 +327,8 @@ if (header === 0x02 || header === 0x03) | ||
} | ||
toHexX() { | ||
return this.toHex(true).slice(2); | ||
} | ||
toRawX() { | ||
return this.toRawBytes(true).slice(1); | ||
} | ||
assertValidity() { | ||
@@ -352,3 +370,3 @@ const { x, y } = this; | ||
} | ||
class SignResult { | ||
class Signature { | ||
constructor(r, s) { | ||
@@ -366,3 +384,3 @@ this.r = r; | ||
if (check1 !== '30' || length !== str.length - 4 || check2 !== '02') { | ||
throw new Error('SignResult.fromHex: Invalid signature'); | ||
throw new Error('Signature.fromHex: Invalid signature'); | ||
} | ||
@@ -379,3 +397,3 @@ const rLen = parseByte(str.slice(6, 8)); | ||
const s = hexToNumber(str.slice(sStart, sStart + sLen)); | ||
return new SignResult(r, s); | ||
return new Signature(r, s); | ||
} | ||
@@ -396,3 +414,4 @@ toRawBytes(isCompressed = false) { | ||
} | ||
exports.SignResult = SignResult; | ||
exports.Signature = Signature; | ||
exports.SignResult = Signature; | ||
function concatBytes(...arrays) { | ||
@@ -589,3 +608,3 @@ if (arrays.length === 1) | ||
function normalizeSignature(signature) { | ||
return signature instanceof SignResult ? signature : SignResult.fromHex(signature); | ||
return signature instanceof Signature ? signature : Signature.fromHex(signature); | ||
} | ||
@@ -646,3 +665,3 @@ function getPublicKey(privateKey, isCompressed = false) { | ||
} | ||
const sig = new SignResult(r, adjustedS); | ||
const sig = new Signature(r, adjustedS); | ||
const hashed = typeof msgHash === 'string' ? sig.toHex() : sig.toRawBytes(); | ||
@@ -663,5 +682,2 @@ return recovered ? [hashed, recovery] : hashed; | ||
exports.verify = verify; | ||
function rawX(point) { | ||
return point.toRawBytes(true).slice(1); | ||
} | ||
async function taggedHash(tag, ...messages) { | ||
@@ -673,5 +689,5 @@ const tagB = new Uint8Array(tag.split('').map((c) => c.charCodeAt(0))); | ||
} | ||
async function createChallenge(x, p, message) { | ||
async function createChallenge(x, P, message) { | ||
const rx = pad32b(x); | ||
const t = await taggedHash('BIP0340/challenge', rx, rawX(p), message); | ||
const t = await taggedHash('BIP0340/challenge', rx, P.toRawX(), message); | ||
return mod(t, CURVE.n); | ||
@@ -682,7 +698,18 @@ } | ||
} | ||
class SchnorrSignResult { | ||
class SchnorrSignature { | ||
constructor(r, s) { | ||
this.r = r; | ||
this.s = s; | ||
if (r === 0n || s === 0n || r >= CURVE.P || s >= CURVE.n) | ||
throw new Error('Invalid signature'); | ||
} | ||
static fromHex(hex) { | ||
const bytes = hex instanceof Uint8Array ? hex : hexToBytes(hex); | ||
if (bytes.length !== 64) { | ||
throw new TypeError(`SchnorrSignature.fromHex: expected 64 bytes, not ${bytes.length}`); | ||
} | ||
const r = bytesToNumber(bytes.slice(0, 32)); | ||
const s = bytesToNumber(bytes.slice(32)); | ||
return new SchnorrSignature(r, s); | ||
} | ||
toHex() { | ||
@@ -695,39 +722,53 @@ return pad64(this.r) + pad64(this.s); | ||
} | ||
function schnorrGetPublicKey(privateKey) { | ||
const P = Point.fromPrivateKey(privateKey); | ||
return typeof privateKey === 'string' ? P.toHexX() : P.toRawX(); | ||
} | ||
async function schnorrSign(msgHash, privateKey, auxRand = exports.utils.randomPrivateKey()) { | ||
if (msgHash == null) | ||
throw new TypeError(`Expected valid message, not "${msgHash}"`); | ||
if (!privateKey) | ||
privateKey = 0n; | ||
const { n } = CURVE; | ||
const m = typeof msgHash === 'string' ? hexToBytes(msgHash) : msgHash; | ||
const d0 = normalizePrivateKey(privateKey); | ||
if (!(0 < d0 && d0 < n)) | ||
throw new Error('Invalid private key'); | ||
const rand = typeof auxRand === 'string' ? hexToBytes(auxRand) : auxRand; | ||
if (rand.length !== 32) | ||
throw new TypeError('Expected 32 bytes of aux randomness'); | ||
const P = Point.fromPrivateKey(d0); | ||
const d = hasEvenY(P) ? d0 : n - d0; | ||
const t0h = await taggedHash('BIP0340/aux', rand); | ||
const t = d ^ t0h; | ||
const k0h = await taggedHash('BIP0340/nonce', pad32b(t), P.toRawX(), m); | ||
const k0 = mod(k0h, n); | ||
if (k0 === 0n) | ||
throw new Error('Creation of signature failed. k is zero'); | ||
const R = Point.fromPrivateKey(k0); | ||
const k = hasEvenY(R) ? k0 : n - k0; | ||
const e = await createChallenge(R.x, P, m); | ||
const sig = new SchnorrSignature(R.x, mod(k + e * d, n)); | ||
const isValid = await schnorrVerify(sig.toRawBytes(), m, P.toRawX()); | ||
if (!isValid) | ||
throw new Error('Invalid signature produced'); | ||
return typeof msgHash === 'string' ? sig.toHex() : sig.toRawBytes(); | ||
} | ||
async function schnorrVerify(signature, msgHash, publicKey) { | ||
const sig = signature instanceof SchnorrSignature ? signature : SchnorrSignature.fromHex(signature); | ||
const m = typeof msgHash === 'string' ? hexToBytes(msgHash) : msgHash; | ||
const P = normalizePublicKey(publicKey); | ||
const e = await createChallenge(sig.r, P, m); | ||
const sG = Point.fromPrivateKey(sig.s); | ||
const eP = P.multiply(e); | ||
const R = sG.subtract(eP); | ||
if (R.equals(Point.BASE) || !hasEvenY(R) || R.x !== sig.r) | ||
return false; | ||
return true; | ||
} | ||
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; | ||
}, | ||
Signature: SchnorrSignature, | ||
getPublicKey: schnorrGetPublicKey, | ||
sign: schnorrSign, | ||
verify: schnorrVerify, | ||
}; | ||
@@ -734,0 +775,0 @@ Point.BASE._setWindowSize(8); |
{ | ||
"name": "noble-secp256k1", | ||
"version": "1.1.0", | ||
"version": "1.1.1", | ||
"description": "Noble secp256k1. Very fast, high-security, auditable, 0-dep, 1-file ECDSA & Schnorr.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -5,4 +5,6 @@ # 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 deterministic ECDSA signature scheme from RFC6979. Supports **Schnorr** signatures as per BIP0340. | ||
ECDH key agreement protocol and signature schemes. | ||
Supports deterministic **ECDSA** from RFC6979 and **Schnorr** signatures from BIP0340. | ||
Algorithmically resistant to timing attacks. Tested against thousands of vectors from tiny-secp256k1. | ||
@@ -133,5 +135,16 @@ | ||
##### `schnorr.getPublicKey(privateKey)` | ||
```typescript | ||
function schnorrGetPublicKey(privateKey: Uint8Array): Uint8Array; | ||
function schnorrGetPublicKey(privateKey: string): string; | ||
``` | ||
Returns 32-byte public key. *Warning:* it is incompatible with non-schnorr pubkey. | ||
Specifically, its *y* coordinate may be flipped. See BIP0340 for clarification. | ||
##### `schnorr.sign(hash, privateKey)` | ||
```typescript | ||
function schnorrSign(msgHash: Uint8Array, privateKey: Uint8Array, auxilaryRandom?: Uint8Array): Promise<schnorr.SignResult>; | ||
function schnorrSign(msgHash: Uint8Array, privateKey: Uint8Array, auxilaryRandom?: Uint8Array): Promise<Uint8Array>; | ||
function schnorrSign(msgHash: string, privateKey: string, auxilaryRandom?: string): Promise<string>; | ||
``` | ||
@@ -144,7 +157,7 @@ | ||
- `auxilaryRandom?: Uint8Array` — optional 32 random bytes. By default, the method gathers cryptogarphically secure random. | ||
- Returns Schnorr signature. | ||
- Returns Schnorr signature in Hex format. | ||
##### `schnorr.verify(signature, hash, publicKey)` | ||
```typescript | ||
function schnorrVerify(signature: Uint8Array, msgHash: Uint8Array, publicKey: Uint8Array): boolean | ||
function schnorrVerify(signature: Uint8Array | string, msgHash: Uint8Array | string, publicKey: Uint8Array | string): boolean | ||
``` | ||
@@ -236,4 +249,4 @@ - `signature: Uint8Array | string | { r: bigint, s: bigint }` - object returned by the `sign` function | ||
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 | ||
schnorr.sign x 252 ops/sec @ 3ms/op | ||
schnorr.verify x 319 ops/sec @ 3ms/op | ||
@@ -240,0 +253,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
47935
910
281