551 lines
12 KiB
V
551 lines
12 KiB
V
module edwards25519
|
|
|
|
const (
|
|
// d is a constant in the curve equation.
|
|
d_bytes = [u8(0xa3), 0x78, 0x59, 0x13, 0xca, 0x4d, 0xeb, 0x75, 0xab, 0xd8, 0x41, 0x41, 0x4d,
|
|
0x0a, 0x70, 0x00, 0x98, 0xe8, 0x79, 0x77, 0x79, 0x40, 0xc7, 0x8c, 0x73, 0xfe, 0x6f, 0x2b,
|
|
0xee, 0x6c, 0x03, 0x52]
|
|
id_bytes = [u8(1), 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]
|
|
gen_bytes = [u8(0x58), 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
|
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
|
0x66, 0x66, 0x66, 0x66]
|
|
d_const = d_const_generate() or { panic(err) }
|
|
d2_const = d2_const_generate() or { panic(err) }
|
|
// id_point is the point at infinity.
|
|
id_point = id_point_generate() or { panic(err) }
|
|
// generator point
|
|
gen_point = generator() or { panic(err) }
|
|
)
|
|
|
|
fn d_const_generate() ?Element {
|
|
mut v := Element{}
|
|
v.set_bytes(edwards25519.d_bytes) ?
|
|
return v
|
|
}
|
|
|
|
fn d2_const_generate() ?Element {
|
|
mut v := Element{}
|
|
v.add(edwards25519.d_const, edwards25519.d_const)
|
|
return v
|
|
}
|
|
|
|
// id_point_generate is the point at infinity.
|
|
fn id_point_generate() ?Point {
|
|
mut p := Point{}
|
|
p.set_bytes(edwards25519.id_bytes) ?
|
|
return p
|
|
}
|
|
|
|
// generator is the canonical curve basepoint. See TestGenerator for the
|
|
// correspondence of this encoding with the values in RFC 8032.
|
|
fn generator() ?Point {
|
|
mut p := Point{}
|
|
p.set_bytes(edwards25519.gen_bytes) ?
|
|
return p
|
|
}
|
|
|
|
// Point types.
|
|
struct ProjectiveP1 {
|
|
mut:
|
|
x Element
|
|
y Element
|
|
z Element
|
|
t Element
|
|
}
|
|
|
|
struct ProjectiveP2 {
|
|
mut:
|
|
x Element
|
|
y Element
|
|
z Element
|
|
}
|
|
|
|
// Point represents a point on the edwards25519 curve.
|
|
//
|
|
// This type works similarly to math/big.Int, and all arguments and receivers
|
|
// are allowed to alias.
|
|
//
|
|
// The zero value is NOT valid, and it may be used only as a receiver.
|
|
pub struct Point {
|
|
mut:
|
|
// The point is internally represented in extended coordinates (x, y, z, T)
|
|
// where x = x/z, y = y/z, and xy = T/z per https://eprint.iacr.org/2008/522.
|
|
x Element
|
|
y Element
|
|
z Element
|
|
t Element
|
|
// Make the type not comparable (i.e. used with == or as a map key), as
|
|
// equivalent points can be represented by different values.
|
|
// _ incomparable
|
|
}
|
|
|
|
fn check_initialized(points ...Point) {
|
|
for _, p in points {
|
|
if p.x == fe_zero && p.y == fe_zero {
|
|
panic('edwards25519: use of uninitialized Point')
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ProjectiveCached {
|
|
mut:
|
|
ypx Element // y + x
|
|
ymx Element // y - x
|
|
z Element
|
|
t2d Element
|
|
}
|
|
|
|
struct AffineCached {
|
|
mut:
|
|
ypx Element // y + x
|
|
ymx Element // y - x
|
|
t2d Element
|
|
}
|
|
|
|
fn (mut v ProjectiveP2) zero() ProjectiveP2 {
|
|
v.x.zero()
|
|
v.y.one()
|
|
v.z.one()
|
|
return v
|
|
}
|
|
|
|
// set_bytes sets v = x, where x is a 32-byte encoding of v. If x does not
|
|
// represent a valid point on the curve, set_bytes returns an error and
|
|
// the receiver is unchanged. Otherwise, set_bytes returns v.
|
|
//
|
|
// Note that set_bytes accepts all non-canonical encodings of valid points.
|
|
// That is, it follows decoding rules that match most implementations in
|
|
// the ecosystem rather than RFC 8032.
|
|
pub fn (mut v Point) set_bytes(x []u8) ?Point {
|
|
// Specifically, the non-canonical encodings that are accepted are
|
|
// 1) the ones where the edwards25519 element is not reduced (see the
|
|
// (*edwards25519.Element).set_bytes docs) and
|
|
// 2) the ones where the x-coordinate is zero and the sign bit is set.
|
|
//
|
|
// This is consistent with crypto/ed25519/internal/edwards25519. Read more
|
|
// at https://hdevalence.ca/blog/2020-10-04-its-25519am, specifically the
|
|
// "Canonical A, R" section.
|
|
mut el0 := Element{}
|
|
y := el0.set_bytes(x) or { return error('edwards25519: invalid point encoding length') }
|
|
|
|
// -x² + y² = 1 + dx²y²
|
|
// x² + dx²y² = x²(dy² + 1) = y² - 1
|
|
// x² = (y² - 1) / (dy² + 1)
|
|
|
|
// u = y² - 1
|
|
mut el1 := Element{}
|
|
y2 := el1.square(y)
|
|
mut el2 := Element{}
|
|
u := el2.subtract(y2, fe_one)
|
|
|
|
// v = dy² + 1
|
|
mut el3 := Element{}
|
|
mut vv := el3.multiply(y2, edwards25519.d_const)
|
|
vv = vv.add(vv, fe_one)
|
|
|
|
// x = +√(u/v)
|
|
mut el4 := Element{}
|
|
mut xx, was_square := el4.sqrt_ratio(u, vv)
|
|
if was_square == 0 {
|
|
return error('edwards25519: invalid point encoding')
|
|
}
|
|
|
|
// selected the negative square root if the sign bit is set.
|
|
mut el5 := Element{}
|
|
xx_neg := el5.negate(xx)
|
|
xx.selected(xx_neg, xx, int(x[31] >> 7))
|
|
|
|
v.x.set(xx)
|
|
v.y.set(y)
|
|
v.z.one()
|
|
v.t.multiply(xx, y) // xy = T / z
|
|
|
|
return v
|
|
}
|
|
|
|
// set sets v = u, and returns v.
|
|
pub fn (mut v Point) set(u Point) Point {
|
|
v = u
|
|
return v
|
|
}
|
|
|
|
// new_identity_point returns a new Point set to the identity.
|
|
pub fn new_identity_point() Point {
|
|
mut p := Point{}
|
|
return p.set(edwards25519.id_point)
|
|
}
|
|
|
|
// new_generator_point returns a new Point set to the canonical generator.
|
|
pub fn new_generator_point() Point {
|
|
mut p := Point{}
|
|
return p.set(edwards25519.gen_point)
|
|
}
|
|
|
|
fn (mut v ProjectiveCached) zero() ProjectiveCached {
|
|
v.ypx.one()
|
|
v.ymx.one()
|
|
v.z.one()
|
|
v.t2d.zero()
|
|
return v
|
|
}
|
|
|
|
fn (mut v AffineCached) zero() AffineCached {
|
|
v.ypx.one()
|
|
v.ymx.one()
|
|
v.t2d.zero()
|
|
return v
|
|
}
|
|
|
|
// Encoding.
|
|
|
|
// bytes returns the canonical 32-byte encoding of v, according to RFC 8032,
|
|
// Section 5.1.2.
|
|
pub fn (mut v Point) bytes() []u8 {
|
|
// This function is outlined to make the allocations inline in the caller
|
|
// rather than happen on the heap.
|
|
mut buf := [32]u8{}
|
|
return v.bytes_generic(mut buf)
|
|
}
|
|
|
|
fn (mut v Point) bytes_generic(mut buf [32]u8) []u8 {
|
|
check_initialized(v)
|
|
|
|
mut zinv := Element{}
|
|
mut x := Element{}
|
|
mut y := Element{}
|
|
zinv.invert(v.z) // zinv = 1 / z
|
|
x.multiply(v.x, zinv) // x = x / z
|
|
y.multiply(v.y, zinv) // y = y / z
|
|
|
|
mut out := copy_field_element(mut buf, mut y)
|
|
unsafe {
|
|
// out[31] |= u8(x.is_negative() << 7) //original one
|
|
out[31] |= u8(x.is_negative() * 128) // x << 7 == x * 2^7
|
|
}
|
|
return out
|
|
}
|
|
|
|
fn copy_field_element(mut buf [32]u8, mut v Element) []u8 {
|
|
// this fail in test
|
|
/*
|
|
copy(mut buf[..], v.bytes())
|
|
return buf[..]
|
|
*/
|
|
|
|
// this pass the test
|
|
mut out := []u8{len: 32}
|
|
for i := 0; i <= buf.len - 1; i++ {
|
|
out[i] = v.bytes()[i]
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
// Conversions.
|
|
|
|
fn (mut v ProjectiveP2) from_p1(p ProjectiveP1) ProjectiveP2 {
|
|
v.x.multiply(p.x, p.t)
|
|
v.y.multiply(p.y, p.z)
|
|
v.z.multiply(p.z, p.t)
|
|
return v
|
|
}
|
|
|
|
fn (mut v ProjectiveP2) from_p3(p Point) ProjectiveP2 {
|
|
v.x.set(p.x)
|
|
v.y.set(p.y)
|
|
v.z.set(p.z)
|
|
return v
|
|
}
|
|
|
|
fn (mut v Point) from_p1(p ProjectiveP1) Point {
|
|
v.x.multiply(p.x, p.t)
|
|
v.y.multiply(p.y, p.z)
|
|
v.z.multiply(p.z, p.t)
|
|
v.t.multiply(p.x, p.y)
|
|
return v
|
|
}
|
|
|
|
fn (mut v Point) from_p2(p ProjectiveP2) Point {
|
|
v.x.multiply(p.x, p.z)
|
|
v.y.multiply(p.y, p.z)
|
|
v.z.square(p.z)
|
|
v.t.multiply(p.x, p.y)
|
|
return v
|
|
}
|
|
|
|
fn (mut v ProjectiveCached) from_p3(p Point) ProjectiveCached {
|
|
v.ypx.add(p.y, p.x)
|
|
v.ymx.subtract(p.y, p.x)
|
|
v.z.set(p.z)
|
|
v.t2d.multiply(p.t, edwards25519.d2_const)
|
|
return v
|
|
}
|
|
|
|
fn (mut v AffineCached) from_p3(p Point) AffineCached {
|
|
v.ypx.add(p.y, p.x)
|
|
v.ymx.subtract(p.y, p.x)
|
|
v.t2d.multiply(p.t, edwards25519.d2_const)
|
|
|
|
mut invz := Element{}
|
|
invz.invert(p.z)
|
|
v.ypx.multiply(v.ypx, invz)
|
|
v.ymx.multiply(v.ymx, invz)
|
|
v.t2d.multiply(v.t2d, invz)
|
|
return v
|
|
}
|
|
|
|
// (Re)addition and subtraction.
|
|
|
|
// add sets v = p + q, and returns v.
|
|
pub fn (mut v Point) add(p Point, q Point) Point {
|
|
check_initialized(p, q)
|
|
mut pc := ProjectiveCached{}
|
|
mut p1 := ProjectiveP1{}
|
|
qcached := pc.from_p3(q)
|
|
|
|
result := p1.add(p, qcached)
|
|
return v.from_p1(result)
|
|
}
|
|
|
|
// subtract sets v = p - q, and returns v.
|
|
pub fn (mut v Point) subtract(p Point, q Point) Point {
|
|
check_initialized(p, q)
|
|
mut pc := ProjectiveCached{}
|
|
mut p1 := ProjectiveP1{}
|
|
qcached := pc.from_p3(q)
|
|
result := p1.sub(p, qcached)
|
|
return v.from_p1(result)
|
|
}
|
|
|
|
fn (mut v ProjectiveP1) add(p Point, q ProjectiveCached) ProjectiveP1 {
|
|
// var ypx, ymx, pp, mm, tt2d, zz2 edwards25519.Element
|
|
mut ypx := Element{}
|
|
mut ymx := Element{}
|
|
mut pp := Element{}
|
|
mut mm := Element{}
|
|
mut tt2d := Element{}
|
|
mut zz2 := Element{}
|
|
|
|
ypx.add(p.y, p.x)
|
|
ymx.subtract(p.y, p.x)
|
|
|
|
pp.multiply(ypx, q.ypx)
|
|
mm.multiply(ymx, q.ymx)
|
|
tt2d.multiply(p.t, q.t2d)
|
|
zz2.multiply(p.z, q.z)
|
|
|
|
zz2.add(zz2, zz2)
|
|
|
|
v.x.subtract(pp, mm)
|
|
v.y.add(pp, mm)
|
|
v.z.add(zz2, tt2d)
|
|
v.t.subtract(zz2, tt2d)
|
|
return v
|
|
}
|
|
|
|
fn (mut v ProjectiveP1) sub(p Point, q ProjectiveCached) ProjectiveP1 {
|
|
mut ypx := Element{}
|
|
mut ymx := Element{}
|
|
mut pp := Element{}
|
|
mut mm := Element{}
|
|
mut tt2d := Element{}
|
|
mut zz2 := Element{}
|
|
|
|
ypx.add(p.y, p.x)
|
|
ymx.subtract(p.y, p.x)
|
|
|
|
pp.multiply(&ypx, q.ymx) // flipped sign
|
|
mm.multiply(&ymx, q.ypx) // flipped sign
|
|
tt2d.multiply(p.t, q.t2d)
|
|
zz2.multiply(p.z, q.z)
|
|
|
|
zz2.add(zz2, zz2)
|
|
|
|
v.x.subtract(pp, mm)
|
|
v.y.add(pp, mm)
|
|
v.z.subtract(zz2, tt2d) // flipped sign
|
|
v.t.add(zz2, tt2d) // flipped sign
|
|
return v
|
|
}
|
|
|
|
fn (mut v ProjectiveP1) add_affine(p Point, q AffineCached) ProjectiveP1 {
|
|
mut ypx := Element{}
|
|
mut ymx := Element{}
|
|
mut pp := Element{}
|
|
mut mm := Element{}
|
|
mut tt2d := Element{}
|
|
mut z2 := Element{}
|
|
|
|
ypx.add(p.y, p.x)
|
|
ymx.subtract(p.y, p.x)
|
|
|
|
pp.multiply(&ypx, q.ypx)
|
|
mm.multiply(&ymx, q.ymx)
|
|
tt2d.multiply(p.t, q.t2d)
|
|
|
|
z2.add(p.z, p.z)
|
|
|
|
v.x.subtract(pp, mm)
|
|
v.y.add(pp, mm)
|
|
v.z.add(z2, tt2d)
|
|
v.t.subtract(z2, tt2d)
|
|
return v
|
|
}
|
|
|
|
fn (mut v ProjectiveP1) sub_affine(p Point, q AffineCached) ProjectiveP1 {
|
|
mut ypx := Element{}
|
|
mut ymx := Element{}
|
|
mut pp := Element{}
|
|
mut mm := Element{}
|
|
mut tt2d := Element{}
|
|
mut z2 := Element{}
|
|
|
|
ypx.add(p.y, p.x)
|
|
ymx.subtract(p.y, p.x)
|
|
|
|
pp.multiply(ypx, q.ymx) // flipped sign
|
|
mm.multiply(ymx, q.ypx) // flipped sign
|
|
tt2d.multiply(p.t, q.t2d)
|
|
|
|
z2.add(p.z, p.z)
|
|
|
|
v.x.subtract(pp, mm)
|
|
v.y.add(pp, mm)
|
|
v.z.subtract(z2, tt2d) // flipped sign
|
|
v.t.add(z2, tt2d) // flipped sign
|
|
return v
|
|
}
|
|
|
|
// Doubling.
|
|
|
|
fn (mut v ProjectiveP1) double(p ProjectiveP2) ProjectiveP1 {
|
|
// var xx, yy, zz2, xplusysq edwards25519.Element
|
|
mut xx := Element{}
|
|
mut yy := Element{}
|
|
mut zz2 := Element{}
|
|
mut xplusysq := Element{}
|
|
|
|
xx.square(p.x)
|
|
yy.square(p.y)
|
|
zz2.square(p.z)
|
|
zz2.add(zz2, zz2)
|
|
xplusysq.add(p.x, p.y)
|
|
xplusysq.square(xplusysq)
|
|
|
|
v.y.add(yy, xx)
|
|
v.z.subtract(yy, xx)
|
|
|
|
v.x.subtract(xplusysq, v.y)
|
|
v.t.subtract(zz2, v.z)
|
|
return v
|
|
}
|
|
|
|
// Negation.
|
|
|
|
// negate sets v = -p, and returns v.
|
|
pub fn (mut v Point) negate(p Point) Point {
|
|
check_initialized(p)
|
|
v.x.negate(p.x)
|
|
v.y.set(p.y)
|
|
v.z.set(p.z)
|
|
v.t.negate(p.t)
|
|
return v
|
|
}
|
|
|
|
// equal returns 1 if v is equivalent to u, and 0 otherwise.
|
|
pub fn (mut v Point) equal(u Point) int {
|
|
check_initialized(v, u)
|
|
|
|
mut t1 := Element{}
|
|
mut t2 := Element{}
|
|
mut t3 := Element{}
|
|
mut t4 := Element{}
|
|
|
|
t1.multiply(v.x, u.z)
|
|
t2.multiply(u.x, v.z)
|
|
t3.multiply(v.y, u.z)
|
|
t4.multiply(u.y, v.z)
|
|
|
|
return t1.equal(t2) & t3.equal(t4)
|
|
}
|
|
|
|
// Constant-time operations
|
|
|
|
// selected sets v to a if cond == 1 and to b if cond == 0.
|
|
fn (mut v ProjectiveCached) selected(a ProjectiveCached, b ProjectiveCached, cond int) ProjectiveCached {
|
|
v.ypx.selected(a.ypx, b.ypx, cond)
|
|
v.ymx.selected(a.ymx, b.ymx, cond)
|
|
v.z.selected(a.z, b.z, cond)
|
|
v.t2d.selected(a.t2d, b.t2d, cond)
|
|
return v
|
|
}
|
|
|
|
// selected sets v to a if cond == 1 and to b if cond == 0.
|
|
fn (mut v AffineCached) selected(a AffineCached, b AffineCached, cond int) AffineCached {
|
|
v.ypx.selected(a.ypx, b.ypx, cond)
|
|
v.ymx.selected(a.ymx, b.ymx, cond)
|
|
v.t2d.selected(a.t2d, b.t2d, cond)
|
|
return v
|
|
}
|
|
|
|
// cond_neg negates v if cond == 1 and leaves it unchanged if cond == 0.
|
|
fn (mut v ProjectiveCached) cond_neg(cond int) ProjectiveCached {
|
|
mut el := Element{}
|
|
v.ypx.swap(mut v.ymx, cond)
|
|
v.t2d.selected(el.negate(v.t2d), v.t2d, cond)
|
|
return v
|
|
}
|
|
|
|
// cond_neg negates v if cond == 1 and leaves it unchanged if cond == 0.
|
|
fn (mut v AffineCached) cond_neg(cond int) AffineCached {
|
|
mut el := Element{}
|
|
v.ypx.swap(mut v.ymx, cond)
|
|
v.t2d.selected(el.negate(v.t2d), v.t2d, cond)
|
|
return v
|
|
}
|
|
|
|
fn check_on_curve(points ...Point) bool {
|
|
for p in points {
|
|
mut xx := Element{}
|
|
mut yy := Element{}
|
|
mut zz := Element{}
|
|
mut zzzz := Element{}
|
|
xx.square(p.x)
|
|
yy.square(p.y)
|
|
zz.square(p.z)
|
|
zzzz.square(zz)
|
|
// -x² + y² = 1 + dx²y²
|
|
// -(X/Z)² + (Y/Z)² = 1 + d(X/Z)²(Y/Z)²
|
|
// (-X² + Y²)/Z² = 1 + (dX²Y²)/Z⁴
|
|
// (-X² + Y²)*Z² = Z⁴ + dX²Y²
|
|
mut lhs := Element{}
|
|
mut rhs := Element{}
|
|
lhs.subtract(yy, xx)
|
|
lhs.multiply(lhs, zz)
|
|
rhs.multiply(edwards25519.d_const, xx)
|
|
rhs.multiply(rhs, yy)
|
|
rhs.add(rhs, zzzz)
|
|
|
|
if lhs.equal(rhs) != 1 {
|
|
return false
|
|
}
|
|
/*
|
|
if lhs.equal(rhs) != 1 {
|
|
lg.error('X, Y, and Z do not specify a point on the curve\nX = $p.x \nY = $p.y\nZ = $p.z')
|
|
}*/
|
|
|
|
// xy = T/Z
|
|
lhs.multiply(p.x, p.y)
|
|
rhs.multiply(p.z, p.t)
|
|
/*
|
|
if lhs.equal(rhs) != 1 {
|
|
lg.error('point $i is not valid\nX = $p.x\nY = $p.y\nZ = $p.z')
|
|
}*/
|
|
if lhs.equal(rhs) != 1 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|