commit dd80eb36907f0c8f5d01145cf240f49fdd56f5e9
parent 0ac2780a5a11f54461a4fba5c460582f2e1c555d
Author: Armin Preiml <apreiml@strohwolke.at>
Date: Thu, 7 Mar 2024 14:50:04 +0100
add crypto::ec
Add the ec interface adopted from BearSSL
Signed-off-by: Armin Preiml <apreiml@strohwolke.at>
Diffstat:
4 files changed, 161 insertions(+), 0 deletions(-)
diff --git a/crypto/ec/README b/crypto/ec/README
@@ -0,0 +1,10 @@
+The crypto::ec module provides implementations for a selection of eliptic
+curves and their operations.
+
+This is a low-level module which implements cryptographic primitives. Direct use
+of cryptographic primitives is not recommended for non-experts, as incorrect use
+of these primitives can easily lead to the introduction of security
+vulnerabilities. Non-experts are advised to use the high-level operations
+available in the top-level [[crypto::]] module.
+
+Be advised that Hare's cryptography implementations have not been audited.
diff --git a/crypto/ec/keygen.ha b/crypto/ec/keygen.ha
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use io;
+
+
+// Generates a random private key scalar suitable for given curve 'c'.
+// 'rand' must be cryptographic random stream like the one provided by
+// [[crypto::random::stream]].
+export fn keygen(c: *curve, priv: []u8, rand: io::handle) (size | io::error) =
+ c.keygen(c, priv, rand);
+
+// A keygen that generates random keys until one is found that fits within
+// the order of curve 'c'.
+fn mask_keygen(
+ c: *curve,
+ priv: []u8,
+ rand: io::handle
+) (size | io::error) = {
+ const order = c.order();
+ assert(len(priv) == len(order));
+ assert(order[0] != 0);
+
+ // mask all bits until including the highest value one.
+ let mask = order[0];
+ mask |= (mask >> 1);
+ mask |= (mask >> 2);
+ mask |= (mask >> 4);
+
+ for (true) {
+ match (io::readall(rand, priv)?) {
+ case let s: size =>
+ assert(s == len(priv));
+ case io::EOF =>
+ return (0: io::underread): io::error;
+ };
+ priv[0] &= mask;
+
+ if (validate_scalar(c, priv) is void) {
+ return len(priv);
+ };
+ };
+};
+
diff --git a/crypto/ec/types.ha b/crypto/ec/types.ha
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+use io;
+
+
+// Interface for common operations over a specific curve.
+//
+// The encoding of points depends on the curve. For the NIST curves
+// ([[p256]], [[p384]] and [[p521]] the point is required to be
+// uncompressed with a leading byte of value 0x04. The coordinates must be of
+// length 'pointsz' / 2, left padded by 0x0.
+//
+// Scalar values must be provided in big-endian encoding. They MUST be non zero
+// and less than the order, otherwise result values will be indeterminate and
+// an error code is not guaranteed.
+export type curve = struct {
+ // Size in bytes of an encoded point.
+ pointsz: size,
+
+ // Returns the order of the subgroup generated by the conventional
+ // generator. Unsigned big-endian encoding is used.
+ order: *fn () const []u8, // XXX: change to const []u8, when possible
+
+ // Get the conventional generator as an encoded curve point.
+ generator: *fn () const []u8, // XXX: change to const []u8, when possible
+
+ // Multiply curve point 'p' by scalar 'x'. The result is stored in 'r'.
+ // Returns a value > 0 on success.
+ //
+ // Point 'p' must be a valid point on the curve subgroup. If this is
+ // not the case the function fails with 0 as result.
+ //
+ // On error the results in 'p' are indeterminate.
+ mul: *fn (p: []u8, x: []u8) u32,
+
+ // Multiply the generator by the scalar 'x' and write the result to 'r'.
+ //
+ // Returns the encoded point length in bytes.
+ mulgen: *fn (r: []u8, x: []u8) size,
+
+ // Multiply two curve points ('a' and 'b') by two integers ('x' and 'y')
+ // and stores the sum in 'a' ('a' = 'a' * 'x' + 'b' * 'y').
+ //
+ // If an empty slice is given as 'b', the curve generator is used
+ // instead of 'b'.
+ //
+ // Returns 0 in case of failure. Validates that the provided points are
+ // part of the relevant curve subgroup.
+ //
+ // Returns a value > 0 on success and 0 otherwise.
+ muladd: *fn (a: []u8, b: []u8, x: []u8, y: []u8) u32,
+
+ // Generate a private key from given random seed 'rand'. The function
+ // may read repeatedly from 'rand' until a suitable key is found.
+ //
+ // Returns the size of bytes read into 'priv' on success or
+ // [[io::error]], if reading from 'rand' failed.
+ keygen: *fn (c: *curve, priv: []u8, rand: io::handle) (size | io::error),
+};
+
+// Invalid curve parameter.
+export type invalid = !void;
+
diff --git a/crypto/ec/validate.ha b/crypto/ec/validate.ha
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: MPL-2.0
+// (c) Hare authors <https://harelang.org>
+
+// Checks whether the point is encoded in the curves point format. Does NOT
+// check if it is a valid point on the curve. For such point validation use
+// [[validate_point]].
+export fn validate_pointformat(c: *curve, p: []u8) (void | invalid) = {
+ if (len(p) != c.pointsz || p[0] != 0x04) {
+ return invalid;
+ };
+};
+
+// Checks if given point is properly encoded and a valid point on given curve
+// 'c'. This operation is quite expensive. Note that in any case point
+// validation will be done on every mul and muladd operation.
+export fn validate_point(c: *curve, p: []u8) (void | invalid) = {
+ validate_pointformat(c, p)?;
+
+ static let scalarbuf: [133]u8 = [0...];
+ let scalarbuf = scalarbuf[..len(c.order())];
+ scalarbuf[len(scalarbuf) - 1] = 1;
+
+ if (c.mul(p, scalarbuf) == 0) {
+ return invalid;
+ };
+};
+
+// Validates if given scalar is less than the curve order and greater then zero.
+export fn validate_scalar(c: *curve, n: []u8) (void | invalid) = {
+ const order = c.order();
+ let cc: u16 = 0;
+ let zz: u8 = 0;
+ for (let i = len(n); i > 0; i -= 1) {
+ // subtraction with carry
+ cc = ((n[i - 1]: u16 - order[i - 1] - cc) >> 8) & 1;
+ zz |= n[i - 1];
+ };
+
+ // cc == 0 means the carry is not set because order < priv
+ if (cc == 0 || zz == 0) {
+ return invalid;
+ };
+};