mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-23 14:14:59 -07:00
426 lines
12 KiB
Odin
426 lines
12 KiB
Odin
package _edwards25519
|
|
|
|
/*
|
|
This implements the edwards25519 composite-order group, primarily for
|
|
the purpose of implementing X25519, Ed25519, and ristretto255. Use of
|
|
this package for other purposes is NOT RECOMMENDED.
|
|
|
|
See:
|
|
- https://eprint.iacr.org/2011/368.pdf
|
|
- https://datatracker.ietf.org/doc/html/rfc8032
|
|
- https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
|
|
*/
|
|
|
|
import "core:crypto"
|
|
import field "core:crypto/_fiat/field_curve25519"
|
|
import "core:mem"
|
|
|
|
// Group_Element is an edwards25519 group element, as extended homogenous
|
|
// coordinates, which represents the affine point `(x, y)` as `(X, Y, Z, T)`,
|
|
// with the relations `x = X/Z`, `y = Y/Z`, and `x * y = T/Z`.
|
|
//
|
|
// d = -121665/121666 = 37095705934669439343138083508754565189542113879843219016388785533085940283555
|
|
// a = -1
|
|
//
|
|
// Notes:
|
|
// - There is considerable scope for optimization, however that
|
|
// will not change the external API, and this is simple and reasonably
|
|
// performant.
|
|
// - The API delibarately makes it hard to create arbitrary group
|
|
// elements that are not on the curve.
|
|
// - The group element decoding routine takes the opinionated stance of
|
|
// rejecting non-canonical encodings.
|
|
|
|
@(rodata)
|
|
FE_D := field.Tight_Field_Element {
|
|
929955233495203,
|
|
466365720129213,
|
|
1662059464998953,
|
|
2033849074728123,
|
|
1442794654840575,
|
|
}
|
|
@(private, rodata)
|
|
FE_A := field.Tight_Field_Element {
|
|
2251799813685228,
|
|
2251799813685247,
|
|
2251799813685247,
|
|
2251799813685247,
|
|
2251799813685247,
|
|
}
|
|
@(private, rodata)
|
|
FE_D2 := field.Tight_Field_Element {
|
|
1859910466990425,
|
|
932731440258426,
|
|
1072319116312658,
|
|
1815898335770999,
|
|
633789495995903,
|
|
}
|
|
@(private, rodata)
|
|
GE_BASEPOINT := Group_Element {
|
|
field.Tight_Field_Element {
|
|
1738742601995546,
|
|
1146398526822698,
|
|
2070867633025821,
|
|
562264141797630,
|
|
587772402128613,
|
|
},
|
|
field.Tight_Field_Element {
|
|
1801439850948184,
|
|
1351079888211148,
|
|
450359962737049,
|
|
900719925474099,
|
|
1801439850948198,
|
|
},
|
|
field.Tight_Field_Element{1, 0, 0, 0, 0},
|
|
field.Tight_Field_Element {
|
|
1841354044333475,
|
|
16398895984059,
|
|
755974180946558,
|
|
900171276175154,
|
|
1821297809914039,
|
|
},
|
|
}
|
|
@(rodata)
|
|
GE_IDENTITY := Group_Element {
|
|
field.Tight_Field_Element{0, 0, 0, 0, 0},
|
|
field.Tight_Field_Element{1, 0, 0, 0, 0},
|
|
field.Tight_Field_Element{1, 0, 0, 0, 0},
|
|
field.Tight_Field_Element{0, 0, 0, 0, 0},
|
|
}
|
|
|
|
Group_Element :: struct {
|
|
x: field.Tight_Field_Element,
|
|
y: field.Tight_Field_Element,
|
|
z: field.Tight_Field_Element,
|
|
t: field.Tight_Field_Element,
|
|
}
|
|
|
|
ge_clear :: proc "contextless" (ge: ^Group_Element) {
|
|
mem.zero_explicit(ge, size_of(Group_Element))
|
|
}
|
|
|
|
ge_set :: proc "contextless" (ge, a: ^Group_Element) {
|
|
field.fe_set(&ge.x, &a.x)
|
|
field.fe_set(&ge.y, &a.y)
|
|
field.fe_set(&ge.z, &a.z)
|
|
field.fe_set(&ge.t, &a.t)
|
|
}
|
|
|
|
@(require_results)
|
|
ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool {
|
|
ensure_contextless(len(b) == 32, "edwards25519: invalid group element size")
|
|
b_ := (^[32]byte)(raw_data(b))
|
|
|
|
// Do the work in a scratch element, so that ge is unchanged on
|
|
// failure.
|
|
tmp: Group_Element = ---
|
|
defer ge_clear(&tmp)
|
|
field.fe_one(&tmp.z) // Z = 1
|
|
|
|
// The encoding is the y-coordinate, with the x-coordinate polarity
|
|
// (odd/even) encoded in the MSB.
|
|
field.fe_from_bytes(&tmp.y, b_) // ignores high bit
|
|
|
|
// Recover the candidate x-coordinate via the curve equation:
|
|
// x^2 = (y^2 - 1) / (d * y^2 + 1) (mod p)
|
|
|
|
fe_tmp := &tmp.t // Use this to store intermediaries.
|
|
fe_one := &tmp.z
|
|
|
|
// x = num = y^2 - 1
|
|
field.fe_carry_square(fe_tmp, field.fe_relax_cast(&tmp.y)) // fe_tmp = y^2
|
|
field.fe_carry_sub(&tmp.x, fe_tmp, fe_one)
|
|
|
|
// den = d * y^2 + 1
|
|
field.fe_carry_mul(fe_tmp, field.fe_relax_cast(fe_tmp), field.fe_relax_cast(&FE_D))
|
|
field.fe_carry_add(fe_tmp, fe_tmp, fe_one)
|
|
|
|
// x = invsqrt(den/num)
|
|
is_square := field.fe_carry_sqrt_ratio_m1(
|
|
&tmp.x,
|
|
field.fe_relax_cast(&tmp.x),
|
|
field.fe_relax_cast(fe_tmp),
|
|
)
|
|
if is_square == 0 {
|
|
return false
|
|
}
|
|
|
|
// Pick the right x-coordinate.
|
|
field.fe_cond_negate(&tmp.x, &tmp.x, int(b[31] >> 7))
|
|
|
|
// t = x * y
|
|
field.fe_carry_mul(&tmp.t, field.fe_relax_cast(&tmp.x), field.fe_relax_cast(&tmp.y))
|
|
|
|
// Reject non-canonical encodings of ge.
|
|
buf: [32]byte = ---
|
|
field.fe_to_bytes(&buf, &tmp.y)
|
|
buf[31] |= byte(field.fe_is_negative(&tmp.x)) << 7
|
|
is_canonical := crypto.compare_constant_time(b, buf[:])
|
|
|
|
ge_cond_assign(ge, &tmp, is_canonical)
|
|
|
|
mem.zero_explicit(&buf, size_of(buf))
|
|
|
|
return is_canonical == 1
|
|
}
|
|
|
|
ge_bytes :: proc "contextless" (ge: ^Group_Element, dst: []byte) {
|
|
ensure_contextless(len(dst) == 32, "edwards25519: invalid group element size")
|
|
dst_ := (^[32]byte)(raw_data(dst))
|
|
|
|
// Convert the element to affine (x, y) representation.
|
|
x, y, z_inv: field.Tight_Field_Element = ---, ---, ---
|
|
field.fe_carry_inv(&z_inv, field.fe_relax_cast(&ge.z))
|
|
field.fe_carry_mul(&x, field.fe_relax_cast(&ge.x), field.fe_relax_cast(&z_inv))
|
|
field.fe_carry_mul(&y, field.fe_relax_cast(&ge.y), field.fe_relax_cast(&z_inv))
|
|
|
|
// Encode the y-coordinate.
|
|
field.fe_to_bytes(dst_, &y)
|
|
|
|
// Copy the least significant bit of the x-coordinate to the most
|
|
// significant bit of the encoded y-coordinate.
|
|
dst_[31] |= byte((x[0] & 1) << 7)
|
|
|
|
field.fe_clear_vec([]^field.Tight_Field_Element{&x, &y, &z_inv})
|
|
}
|
|
|
|
ge_identity :: proc "contextless" (ge: ^Group_Element) {
|
|
field.fe_zero(&ge.x)
|
|
field.fe_one(&ge.y)
|
|
field.fe_one(&ge.z)
|
|
field.fe_zero(&ge.t)
|
|
}
|
|
|
|
ge_generator :: proc "contextless" (ge: ^Group_Element) {
|
|
ge_set(ge, &GE_BASEPOINT)
|
|
}
|
|
|
|
@(private)
|
|
Addend_Group_Element :: struct {
|
|
y2_minus_x2: field.Loose_Field_Element, // t1
|
|
y2_plus_x2: field.Loose_Field_Element, // t3
|
|
k_times_t2: field.Tight_Field_Element, // t4
|
|
two_times_z2: field.Loose_Field_Element, // t5
|
|
}
|
|
|
|
@(private)
|
|
ge_addend_set :: proc "contextless" (ge_a: ^Addend_Group_Element, ge: ^Group_Element) {
|
|
field.fe_sub(&ge_a.y2_minus_x2, &ge.y, &ge.x)
|
|
field.fe_add(&ge_a.y2_plus_x2, &ge.y, &ge.x)
|
|
field.fe_carry_mul(&ge_a.k_times_t2, field.fe_relax_cast(&FE_D2), field.fe_relax_cast(&ge.t))
|
|
field.fe_add(&ge_a.two_times_z2, &ge.z, &ge.z)
|
|
}
|
|
|
|
@(private)
|
|
ge_addend_conditional_assign :: proc "contextless" (ge_a, a: ^Addend_Group_Element, ctrl: int) {
|
|
field.fe_cond_select(&ge_a.y2_minus_x2, &ge_a.y2_minus_x2, &a.y2_minus_x2, ctrl)
|
|
field.fe_cond_select(&ge_a.y2_plus_x2, &ge_a.y2_plus_x2, &a.y2_plus_x2, ctrl)
|
|
field.fe_cond_select(&ge_a.k_times_t2, &ge_a.k_times_t2, &a.k_times_t2, ctrl)
|
|
field.fe_cond_select(&ge_a.two_times_z2, &ge_a.two_times_z2, &a.two_times_z2, ctrl)
|
|
}
|
|
|
|
@(private)
|
|
Add_Scratch :: struct {
|
|
A, B, C, D: field.Tight_Field_Element,
|
|
E, F, G, H: field.Loose_Field_Element,
|
|
t0, t2: field.Loose_Field_Element,
|
|
}
|
|
|
|
ge_add :: proc "contextless" (ge, a, b: ^Group_Element) {
|
|
b_: Addend_Group_Element = ---
|
|
ge_addend_set(&b_, b)
|
|
|
|
scratch: Add_Scratch = ---
|
|
ge_add_addend(ge, a, &b_, &scratch)
|
|
|
|
mem.zero_explicit(&b_, size_of(Addend_Group_Element))
|
|
mem.zero_explicit(&scratch, size_of(Add_Scratch))
|
|
}
|
|
|
|
@(private)
|
|
ge_add_addend :: proc "contextless" (
|
|
ge, a: ^Group_Element,
|
|
b: ^Addend_Group_Element,
|
|
scratch: ^Add_Scratch,
|
|
) {
|
|
// https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3
|
|
// Assumptions: k=2*d.
|
|
//
|
|
// t0 = Y1-X1
|
|
// t1 = Y2-X2
|
|
// A = t0*t1
|
|
// t2 = Y1+X1
|
|
// t3 = Y2+X2
|
|
// B = t2*t3
|
|
// t4 = k*T2
|
|
// C = T1*t4
|
|
// t5 = 2*Z2
|
|
// D = Z1*t5
|
|
// E = B-A
|
|
// F = D-C
|
|
// G = D+C
|
|
// H = B+A
|
|
// X3 = E*F
|
|
// Y3 = G*H
|
|
// T3 = E*H
|
|
// Z3 = F*G
|
|
//
|
|
// In order to make the scalar multiply faster, the addend is provided
|
|
// as a `Addend_Group_Element` with t1, t3, t4, and t5 precomputed, as
|
|
// it is trivially obvious that those are the only values used by the
|
|
// formula that are directly dependent on `b`, and are only dependent
|
|
// on `b` and constants. This saves 1 sub, 2 adds, and 1 multiply,
|
|
// each time the intermediate representation can be reused.
|
|
|
|
A, B, C, D := &scratch.A, &scratch.B, &scratch.C, &scratch.D
|
|
E, F, G, H := &scratch.E, &scratch.F, &scratch.G, &scratch.H
|
|
t0, t2 := &scratch.t0, &scratch.t2
|
|
|
|
field.fe_sub(t0, &a.y, &a.x)
|
|
t1 := &b.y2_minus_x2
|
|
field.fe_carry_mul(A, t0, t1)
|
|
field.fe_add(t2, &a.y, &a.x)
|
|
t3 := &b.y2_plus_x2
|
|
field.fe_carry_mul(B, t2, t3)
|
|
t4 := &b.k_times_t2
|
|
field.fe_carry_mul(C, field.fe_relax_cast(&a.t), field.fe_relax_cast(t4))
|
|
t5 := &b.two_times_z2
|
|
field.fe_carry_mul(D, field.fe_relax_cast(&a.z), t5)
|
|
field.fe_sub(E, B, A)
|
|
field.fe_sub(F, D, C)
|
|
field.fe_add(G, D, C)
|
|
field.fe_add(H, B, A)
|
|
field.fe_carry_mul(&ge.x, E, F)
|
|
field.fe_carry_mul(&ge.y, G, H)
|
|
field.fe_carry_mul(&ge.t, E, H)
|
|
field.fe_carry_mul(&ge.z, F, G)
|
|
}
|
|
|
|
@(private)
|
|
Double_Scratch :: struct {
|
|
A, B, C, D, G: field.Tight_Field_Element,
|
|
t0, t2, t3: field.Tight_Field_Element,
|
|
E, F, H: field.Loose_Field_Element,
|
|
t1: field.Loose_Field_Element,
|
|
}
|
|
|
|
ge_double :: proc "contextless" (ge, a: ^Group_Element, scratch: ^Double_Scratch = nil) {
|
|
// https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#doubling-dbl-2008-hwcd
|
|
//
|
|
// A = X1^2
|
|
// B = Y1^2
|
|
// t0 = Z1^2
|
|
// C = 2*t0
|
|
// D = a*A
|
|
// t1 = X1+Y1
|
|
// t2 = t1^2
|
|
// t3 = t2-A
|
|
// E = t3-B
|
|
// G = D+B
|
|
// F = G-C
|
|
// H = D-B
|
|
// X3 = E*F
|
|
// Y3 = G*H
|
|
// T3 = E*H
|
|
// Z3 = F*G
|
|
|
|
sanitize, scratch := scratch == nil, scratch
|
|
if sanitize {
|
|
tmp: Double_Scratch = ---
|
|
scratch = &tmp
|
|
}
|
|
|
|
A, B, C, D, G := &scratch.A, &scratch.B, &scratch.C, &scratch.D, &scratch.G
|
|
t0, t2, t3 := &scratch.t0, &scratch.t2, &scratch.t3
|
|
E, F, H := &scratch.E, &scratch.F, &scratch.H
|
|
t1 := &scratch.t1
|
|
|
|
field.fe_carry_square(A, field.fe_relax_cast(&a.x))
|
|
field.fe_carry_square(B, field.fe_relax_cast(&a.y))
|
|
field.fe_carry_square(t0, field.fe_relax_cast(&a.z))
|
|
field.fe_carry_add(C, t0, t0)
|
|
field.fe_carry_mul(D, field.fe_relax_cast(&FE_A), field.fe_relax_cast(A))
|
|
field.fe_add(t1, &a.x, &a.y)
|
|
field.fe_carry_square(t2, t1)
|
|
field.fe_carry_sub(t3, t2, A)
|
|
field.fe_sub(E, t3, B)
|
|
field.fe_carry_add(G, D, B)
|
|
field.fe_sub(F, G, C)
|
|
field.fe_sub(H, D, B)
|
|
G_ := field.fe_relax_cast(G)
|
|
field.fe_carry_mul(&ge.x, E, F)
|
|
field.fe_carry_mul(&ge.y, G_, H)
|
|
field.fe_carry_mul(&ge.t, E, H)
|
|
field.fe_carry_mul(&ge.z, F, G_)
|
|
|
|
if sanitize {
|
|
mem.zero_explicit(scratch, size_of(Double_Scratch))
|
|
}
|
|
}
|
|
|
|
ge_negate :: proc "contextless" (ge, a: ^Group_Element) {
|
|
field.fe_carry_opp(&ge.x, &a.x)
|
|
field.fe_set(&ge.y, &a.y)
|
|
field.fe_set(&ge.z, &a.z)
|
|
field.fe_carry_opp(&ge.t, &a.t)
|
|
}
|
|
|
|
ge_cond_negate :: proc "contextless" (ge, a: ^Group_Element, ctrl: int) {
|
|
tmp: Group_Element = ---
|
|
ge_negate(&tmp, a)
|
|
ge_cond_assign(ge, &tmp, ctrl)
|
|
|
|
ge_clear(&tmp)
|
|
}
|
|
|
|
ge_cond_assign :: proc "contextless" (ge, a: ^Group_Element, ctrl: int) {
|
|
field.fe_cond_assign(&ge.x, &a.x, ctrl)
|
|
field.fe_cond_assign(&ge.y, &a.y, ctrl)
|
|
field.fe_cond_assign(&ge.z, &a.z, ctrl)
|
|
field.fe_cond_assign(&ge.t, &a.t, ctrl)
|
|
}
|
|
|
|
ge_cond_select :: proc "contextless" (ge, a, b: ^Group_Element, ctrl: int) {
|
|
field.fe_cond_select(&ge.x, &a.x, &b.x, ctrl)
|
|
field.fe_cond_select(&ge.y, &a.y, &b.y, ctrl)
|
|
field.fe_cond_select(&ge.z, &a.z, &b.z, ctrl)
|
|
field.fe_cond_select(&ge.t, &a.t, &b.t, ctrl)
|
|
}
|
|
|
|
@(require_results)
|
|
ge_equal :: proc "contextless" (a, b: ^Group_Element) -> int {
|
|
// (x, y) ?= (x', y') -> (X/Z, Y/Z) ?= (X'/Z', Y'/Z')
|
|
// X/Z ?= X'/Z', Y/Z ?= Y'/Z' -> X*Z' ?= X'*Z, Y*Z' ?= Y'*Z
|
|
ax_bz, bx_az, ay_bz, by_az: field.Tight_Field_Element = ---, ---, ---, ---
|
|
field.fe_carry_mul(&ax_bz, field.fe_relax_cast(&a.x), field.fe_relax_cast(&b.z))
|
|
field.fe_carry_mul(&bx_az, field.fe_relax_cast(&b.x), field.fe_relax_cast(&a.z))
|
|
field.fe_carry_mul(&ay_bz, field.fe_relax_cast(&a.y), field.fe_relax_cast(&b.z))
|
|
field.fe_carry_mul(&by_az, field.fe_relax_cast(&b.y), field.fe_relax_cast(&a.z))
|
|
|
|
ret := field.fe_equal(&ax_bz, &bx_az) & field.fe_equal(&ay_bz, &by_az)
|
|
|
|
field.fe_clear_vec([]^field.Tight_Field_Element{&ax_bz, &ay_bz, &bx_az, &by_az})
|
|
|
|
return ret
|
|
}
|
|
|
|
@(require_results)
|
|
ge_is_small_order :: proc "contextless" (ge: ^Group_Element) -> bool {
|
|
tmp: Group_Element = ---
|
|
ge_double(&tmp, ge)
|
|
ge_double(&tmp, &tmp)
|
|
ge_double(&tmp, &tmp)
|
|
return ge_equal(&tmp, &GE_IDENTITY) == 1
|
|
}
|
|
|
|
@(require_results)
|
|
ge_in_prime_order_subgroup_vartime :: proc "contextless" (ge: ^Group_Element) -> bool {
|
|
// This is currently *very* expensive. The faster method would be
|
|
// something like (https://eprint.iacr.org/2022/1164.pdf), however
|
|
// that is a ~50% speedup, and a lot of added complexity for something
|
|
// that is better solved by "just use ristretto255".
|
|
tmp: Group_Element = ---
|
|
_ge_scalarmult(&tmp, ge, &SC_ELL, true)
|
|
return ge_equal(&tmp, &GE_IDENTITY) == 1
|
|
}
|