mirror of
https://github.com/odin-lang/Odin.git
synced 2026-05-29 15:15:08 +00:00
155 lines
4.7 KiB
Odin
155 lines
4.7 KiB
Odin
/*
|
|
`X448` (aka `curve448`) Elliptic-Curve Diffie-Hellman key exchange protocol.
|
|
|
|
See:
|
|
- [[ https://www.rfc-editor.org/rfc/rfc7748 ]]
|
|
*/
|
|
package x448
|
|
|
|
import "core:crypto"
|
|
import field "core:crypto/_fiat/field_curve448"
|
|
|
|
// SCALAR_SIZE is the size of a X448 scalar (private key) in bytes.
|
|
SCALAR_SIZE :: 56
|
|
// POINT_SIZE is the size of a X448 point (public key/shared secret) in bytes.
|
|
POINT_SIZE :: 56
|
|
|
|
@(private, rodata)
|
|
_BASE_POINT: [56]byte = {
|
|
5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
}
|
|
|
|
@(private)
|
|
_scalar_bit :: #force_inline proc "contextless" (s: ^[56]byte, i: int) -> u8 {
|
|
if i < 0 {
|
|
return 0
|
|
}
|
|
return (s[i >> 3] >> uint(i & 7)) & 1
|
|
}
|
|
|
|
@(private)
|
|
_scalarmult :: proc "contextless" (out, scalar, point: ^[56]byte) {
|
|
// Montgomery pseudo-multiplication, using the RFC 7748 formula.
|
|
t1, t2: field.Loose_Field_Element = ---, ---
|
|
|
|
// x_1 = u
|
|
// x_2 = 1
|
|
// z_2 = 0
|
|
// x_3 = u
|
|
// z_3 = 1
|
|
x1: field.Tight_Field_Element = ---
|
|
field.fe_from_bytes(&x1, point)
|
|
|
|
x2, x3, z2, z3: field.Tight_Field_Element = ---, ---, ---, ---
|
|
field.fe_one(&x2)
|
|
field.fe_zero(&z2)
|
|
field.fe_set(&x3, &x1)
|
|
field.fe_one(&z3)
|
|
|
|
// swap = 0
|
|
swap: int
|
|
|
|
// For t = bits-1 down to 0:a
|
|
for t := 448 - 1; t >= 0; t -= 1 {
|
|
// k_t = (k >> t) & 1
|
|
k_t := int(_scalar_bit(scalar, t))
|
|
// swap ^= k_t
|
|
swap ~= k_t
|
|
// Conditional swap; see text below.
|
|
// (x_2, x_3) = cswap(swap, x_2, x_3)
|
|
field.fe_cond_swap(&x2, &x3, swap)
|
|
// (z_2, z_3) = cswap(swap, z_2, z_3)
|
|
field.fe_cond_swap(&z2, &z3, swap)
|
|
// swap = k_t
|
|
swap = k_t
|
|
|
|
// Note: This deliberately omits reductions after add/sub operations
|
|
// if the result is only ever used as the input to a mul/square since
|
|
// the implementations of those can deal with non-reduced inputs.
|
|
//
|
|
// fe_tighten_cast is only used to store a fully reduced
|
|
// output in a Loose_Field_Element, or to provide such a
|
|
// Loose_Field_Element as a Tight_Field_Element argument.
|
|
|
|
// A = x_2 + z_2
|
|
field.fe_add(&t1, &x2, &z2)
|
|
// B = x_2 - z_2
|
|
field.fe_sub(&t2, &x2, &z2)
|
|
// D = x_3 - z_3
|
|
field.fe_sub(field.fe_relax_cast(&z2), &x3, &z3) // (z2 unreduced)
|
|
// DA = D * A
|
|
field.fe_carry_mul(&x2, field.fe_relax_cast(&z2), &t1)
|
|
// C = x_3 + z_3
|
|
field.fe_add(field.fe_relax_cast(&z3), &x3, &z3) // (z3 unreduced)
|
|
// CB = C * B
|
|
field.fe_carry_mul(&x3, &t2, field.fe_relax_cast(&z3))
|
|
// z_3 = x_1 * (DA - CB)^2
|
|
field.fe_sub(field.fe_relax_cast(&z3), &x2, &x3) // (z3 unreduced)
|
|
field.fe_carry_square(&z3, field.fe_relax_cast(&z3))
|
|
field.fe_carry_mul(&z3, field.fe_relax_cast(&x1), field.fe_relax_cast(&z3))
|
|
// x_3 = (DA + CB)^2
|
|
field.fe_add(field.fe_relax_cast(&z2), &x2, &x3) // (z2 unreduced)
|
|
field.fe_carry_square(&x3, field.fe_relax_cast(&z2))
|
|
|
|
// AA = A^2
|
|
field.fe_carry_square(&z2, &t1)
|
|
// BB = B^2
|
|
field.fe_carry_square(field.fe_tighten_cast(&t1), &t2) // (t1 reduced)
|
|
// x_2 = AA * BB
|
|
field.fe_carry_mul(&x2, field.fe_relax_cast(&z2), &t1)
|
|
// E = AA - BB
|
|
field.fe_sub(&t2, &z2, field.fe_tighten_cast(&t1)) // (t1 (input) is reduced)
|
|
// z_2 = E * (AA + a24 * E)
|
|
field.fe_carry_mul_small(field.fe_tighten_cast(&t1), &t2, 39081) // (t1 reduced)
|
|
field.fe_add(&t1, &z2, field.fe_tighten_cast(&t1)) // (t1 (input) is reduced)
|
|
field.fe_carry_mul(&z2, &t2, &t1)
|
|
}
|
|
|
|
// Conditional swap; see text below.
|
|
// (x_2, x_3) = cswap(swap, x_2, x_3)
|
|
field.fe_cond_swap(&x2, &x3, swap)
|
|
// (z_2, z_3) = cswap(swap, z_2, z_3)
|
|
field.fe_cond_swap(&z2, &z3, swap)
|
|
|
|
// Return x_2 * (z_2^(p - 2))
|
|
field.fe_carry_inv(&z2, field.fe_relax_cast(&z2))
|
|
field.fe_carry_mul(&x2, field.fe_relax_cast(&x2), field.fe_relax_cast(&z2))
|
|
field.fe_to_bytes(out, &x2)
|
|
|
|
field.fe_clear_vec([]^field.Tight_Field_Element{&x1, &x2, &x3, &z2, &z3})
|
|
field.fe_clear_vec([]^field.Loose_Field_Element{&t1, &t2})
|
|
}
|
|
|
|
// scalarmult "multiplies" the provided scalar and point, and writes the
|
|
// resulting point to dst.
|
|
scalarmult :: proc(dst, scalar, point: []byte) {
|
|
ensure(len(scalar) == SCALAR_SIZE, "crypto/x448: invalid scalar size")
|
|
ensure(len(point) == POINT_SIZE, "crypto/x448: invalid point size")
|
|
ensure(len(dst) == POINT_SIZE, "crypto/x448: invalid destination point size")
|
|
|
|
// "clamp" the scalar
|
|
e: [56]byte = ---
|
|
copy_slice(e[:], scalar)
|
|
e[0] &= 252
|
|
e[55] |= 128
|
|
|
|
p: [56]byte = ---
|
|
copy_slice(p[:], point)
|
|
|
|
d: [56]byte = ---
|
|
_scalarmult(&d, &e, &p)
|
|
copy_slice(dst, d[:])
|
|
|
|
crypto.zero_explicit(&e, size_of(e))
|
|
crypto.zero_explicit(&d, size_of(d))
|
|
}
|
|
|
|
// scalarmult_basepoint "multiplies" the provided scalar with the X448
|
|
// base point and writes the resulting point to dst.
|
|
scalarmult_basepoint :: proc(dst, scalar: []byte) {
|
|
scalarmult(dst, scalar, _BASE_POINT[:])
|
|
}
|