Implement Ed25519 (#2297)

* Implement Ed25519
* Overloading addition with a pointer would not work.
* Added @addr macro.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
This commit is contained in:
Airtz
2025-07-19 01:13:15 +02:00
committed by GitHub
parent e9aee55714
commit b1a22b5002
6 changed files with 786 additions and 32 deletions

View File

@@ -96,6 +96,14 @@ macro anycast(any v, $Type) @builtin
return ($Type*)v.ptr; return ($Type*)v.ptr;
} }
macro @addr(#val) @builtin
{
$if $defined(&#val):
return &#val;
$else
return &&#val;
$endif
}
fn bool print_backtrace(String message, int backtraces_to_ignore) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem) fn bool print_backtrace(String message, int backtraces_to_ignore) @if (env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
{ {
void*[256] buffer; void*[256] buffer;

737
lib/std/crypto/ed25519.c3 Normal file
View File

@@ -0,0 +1,737 @@
/*
Ed25519 Digital Signature Algorithm
*/
module std::crypto::ed25519;
import std::hash::sha512;
alias PrivateKey = char[32];
alias PublicKey = char[PrivateKey.len];
alias Signature = char[2 * PublicKey.len];
<*
Generate a public key from a private key.
@param [in] private_key : "32 bytes of cryptographically secure random data"
@require private_key.len == PrivateKey.len
*>
fn PublicKey public_keygen(char[] private_key)
{
return pack(&&unproject(&&(BASE * expand_private_key(private_key)[:FBaseInt.len])));
}
<*
Sign a message.
@param [in] message
@param [in] private_key
@param [in] public_key
@require private_key.len == PrivateKey.len
@require public_key.len == PublicKey.len
*>
fn Signature sign(char[] message, char[] private_key, char[] public_key)
{
Signature r @noinit;
char[*] exp = expand_private_key(private_key);
Sha512 sha @noinit;
sha.init();
sha.update(exp[FBaseInt.len..]);
sha.update(message);
FBaseInt k = from_bytes(&&sha.final());
r[:F25519Int.len] = pack(&&unproject(&&(BASE * k[..])))[..];
sha.init();
sha.update(r[:F25519Int.len]);
sha.update(public_key);
sha.update(message);
FBaseInt z = from_bytes(&&sha.final());
FBaseInt e = from_bytes(exp[:FBaseInt.len]);
r[F25519Int.len..] = (z * e + k)[..];
return r;
}
<*
Verify the signature of a message.
@param [in] message
@param [in] signature
@param [in] public_key
@require signature.len == Signature.len
@require public_key.len == PublicKey.len
*>
fn bool verify(char[] message, char[] signature, char[] public_key)
{
char ok = 1;
F25519Int lhs = pack(&&unproject(&&(BASE * signature[F25519Int.len..])));
Unpacking unp_p = unpack_on_curve((F25519Int*)public_key);
Projection p = project(&unp_p.point);
ok &= unp_p.on_curve;
Sha512 sha @noinit;
sha.init();
sha.update(signature[:F25519Int.len]);
sha.update(public_key);
sha.update(message);
FBaseInt z = from_bytes(&&sha.final());
p = p * z[..];
Unpacking unp_q = unpack_on_curve((F25519Int*)signature[:F25519Int.len]);
Projection q = project(&unp_q.point);
ok &= unp_q.on_curve;
p = p + q;
F25519Int rhs = pack(&&unproject(&p));
return (bool)(ok & eq(&lhs, &rhs));
}
// Base point for Ed25519. Generate a subgroup of order 2^252+0x14def9dea2f79cd65812631a5cf5d3ed
const Projection BASE @private =
{
x"1ad5258f602d56c9 b2a7259560c72c69 5cdcd6fd31e2a4c0 fe536ecdd3366921",
x"5866666666666666 6666666666666666 6666666666666666 6666666666666666",
x"a3ddb7a5b38ade6d f5525177809ff020 7de3ab648e4eea66 65768bd70f5f8767",
ONE
};
<*
Compute the pruned SHA-512 hash of a private key.
@param [in] private_key
@require private_key.len == PrivateKey.len
*>
fn char[sha512::HASH_SIZE] expand_private_key(char[] private_key) @local
{
char[*] r = sha512::hash(private_key);
r[0] &= 0b11111000;
r[FBaseInt.len - 1] &= 0b01111111;
r[FBaseInt.len - 1] |= 0b01000000;
return r;
}
/*
Operations on the twisted Edwards curve -x^2+y^2=1-121665/121666*x^2*y^2 over the prime field F_(2^255-19) (edwards25519)
The set of F_(2^255-19)-rational curve points is a group of order 2^3*(2^252+0x14def9dea2f79cd65812631a5cf5d3ed)
*/
module std::crypto::ed25519 @private;
// Affine coordinates.
struct Point
{
F25519Int x;
F25519Int y;
}
// Projective coordinates.
struct Projection
{
F25519Int x;
F25519Int y;
F25519Int t;
F25519Int z;
}
// Neutral.
const Projection NEUTRAL =
{
ZERO,
ONE,
ZERO,
ONE
};
<*
Convert affine to projective coordinates.
@param [&in] p
*>
fn Projection project(Point* p) => { p.x, p.y, p.x * p.y, ONE };
<*
Convert projective to affine coordinates.
@param [&in] p
*>
fn Point unproject(Projection* p)
{
Point r @noinit;
F25519Int inv = p.z.inv();
r.x = p.x * inv;
r.y = p.y * inv;
r.x.normalize();
r.y.normalize();
return r;
}
// d parameter for edwards25519 : -121665/121666
const F25519Int D = x"a3785913ca4deb75 abd841414d0a7000 98e879777940c78c 73fe6f2bee6c0352";
// 2*d
const F25519Int DD = x"59f1b226949bd6eb 56b183829a14e000 30d1f3eef2808e19 e7fcdf56dcd90624";
<*
Compress a point.
@param [&in] p
*>
fn F25519Int pack(Point* p)
{
Point r = *p;
r.x.normalize();
r.y.normalize();
r.y[^1] |= (r.x[0] & 1) << 7;
return r.y;
}
struct Unpacking
{
Point point;
char on_curve; // Non-zero if true.
}
<*
Uncompress a point. Check if it is on the curve.
@param [&in] encoding
*>
fn Unpacking unpack_on_curve(F25519Int* encoding)
{
Point p @noinit;
char parity = (*encoding)[^1] >> 7;
p.y = *encoding;
p.y[^1] &= 0b01111111;
F25519Int y2 = p.y * p.y;
F25519Int x2 = (D * y2 + ONE).inv() * (y2 - ONE);
F25519Int x = x2.sqrt();
p.x = f25519_select(&x, &&-x, (x[0] ^ parity) & 1);
F25519Int _x2 = p.x * p.x;
x2.normalize();
_x2.normalize();
return {p, eq(&x2, &_x2)};
}
macro Projection Projection.@add(&s, Projection #p) @operator(+) => s.add(@addr(#p));
<*
Addition.
@param [&in] s
*>
fn Projection Projection.add(&s, Projection* p) @operator(+)
{
Projection r @noinit;
F25519Int a = (s.y - s.x) * (p.y - p.x);
F25519Int b = (s.y + s.x) * (p.y + p.x);
F25519Int c = s.t * DD * p.t;
F25519Int d = (s.z * p.z).mul_s(2);
F25519Int e = b - a;
F25519Int f = d - c;
F25519Int g = d + c;
F25519Int h = b + a;
r.x = e * f;
r.y = g * h;
r.t = e * h;
r.z = f * g;
return r;
}
<*
Double a point.
@param [&in] s
*>
fn Projection Projection.twice(&s)
{
Projection r @noinit;
F25519Int a = s.x * s.x;
F25519Int b = s.y * s.y;
F25519Int c = (s.z * s.z).mul_s(2);
F25519Int d = s.x + s.y;
F25519Int e = d * d - a - b;
F25519Int g = b - a;
F25519Int f = g - c;
F25519Int h = -b - a;
r.x = e * f;
r.y = g * h;
r.t = e * h;
r.z = f * g;
return r;
}
<*
Variable base scalar multiplication.
@param [&in] s
@param [in] n
*>
fn Projection Projection.mul(&s, char[] n) @operator(*)
{
Projection r = NEUTRAL;
for (isz i = n.len << 3 - 1; i >= 0; i--)
{
r = r.twice();
Projection t = r + s;
char bit = n[i >> 3] >> (i & 7) & 1;
r.x = f25519_select(&r.x, &t.x, bit);
r.y = f25519_select(&r.y, &t.y, bit);
r.z = f25519_select(&r.z, &t.z, bit);
r.t = f25519_select(&r.t, &t.t, bit);
}
return r;
}
/*
Modular arithmetic over the prime field F_(2^255-19)
*/
module std::crypto::ed25519 @private;
typedef F25519Int = inline char[32];
const F25519Int ZERO = {};
const F25519Int ONE = {[0] = 1};
<*
Reduce an element with carry to at most 2^255+18 (32 bytes)
@param [&inout] s
*>
fn void F25519Int.reduce_carry(&s, uint carry)
{
// Reduce using 2^255 = 19 mod p
(*s)[^1] &= 0b01111111;
carry *= 19;
for (usz i; i < F25519Int.len; i++)
{
carry += (*s)[i];
(*s)[i] = (char)carry;
carry >>= 8;
}
}
<*
Reduce an element to at most 2^255-19
@param [&inout] s
*>
fn void F25519Int.normalize(&s)
{
s.reduce_carry((*s)[^1] >> 7);
// Substract p
F25519Int sub @noinit;
ushort c = 19;
for (usz i; i + 1 < F25519Int.len; i++)
{
c += (*s)[i];
sub[i] = (char)c;
c >>= 8;
}
c += (*s)[^1] - 0b10000000;
sub[^1] = (char)c;
*s = f25519_select(&sub, s, (char)(c >> 15));
}
<*
Constant-time equality comparison. Return is non-zero if true.
@param [&in] a
@param [&in] b
*>
fn char eq(F25519Int* a, F25519Int* b)
{
char e;
for (usz i; i < F25519Int.len; i++) e |= (*a)[i] ^ (*b)[i];
e |= (e >> 4);
e |= (e >> 2);
e |= (e >> 1);
return e ^ 1;
}
<*
Constant-time conditonal selection. Result is undefined if condition is neither 0 nor 1.
@param [&in] zero : "selected if condition is 0"
@param [&in] one : "selected if condition is 1"
*>
fn F25519Int f25519_select(F25519Int* zero, F25519Int* one, char condition)
{
F25519Int r @noinit;
for (usz i; i < F25519Int.len; i++) r[i] = (*zero)[i] ^ (-condition & ((*one)[i] ^ (*zero)[i]));
return r;
}
macro F25519Int F25519Int.@add(&s, F25519Int #n) @operator(+) => s.add(@addr(#n));
<*
Addition.
@param [&in] s
@param [&in] n
*>
fn F25519Int F25519Int.add(&s, F25519Int* n) @operator(+)
{
F25519Int r @noinit;
ushort c;
foreach (i, v : *s)
{
c >>= 8;
c += v + (*n)[i];
r[i] = (char)c;
}
r.reduce_carry(c >> 7);
return r;
}
macro F25519Int F25519Int.@sub(&s, F25519Int #n) @operator(-) => s.sub(@addr(#n));
<*
Substraction.
@param [&in] s
@param [&in] n
*>
fn F25519Int F25519Int.sub(&s, F25519Int* n) @operator(-)
{
// Compute s+2*p-n instead of s-n to avoid underflow.
F25519Int r @noinit;
uint c = (char)~(2 * 19 - 1);
for (usz i; i + 1 < F25519Int.len; i++)
{
c += 0b11111111_00000000 + (*s)[i] - (*n)[i];
r[i] = (char)c;
c >>= 8;
}
c += (*s)[^1] - (*n)[^1];
r[^1] = (char)c;
r.reduce_carry(c >> 7);
return r;
}
<*
Negation.
@param [&in] s
*>
fn F25519Int F25519Int.neg(&s) @operator(-)
{
// Compute 2*p-s instead of -s to avoid underflow.
F25519Int r @noinit;
uint c = (char)~(2 * 19 - 1);
foreach (i, v : ((char*)s)[:F25519Int.len - 1])
{
c += 0b11111111_00000000 - v;
r[i] = (char)c;
c >>= 8;
}
c -= (*s)[^1];
r[^1] = (char)c;
r.reduce_carry(c >> 7);
return r;
}
macro F25519Int F25519Int.@mul(&s, F25519Int #n) @operator(*) => s.mul(@addr(#n));
<*
Multiplication.
@param [&in] s
@param [&in] n
*>
fn F25519Int F25519Int.mul(&s, F25519Int* n) @operator(*)
{
F25519Int r @noinit;
uint c;
for (usz i = 0; i < F25519Int.len; i++)
{
c >>= 8;
for (usz j; j <= i; j++) c += (*s)[j] * (*n)[i - j];
// Reduce using 2^256 = 2*19 mod p
for (usz j = i + 1; j < F25519Int.len; j++) c += (*s)[j] * (*n)[^j - i] * 2 * 19;
r[i] = (char)c;
}
r.reduce_carry(c >> 7);
return r;
}
<*
Multiplication by a small element.
@param [&in] s
*>
fn F25519Int F25519Int.mul_s(&s, uint n)
{
F25519Int r @noinit;
uint c;
foreach (i, v : *s)
{
c >>= 8;
c += v * n;
r[i] = (char)c;
}
r.reduce_carry(c >> 7);
return r;
}
<*
Inverse an element.
@param [&in] s
*>
fn F25519Int F25519Int.inv(&s)
{
//Compute s^(p-2)
F25519Int r = *s;
for (usz i; i < 255 - 1 - 5; i++) r = r * r * *s;
r *= r;
r = r * r * s;
r *= r;
r = r * r * s;
r = r * r * s;
return r;
}
<*
Raise an element to the power of 2^252-3
@param [&in] s
*>
fn F25519Int F25519Int.pow_2523(&s) @local
{
F25519Int r = *s;
for (usz i; i < 252 - 1 - 2; i++) r = r * r * *s;
r = r * r;
r = r * r * s;
return r;
}
<*
Compute the square root of an element.
@param [&in] s
*>
fn F25519Int F25519Int.sqrt(&s)
{
F25519Int twice = s.mul_s(2);
F25519Int pow = twice.pow_2523();
return ((twice * pow * pow) - ONE) * s * pow;
}
/*
Modular arithmetic over the prime field F_(2^252+0x14def9dea2f79cd65812631a5cf5d3ed)
*/
module std::crypto::ed25519 @private;
import std::math;
typedef FBaseInt = inline char[32];
// Order of the field : 2^252+0x14def9dea2f79cd65812631a5cf5d3ed
const FBaseInt ORDER = x"edd3f55c1a631258 d69cf7a2def9de14 0000000000000000 0000000000000010";
<*
FBaseInterpret bytes as a normalized element.
@param [in] bytes
*>
fn FBaseInt from_bytes(char[] bytes)
{
FBaseInt r;
usz bitc = min(252 - 1, bytes.len << 3);
usz bytec = bitc >> 3;
usz mod = bitc & 7;
usz rem = bytes.len << 3 - bitc;
r[:bytec] = bytes[^bytec..];
if (mod)
{
r <<= mod;
r[0] |= bytes[^bytec + 1] >> (8 - mod);
}
for (isz i = rem - 1; i >= 0; i--)
{
r <<= 1;
r[0] |= bytes[i >> 3] >> (i & 7) & 1;
r = r.sub_l(&ORDER);
}
return r;
}
<*
Constant-time conditonal selection. Result is undefined if condition is neither 0 nor 1.
@param [&in] zero : "selected if condition is 0"
@param [&in] one : "selected if condition is 1"
*>
fn FBaseInt fbase_select(FBaseInt* zero, FBaseInt* one, char condition)
{
FBaseInt r @noinit;
for (usz i; i < FBaseInt.len; i++) r[i] = (*zero)[i] ^ (-condition & ((*one)[i] ^ (*zero)[i]));
return r;
}
macro FBaseInt FBaseInt.@add(&s, FBaseInt #n) @operator(+) => s.add(@addr(#n));
<*
Addition.
@param [&in] s
@param [&in] n
*>
fn FBaseInt FBaseInt.add(&s, FBaseInt* n) @operator(+)
{
FBaseInt r @noinit;
ushort c;
for (usz i; i < FBaseInt.len; i++)
{
c += (*s)[i] + (*n)[i];
r[i] = (char)c;
c >>= 8;
}
return r.sub_l(&ORDER);
}
<*
Substraction if RHS is less than LHS else identity.
@param [&in] s
@param [&in] n
*>
fn FBaseInt FBaseInt.sub_l(&s, FBaseInt* n)
{
FBaseInt sub @noinit;
ushort c;
for (usz i; i < FBaseInt.len; i++)
{
c = (*s)[i] - (*n)[i] - c;
sub[i] = (char)c;
c = (c >> 8) & 1;
}
return fbase_select(&sub, s, (char)c);
}
<*
Left shift.
@param [&in] s
*>
fn FBaseInt FBaseInt.shl(&s, usz n) @operator(<<)
{
FBaseInt r @noinit;
ushort c;
for (usz i; i < FBaseInt.len; i++)
{
c |= (*s)[i] << n;
r[i] = (char)c;
c >>= 8;
}
return r;
}
macro FBaseInt FBaseInt.@mul(&s, FBaseInt #n) @operator(*) => s.mul(@addr(#n));
<*
Multiplication.
@param [&in] s
@param [&in] n
*>
fn FBaseInt FBaseInt.mul(&s, FBaseInt* n) @operator(*)
{
FBaseInt r;
for (isz i = 252; i >= 0; i--)
{
r = (r << 1).sub_l(&ORDER);
r = fbase_select(&r, &&(r + s), (*n)[i >> 3] >> (i & 7) & 1);
}
return r;
}

View File

@@ -63,14 +63,17 @@
- `@links` on macros would not be added to calling functions. - `@links` on macros would not be added to calling functions.
- Fix `Formatter.print` returning incorrect size. - Fix `Formatter.print` returning incorrect size.
- A distinct type based on an array would yield .len == 0 - A distinct type based on an array would yield .len == 0
- Overloading addition with a pointer would not work.
### Stdlib changes ### Stdlib changes
- Improve contract for readline. #2280 - Improve contract for readline. #2280
- Added Whirlpool hash. - Added Whirlpool hash.
- Added Ed25519.
- Added string::bformat. - Added string::bformat.
- Virtual memory library. - Virtual memory library.
- New virtual emory arena allocator. - New virtual emory arena allocator.
- Added `WString.len`. - Added `WString.len`.
- Added `@addr` macro.
## 0.7.3 Change list ## 0.7.3 Change list

View File

@@ -964,6 +964,7 @@ typedef enum
OVERLOADS_COUNT = OVERLOAD_SHR_ASSIGN OVERLOADS_COUNT = OVERLOAD_SHR_ASSIGN
} OperatorOverload; } OperatorOverload;
#define OVERLOAD_NONE ((OperatorOverload)0)
typedef enum typedef enum
{ {
OS_TYPE_UNKNOWN, OS_TYPE_UNKNOWN,

View File

@@ -208,8 +208,10 @@ static inline bool sema_analyse_maybe_dead_expr(SemaContext *, Expr *expr, bool
static inline bool sema_insert_binary_overload(SemaContext *context, Expr *expr, Decl *overload, Expr *lhs, Expr *rhs, bool reverse); static inline bool sema_insert_binary_overload(SemaContext *context, Expr *expr, Decl *overload, Expr *lhs, Expr *rhs, bool reverse);
static bool sema_replace_with_overload(SemaContext *context, Expr *expr, Expr *left, Expr *right, Type *left_type, OperatorOverload* operator_overload_ref); static bool sema_replace_with_overload(SemaContext *context, Expr *expr, Expr *left, Expr *right, Type *left_type, OperatorOverload* operator_overload_ref);
// -- implementations INLINE bool sema_expr_analyse_ptr_add(SemaContext *context, Expr *expr, Expr *left, Expr *right, CanonicalType *left_type, CanonicalType *right_type, Type *cast_to_iptr, bool *failed_ref);
static Type *defer_iptr_cast(Expr *maybe_pointer);
// -- implementations
typedef struct typedef struct
{ {
@@ -7188,12 +7190,31 @@ static bool sema_binary_arithmetic_promotion(SemaContext *context, Expr *left, E
OperatorOverload *operator_overload_ref, OperatorOverload *operator_overload_ref,
bool *failed_ref) bool *failed_ref)
{ {
OperatorOverload overload = *operator_overload_ref;
if (type_is_user_defined(left_type) || type_is_user_defined(right_type)) if (type_is_user_defined(left_type) || type_is_user_defined(right_type))
{ {
if (!sema_replace_with_overload(context, parent, left, right, left_type, operator_overload_ref)) return false; if (!sema_replace_with_overload(context, parent, left, right, left_type, operator_overload_ref)) return false;
if (!*operator_overload_ref) return true; if (!*operator_overload_ref) return true;
} }
if (overload == OVERLOAD_PLUS)
{
Type *cast_to_iptr = defer_iptr_cast(left);
if (!cast_to_iptr) cast_to_iptr = defer_iptr_cast(right);
left_type = type_no_optional(left->type)->canonical;
right_type = type_no_optional(right->type)->canonical;
if (type_is_pointer_like(left_type))
{
*operator_overload_ref = OVERLOAD_NONE;
return sema_expr_analyse_ptr_add(context, parent, left, right, left_type, right_type, cast_to_iptr, failed_ref);
}
if (type_is_pointer_like(right_type))
{
*operator_overload_ref = OVERLOAD_NONE;
return sema_expr_analyse_ptr_add(context, parent, right, left, right_type, left_type, cast_to_iptr, failed_ref);
}
}
Type *max = cast_numeric_arithmetic_promotion(type_find_max_type(left_type, right_type)); Type *max = cast_numeric_arithmetic_promotion(type_find_max_type(left_type, right_type));
if (!max || (!type_underlying_is_numeric(max) && !(allow_bool_vec && type_flat_is_bool_vector(max)))) if (!max || (!type_underlying_is_numeric(max) && !(allow_bool_vec && type_flat_is_bool_vector(max))))
{ {
@@ -7525,46 +7546,15 @@ static bool sema_expr_analyse_add(SemaContext *context, Expr *expr, Expr *left,
// this is safe in the pointer case actually. // this is safe in the pointer case actually.
if (!sema_binary_analyse_subexpr(context, left, right)) return false; if (!sema_binary_analyse_subexpr(context, left, right)) return false;
Type *cast_to_iptr = defer_iptr_cast(left);
if (!cast_to_iptr) cast_to_iptr = defer_iptr_cast(right);
Type *left_type = type_no_optional(left->type)->canonical; Type *left_type = type_no_optional(left->type)->canonical;
Type *right_type = type_no_optional(right->type)->canonical; Type *right_type = type_no_optional(right->type)->canonical;
bool right_is_pointer = type_is_pointer_like(right_type);
bool left_is_pointer = type_is_pointer_like(left_type);
// 2. To detect pointer additions, reorder if needed
if (right_is_pointer && !left_is_pointer)
{
Expr *temp = right;
right = left;
left = temp;
right_type = left_type;
left_type = left->type->canonical;
left_is_pointer = true;
right_is_pointer = false;
(void)right_is_pointer;
expr->binary_expr.left = exprid(left);
expr->binary_expr.right = exprid(right);
}
// 3. The "left" will now always be the pointer.
// so check if we want to do the normal pointer add special handling.
if (left_is_pointer)
{
return sema_expr_analyse_ptr_add(context, expr, left, right, left_type, right_type, cast_to_iptr, failed_ref);
}
if (left_type->type_kind == TYPE_ENUM || right_type->type_kind == TYPE_ENUM) if (left_type->type_kind == TYPE_ENUM || right_type->type_kind == TYPE_ENUM)
{ {
return sema_expr_analyse_enum_add_sub(context, expr, left, right, failed_ref); return sema_expr_analyse_enum_add_sub(context, expr, left, right, failed_ref);
} }
left_type = type_no_optional(left->type)->canonical;
right_type = type_no_optional(right->type)->canonical;
ASSERT_SPAN(expr, !cast_to_iptr);
// 4. Do a binary arithmetic promotion // 4. Do a binary arithmetic promotion
OperatorOverload overload = OVERLOAD_PLUS; OperatorOverload overload = OVERLOAD_PLUS;
if (!sema_binary_arithmetic_promotion(context, left, right, left_type, right_type, expr, if (!sema_binary_arithmetic_promotion(context, left, right, left_type, right_type, expr,

View File

@@ -0,0 +1,15 @@
module std::crypto::ed25519_test @test;
import std::crypto::ed25519;
fn void ed25519_keygen_sign_verify() @test {
PrivateKey private_key = x"97b0d0435be3a583df0b77aff277e5fc8f857cae1a827723241bb9bbf7e96018";
PublicKey public_key = ed25519::public_keygen(&private_key);
assert(public_key == x"fd462c5c7044b224f4a6cc0f0e5e16511be881be639b3fbc600f7b5d6da54e2f");
String message = "abcdefghijklmnopqrstuvwxyz0123456789";
Signature signature = ed25519::sign(message, &private_key, &public_key);
assert(signature == x"c03cf7b181583f6353a9434be910a484e1720247a0dc384d601e495294d0202728dda5f31bc62b00158b1588bc2a65f48ccfaa1b1431aff3d8da9271b7de9403");
assert(ed25519::verify(message, &signature, &public_key));
}