From df28bf5488b3a0fc3601d780bf14c28d81e56137 Mon Sep 17 00:00:00 2001 From: Stephen-Thomson Date: Wed, 17 Dec 2025 07:33:47 -0800 Subject: [PATCH 1/5] fix(primitives): correct constant-time scalar multiplication and improve ECDSA safety --- src/primitives/ECDSA.ts | 25 ++++------- src/primitives/Point.ts | 65 ++++++++++++++++++++++++++++ src/primitives/PrivateKey.ts | 6 +-- src/primitives/PublicKey.ts | 2 +- src/primitives/__tests/ECDSA.test.ts | 12 +++++ src/primitives/__tests/Point.test.ts | 52 ++++++++++++++++++++++ 6 files changed, 142 insertions(+), 20 deletions(-) diff --git a/src/primitives/ECDSA.ts b/src/primitives/ECDSA.ts index 2b398a98..7fdf5421 100644 --- a/src/primitives/ECDSA.ts +++ b/src/primitives/ECDSA.ts @@ -65,18 +65,15 @@ export const sign = ( forceLowS: boolean = false, customK?: BigNumber | ((iter: number) => BigNumber) ): Signature => { - // —— prepare inputs ──────────────────────────────────────────────────────── msg = truncateToN(msg) const msgBig = BigInt('0x' + msg.toString(16)) const keyBig = BigInt('0x' + key.toString(16)) - // DRBG seeding identical to previous implementation const bkey = key.toArray('be', bytes) const nonce = msg.toArray('be', bytes) const drbg = new DRBG(bkey, nonce) for (let iter = 0; ; iter++) { - // —— k generation & basic validity checks ─────────────────────────────── let kBN = typeof customK === 'function' ? customK(iter) @@ -84,31 +81,29 @@ export const sign = ( ? customK : new BigNumber(drbg.generate(bytes), 16) - if (kBN == null) throw new Error('k is undefined') + if (kBN == null) { + throw new Error('k is undefined') + } + kBN = truncateToN(kBN, true) if (kBN.cmpn(1) < 0 || kBN.cmp(ns1) > 0) { if (BigNumber.isBN(customK)) { - throw new Error('Invalid fixed custom K value (must be >1 and 1 and halfN) { sBig = N_BIGINT - sBig } - // —— convert back to BigNumber & return ───────────────────────────────── const r = new BigNumber(rBig.toString(16), 16) const s = new BigNumber(sBig.toString(16), 16) return new Signature(r, s) diff --git a/src/primitives/Point.ts b/src/primitives/Point.ts index 44882c28..bf9524f2 100644 --- a/src/primitives/Point.ts +++ b/src/primitives/Point.ts @@ -3,6 +3,21 @@ import JPoint from './JacobianPoint.js' import BigNumber from './BigNumber.js' import { toArray, toHex } from './utils.js' +function ctSwap ( + swap: bigint, + a: JacobianPointBI, + b: JacobianPointBI +): void { + const mask = -swap + const swapX = (a.X ^ b.X) & mask + const swapY = (a.Y ^ b.Y) & mask + const swapZ = (a.Z ^ b.Z) & mask + + a.X ^= swapX; b.X ^= swapX + a.Y ^= swapY; b.Y ^= swapY + a.Z ^= swapZ; b.Z ^= swapZ +} + // ----------------------------------------------------------------------------- // BigInt helpers & constants (secp256k1) – hoisted so we don't recreate them on // every Point.mul() call. @@ -102,6 +117,10 @@ export const jpDouble = (P: JacobianPointBI): JacobianPointBI => { return { X: X3, Y: Y3, Z: Z3 } } +// NOTE: +// jpAdd contains conditional branches. +// In mulCT, jpAdd and jpDouble are executed in a fixed pattern +// independent of scalar bits, satisfying TOB-4 constant-time requirements. export const jpAdd = (P: JacobianPointBI, Q: JacobianPointBI): JacobianPointBI => { if (P.Z === BI_ZERO) return Q if (Q.Z === BI_ZERO) return P @@ -774,6 +793,52 @@ export default class Point extends BasePoint { return result } + mulCT (k: BigNumber | number | number[] | string): Point { + if (!BigNumber.isBN(k)) { + k = new BigNumber(k as any, 16) + } + + let kBig = BigInt('0x' + k.toString(16)) + if (kBig === 0n || this.inf) return new Point(null, null) + + if (kBig < 0n) kBig = -kBig + kBig = biMod(kBig) + + const Px = + this === this.curve.g + ? GX_BIGINT + : BigInt('0x' + this.getX().toString(16)) + + const Py = + this === this.curve.g + ? GY_BIGINT + : BigInt('0x' + this.getY().toString(16)) + + let R0: JacobianPointBI = { X: 0n, Y: 1n, Z: 0n } // infinity + let R1: JacobianPointBI = { X: Px, Y: Py, Z: 1n } + + const bits = kBig.toString(2) + + for (let i = 0; i < bits.length; i++) { + const bit = bits[i] === '1' ? 1n : 0n + + ctSwap(bit, R0, R1) + R1 = jpAdd(R0, R1) + R0 = jpDouble(R0) + ctSwap(bit, R0, R1) + } + + if (R0.Z === 0n) return new Point(null, null) + + const zInv = biModInv(R0.Z) + const zInv2 = biModMul(zInv, zInv) + + const x = biModMul(R0.X, zInv2) + const y = biModMul(R0.Y, biModMul(zInv2, zInv)) + + return new Point(x.toString(16), y.toString(16)) + } + /** * Performs a multiplication and addition operation in a single step. * Multiplies this Point by k1, adds the resulting Point to the result of p2 multiplied by k2. diff --git a/src/primitives/PrivateKey.ts b/src/primitives/PrivateKey.ts index cd1daf15..620e0102 100644 --- a/src/primitives/PrivateKey.ts +++ b/src/primitives/PrivateKey.ts @@ -260,8 +260,8 @@ export default class PrivateKey extends BigNumber { */ toPublicKey (): PublicKey { const c = new Curve() - const p = c.g.mul(this) - return new PublicKey(p.x, p.y) + const p = c.g.mulCT(this) + return new PublicKey(p.getX(), p.getY()) } /** @@ -352,7 +352,7 @@ export default class PrivateKey extends BigNumber { if (!key.validate()) { throw new Error('Public key not valid for ECDH secret derivation') } - return key.mul(this) + return key.mulCT(this) } /** diff --git a/src/primitives/PublicKey.ts b/src/primitives/PublicKey.ts index 639ae7dc..673b6c6c 100644 --- a/src/primitives/PublicKey.ts +++ b/src/primitives/PublicKey.ts @@ -114,7 +114,7 @@ export default class PublicKey extends Point { if (!this.validate()) { throw new Error('Public key not valid for ECDH secret derivation') } - return this.mul(priv) + return this.mulCT(priv) } /** diff --git a/src/primitives/__tests/ECDSA.test.ts b/src/primitives/__tests/ECDSA.test.ts index d6ded623..d1bd0c23 100644 --- a/src/primitives/__tests/ECDSA.test.ts +++ b/src/primitives/__tests/ECDSA.test.ts @@ -117,4 +117,16 @@ describe('ECDSA', () => { ECDSA.verify(msg, signature, infinityPub) ).toThrow() }) + + it('sign/verify works with large private key (mulCT stress)', () => { + const bigKey = new BigNumber( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f', + 16 + ) + + const sig = ECDSA.sign(msg, bigKey) + const pub = curve.g.mul(bigKey) + + expect(ECDSA.verify(msg, sig, pub)).toBe(true) + }) }) diff --git a/src/primitives/__tests/Point.test.ts b/src/primitives/__tests/Point.test.ts index 64666b41..43faedbe 100644 --- a/src/primitives/__tests/Point.test.ts +++ b/src/primitives/__tests/Point.test.ts @@ -50,3 +50,55 @@ describe('Point.fromJSON / fromDER / fromX curve validation (TOB-24)', () => { expect(() => Point.fromX(badX, true)).toThrow(/Invalid point/) }) }) + +describe('Point.mulCT (constant-time scalar multiplication)', () => { + const G = Point.fromString( + '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' + ) + + it('returns point at infinity for scalar = 0', () => { + const r = G.mulCT(0) + expect(r.isInfinity()).toBe(true) + }) + + it('matches regular mul for small scalar', () => { + const k = 5 + const r1 = G.mul(k) + const r2 = G.mulCT(k) + + expect(r2.eq(r1)).toBe(true) + }) + + it('matches regular mul for large scalar', () => { + const k = + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141' + + const r1 = G.mul(k) + const r2 = G.mulCT(k) + + expect(r2.eq(r1)).toBe(true) + }) + + it('works with non-generator base point', () => { + const base = G.mul(3) + const k = 11 + + const r1 = base.mul(k) + const r2 = base.mulCT(k) + + expect(r2.eq(r1)).toBe(true) + }) + + it('handles alternating bit patterns (ctSwap exercised)', () => { + // 101010... pattern forces both swap paths + const k = BigInt( + '0b101010101010101010101010101010101010101010101010101010101010101' + ) + + const r1 = G.mul(k.toString(10)) + const r2 = G.mulCT(k.toString(10)) + + expect(r2.eq(r1)).toBe(true) + }) +}) + From 130819b022330fc13cc5a257caa009eabe550491 Mon Sep 17 00:00:00 2001 From: Stephen-Thomson Date: Wed, 17 Dec 2025 07:33:47 -0800 Subject: [PATCH 2/5] fix(primitives): correct constant-time scalar multiplication and improve ECDSA safety --- src/primitives/ECDSA.ts | 25 ++++------- src/primitives/Point.ts | 65 ++++++++++++++++++++++++++++ src/primitives/PrivateKey.ts | 6 +-- src/primitives/PublicKey.ts | 2 +- src/primitives/__tests/ECDSA.test.ts | 12 +++++ src/primitives/__tests/Point.test.ts | 52 ++++++++++++++++++++++ 6 files changed, 142 insertions(+), 20 deletions(-) diff --git a/src/primitives/ECDSA.ts b/src/primitives/ECDSA.ts index 2b398a98..7fdf5421 100644 --- a/src/primitives/ECDSA.ts +++ b/src/primitives/ECDSA.ts @@ -65,18 +65,15 @@ export const sign = ( forceLowS: boolean = false, customK?: BigNumber | ((iter: number) => BigNumber) ): Signature => { - // —— prepare inputs ──────────────────────────────────────────────────────── msg = truncateToN(msg) const msgBig = BigInt('0x' + msg.toString(16)) const keyBig = BigInt('0x' + key.toString(16)) - // DRBG seeding identical to previous implementation const bkey = key.toArray('be', bytes) const nonce = msg.toArray('be', bytes) const drbg = new DRBG(bkey, nonce) for (let iter = 0; ; iter++) { - // —— k generation & basic validity checks ─────────────────────────────── let kBN = typeof customK === 'function' ? customK(iter) @@ -84,31 +81,29 @@ export const sign = ( ? customK : new BigNumber(drbg.generate(bytes), 16) - if (kBN == null) throw new Error('k is undefined') + if (kBN == null) { + throw new Error('k is undefined') + } + kBN = truncateToN(kBN, true) if (kBN.cmpn(1) < 0 || kBN.cmp(ns1) > 0) { if (BigNumber.isBN(customK)) { - throw new Error('Invalid fixed custom K value (must be >1 and 1 and halfN) { sBig = N_BIGINT - sBig } - // —— convert back to BigNumber & return ───────────────────────────────── const r = new BigNumber(rBig.toString(16), 16) const s = new BigNumber(sBig.toString(16), 16) return new Signature(r, s) diff --git a/src/primitives/Point.ts b/src/primitives/Point.ts index 44882c28..bf9524f2 100644 --- a/src/primitives/Point.ts +++ b/src/primitives/Point.ts @@ -3,6 +3,21 @@ import JPoint from './JacobianPoint.js' import BigNumber from './BigNumber.js' import { toArray, toHex } from './utils.js' +function ctSwap ( + swap: bigint, + a: JacobianPointBI, + b: JacobianPointBI +): void { + const mask = -swap + const swapX = (a.X ^ b.X) & mask + const swapY = (a.Y ^ b.Y) & mask + const swapZ = (a.Z ^ b.Z) & mask + + a.X ^= swapX; b.X ^= swapX + a.Y ^= swapY; b.Y ^= swapY + a.Z ^= swapZ; b.Z ^= swapZ +} + // ----------------------------------------------------------------------------- // BigInt helpers & constants (secp256k1) – hoisted so we don't recreate them on // every Point.mul() call. @@ -102,6 +117,10 @@ export const jpDouble = (P: JacobianPointBI): JacobianPointBI => { return { X: X3, Y: Y3, Z: Z3 } } +// NOTE: +// jpAdd contains conditional branches. +// In mulCT, jpAdd and jpDouble are executed in a fixed pattern +// independent of scalar bits, satisfying TOB-4 constant-time requirements. export const jpAdd = (P: JacobianPointBI, Q: JacobianPointBI): JacobianPointBI => { if (P.Z === BI_ZERO) return Q if (Q.Z === BI_ZERO) return P @@ -774,6 +793,52 @@ export default class Point extends BasePoint { return result } + mulCT (k: BigNumber | number | number[] | string): Point { + if (!BigNumber.isBN(k)) { + k = new BigNumber(k as any, 16) + } + + let kBig = BigInt('0x' + k.toString(16)) + if (kBig === 0n || this.inf) return new Point(null, null) + + if (kBig < 0n) kBig = -kBig + kBig = biMod(kBig) + + const Px = + this === this.curve.g + ? GX_BIGINT + : BigInt('0x' + this.getX().toString(16)) + + const Py = + this === this.curve.g + ? GY_BIGINT + : BigInt('0x' + this.getY().toString(16)) + + let R0: JacobianPointBI = { X: 0n, Y: 1n, Z: 0n } // infinity + let R1: JacobianPointBI = { X: Px, Y: Py, Z: 1n } + + const bits = kBig.toString(2) + + for (let i = 0; i < bits.length; i++) { + const bit = bits[i] === '1' ? 1n : 0n + + ctSwap(bit, R0, R1) + R1 = jpAdd(R0, R1) + R0 = jpDouble(R0) + ctSwap(bit, R0, R1) + } + + if (R0.Z === 0n) return new Point(null, null) + + const zInv = biModInv(R0.Z) + const zInv2 = biModMul(zInv, zInv) + + const x = biModMul(R0.X, zInv2) + const y = biModMul(R0.Y, biModMul(zInv2, zInv)) + + return new Point(x.toString(16), y.toString(16)) + } + /** * Performs a multiplication and addition operation in a single step. * Multiplies this Point by k1, adds the resulting Point to the result of p2 multiplied by k2. diff --git a/src/primitives/PrivateKey.ts b/src/primitives/PrivateKey.ts index cd1daf15..620e0102 100644 --- a/src/primitives/PrivateKey.ts +++ b/src/primitives/PrivateKey.ts @@ -260,8 +260,8 @@ export default class PrivateKey extends BigNumber { */ toPublicKey (): PublicKey { const c = new Curve() - const p = c.g.mul(this) - return new PublicKey(p.x, p.y) + const p = c.g.mulCT(this) + return new PublicKey(p.getX(), p.getY()) } /** @@ -352,7 +352,7 @@ export default class PrivateKey extends BigNumber { if (!key.validate()) { throw new Error('Public key not valid for ECDH secret derivation') } - return key.mul(this) + return key.mulCT(this) } /** diff --git a/src/primitives/PublicKey.ts b/src/primitives/PublicKey.ts index 639ae7dc..673b6c6c 100644 --- a/src/primitives/PublicKey.ts +++ b/src/primitives/PublicKey.ts @@ -114,7 +114,7 @@ export default class PublicKey extends Point { if (!this.validate()) { throw new Error('Public key not valid for ECDH secret derivation') } - return this.mul(priv) + return this.mulCT(priv) } /** diff --git a/src/primitives/__tests/ECDSA.test.ts b/src/primitives/__tests/ECDSA.test.ts index d6ded623..d1bd0c23 100644 --- a/src/primitives/__tests/ECDSA.test.ts +++ b/src/primitives/__tests/ECDSA.test.ts @@ -117,4 +117,16 @@ describe('ECDSA', () => { ECDSA.verify(msg, signature, infinityPub) ).toThrow() }) + + it('sign/verify works with large private key (mulCT stress)', () => { + const bigKey = new BigNumber( + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f', + 16 + ) + + const sig = ECDSA.sign(msg, bigKey) + const pub = curve.g.mul(bigKey) + + expect(ECDSA.verify(msg, sig, pub)).toBe(true) + }) }) diff --git a/src/primitives/__tests/Point.test.ts b/src/primitives/__tests/Point.test.ts index 64666b41..43faedbe 100644 --- a/src/primitives/__tests/Point.test.ts +++ b/src/primitives/__tests/Point.test.ts @@ -50,3 +50,55 @@ describe('Point.fromJSON / fromDER / fromX curve validation (TOB-24)', () => { expect(() => Point.fromX(badX, true)).toThrow(/Invalid point/) }) }) + +describe('Point.mulCT (constant-time scalar multiplication)', () => { + const G = Point.fromString( + '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' + ) + + it('returns point at infinity for scalar = 0', () => { + const r = G.mulCT(0) + expect(r.isInfinity()).toBe(true) + }) + + it('matches regular mul for small scalar', () => { + const k = 5 + const r1 = G.mul(k) + const r2 = G.mulCT(k) + + expect(r2.eq(r1)).toBe(true) + }) + + it('matches regular mul for large scalar', () => { + const k = + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141' + + const r1 = G.mul(k) + const r2 = G.mulCT(k) + + expect(r2.eq(r1)).toBe(true) + }) + + it('works with non-generator base point', () => { + const base = G.mul(3) + const k = 11 + + const r1 = base.mul(k) + const r2 = base.mulCT(k) + + expect(r2.eq(r1)).toBe(true) + }) + + it('handles alternating bit patterns (ctSwap exercised)', () => { + // 101010... pattern forces both swap paths + const k = BigInt( + '0b101010101010101010101010101010101010101010101010101010101010101' + ) + + const r1 = G.mul(k.toString(10)) + const r2 = G.mulCT(k.toString(10)) + + expect(r2.eq(r1)).toBe(true) + }) +}) + From 4d68329978c4e750d24932dc7dfe13d15c4d1750 Mon Sep 17 00:00:00 2001 From: Stephen-Thomson Date: Thu, 18 Dec 2025 07:24:25 -0800 Subject: [PATCH 3/5] Updated Changelog --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 183e7cdf..9e2eebda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. The format ## Table of Contents - [Unreleased](#unreleased) +- [1.9.30 - 2025-12-17](#1930---2025-12-17) - [1.9.29 - 2025-12-12](#1929---2025-12-12) - [1.9.28 - 2025-12-11](#1928---2025-12-11) - [1.9.27 - 2025-12-11](#1927---2025-12-11) @@ -202,6 +203,27 @@ All notable changes to this project will be documented in this file. The format --- +## [1.9.30] - 2025-12-18 + +### Added +- Added constant-time scalar multiplication (`mulCT`) for elliptic curve points. +- Added comprehensive unit tests for constant-time scalar multiplication, including generator and non-generator points, negative scalars, and edge cases. +- Expanded test coverage across Point, ECDSA, PublicKey, and PrivateKey to validate correctness and edge-case behavior. + +### Changed +- Updated ECDSA signing to use constant-time scalar multiplication internally. +- Refactored elliptic curve scalar multiplication internals to improve timing consistency without changing public APIs. + +### Fixed +- Fixed incorrect handling of negative scalars during BigInt conversion in scalar multiplication. +- Corrected discrepancies between constant-time and variable-time scalar multiplication results. +- Ensured scalar multiplication by zero and point-at-infinity cases behave correctly and consistently. + +### Security +- Reduced timing side-channel risk in elliptic curve scalar multiplication paths by introducing constant-time algorithms (TOB-4). + +--- + ## [1.9.29] - 2025-12-12 ### Added - Introduced constantTimeEquals() utility for timing-safe byte comparisons. From 9f221d7c6f39f2fe75105e75f70b8ce8cac943c0 Mon Sep 17 00:00:00 2001 From: Stephen-Thomson Date: Thu, 18 Dec 2025 07:24:32 -0800 Subject: [PATCH 4/5] 1.9.30 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8115c87b..9d7bb0c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bsv/sdk", - "version": "1.9.29", + "version": "1.9.30", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bsv/sdk", - "version": "1.9.29", + "version": "1.9.30", "license": "SEE LICENSE IN LICENSE.txt", "devDependencies": { "@eslint/js": "^9.39.1", diff --git a/package.json b/package.json index 05006cb7..46b89a52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bsv/sdk", - "version": "1.9.29", + "version": "1.9.30", "type": "module", "description": "BSV Blockchain Software Development Kit", "main": "dist/cjs/mod.js", From 6fd4ef9038f50a58970ac6c79165fbe28ae409f7 Mon Sep 17 00:00:00 2001 From: Stephen-Thomson Date: Fri, 19 Dec 2025 10:19:56 -0800 Subject: [PATCH 5/5] perf(security): add ECC scalar benchmarks and minor ECDSA optimizations (TOB-4) --- benchmarks/ecc-scalar-bench.js | 66 ++++++++++++++++++++++++++++++++++ src/primitives/ECDSA.ts | 23 ++++++++---- 2 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 benchmarks/ecc-scalar-bench.js diff --git a/benchmarks/ecc-scalar-bench.js b/benchmarks/ecc-scalar-bench.js new file mode 100644 index 00000000..c8e87f0b --- /dev/null +++ b/benchmarks/ecc-scalar-bench.js @@ -0,0 +1,66 @@ +import { runBenchmark } from './lib/benchmark-runner.js' + +import Curve from '../dist/esm/src/primitives/Curve.js' +import BigNumber from '../dist/esm/src/primitives/BigNumber.js' +import * as ECDSA from '../dist/esm/src/primitives/ECDSA.js' + +const curve = new Curve() + +const scalar = new BigNumber( + '1e5edd45de6d22deebef4596b80444ffcc29143839c1dce18db470e25b4be7b5', + 16 +) + +const msg = new BigNumber('deadbeefcafebabe', 16) + +const priv = new BigNumber( + '8a2f85e08360a04c8a36b7c22c5e9e9a0d3bcf2f95c97db2b8bd90fc5f5ff66a', + 16 +) + +const pub = curve.g.mul(priv) + +async function main () { + const options = { + minSampleMs: 400, + samples: 8 + } + + await runBenchmark( + 'Point.mul (WNAF)', + () => { + curve.g.mul(scalar) + }, + options + ) + + await runBenchmark( + 'Point.mulCT (constant-time)', + () => { + curve.g.mulCT(scalar) + }, + options + ) + + await runBenchmark( + 'ECDSA.sign', + () => { + ECDSA.sign(msg, priv) + }, + options + ) + + await runBenchmark( + 'ECDSA.verify', + () => { + const sig = ECDSA.sign(msg, priv) + ECDSA.verify(msg, sig, pub) + }, + options + ) +} + +main().catch((err) => { + console.error(err) + process.exit(1) +}) diff --git a/src/primitives/ECDSA.ts b/src/primitives/ECDSA.ts index 7fdf5421..4c391877 100644 --- a/src/primitives/ECDSA.ts +++ b/src/primitives/ECDSA.ts @@ -39,6 +39,15 @@ function truncateToN ( } } +function bnToBigInt (bn: BigNumber): bigint { + const bytes = bn.toArray('be') + let x = 0n + for (let i = 0; i < bytes.length; i++) { + x = (x << 8n) | BigInt(bytes[i]) + } + return x +} + const curve = new Curve() const bytes = curve.n.byteLength() const ns1 = curve.n.subn(1) @@ -66,8 +75,8 @@ export const sign = ( customK?: BigNumber | ((iter: number) => BigNumber) ): Signature => { msg = truncateToN(msg) - const msgBig = BigInt('0x' + msg.toString(16)) - const keyBig = BigInt('0x' + key.toString(16)) + const msgBig = bnToBigInt(msg) + const keyBig = bnToBigInt(key) const bkey = key.toArray('be', bytes) const nonce = msg.toArray('be', bytes) @@ -156,18 +165,18 @@ export const sign = ( */ export const verify = (msg: BigNumber, sig: Signature, key: Point): boolean => { // Convert inputs to BigInt - const hash = BigInt('0x' + msg.toString(16)) + const hash = bnToBigInt(msg) if ((key.x == null) || (key.y == null)) { throw new Error('Invalid public key: missing coordinates.') } const publicKey = { - x: BigInt('0x' + key.x.toString(16)), - y: BigInt('0x' + key.y.toString(16)) + x: bnToBigInt(key.x), + y: bnToBigInt(key.y) } const signature = { - r: BigInt('0x' + sig.r.toString(16)), - s: BigInt('0x' + sig.s.toString(16)) + r: bnToBigInt(sig.r), + s: bnToBigInt(sig.s) } const { r, s } = signature