Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
name: Unit Tests

permissions: {}

on:
push:
branches: [ main, master ]
Expand All @@ -11,14 +13,19 @@ jobs:

runs-on: ubuntu-latest

permissions:
contents: read

strategy:
matrix:
node-version: [14.x, 15.x, 16.x, 17.x, 18.x]
node-version: [22.x, 24.x, 26.x]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,16 @@ This is just a quick outline of what each function does.
* [Explanation](https://soatok.blog/2020/08/27/soatoks-guide-to-side-channel-attacks/#integer-division)
* Returns an integer.
* `resize(buf, size)` - Return a resized `Uint8Array` object (to side-step memory access leakage)
* `select(x, a, b)` - Read it as a ternary. If `x` is true, returns `a`. Otherwise, returns `b`.
* `select(x, a, b)` - Read it as a ternary. If `x` is `1`, returns `a`. If it is `0`, returns `b`.
* [Explanation](https://soatok.blog/2020/08/27/soatoks-guide-to-side-channel-attacks/#conditional-select)
* `x` must be a `boolean`
* `x` must be the number `0` or `1`
* `a` must be a `Uint8Array`
* `b` must be a `Uint8Array`
* Throws an `Error` if `a.length !== b.length`
* `select_ints(x, a, b)` - Read it as a ternary. If `x` is even, returns `a`. Otherwise, returns `b`.
* `select_ints(x, a, b)` - Read it as a ternary. If `x` is `1`, returns `a`. If it is `0`, returns `b`.
(You should pass `1` or `0` for `x`).
* [Explanation](https://soatok.blog/2020/08/27/soatoks-guide-to-side-channel-attacks/#conditional-select)
* `x` must be a `boolean`
* `x` must be the number `0` or `1`
* `a` must be a `number`
* `b` must be a `number`
* `trim_zeroes_left(buf)`
Expand Down
18 changes: 16 additions & 2 deletions lib/bignum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,25 @@ export function gcd(a: Uint8Array, b: Uint8Array): Uint8Array {
* @returns {boolean}
*/
export function is_nonzero(num: Uint8Array): boolean {
return is_nonzero_flag(num) === 1;
}

/**
* Return 1 if this number is nonzero, or 0 otherwise.
*
* This numeric form is used as input to the selection functions so that a
* secret-derived boolean is never passed through V8's ToBoolean machinery.
*
* @param {Uint8Array} num
* @returns {number}
*/
function is_nonzero_flag(num: Uint8Array): number {
let d: number = 0;
for (let i: number = num.length - 1; i >= 0; i--) {
d |= num[i];
}
return d !== 0;
// d is in [0, 255], so this maps zero to 0 and every other value to 1.
return (d + 0xff) >>> 8;
}

/**
Expand Down Expand Up @@ -315,7 +329,7 @@ export function pow(a: Uint8Array, n: Uint8Array): Uint8Array {
rshift1(e, true);
Sprime = multiply(S, S);
S = select(
is_nonzero(e),
is_nonzero_flag(e),
Sprime,
normalize(S, Sprime.length)
);
Expand Down
19 changes: 12 additions & 7 deletions lib/select.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { int32 } from './int32';

/**
* If TRUE, return left; else, return right.
* If choice is 1, return left; if it is 0, return right.
*
* @param {boolean} returnLeft
* @param {number} choice (0 or 1)
* @param {Uint8Array} left
* @param {Uint8Array} right
* @returns {Uint8Array}
*/
export function select(returnLeft: boolean, left: Uint8Array, right: Uint8Array): Uint8Array {
export function select(choice: number, left: Uint8Array, right: Uint8Array): Uint8Array {
if (left.length !== right.length) {
throw new Error('select() expects two Uint8Array objects of equal length');
}
/*
If returnLeft, mask = 0xFF; else, mask = 0x00;
If choice is 1, mask = 0xFF; else, mask = 0x00.

Keep this as subtraction from positive zero. Unary negation produces -0
for a zero choice, which V8 represents differently from the Smi value -1.
*/
const mask: number = (-!!returnLeft) & 0xff;
const mask: number = (0 - (choice & 1)) & 0xff;
const out: Uint8Array = new Uint8Array(left.length);
for (let i: number = 0; i < left.length; i++) {
out[i] = right[i] ^ ((left[i] ^ right[i]) & mask);
Expand All @@ -35,7 +38,8 @@ export function select_alt(choice: number, m: Uint8Array, n: Uint8Array): Uint8A
if (m.length !== n.length) {
throw new Error('Both Uint8Arrays must be the same length');
}
const mask = (-choice) & 0xff;
// See select(): subtraction from positive zero avoids materializing -0.
const mask = (0 - (choice & 1)) & 0xff;
const out = new Uint8Array(m.length);
for (let i: number = 0; i < out.length; i++) {
out[i] = n[i] ^ ((m[i] ^ n[i]) & mask);
Expand All @@ -55,7 +59,8 @@ export function select_ints(returnLeft: number, left: number, right: number): nu
/*
If returnLeft, mask = 0xFFFFFFFF; else, mask = 0x00000000;
*/
const mask: number = (-(returnLeft & 1)) & 0xfffffffff;
// See select(): subtraction from positive zero avoids materializing -0.
const mask: number = 0 - (returnLeft & 1);
return right ^ ((left ^ right) & mask);
}

Expand Down
Loading