Files
p256k1/group.go
2025-11-02 02:45:59 +00:00

710 lines
16 KiB
Go

package p256k1
// No imports needed for basic group operations
// GroupElementAffine represents a point on the secp256k1 curve in affine coordinates (x, y)
type GroupElementAffine struct {
x, y FieldElement
infinity bool
}
// GroupElementJacobian represents a point on the secp256k1 curve in Jacobian coordinates (x, y, z)
// where the affine coordinates are (x/z^2, y/z^3)
type GroupElementJacobian struct {
x, y, z FieldElement
infinity bool
}
// GroupElementStorage represents a point in storage format (compressed coordinates)
type GroupElementStorage struct {
x [32]byte
y [32]byte
}
// Generator point G for secp256k1 curve
var (
// Generator point in affine coordinates
// G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
// 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8)
GeneratorX FieldElement
GeneratorY FieldElement
Generator GroupElementAffine
)
// Initialize generator point
func init() {
// Generator X coordinate: 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
gxBytes := []byte{
0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87, 0x0B, 0x07,
0x02, 0x9B, 0xFC, 0xDB, 0x2D, 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, 0x98,
}
// Generator Y coordinate: 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
gyBytes := []byte{
0x48, 0x3A, 0xDA, 0x77, 0x26, 0xA3, 0xC4, 0x65, 0x5D, 0xA4, 0xFB, 0xFC, 0x0E, 0x11, 0x08, 0xA8,
0xFD, 0x17, 0xB4, 0x48, 0xA6, 0x85, 0x54, 0x19, 0x9C, 0x47, 0xD0, 0x8F, 0xFB, 0x10, 0xD4, 0xB8,
}
GeneratorX.setB32(gxBytes)
GeneratorY.setB32(gyBytes)
// Create generator point
Generator = GroupElementAffine{
x: GeneratorX,
y: GeneratorY,
infinity: false,
}
}
// NewGroupElementAffine creates a new affine group element
func NewGroupElementAffine() *GroupElementAffine {
return &GroupElementAffine{
x: FieldElementZero,
y: FieldElementZero,
infinity: true,
}
}
// NewGroupElementJacobian creates a new Jacobian group element
func NewGroupElementJacobian() *GroupElementJacobian {
return &GroupElementJacobian{
x: FieldElementZero,
y: FieldElementZero,
z: FieldElementZero,
infinity: true,
}
}
// setXY sets a group element to the point with given coordinates
func (r *GroupElementAffine) setXY(x, y *FieldElement) {
r.x = *x
r.y = *y
r.infinity = false
}
// setXOVar sets a group element to the point with given X coordinate and Y oddness
func (r *GroupElementAffine) setXOVar(x *FieldElement, odd bool) bool {
// Compute y^2 = x^3 + 7 (secp256k1 curve equation)
var x2, x3, y2 FieldElement
x2.sqr(x)
x3.mul(&x2, x)
// Add 7 (the curve parameter b)
var seven FieldElement
seven.setInt(7)
y2 = x3
y2.add(&seven)
// Try to compute square root
var y FieldElement
if !y.sqrt(&y2) {
return false // x is not on the curve
}
// Choose the correct square root based on oddness
y.normalize()
if y.isOdd() != odd {
y.negate(&y, 1)
y.normalize()
}
r.setXY(x, &y)
return true
}
// isInfinity returns true if the group element is the point at infinity
func (r *GroupElementAffine) isInfinity() bool {
return r.infinity
}
// isValid checks if the group element is valid (on the curve)
func (r *GroupElementAffine) isValid() bool {
if r.infinity {
return true
}
// Check curve equation: y^2 = x^3 + 7
var lhs, rhs, x2, x3 FieldElement
// Normalize coordinates
var xNorm, yNorm FieldElement
xNorm = r.x
yNorm = r.y
xNorm.normalize()
yNorm.normalize()
// Compute y^2
lhs.sqr(&yNorm)
// Compute x^3 + 7
x2.sqr(&xNorm)
x3.mul(&x2, &xNorm)
rhs = x3
var seven FieldElement
seven.setInt(7)
rhs.add(&seven)
// Normalize both sides
lhs.normalize()
rhs.normalize()
return lhs.equal(&rhs)
}
// negate sets r to the negation of a (mirror around X axis)
func (r *GroupElementAffine) negate(a *GroupElementAffine) {
if a.infinity {
r.setInfinity()
return
}
r.x = a.x
r.y.negate(&a.y, a.y.magnitude)
r.infinity = false
}
// setInfinity sets the group element to the point at infinity
func (r *GroupElementAffine) setInfinity() {
r.x = FieldElementZero
r.y = FieldElementZero
r.infinity = true
}
// equal returns true if two group elements are equal
func (r *GroupElementAffine) equal(a *GroupElementAffine) bool {
if r.infinity && a.infinity {
return true
}
if r.infinity || a.infinity {
return false
}
// Normalize both points
var rNorm, aNorm GroupElementAffine
rNorm = *r
aNorm = *a
rNorm.x.normalize()
rNorm.y.normalize()
aNorm.x.normalize()
aNorm.y.normalize()
return rNorm.x.equal(&aNorm.x) && rNorm.y.equal(&aNorm.y)
}
// Jacobian coordinate operations
// setInfinity sets the Jacobian group element to the point at infinity
func (r *GroupElementJacobian) setInfinity() {
r.x = FieldElementZero
r.y = FieldElementOne
r.z = FieldElementZero
r.infinity = true
}
// isInfinity returns true if the Jacobian group element is the point at infinity
func (r *GroupElementJacobian) isInfinity() bool {
return r.infinity
}
// setGE sets a Jacobian element from an affine element
func (r *GroupElementJacobian) setGE(a *GroupElementAffine) {
if a.infinity {
r.setInfinity()
return
}
r.x = a.x
r.y = a.y
r.z = FieldElementOne
r.infinity = false
}
// setGEJ sets an affine element from a Jacobian element
// This follows the C secp256k1_ge_set_gej_var implementation exactly
// Optimized: avoid copy when we can modify in-place or when caller guarantees no reuse
func (r *GroupElementAffine) setGEJ(a *GroupElementJacobian) {
if a.infinity {
r.setInfinity()
return
}
// Optimization: if r == a (shouldn't happen but handle gracefully), or if we can work directly
// For now, we still need a copy since we modify fields, but we can optimize the copy
var aCopy GroupElementJacobian
aCopy = *a // Copy once, then work with copy
r.infinity = false
// secp256k1_fe_inv_var(&a->z, &a->z);
// Note: inv normalizes the input internally
aCopy.z.inv(&aCopy.z)
// secp256k1_fe_sqr(&z2, &a->z);
var z2 FieldElement
z2.sqr(&aCopy.z)
// secp256k1_fe_mul(&z3, &a->z, &z2);
var z3 FieldElement
z3.mul(&aCopy.z, &z2)
// secp256k1_fe_mul(&a->x, &a->x, &z2);
aCopy.x.mul(&aCopy.x, &z2)
// secp256k1_fe_mul(&a->y, &a->y, &z3);
aCopy.y.mul(&aCopy.y, &z3)
// secp256k1_fe_set_int(&a->z, 1);
aCopy.z.setInt(1)
// secp256k1_ge_set_xy(r, &a->x, &a->y);
r.x = aCopy.x
r.y = aCopy.y
}
// negate sets r to the negation of a Jacobian point
func (r *GroupElementJacobian) negate(a *GroupElementJacobian) {
if a.infinity {
r.setInfinity()
return
}
r.x = a.x
r.y.negate(&a.y, a.y.magnitude)
r.z = a.z
r.infinity = false
}
// double sets r = 2*a (point doubling in Jacobian coordinates)
// This follows the C secp256k1_gej_double implementation exactly
func (r *GroupElementJacobian) double(a *GroupElementJacobian) {
// Exact C translation - no early return for infinity
// From C code - exact translation with proper variable reuse:
// secp256k1_fe_mul(&r->z, &a->z, &a->y); /* Z3 = Y1*Z1 (1) */
// secp256k1_fe_sqr(&s, &a->y); /* S = Y1^2 (1) */
// secp256k1_fe_sqr(&l, &a->x); /* L = X1^2 (1) */
// secp256k1_fe_mul_int(&l, 3); /* L = 3*X1^2 (3) */
// secp256k1_fe_half(&l); /* L = 3/2*X1^2 (2) */
// secp256k1_fe_negate(&t, &s, 1); /* T = -S (2) */
// secp256k1_fe_mul(&t, &t, &a->x); /* T = -X1*S (1) */
// secp256k1_fe_sqr(&r->x, &l); /* X3 = L^2 (1) */
// secp256k1_fe_add(&r->x, &t); /* X3 = L^2 + T (2) */
// secp256k1_fe_add(&r->x, &t); /* X3 = L^2 + 2*T (3) */
// secp256k1_fe_sqr(&s, &s); /* S' = S^2 (1) */
// secp256k1_fe_add(&t, &r->x); /* T' = X3 + T (4) */
// secp256k1_fe_mul(&r->y, &t, &l); /* Y3 = L*(X3 + T) (1) */
// secp256k1_fe_add(&r->y, &s); /* Y3 = L*(X3 + T) + S^2 (2) */
// secp256k1_fe_negate(&r->y, &r->y, 2); /* Y3 = -(L*(X3 + T) + S^2) (3) */
var l, s, t FieldElement
r.infinity = a.infinity
// Z3 = Y1*Z1 (1)
r.z.mul(&a.z, &a.y)
// S = Y1^2 (1)
s.sqr(&a.y)
// L = X1^2 (1)
l.sqr(&a.x)
// L = 3*X1^2 (3)
l.mulInt(3)
// L = 3/2*X1^2 (2)
l.half(&l)
// T = -S (2) where S = Y1^2
t.negate(&s, 1)
// T = -X1*S = -X1*Y1^2 (1)
t.mul(&t, &a.x)
// X3 = L^2 (1)
r.x.sqr(&l)
// X3 = L^2 + T (2)
r.x.add(&t)
// X3 = L^2 + 2*T (3)
r.x.add(&t)
// S = S^2 = (Y1^2)^2 = Y1^4 (1)
s.sqr(&s)
// T = X3 + T = X3 + (-X1*Y1^2) (4)
t.add(&r.x)
// Y3 = L*(X3 + T) = L*(X3 + (-X1*Y1^2)) (1)
r.y.mul(&t, &l)
// Y3 = L*(X3 + T) + S^2 = L*(X3 + (-X1*Y1^2)) + Y1^4 (2)
r.y.add(&s)
// Y3 = -(L*(X3 + T) + S^2) (3)
r.y.negate(&r.y, 2)
}
// addVar sets r = a + b (variable-time point addition in Jacobian coordinates)
// This follows the C secp256k1_gej_add_var implementation exactly
// Operations: 12 mul, 4 sqr, 11 add/negate/normalizes_to_zero
func (r *GroupElementJacobian) addVar(a, b *GroupElementJacobian) {
// Handle infinity cases
if a.infinity {
*r = *b
return
}
if b.infinity {
*r = *a
return
}
// Following C code exactly: secp256k1_gej_add_var
// z22 = b->z^2
// z12 = a->z^2
// u1 = a->x * z22
// u2 = b->x * z12
// s1 = a->y * z22 * b->z
// s2 = b->y * z12 * a->z
// h = u2 - u1
// i = s2 - s1
// If h == 0 and i == 0: double(a)
// If h == 0 and i != 0: infinity
// Otherwise: add
var z22, z12, u1, u2, s1, s2, h, i, h2, h3, t FieldElement
// z22 = b->z^2
z22.sqr(&b.z)
// z12 = a->z^2
z12.sqr(&a.z)
// u1 = a->x * z22
u1.mul(&a.x, &z22)
// u2 = b->x * z12
u2.mul(&b.x, &z12)
// s1 = a->y * z22 * b->z
s1.mul(&a.y, &z22)
s1.mul(&s1, &b.z)
// s2 = b->y * z12 * a->z
s2.mul(&b.y, &z12)
s2.mul(&s2, &a.z)
// h = u2 - u1
h.negate(&u1, 1)
h.add(&u2)
// i = s2 - s1
i.negate(&s2, 1)
i.add(&s1)
// Check if h normalizes to zero
if h.normalizesToZeroVar() {
if i.normalizesToZeroVar() {
// Points are equal - double
r.double(a)
return
} else {
// Points are negatives - result is infinity
r.setInfinity()
return
}
}
// General addition case
r.infinity = false
// t = h * b->z
t.mul(&h, &b.z)
// r->z = a->z * t
r.z.mul(&a.z, &t)
// h2 = h^2
h2.sqr(&h)
// h2 = -h2
h2.negate(&h2, 1)
// h3 = h2 * h
h3.mul(&h2, &h)
// t = u1 * h2
t.mul(&u1, &h2)
// r->x = i^2
r.x.sqr(&i)
// r->x = i^2 + h3
r.x.add(&h3)
// r->x = i^2 + h3 + t
r.x.add(&t)
// r->x = i^2 + h3 + 2*t
r.x.add(&t)
// t = t + r->x
t.add(&r.x)
// r->y = t * i
r.y.mul(&t, &i)
// h3 = h3 * s1
h3.mul(&h3, &s1)
// r->y = t * i + h3
r.y.add(&h3)
}
// addGEWithZR sets r = a + b where a is Jacobian and b is affine
// If rzr is not nil, sets *rzr = h such that r->z == a->z * h
// This follows the C secp256k1_gej_add_ge_var implementation exactly
// Operations: 8 mul, 3 sqr, 11 add/negate/normalizes_to_zero
func (r *GroupElementJacobian) addGEWithZR(a *GroupElementJacobian, b *GroupElementAffine, rzr *FieldElement) {
if a.infinity {
if rzr != nil {
// C code: VERIFY_CHECK(rzr == NULL) for infinity case
// But we'll handle it gracefully
}
r.setGE(b)
return
}
if b.infinity {
if rzr != nil {
// C code: secp256k1_fe_set_int(rzr, 1)
rzr.setInt(1)
}
*r = *a
return
}
// Following C code exactly: secp256k1_gej_add_ge_var
var z12, u1, u2, s1, s2, h, i, h2, h3, t FieldElement
// z12 = a->z^2
z12.sqr(&a.z)
// u1 = a->x
u1 = a.x
// u2 = b->x * z12
u2.mul(&b.x, &z12)
// s1 = a->y
s1 = a.y
// s2 = b->y * z12 * a->z
s2.mul(&b.y, &z12)
s2.mul(&s2, &a.z)
// h = u2 - u1
// C code uses SECP256K1_GEJ_X_MAGNITUDE_MAX but we use a.x.magnitude
h.negate(&u1, a.x.magnitude)
h.add(&u2)
// i = s2 - s1
i.negate(&s2, 1)
i.add(&s1)
// Check if h normalizes to zero
if h.normalizesToZeroVar() {
if i.normalizesToZeroVar() {
// Points are equal - double
// C code: secp256k1_gej_double_var(r, a, rzr)
// For doubling, rzr should be set to 2*a->y (but we'll use a simpler approach)
// Actually, rzr = 2*a->y based on the double_var implementation
// But for our use case (building odd multiples), we shouldn't hit this case
if rzr != nil {
// Approximate: rzr = 2*a->y (from double_var logic)
// But simpler: just set to 0 since we shouldn't hit this
rzr.setInt(0)
}
r.double(a)
return
} else {
// Points are negatives - result is infinity
if rzr != nil {
// C code: secp256k1_fe_set_int(rzr, 0)
rzr.setInt(0)
}
r.setInfinity()
return
}
}
// General addition case
r.infinity = false
// C code: if (rzr != NULL) *rzr = h;
if rzr != nil {
*rzr = h
}
// r->z = a->z * h
r.z.mul(&a.z, &h)
// h2 = h^2
h2.sqr(&h)
// h2 = -h2
h2.negate(&h2, 1)
// h3 = h2 * h
h3.mul(&h2, &h)
// t = u1 * h2
t.mul(&u1, &h2)
// r->x = i^2
r.x.sqr(&i)
// r->x = i^2 + h3
r.x.add(&h3)
// r->x = i^2 + h3 + t
r.x.add(&t)
// r->x = i^2 + h3 + 2*t
r.x.add(&t)
// t = t + r->x
t.add(&r.x)
// r->y = t * i
r.y.mul(&t, &i)
// h3 = h3 * s1
h3.mul(&h3, &s1)
// r->y = t * i + h3
r.y.add(&h3)
}
// addGE sets r = a + b where a is Jacobian and b is affine
// This follows the C secp256k1_gej_add_ge_var implementation exactly
// Operations: 8 mul, 3 sqr, 11 add/negate/normalizes_to_zero
func (r *GroupElementJacobian) addGE(a *GroupElementJacobian, b *GroupElementAffine) {
r.addGEWithZR(a, b, nil)
}
// clear clears a group element to prevent leaking sensitive information
func (r *GroupElementAffine) clear() {
r.x.clear()
r.y.clear()
r.infinity = true
}
// clear clears a Jacobian group element
func (r *GroupElementJacobian) clear() {
r.x.clear()
r.y.clear()
r.z.clear()
r.infinity = true
}
// toStorage converts a group element to storage format
// Optimized: normalize in-place when possible to avoid copy
func (r *GroupElementAffine) toStorage(s *GroupElementStorage) {
if r.infinity {
// Store infinity as all zeros
for i := range s.x {
s.x[i] = 0
s.y[i] = 0
}
return
}
// Normalize in-place if needed, then convert to bytes
// Optimization: check if already normalized before copying
if !r.x.normalized {
r.x.normalize()
}
if !r.y.normalized {
r.y.normalize()
}
r.x.getB32(s.x[:])
r.y.getB32(s.y[:])
}
// fromStorage converts from storage format to group element
func (r *GroupElementAffine) fromStorage(s *GroupElementStorage) {
// Check if it's the infinity point (all zeros)
var allZero bool = true
for i := range s.x {
if s.x[i] != 0 || s.y[i] != 0 {
allZero = false
break
}
}
if allZero {
r.setInfinity()
return
}
// Convert from bytes
r.x.setB32(s.x[:])
r.y.setB32(s.y[:])
r.infinity = false
}
// toBytes converts a group element to byte representation
// Optimized: normalize in-place when possible to avoid copy
func (r *GroupElementAffine) toBytes(buf []byte) {
if len(buf) < 64 {
panic("buffer too small for group element")
}
if r.infinity {
// Represent infinity as all zeros
for i := range buf[:64] {
buf[i] = 0
}
return
}
// Normalize in-place if needed, then convert to bytes
// Optimization: check if already normalized before copying
if !r.x.normalized {
r.x.normalize()
}
if !r.y.normalized {
r.y.normalize()
}
r.x.getB32(buf[:32])
r.y.getB32(buf[32:64])
}
// fromBytes converts from byte representation to group element
func (r *GroupElementAffine) fromBytes(buf []byte) {
if len(buf) < 64 {
panic("buffer too small for group element")
}
// Check if it's all zeros (infinity)
var allZero bool = true
for i := 0; i < 64; i++ {
if buf[i] != 0 {
allZero = false
break
}
}
if allZero {
r.setInfinity()
return
}
// Convert from bytes
r.x.setB32(buf[:32])
r.y.setB32(buf[32:64])
r.infinity = false
}