mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
737 lines
12 KiB
Plaintext
737 lines
12 KiB
Plaintext
/*
|
|
Ed25519 Digital Signature Algorithm
|
|
*/
|
|
|
|
module std::crypto::ed25519;
|
|
import std::hash::sha512;
|
|
|
|
alias Ed25519PrivateKey = char[32];
|
|
alias Ed25519PublicKey = char[Ed25519PrivateKey.len];
|
|
alias Ed25519Signature = char[2 * Ed25519PublicKey.len];
|
|
|
|
<*
|
|
Generate a public key from a private key.
|
|
|
|
@param [in] private_key : "32 bytes of cryptographically secure random data"
|
|
@require private_key.len == Ed25519PrivateKey.len
|
|
*>
|
|
fn Ed25519PublicKey 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 == Ed25519PrivateKey.len
|
|
@require public_key.len == Ed25519PublicKey.len
|
|
*>
|
|
fn Ed25519Signature sign(char[] message, char[] private_key, char[] public_key)
|
|
{
|
|
Ed25519Signature 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 == Ed25519Signature.len
|
|
@require public_key.len == Ed25519PublicKey.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 == Ed25519PrivateKey.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;
|
|
|
|
foreach (i, &v : s)
|
|
{
|
|
carry += *v;
|
|
*v = (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;
|
|
foreach (i, v : (*s)[:^1])
|
|
{
|
|
c += v;
|
|
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;
|
|
foreach (i, v : a) e |= v ^ (*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;
|
|
|
|
foreach (i, z : zero) r[i] = z ^ (-condition & ((*one)[i] ^ z));
|
|
|
|
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);
|
|
foreach (i, v : (*s)[:^1])
|
|
{
|
|
c += 0b11111111_00000000 + v - (*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 : (*s)[:^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 * 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";
|
|
|
|
<*
|
|
Interpret 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;
|
|
|
|
foreach (i, z : zero) r[i] = z ^ (-condition & ((*one)[i] ^ z));
|
|
|
|
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;
|
|
foreach (i, v : s)
|
|
{
|
|
c += v + (*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;
|
|
foreach (i, v : s)
|
|
{
|
|
c = v - (*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;
|
|
foreach (i, v : s)
|
|
{
|
|
c |= v << 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;
|
|
}
|