hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 905e9986566e33998a675eedb91f77c163f1a8f9
parent c1b553ed5326df5a109d76910c48428d2debaed3
Author: lukechampine <luke.champine@gmail.com>
Date:   Thu, 14 Apr 2022 13:55:25 -0400

crypto::ed25519: add package

Signed-off-by: lukechampine <luke.champine@gmail.com>

Diffstat:
Acrypto/ed25519/+test.ha | 47+++++++++++++++++++++++++++++++++++++++++++++++
Acrypto/ed25519/ed25519.ha | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrypto/ed25519/edwards25519.ha | 361+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mscripts/gen-stdlib | 12++++++++++++
Mstdlib.mk | 35+++++++++++++++++++++++++++++++++++
5 files changed, 583 insertions(+), 0 deletions(-)

diff --git a/crypto/ed25519/+test.ha b/crypto/ed25519/+test.ha @@ -0,0 +1,47 @@ +// License: MPL-2.0 +// (c) 2022 Luke Champine <luke.champine@gmail.com> +use bytes; +use encoding::hex; +use strings; + +@test fn roundtrip() void = { + let seed: seed = [1...]; + let priv_key: privatekey = [0...]; + private_init(&priv_key, &seed); + let pub_key = skey_getpublic(&priv_key); + + const msg = strings::toutf8("hello, world!"); + let sig = sign(&priv_key, msg); + assert(verify(&pub_key, msg, &sig)); + + let bad_pub_key = pub_key; + bad_pub_key[0] ^= 1; + const bad_msg = strings::toutf8("HELLO, WORLD!"); + let bad_sig = sig; + bad_sig[0] ^= 1; + + assert(!verify(&bad_pub_key, msg, &sig)); + assert(!verify(&pub_key, bad_msg, &sig)); + assert(!verify(&pub_key, msg, &bad_sig)); +}; + +@test fn golden() void = { + // TODO https://todo.sr.ht/~sircmpwn/hare/596 + let priv_key: privatekey = [0...]; + let priv_key_u8 = hex::decode("8ed7a797b9cea8a8370d419136bcdf683b759d2e3c6947f17e13e2485aa9d420b49f3a78b1c6a7fca8f3466f33bc0e929f01fba04306c2a7465f46c3759316d9") as []u8; + priv_key[..] = priv_key_u8[..]; + free(priv_key_u8); + const pub_key = skey_getpublic(&priv_key); + + let msg = hex::decode("a750c232933dc14b1184d86d8b4ce72e16d69744ba69818b6ac33b1d823bb2c3") as []u8; + defer free(msg); + + let good_sig: privatekey = [0...]; + let sig_u8 = hex::decode("04266c033b91c1322ceb3446c901ffcf3cc40c4034e887c9597ca1893ba7330becbbd8b48142ef35c012c6ba51a66df9308cb6268ad6b1e4b03e70102495790b") as []u8; + good_sig[..] = sig_u8[..]; + free(sig_u8); + + const sig = sign(&priv_key, msg); + assert(bytes::equal(sig, good_sig)); + assert(verify(&pub_key, msg, &sig)); +}; diff --git a/crypto/ed25519/ed25519.ha b/crypto/ed25519/ed25519.ha @@ -0,0 +1,128 @@ +// License: MPL-2.0 +// (c) 2022 Luke Champine <luke.champine@gmail.com> + +// Implements the Ed25519 signature scheme. +// +// This implementation is a straightforward port of TweetNaCl, +// with the API of crypto/ed25519 from the Go standard library. +use bytes; +use crypto::sha512; +use hash; + +// The size of an Ed25519 seed. +export def SEEDSZ: size = 32; + +// The size of an Ed25519 public key. +export def PUBLICKEYSZ: size = 32; + +// The size of an Ed25519 private key. +export def PRIVATEKEYSZ: size = 64; + +// The size of an Ed25519 signature. +export def SIGNATURESZ: size = 64; + +export type privatekey = [PRIVATEKEYSZ]u8; +export type publickey =[PUBLICKEYSZ]u8; +export type seed = [SEEDSZ]u8; + +// Derives a new Ed25519 private key from a given seed. The seed must be +// initialized to cryptographically random data; [[crypto::random]] is +// recommended for this purpose. +export fn private_init(out: *privatekey, seed: *seed) void = { + let h: [64]u8 = [0...]; + let sha = sha512::sha512(); + hash::write(&sha, seed[..]); + hash::finish(&sha, h[..]); + hash::close(&sha); + + let s: scalar = [0...]; + s[..] = h[..SCALARSZ]; + scalar_clamp(&s); + + let A = point { ... }; + scalarmult_base(&A, &s); + let A_bytes: [POINTSZ]u8 = [0...]; + point_encode(&A_bytes, &A); + + out[0..SEEDSZ] = seed[..]; + out[SEEDSZ..PRIVATEKEYSZ] = A_bytes[..]; +}; + +// Derive the public key for a given private key. +export fn skey_getpublic(priv: *privatekey) publickey = { + let pk: publickey = [0...]; + pk[0..] = priv[SEEDSZ..]; + return pk; +}; + +// Signs a message with a private key, returning the signature. +export fn sign(priv: *privatekey, msg: []u8) [SIGNATURESZ]u8 = { + let h: [64]u8 = [0...]; + let sha = sha512::sha512(); + hash::write(&sha, priv[0..SEEDSZ]); + hash::finish(&sha, h); + let esk: scalar = [0...]; + esk[..] = h[0..32]; + scalar_clamp(&esk); + + hash::reset(&sha); + hash::write(&sha, h[32..64]); + hash::write(&sha, msg); + let msg_digest: [64]u8 = [0...]; + hash::finish(&sha, msg_digest); + let msg_reduced: scalar = [0...]; + scalar_reduce(&msg_reduced, &msg_digest); + + let R = point {...}; + scalarmult_base(&R, &msg_reduced); + let R_bytes: [POINTSZ]u8 = [0...]; + point_encode(&R_bytes, &R); + + hash::reset(&sha); + hash::write(&sha, R_bytes[..]); + hash::write(&sha, priv[32..64]); + hash::write(&sha, msg); + let hram: [64]u8 = [0...]; + hash::finish(&sha, hram); + hash::close(&sha); + let hram_reduced: scalar = [0...]; + scalar_reduce(&hram_reduced, &hram); + + let s: scalar = [0...]; + scalar_multiply_add(&s, &hram_reduced, &esk, &msg_reduced); + + let sig: [SIGNATURESZ]u8 =[0...]; + sig[0..32] = R_bytes[..]; + sig[32..64] = s[..]; + return sig; +}; + +// Given a public key, verifies a signature produced with the +// corresponding private key for a given message, returning true if the +// signature is valid and false otherwise. +export fn verify(pub: *publickey, msg: []u8, sig: *[SIGNATURESZ]u8) bool = { + let A = point { ... }; + if (!point_decode(&A, pub)) { + return false; + }; + + let sha = sha512::sha512(); + hash::write(&sha, sig[0..32]); + hash::write(&sha, pub[..]); + hash::write(&sha, msg); + let hram: [64]u8 = [0...]; + hash::finish(&sha, hram); + + let hram_reduced: scalar = [0...]; + scalar_reduce(&hram_reduced, &hram); + let check_R = point { ... }; + scalarmult(&check_R, &A, &hram_reduced); + + let s: scalar = [0...]; + s[..] = sig[32..64]; + scalarmult_base(&A, &s); + point_add(&check_R, &check_R, &A); + let R_bytes: [POINTSZ]u8 = [0...]; + point_encode(&R_bytes, &check_R); + return bytes::equal(R_bytes, sig[0..32]); +}; diff --git a/crypto/ed25519/edwards25519.ha b/crypto/ed25519/edwards25519.ha @@ -0,0 +1,361 @@ +// License: MPL-2.0 +// (c) 2022 Luke Champine <luke.champine@gmail.com> + +def FIELDSZ: size = 16; +type elem = [FIELDSZ]i64; + +const feZero: elem = [0...]; +const feOne: elem = [1, 0...]; +const D: elem = [0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]; +const D2: elem = [0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406]; +const X: elem = [0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]; +const Y: elem = [0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]; +const I: elem = [0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]; + +fn fe_reduce(fe: *elem) void = { + let carry: i64 = 0; + for (let i = 0z; i < FIELDSZ; i += 1) { + carry = fe[i] >> 16; + fe[i] -= (carry << 16); + if (i+1 < FIELDSZ) { + fe[i + 1] += carry; + } else { + fe[0] += (38 * carry); + }; + }; +}; + +fn fe_add(out: *elem, a: const *elem, b: const *elem) *elem = { + for (let i = 0z; i < FIELDSZ; i += 1) { + out[i] = a[i] + b[i]; + }; + return out; +}; + +fn fe_sub(out: *elem, a: const *elem, b: const *elem) *elem = { + for (let i = 0z; i < FIELDSZ; i += 1) { + out[i] = a[i] - b[i]; + }; + return out; +}; + +fn fe_negate(out: *elem, a: const *elem) *elem = { + return fe_sub(out, &feZero, a); +}; + +fn fe_mul(out: *elem, a: const *elem, b: const *elem) *elem = { + let prod: [31]i64 = [0...]; + for (let i = 0z; i < FIELDSZ; i += 1) { + for (let j = 0z; j < FIELDSZ; j += 1) { + prod[i + j] += a[i] * b[j]; + }; + }; + for (let i = 0; i < 15; i += 1) { + prod[i] += (38 * prod[i + 16]); + }; + out[0..FIELDSZ] = prod[0..FIELDSZ]; + fe_reduce(out); + fe_reduce(out); + return out; +}; + +fn fe_square(out: *elem, a: const *elem) *elem = { + return fe_mul(out, a, a); +}; + +// out = i ** (2**252 - 3) +fn fe_pow2523(out: *elem, a: *elem) *elem = { + let c: elem = [0...]; + c[..] = a[..]; + for (let i = 250i; i >= 0; i -= 1) { + fe_square(&c, &c); + if (i != 1) { + fe_mul(&c, &c, a); + }; + }; + out[..] = c[..]; + return out; +}; + +fn fe_inv(out: *elem, a: const *elem) *elem = { + let c: elem = [0...]; + c[..] = a[..]; + for (let i = 253i; i >= 0; i -= 1) { + fe_square(&c, &c); + if (i != 2 && i != 4) { + fe_mul(&c, &c, a); + }; + }; + out[..] = c[..]; + return out; +}; + +fn fe_parity(a: const *elem) u8 = { + let d: scalar = [0...]; + fe_encode(&d, a); + return d[0]&1; +}; + +// a == b -> 0 +// a != b -> 1 +fn fe_cmp(a: const *elem, b: const *elem) u8 = { + let x: scalar = [0...]; + fe_encode(&x, a); + let y: scalar = [0...]; + fe_encode(&y, a); + + // constant-time compare + let d: u32 = 0; + for (let i = 0z; i < SCALARSZ; i += 1) { + d |= x[i] ^ y[i]; + }; + return (1 & ((d - 1) >> 8): u8) - 1; +}; + +// swap p and q if bit is 1, otherwise noop +fn fe_swap(p: *elem, q: *elem, bit: u8) void = { + let c = ~(bit: u64 - 1): i64; + for (let i = 0z; i < FIELDSZ; i += 1) { + let t = c & (p[i] ^ q[i]); + p[i] ^= t; + q[i] ^= t; + }; +}; + +fn fe_encode(out: *scalar, a: const *elem) void = { + let m: elem = [0...]; + let t: elem = *a; + + fe_reduce(&t); + fe_reduce(&t); + fe_reduce(&t); + + for (let _i = 0; _i < 2; _i += 1) { + m[0] = t[0] - 0xffed; + for (let i = 1z; i < 15; i += 1) { + m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1); + m[i - 1] &= 0xffff; + }; + m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1); + let b = ((m[15] >> 16): u8) & 1; + m[14] &= 0xffff; + fe_swap(&t, &m, 1-b); + }; + + for (let i = 0z; i < FIELDSZ; i += 1) { + out[2*i+0] = (t[i] & 0xff) : u8; + out[2*i+1] = (t[i] >> 8) : u8; + }; +}; + +fn fe_decode(fe: *elem, in: *[SCALARSZ]u8) *elem = { + for (let i = 0z; i < FIELDSZ; i += 1) { + fe[i] = in[2 * i] : i64 + ((in[2 * i + 1] : i64) << 8); + }; + fe[15] &= 0x7fff; + return fe; +}; + + +def SCALARSZ: size = 32; +type scalar = [SCALARSZ]u8; + +const L: scalar = [ + 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, + 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, +]; + +fn scalar_clamp(s: *scalar) void = { + s[0] &= 248; + s[31] &= 127; + s[31] |= 64; +}; + +// r = x % -1 +fn scalar_mod_L(r: *scalar, x: *[64]i64) void = { + for (let i: i64 = 63; i >= 32; i -= 1) { + let carry: i64 = 0; + let j = i - 32; + for (j < i - 12; j += 1) { + x[j] += carry - 16 * x[i] * (L[j - (i - 32)]: i64); + carry = (x[j] + 128) >> 8; + x[j] -= carry << 8; + }; + x[j] += carry; + x[i] = 0; + }; + + let carry: i64 = 0; + for (let j = 0; j < 32; j += 1) { + x[j] += carry - (x[31] >> 4) * (L[j]: i64); + carry = x[j] >> 8; + x[j] &= 255; + }; + for (let j = 0; j < 32; j += 1) { + x[j] -= carry * (L[j]: i64); + }; + for (let i = 0; i < 32; i += 1) { + x[i+1] += x[i] >> 8; + r[i] = (x[i]&255): u8; + }; +}; + +fn scalar_reduce(r: *scalar, h: *[64]u8) void = { + let x: [64]i64 = [0...]; + for (let i = 0; i < 64; i += 1) { + x[i] = h[i]: i64; + }; + scalar_mod_L(r, &x); +}; + +// s = a*b + c +fn scalar_multiply_add(s: *scalar, a: *scalar, b: *scalar, c: *scalar) void = { + let x: [64]i64 = [0...]; + for (let i = 0; i < 32; i += 1) { + for (let j = 0; j < 32; j += 1) { + x[i+j] += (a[i]: i64) * (b[j]: i64); + }; + }; + for (let i = 0; i < 32; i += 1) { + x[i] += (c[i]: i64); + }; + scalar_mod_L(s, &x); +}; + + +def POINTSZ: size = 32; + +type point = struct { + x: elem, + y: elem, + z: elem, + t: elem, +}; + +// out = p += q +fn point_add(out: *point, p: *point, q: *point) *point = { + let a: elem = [0...]; + let b: elem = [0...]; + let c: elem = [0...]; + let d: elem = [0...]; + let t: elem = [0...]; + let e: elem = [0...]; + let f: elem = [0...]; + let g: elem = [0...]; + let h: elem = [0...]; + + fe_sub(&a, &p.y, &p.x); + fe_sub(&t, &q.y, &q.x); + fe_mul(&a, &a, &t); + fe_add(&b, &p.x, &p.y); + fe_add(&t, &q.x, &q.y); + fe_mul(&b, &b, &t); + fe_mul(&c, &p.t, &q.t); + fe_mul(&c, &c, &D2); + fe_mul(&d, &p.z, &q.z); + fe_add(&d, &d, &d); + fe_sub(&e, &b, &a); + fe_sub(&f, &d, &c); + fe_add(&g, &d, &c); + fe_add(&h, &b, &a); + + fe_mul(&out.x, &e, &f); + fe_mul(&out.y, &h, &g); + fe_mul(&out.z, &g, &f); + fe_mul(&out.t, &e, &h); + return out; +}; + +// swap p and q if bit is 1, otherwise noop +fn point_swap(p: *point, q: *point, bit: u8) void = { + fe_swap(&p.x, &q.x, bit); + fe_swap(&p.y, &q.y, bit); + fe_swap(&p.z, &q.z, bit); + fe_swap(&p.t, &q.t, bit); +}; + +// p = q * s +fn scalarmult(p: *point, q: *point, s: const *scalar) *point = { + p.x[..] = feZero[..]; + p.y[..] = feOne[..]; + p.z[..] = feOne[..]; + p.t[..] = feZero[..]; + for (let i = 255; i >= 0; i -= 1) { + let b: u8 = (s[i/8]>>((i: u8)&7))&1; + point_swap(p, q, b); + point_add(q, q, p); + point_add(p, p, p); + point_swap(p, q, b); + }; + return p; +}; + +// p = B * s +fn scalarmult_base(p: *point, s: const *scalar) *point = { + let B = point {...}; + B.x[..] = X[..]; + B.y[..] = Y[..]; + B.z[..] = feOne[..]; + fe_mul(&B.t, &X, &Y); + + return scalarmult(p, &B, s); +}; + +fn point_encode(out: *[POINTSZ]u8, p: *point) void = { + let tx: elem = [0...]; + let ty: elem = [0...]; + let zi: elem = [0...]; + fe_inv(&zi, &p.z); + fe_mul(&tx, &p.x, &zi); + fe_mul(&ty, &p.y, &zi); + fe_encode(out, &ty); + out[31] ^= fe_parity(&tx) << 7; +}; + +fn point_decode(p: *point, in: *[POINTSZ]u8) bool = { + let t: elem = [0...]; + let chk: elem = [0...]; + let num: elem = [0...]; + let den: elem = [0...]; + let den2: elem = [0...]; + let den4: elem = [0...]; + let den6: elem = [0...]; + p.z[..] = feOne[..]; + fe_decode(&p.y, in); + fe_square(&num, &p.y); + fe_mul(&den, &num, &D); + fe_sub(&num, &num, &p.z); + fe_add(&den, &p.z, &den); + + fe_square(&den2, &den); + fe_square(&den4, &den2); + fe_mul(&den6, &den4, &den2); + fe_mul(&t, &den6, &num); + fe_mul(&t, &t, &den); + + fe_pow2523(&t, &t); + fe_mul(&t, &t, &num); + fe_mul(&t, &t, &den); + fe_mul(&t, &t, &den); + fe_mul(&p.x, &t, &den); + + fe_square(&chk, &p.x); + fe_mul(&chk, &chk, &den); + if (fe_cmp(&chk, &num) == 1) { + fe_mul(&p.x, &p.x, &I); + }; + + fe_square(&chk, &p.x); + fe_mul(&chk, &chk, &den); + if (fe_cmp(&chk, &num) == 1) { + return false; + }; + + if (fe_parity(&p.x) == (in[31]>>7)) { + fe_negate(&p.x, &p.x); + }; + + fe_mul(&p.t, &p.x, &p.y); + return true; +}; diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -440,6 +440,17 @@ crypto_curve25519() { fi } +crypto_ed25519() { + if [ $testing -eq 0 ] + then + gen_srcs crypto::ed25519 ed25519.ha edwards25519.ha + gen_ssa crypto::ed25519 + else + gen_srcs crypto::ed25519 ed25519.ha edwards25519.ha +test.ha + gen_ssa crypto::ed25519 bytes crypto::sha512 hash encoding::hex strings + fi +} + dirs() { gen_srcs dirs \ xdg.ha @@ -1307,6 +1318,7 @@ crypto::sha1 crypto::sha256 crypto::sha512 crypto::curve25519 +crypto::ed25519 datetime dirs encoding::base64 diff --git a/stdlib.mk b/stdlib.mk @@ -264,6 +264,12 @@ stdlib_deps_any+=$(stdlib_crypto_curve25519_any) stdlib_crypto_curve25519_linux=$(stdlib_crypto_curve25519_any) stdlib_crypto_curve25519_freebsd=$(stdlib_crypto_curve25519_any) +# gen_lib crypto::ed25519 (any) +stdlib_crypto_ed25519_any=$(HARECACHE)/crypto/ed25519/crypto_ed25519-any.o +stdlib_deps_any+=$(stdlib_crypto_ed25519_any) +stdlib_crypto_ed25519_linux=$(stdlib_crypto_ed25519_any) +stdlib_crypto_ed25519_freebsd=$(stdlib_crypto_ed25519_any) + # gen_lib datetime (any) stdlib_datetime_any=$(HARECACHE)/datetime/datetime-any.o stdlib_deps_any+=$(stdlib_datetime_any) @@ -935,6 +941,17 @@ $(HARECACHE)/crypto/curve25519/crypto_curve25519-any.ssa: $(stdlib_crypto_curve2 @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ncrypto::curve25519 \ -t$(HARECACHE)/crypto/curve25519/crypto_curve25519.td $(stdlib_crypto_curve25519_any_srcs) +# crypto::ed25519 (+any) +stdlib_crypto_ed25519_any_srcs= \ + $(STDLIB)/crypto/ed25519/ed25519.ha \ + $(STDLIB)/crypto/ed25519/edwards25519.ha + +$(HARECACHE)/crypto/ed25519/crypto_ed25519-any.ssa: $(stdlib_crypto_ed25519_any_srcs) $(stdlib_rt) + @printf 'HAREC \t$@\n' + @mkdir -p $(HARECACHE)/crypto/ed25519 + @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ncrypto::ed25519 \ + -t$(HARECACHE)/crypto/ed25519/crypto_ed25519.td $(stdlib_crypto_ed25519_any_srcs) + # datetime (+any) stdlib_datetime_any_srcs= \ $(STDLIB)/datetime/arithmetic.ha \ @@ -2236,6 +2253,12 @@ testlib_deps_any+=$(testlib_crypto_curve25519_any) testlib_crypto_curve25519_linux=$(testlib_crypto_curve25519_any) testlib_crypto_curve25519_freebsd=$(testlib_crypto_curve25519_any) +# gen_lib crypto::ed25519 (any) +testlib_crypto_ed25519_any=$(TESTCACHE)/crypto/ed25519/crypto_ed25519-any.o +testlib_deps_any+=$(testlib_crypto_ed25519_any) +testlib_crypto_ed25519_linux=$(testlib_crypto_ed25519_any) +testlib_crypto_ed25519_freebsd=$(testlib_crypto_ed25519_any) + # gen_lib datetime (any) testlib_datetime_any=$(TESTCACHE)/datetime/datetime-any.o testlib_deps_any+=$(testlib_datetime_any) @@ -2924,6 +2947,18 @@ $(TESTCACHE)/crypto/curve25519/crypto_curve25519-any.ssa: $(testlib_crypto_curve @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ncrypto::curve25519 \ -t$(TESTCACHE)/crypto/curve25519/crypto_curve25519.td $(testlib_crypto_curve25519_any_srcs) +# crypto::ed25519 (+any) +testlib_crypto_ed25519_any_srcs= \ + $(STDLIB)/crypto/ed25519/ed25519.ha \ + $(STDLIB)/crypto/ed25519/edwards25519.ha \ + $(STDLIB)/crypto/ed25519/+test.ha + +$(TESTCACHE)/crypto/ed25519/crypto_ed25519-any.ssa: $(testlib_crypto_ed25519_any_srcs) $(testlib_rt) $(testlib_bytes_$(PLATFORM)) $(testlib_crypto_sha512_$(PLATFORM)) $(testlib_hash_$(PLATFORM)) $(testlib_encoding_hex_$(PLATFORM)) $(testlib_strings_$(PLATFORM)) + @printf 'HAREC \t$@\n' + @mkdir -p $(TESTCACHE)/crypto/ed25519 + @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ncrypto::ed25519 \ + -t$(TESTCACHE)/crypto/ed25519/crypto_ed25519.td $(testlib_crypto_ed25519_any_srcs) + # datetime (+any) testlib_datetime_any_srcs= \ $(STDLIB)/datetime/arithmetic.ha \