Files
MeshCore/test/test_ed25519_compact_scalar.py

148 lines
6.4 KiB
Python

"""Check compact ref10 scalars against independent integer arithmetic."""
import ctypes
import itertools
from pathlib import Path
import random
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
ED25519 = ROOT / "lib/ed25519"
ORDER = 2**252 + 27742317777372353535851937790883648493
EDGES = (0, 1, ORDER - 1, ORDER, ORDER + 1, 2**256 - 1)
def octets(value, size):
return value.to_bytes(size, "little")
class Ed25519CompactScalarTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.directory = tempfile.TemporaryDirectory()
cls.libraries = []
for compact in (0, 1):
library_path = Path(cls.directory.name) / f"scalar-{compact}.so"
subprocess.run(["cc", "-std=c99", "-O2", "-Wall", "-Wextra", "-Werror",
"-shared", "-fPIC", f"-DED25519_COMPACT_SCALAR={compact}",
str(ED25519 / "sc.c"), "-o", str(library_path)], check=True)
library = ctypes.CDLL(str(library_path))
library.sc_reduce.argtypes = [ctypes.c_void_p]
library.sc_reduce.restype = None
library.sc_muladd.argtypes = [ctypes.c_void_p] * 4
library.sc_muladd.restype = None
cls.libraries.append(library)
@classmethod
def tearDownClass(cls):
cls.directory.cleanup()
def test_reduction_matches_integer_oracle_and_standard(self):
rng = random.Random(0xED25519)
values = list(EDGES) + [2**512 - 1]
values += [ORDER * factor + delta for factor in (2, 7, 1000000)
for delta in (-1, 0, 1)]
values += [1 << bit for bit in range(512)]
values += [rng.getrandbits(512) for _ in range(4000)]
for value in values:
original = octets(value, 64)
expected = octets(value % ORDER, 32)
for compact, library in enumerate(self.libraries):
with self.subTest(value=value, compact=compact):
data = ctypes.create_string_buffer(original, 64)
library.sc_reduce(data)
self.assertEqual(data.raw[:32], expected)
self.assertEqual(data.raw[32:], original[32:])
def test_multiply_add_matches_integer_oracle_and_aliases(self):
rng = random.Random(0x25519)
triples = list(itertools.product(EDGES, repeat=3))
triples += [tuple(rng.getrandbits(256) for _ in range(3)) for _ in range(4000)]
for a, b, c in triples:
expected = octets((a * b + c) % ORDER, 32)
for compact, library in enumerate(self.libraries):
for alias in range(4):
with self.subTest(a=a, b=b, c=c, compact=compact, alias=alias):
values = [ctypes.create_string_buffer(octets(value, 32), 32)
for value in (a, b, c)]
output = ctypes.create_string_buffer(32) if alias == 3 else values[alias]
library.sc_muladd(output, *values)
self.assertEqual(output.raw, expected)
def test_compact_scalars_under_address_and_undefined_sanitizers(self):
rng = random.Random(0x8032)
reductions = [0, ORDER - 1, ORDER, ORDER + 1, 2**512 - 1]
reductions += [rng.getrandbits(512) for _ in range(64)]
triples = list(itertools.product(EDGES, repeat=3))
triples += [tuple(rng.getrandbits(256) for _ in range(3)) for _ in range(64)]
def array(value, size):
return "{" + ",".join(str(byte) for byte in octets(value, size)) + "}"
reduce_vectors = ",\n".join(
"{" + array(value, 64) + "," + array(value % ORDER, 32) + "}"
for value in reductions)
muladd_vectors = ",\n".join(
"{" + ",".join(array(value, 32) for value in (a, b, c, (a * b + c) % ORDER)) + "}"
for a, b, c in triples)
source = r'''
#include <assert.h>
#include <string.h>
#include "sc.h"
struct ReduceVector { unsigned char input[64], expected[32]; };
struct MuladdVector { unsigned char a[32], b[32], c[32], expected[32]; };
static const struct ReduceVector reductions[] = { @REDUCTIONS@ };
static const struct MuladdVector triples[] = { @TRIPLES@ };
int main(void) {
unsigned char data[64], a[32], b[32], c[32], separate[32];
unsigned int i, alias;
for (i = 0; i < sizeof(reductions) / sizeof(reductions[0]); ++i) {
memcpy(data, reductions[i].input, sizeof(data));
sc_reduce(data);
assert(memcmp(data, reductions[i].expected, 32) == 0);
assert(memcmp(data + 32, reductions[i].input + 32, 32) == 0);
}
for (i = 0; i < sizeof(triples) / sizeof(triples[0]); ++i) {
for (alias = 0; alias < 4; ++alias) {
unsigned char *out;
memcpy(a, triples[i].a, 32);
memcpy(b, triples[i].b, 32);
memcpy(c, triples[i].c, 32);
out = alias == 0 ? a : alias == 1 ? b : alias == 2 ? c : separate;
sc_muladd(out, a, b, c);
assert(memcmp(out, triples[i].expected, 32) == 0);
}
}
return 0;
}
'''.replace("@REDUCTIONS@", reduce_vectors).replace("@TRIPLES@", muladd_vectors)
with tempfile.TemporaryDirectory() as directory:
harness = Path(directory) / "scalars.c"
harness.write_text(source)
executable = Path(directory) / "scalars"
subprocess.run(["cc", "-std=c99", "-O1", "-g", "-Wall", "-Wextra", "-Werror",
"-fsanitize=address,undefined", "-fno-sanitize-recover=all", "-no-pie",
"-DED25519_COMPACT_SCALAR=1", "-I", str(ED25519),
str(harness), str(ED25519 / "sc.c"), "-o", str(executable)], check=True)
subprocess.run([str(executable)], check=True)
def test_only_stm32_companion_profiles_enable_compact_scalars(self):
profiles = []
for ini in (ROOT / "variants").glob("*/platformio.ini"):
section = ""
for line in ini.read_text().splitlines():
if line.startswith("["):
section = line
if "ED25519_COMPACT_SCALAR" in line:
profiles.append((ini.parent.name, section))
self.assertEqual(sorted(profiles), [
("rak3x72", "[env:RAK_3x72_companion_radio_usb]"),
("wio-e5-mini", "[env:wio-e5-mini_companion_radio_usb]"),
])
if __name__ == "__main__":
unittest.main()