-
Notifications
You must be signed in to change notification settings - Fork 29
Implement constant-time scalar multiplication for EC points (TOB-4) #419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
df28bf5
130819b
4d68329
9f221d7
2edb29a
6fd4ef9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
| }) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -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) | ||||||||
|
||||||||
| kBig = biMod(kBig) | |
| kBig = biMod(kBig) | |
| if (kBig === 0n) return new Point(null, null) |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mulCT method doesn't handle negative scalars correctly. Unlike the mul method (lines 757-791), which negates the result when the input scalar is negative, mulCT only takes the absolute value and returns the result without negation. This means mulCT(-k) will incorrectly return the same result as mulCT(k) instead of the negated point. This breaks mathematical correctness and API consistency with the existing mul method.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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()) | ||||||
|
||||||
| return new PublicKey(p.getX(), p.getY()) | |
| return new PublicKey(p.x, p.y) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The date in the section header is inconsistent with the Table of Contents. Line 8 references "1.9.30 - 2025-12-17" but this section header shows "2025-12-18". These dates should match.