ristretto255.benchmarks.js (254 lines of code) (raw):
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/* global ristretto255 */
if (!Uint8Array.prototype.fill) {
// eslint-disable-next-line
Uint8Array.prototype.fill = Array.prototype.fill;
}
/* Set-up */
const t00 = performance.now();
const NUM_OF_REPS = 100;
const ristrettoECPoints = [];
const ristrettoSerializedPoints = [];
const hashes = [];
const scalars = [];
const h = ristretto255.unsafe.point.alloc();
/* Generate random ristretto points */
for (let i = 0; i < NUM_OF_REPS; i++) {
const ristrettoECPoint = ristretto255.unsafe.point.getRandom();
ristrettoECPoints.push(ristrettoECPoint);
ristrettoSerializedPoints.push(
ristretto255.unsafe.point.toBytes(ristrettoECPoint)
);
const hTemp = new Uint8Array(32);
for (let j = 0; j < 32; j++) {
hTemp[j] = i;
}
hashes.push(hTemp);
scalars.push(ristretto255.scalar.getRandom());
}
const functions = [
{
// ristretto points are represented as Uint8Array(32)
name: 'High-level ristretto255 group operations',
functions: [
{
name: 'getRandom',
description: 'Generate a random group element',
execute: () => ristretto255.getRandom()
},
{
name: 'isValid',
description: 'Check if a value represents a valid element',
execute: i => ristretto255.isValid(ristrettoSerializedPoints[i])
},
{
name: 'fromHash',
description:
'Hash to group: generate a group element from 64-element byte array, e.g. an output of SHA-512',
execute: i => ristretto255.fromHash(hashes[i])
},
{
name: 'add',
description: 'Add two group elements',
execute: i =>
ristretto255.add(
ristrettoSerializedPoints[i],
ristrettoSerializedPoints[(i + 1) % NUM_OF_REPS]
)
},
{
name: 'sub',
description: 'Subtract two group elements',
execute: i =>
ristretto255.sub(
ristrettoSerializedPoints[i],
ristrettoSerializedPoints[(i + 1) % NUM_OF_REPS]
)
},
{
name: 'scalarMultBase',
description: 'Multiply a generator of the group by a scalar',
execute: i => ristretto255.scalarMultBase(scalars[i])
},
{
name: 'scalarMult',
description: 'Multiply a group element by a scalar',
execute: i =>
ristretto255.scalarMult(scalars[i], ristrettoSerializedPoints[i])
}
]
},
{
name: 'Scalar operations',
functions: [
{
name: 'scalar.getRandom',
description: 'Generate a random scalar',
execute: () => ristretto255.scalar.getRandom()
},
{
name: 'scalar.invert',
description: 'Invert a scalar',
execute: i => ristretto255.scalar.invert(scalars[i])
},
{
name: 'scalar.negate',
description: 'Negate a scalar',
execute: i =>
ristretto255.scalar.negate(scalars[i], scalars[i % NUM_OF_REPS])
},
{
name: 'scalar.add',
description: 'Add two scalars',
execute: i =>
ristretto255.scalar.add(scalars[i], scalars[i % NUM_OF_REPS])
},
{
name: 'scalar.sub',
description: 'Subtract two scalars',
execute: i =>
ristretto255.scalar.sub(scalars[i], scalars[i % NUM_OF_REPS])
},
{
name: 'scalar.mul',
description: 'Multiply two scalars',
execute: i =>
ristretto255.scalar.mul(scalars[i], scalars[i % NUM_OF_REPS])
}
]
},
{
name: 'Low-level unsafe functions (unless if used by a cryptographer)',
functions: [
{
name: 'unsafe.point.toBytes',
description:
'Serialize a curve25519 point to ristretto255 group element',
execute: i => ristretto255.unsafe.point.toBytes(ristrettoECPoints[i])
},
{
name: 'unsafe.point.fromBytes',
description:
'Deserialize a curve25519 point from ristretto255 group element',
execute: i =>
ristretto255.unsafe.point.fromBytes(h, ristrettoSerializedPoints[i])
},
{
name: 'unsafe.point.getRandom',
description:
'Generate a random ristretto255 group element represented as curve25519 point',
execute: () => ristretto255.unsafe.point.getRandom()
},
{
name: 'unsafe.point.fromHash',
description:
'Generate a ristretto255 group element represented as curve25519 point from a 64 elements byte array such as an output of SHA512',
execute: i => ristretto255.unsafe.point.fromHash(hashes[i])
},
{
name: 'unsafe.point.add',
description: 'Add two curve25519 points',
execute: i =>
ristretto255.unsafe.point.add(
ristrettoECPoints[i],
ristrettoECPoints[i % NUM_OF_REPS]
)
},
{
name: 'unsafe.point.sub',
description: 'Subtract two curve25519 points',
execute: i =>
ristretto255.unsafe.point.sub(
ristrettoECPoints[i],
ristrettoECPoints[i % NUM_OF_REPS]
)
},
{
name: 'unsafe.point.scalarMultBase',
description: "Multiply a curve25519's base point by a scalar",
execute: i =>
ristretto255.unsafe.point.scalarMultBase(
ristrettoECPoints[i],
scalars[i]
)
},
{
name: 'unsafe.point.scalarMult',
description: "Multiply a curve25519's point by a scalar",
execute: i =>
ristretto255.unsafe.point.scalarMult(
ristrettoECPoints[i],
ristrettoECPoints[i % NUM_OF_REPS],
scalars[i]
)
}
]
}
];
const template = (groupName, results) => `
<h2>${groupName}</h2>
<div class="benchmarks">
<table class="benchmarks">
<tr>
<th>Function name</th>
<th>Time in ms</th>
<th>Comments</th>
</tr>
${results
.map(result => {
return `
<tr>
<td>${result.functionName}</td>
<td>${result.timing}</td>
<td>${result.description}</td>
</tr>
`;
})
.join('')}
</table>
</div>`;
function average(data) {
const sum = data.reduce(function add(acc, value) {
return acc + value;
}, 0);
const avg = sum / data.length;
return avg;
}
// The credit for computing std goes to
// https://derickbailey.com/2014/09/21/calculating-standard-deviation-with-array-map-and-array-reduce-in-javascript/
function standardDeviation(values) {
const avg = average(values);
const squareDiffs = values.map(function f(value) {
const diff = value - avg;
const sqrDiff = diff * diff;
return sqrDiff;
});
const stdDev = Math.sqrt(average(squareDiffs));
return stdDev;
}
const generateBenchmarks = () => {
functions.forEach(group => {
const results = group.functions.map(func => {
// const t0 = performance.now();
const timing = [];
for (let i = 0; i < NUM_OF_REPS; i++) {
const t0 = performance.now();
func.execute(i);
const t1 = performance.now();
timing.push(t1 - t0);
}
// compute the average
const avg = average(timing);
const std = standardDeviation(timing);
return {
functionName: func.name,
description: func.description,
timing: `${avg.toFixed(
2
)}<small font-size="smaller"> ± ${std.toFixed(2)}</small>`
};
});
document.getElementById('container').innerHTML += template(
group.name,
results
);
});
};
generateBenchmarks();
const t01 = performance.now();
document.getElementById('total_time').innerHTML = `Benchmark runtime: ${(
(t01 - t00) /
1000
).toFixed(2)} sec with ${NUM_OF_REPS} reps on each operation.`;