Files
p256k1/avx/point.go
2025-11-28 16:35:08 +00:00

426 lines
8.2 KiB
Go

package avx
// Point operations on the secp256k1 curve.
// Affine: (x, y) where y² = x³ + 7
// Jacobian: (X, Y, Z) where affine = (X/Z², Y/Z³)
// SetInfinity sets the point to the point at infinity.
func (p *AffinePoint) SetInfinity() *AffinePoint {
p.X = FieldZero
p.Y = FieldZero
p.Infinity = true
return p
}
// IsInfinity returns true if the point is the point at infinity.
func (p *AffinePoint) IsInfinity() bool {
return p.Infinity
}
// Set sets p to the value of q.
func (p *AffinePoint) Set(q *AffinePoint) *AffinePoint {
p.X = q.X
p.Y = q.Y
p.Infinity = q.Infinity
return p
}
// Equal returns true if two points are equal.
func (p *AffinePoint) Equal(q *AffinePoint) bool {
if p.Infinity && q.Infinity {
return true
}
if p.Infinity || q.Infinity {
return false
}
return p.X.Equal(&q.X) && p.Y.Equal(&q.Y)
}
// Negate sets p = -q (reflection over x-axis).
func (p *AffinePoint) Negate(q *AffinePoint) *AffinePoint {
if q.Infinity {
p.SetInfinity()
return p
}
p.X = q.X
p.Y.Negate(&q.Y)
p.Infinity = false
return p
}
// IsOnCurve returns true if the point is on the secp256k1 curve.
func (p *AffinePoint) IsOnCurve() bool {
if p.Infinity {
return true
}
// Check y² = x³ + 7
var y2, x2, x3, rhs FieldElement
y2.Sqr(&p.Y)
x2.Sqr(&p.X)
x3.Mul(&x2, &p.X)
// rhs = x³ + 7
var seven FieldElement
seven.N[0].Lo = 7
rhs.Add(&x3, &seven)
return y2.Equal(&rhs)
}
// SetXY sets the point to (x, y).
func (p *AffinePoint) SetXY(x, y *FieldElement) *AffinePoint {
p.X = *x
p.Y = *y
p.Infinity = false
return p
}
// SetCompressed sets the point from compressed form (x coordinate + sign bit).
// Returns true if successful.
func (p *AffinePoint) SetCompressed(x *FieldElement, odd bool) bool {
// Compute y² = x³ + 7
var y2, x2, x3 FieldElement
x2.Sqr(x)
x3.Mul(&x2, x)
// y² = x³ + 7
var seven FieldElement
seven.N[0].Lo = 7
y2.Add(&x3, &seven)
// Compute y = sqrt(y²)
var y FieldElement
if !y.Sqrt(&y2) {
return false // No square root exists
}
// Choose the correct sign
if y.IsOdd() != odd {
y.Negate(&y)
}
p.X = *x
p.Y = y
p.Infinity = false
return true
}
// Jacobian point operations
// SetInfinity sets the Jacobian point to the point at infinity.
func (p *JacobianPoint) SetInfinity() *JacobianPoint {
p.X = FieldOne
p.Y = FieldOne
p.Z = FieldZero
p.Infinity = true
return p
}
// IsInfinity returns true if the point is the point at infinity.
func (p *JacobianPoint) IsInfinity() bool {
return p.Infinity || p.Z.IsZero()
}
// Set sets p to the value of q.
func (p *JacobianPoint) Set(q *JacobianPoint) *JacobianPoint {
p.X = q.X
p.Y = q.Y
p.Z = q.Z
p.Infinity = q.Infinity
return p
}
// FromAffine converts an affine point to Jacobian coordinates.
func (p *JacobianPoint) FromAffine(q *AffinePoint) *JacobianPoint {
if q.Infinity {
p.SetInfinity()
return p
}
p.X = q.X
p.Y = q.Y
p.Z = FieldOne
p.Infinity = false
return p
}
// ToAffine converts a Jacobian point to affine coordinates.
func (p *JacobianPoint) ToAffine(q *AffinePoint) *AffinePoint {
if p.IsInfinity() {
q.SetInfinity()
return q
}
// affine = (X/Z², Y/Z³)
var zInv, zInv2, zInv3 FieldElement
zInv.Inverse(&p.Z)
zInv2.Sqr(&zInv)
zInv3.Mul(&zInv2, &zInv)
q.X.Mul(&p.X, &zInv2)
q.Y.Mul(&p.Y, &zInv3)
q.Infinity = false
return q
}
// Double sets p = 2*q using Jacobian coordinates.
// Standard Jacobian doubling for y²=x³+b (secp256k1 has a=0):
// M = 3*X₁²
// S = 4*X₁*Y₁²
// T = 8*Y₁⁴
// X₃ = M² - 2*S
// Y₃ = M*(S - X₃) - T
// Z₃ = 2*Y₁*Z₁
func (p *JacobianPoint) Double(q *JacobianPoint) *JacobianPoint {
if q.IsInfinity() {
p.SetInfinity()
return p
}
var y2, m, x2, s, t, tmp FieldElement
var x3, y3, z3 FieldElement // Use temporaries to avoid aliasing issues
// Y² = Y₁²
y2.Sqr(&q.Y)
// M = 3*X₁² (for a=0 curves like secp256k1)
x2.Sqr(&q.X)
m.MulInt(&x2, 3)
// S = 4*X₁*Y₁²
s.Mul(&q.X, &y2)
s.MulInt(&s, 4)
// T = 8*Y₁⁴
t.Sqr(&y2)
t.MulInt(&t, 8)
// X₃ = M² - 2*S
x3.Sqr(&m)
tmp.Double(&s)
x3.Sub(&x3, &tmp)
// Y₃ = M*(S - X₃) - T
tmp.Sub(&s, &x3)
y3.Mul(&m, &tmp)
y3.Sub(&y3, &t)
// Z₃ = 2*Y₁*Z₁
z3.Mul(&q.Y, &q.Z)
z3.Double(&z3)
// Now copy to output (safe even if p == q)
p.X = x3
p.Y = y3
p.Z = z3
p.Infinity = false
return p
}
// Add sets p = q + r using Jacobian coordinates.
// This is the complete addition formula.
func (p *JacobianPoint) Add(q, r *JacobianPoint) *JacobianPoint {
if q.IsInfinity() {
p.Set(r)
return p
}
if r.IsInfinity() {
p.Set(q)
return p
}
// Algorithm:
// U₁ = X₁*Z₂²
// U₂ = X₂*Z₁²
// S₁ = Y₁*Z₂³
// S₂ = Y₂*Z₁³
// H = U₂ - U₁
// R = S₂ - S₁
// If H = 0 and R = 0: return Double(q)
// If H = 0 and R ≠ 0: return Infinity
// X₃ = R² - H³ - 2*U₁*H²
// Y₃ = R*(U₁*H² - X₃) - S₁*H³
// Z₃ = H*Z₁*Z₂
var u1, u2, s1, s2, h, rr, h2, h3, u1h2 FieldElement
var z1sq, z2sq, z1cu, z2cu FieldElement
var x3, y3, z3 FieldElement // Use temporaries to avoid aliasing issues
z1sq.Sqr(&q.Z)
z2sq.Sqr(&r.Z)
z1cu.Mul(&z1sq, &q.Z)
z2cu.Mul(&z2sq, &r.Z)
u1.Mul(&q.X, &z2sq)
u2.Mul(&r.X, &z1sq)
s1.Mul(&q.Y, &z2cu)
s2.Mul(&r.Y, &z1cu)
h.Sub(&u2, &u1)
rr.Sub(&s2, &s1)
// Check for special cases
if h.IsZero() {
if rr.IsZero() {
// Points are equal, use doubling
return p.Double(q)
}
// Points are inverses, return infinity
p.SetInfinity()
return p
}
h2.Sqr(&h)
h3.Mul(&h2, &h)
u1h2.Mul(&u1, &h2)
// X₃ = R² - H³ - 2*U₁*H²
var r2, u1h2_2 FieldElement
r2.Sqr(&rr)
u1h2_2.Double(&u1h2)
x3.Sub(&r2, &h3)
x3.Sub(&x3, &u1h2_2)
// Y₃ = R*(U₁*H² - X₃) - S₁*H³
var tmp, s1h3 FieldElement
tmp.Sub(&u1h2, &x3)
y3.Mul(&rr, &tmp)
s1h3.Mul(&s1, &h3)
y3.Sub(&y3, &s1h3)
// Z₃ = H*Z₁*Z₂
z3.Mul(&q.Z, &r.Z)
z3.Mul(&z3, &h)
// Now copy to output (safe even if p == q or p == r)
p.X = x3
p.Y = y3
p.Z = z3
p.Infinity = false
return p
}
// AddAffine sets p = q + r where q is Jacobian and r is affine.
// More efficient than converting r to Jacobian first.
func (p *JacobianPoint) AddAffine(q *JacobianPoint, r *AffinePoint) *JacobianPoint {
if q.IsInfinity() {
p.FromAffine(r)
return p
}
if r.Infinity {
p.Set(q)
return p
}
// When Z₂ = 1 (affine point), formulas simplify:
// U₁ = X₁
// U₂ = X₂*Z₁²
// S₁ = Y₁
// S₂ = Y₂*Z₁³
var u2, s2, h, rr, h2, h3, u1h2 FieldElement
var z1sq, z1cu FieldElement
var x3, y3, z3 FieldElement // Use temporaries to avoid aliasing issues
z1sq.Sqr(&q.Z)
z1cu.Mul(&z1sq, &q.Z)
u2.Mul(&r.X, &z1sq)
s2.Mul(&r.Y, &z1cu)
h.Sub(&u2, &q.X)
rr.Sub(&s2, &q.Y)
if h.IsZero() {
if rr.IsZero() {
return p.Double(q)
}
p.SetInfinity()
return p
}
h2.Sqr(&h)
h3.Mul(&h2, &h)
u1h2.Mul(&q.X, &h2)
// X₃ = R² - H³ - 2*U₁*H²
var r2, u1h2_2 FieldElement
r2.Sqr(&rr)
u1h2_2.Double(&u1h2)
x3.Sub(&r2, &h3)
x3.Sub(&x3, &u1h2_2)
// Y₃ = R*(U₁*H² - X₃) - S₁*H³
var tmp, s1h3 FieldElement
tmp.Sub(&u1h2, &x3)
y3.Mul(&rr, &tmp)
s1h3.Mul(&q.Y, &h3)
y3.Sub(&y3, &s1h3)
// Z₃ = H*Z₁
z3.Mul(&q.Z, &h)
// Now copy to output (safe even if p == q)
p.X = x3
p.Y = y3
p.Z = z3
p.Infinity = false
return p
}
// Negate sets p = -q (reflection over x-axis).
func (p *JacobianPoint) Negate(q *JacobianPoint) *JacobianPoint {
if q.IsInfinity() {
p.SetInfinity()
return p
}
p.X = q.X
p.Y.Negate(&q.Y)
p.Z = q.Z
p.Infinity = false
return p
}
// ScalarMult computes p = k*q using double-and-add.
func (p *JacobianPoint) ScalarMult(q *JacobianPoint, k *Scalar) *JacobianPoint {
// Simple double-and-add (not constant-time)
// A proper implementation would use windowed NAF or similar
p.SetInfinity()
// Process bits from high to low
bytes := k.Bytes()
for i := 0; i < 32; i++ {
b := bytes[i]
for j := 7; j >= 0; j-- {
p.Double(p)
if (b>>j)&1 == 1 {
p.Add(p, q)
}
}
}
return p
}
// ScalarBaseMult computes p = k*G where G is the generator.
func (p *JacobianPoint) ScalarBaseMult(k *Scalar) *JacobianPoint {
var g JacobianPoint
g.FromAffine(&Generator)
return p.ScalarMult(&g, k)
}
// BasePointMult computes k*G and returns the result in affine coordinates.
func BasePointMult(k *Scalar) *AffinePoint {
var jac JacobianPoint
var aff AffinePoint
jac.ScalarBaseMult(k)
jac.ToAffine(&aff)
return &aff
}