aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClawd <ai@clawd.bot>2026-02-19 20:55:43 -0800
committerClawd <ai@clawd.bot>2026-02-19 20:55:43 -0800
commit56a59d990f865a6e40e58ee335ee2efe2c0a8a40 (patch)
tree1e42693006633a48d8169bf1fba3d4892f99af8e
parentaa8684e440c619cbff36bba1a9a96c9a503941ab (diff)
Add Point type with addition, doubling, scalar multiplication
-rw-r--r--point.go171
-rw-r--r--point_test.go195
2 files changed, 366 insertions, 0 deletions
diff --git a/point.go b/point.go
new file mode 100644
index 0000000..e8fb440
--- /dev/null
+++ b/point.go
@@ -0,0 +1,171 @@
1package main
2
3import (
4 "fmt"
5 "math/big"
6)
7
8// secp256k1 curve: y² = x³ + 7
9// The 'a' coefficient is 0, 'b' is 7
10var curveB = NewFieldElementFromInt64(7)
11
12// Generator point G for secp256k1
13var (
14 Gx, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16)
15 Gy, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16)
16 G = &Point{
17 x: NewFieldElement(Gx),
18 y: NewFieldElement(Gy),
19 infinity: false,
20 }
21)
22
23// Curve order (number of points on the curve)
24var N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16)
25
26// Point represents a point on the secp256k1 curve
27type Point struct {
28 x, y *FieldElement
29 infinity bool // true if this is the point at infinity (identity)
30}
31
32// NewPoint creates a point from x, y coordinates
33// Returns error if the point is not on the curve
34func NewPoint(x, y *FieldElement) (*Point, error) {
35 p := &Point{x: x, y: y, infinity: false}
36 if !p.IsOnCurve() {
37 return nil, fmt.Errorf("point (%s, %s) is not on the curve", x.String(), y.String())
38 }
39 return p, nil
40}
41
42// Infinity returns the point at infinity (identity element)
43func Infinity() *Point {
44 return &Point{infinity: true}
45}
46
47// IsInfinity returns true if this is the point at infinity
48func (p *Point) IsInfinity() bool {
49 return p.infinity
50}
51
52// IsOnCurve checks if the point satisfies y² = x³ + 7
53func (p *Point) IsOnCurve() bool {
54 if p.infinity {
55 return true
56 }
57 // y² = x³ + 7
58 left := p.y.Square() // y²
59 right := p.x.Square().Mul(p.x).Add(curveB) // x³ + 7
60 return left.Equal(right)
61}
62
63// Equal checks if two points are the same
64func (p *Point) Equal(q *Point) bool {
65 if p.infinity && q.infinity {
66 return true
67 }
68 if p.infinity || q.infinity {
69 return false
70 }
71 return p.x.Equal(q.x) && p.y.Equal(q.y)
72}
73
74// Add returns p + q using the elliptic curve addition formulas
75func (p *Point) Add(q *Point) *Point {
76 // Handle infinity (identity element)
77 if p.infinity {
78 return q
79 }
80 if q.infinity {
81 return p
82 }
83
84 // If points are inverses (same x, opposite y), return infinity
85 if p.x.Equal(q.x) && !p.y.Equal(q.y) {
86 return Infinity()
87 }
88
89 // If points are the same, use doubling formula
90 if p.Equal(q) {
91 return p.Double()
92 }
93
94 // Standard addition formula for P ≠ Q:
95 // slope = (y2 - y1) / (x2 - x1)
96 // x3 = slope² - x1 - x2
97 // y3 = slope * (x1 - x3) - y1
98
99 slope := q.y.Sub(p.y).Div(q.x.Sub(p.x))
100 x3 := slope.Square().Sub(p.x).Sub(q.x)
101 y3 := slope.Mul(p.x.Sub(x3)).Sub(p.y)
102
103 return &Point{x: x3, y: y3, infinity: false}
104}
105
106// Double returns 2P (point added to itself)
107func (p *Point) Double() *Point {
108 if p.infinity {
109 return Infinity()
110 }
111
112 // If y = 0, tangent is vertical, return infinity
113 if p.y.IsZero() {
114 return Infinity()
115 }
116
117 // Doubling formula:
118 // slope = (3x² + a) / (2y) -- for secp256k1, a = 0
119 // x3 = slope² - 2x
120 // y3 = slope * (x - x3) - y
121
122 three := NewFieldElementFromInt64(3)
123 two := NewFieldElementFromInt64(2)
124
125 slope := three.Mul(p.x.Square()).Div(two.Mul(p.y))
126 x3 := slope.Square().Sub(two.Mul(p.x))
127 y3 := slope.Mul(p.x.Sub(x3)).Sub(p.y)
128
129 return &Point{x: x3, y: y3, infinity: false}
130}
131
132// ScalarMul returns k * P (point multiplied by scalar)
133// Uses double-and-add algorithm
134func (p *Point) ScalarMul(k *big.Int) *Point {
135 result := Infinity()
136 addend := p
137
138 // Clone k so we don't modify the original
139 scalar := new(big.Int).Set(k)
140
141 for scalar.Sign() > 0 {
142 // If lowest bit is 1, add current addend
143 if scalar.Bit(0) == 1 {
144 result = result.Add(addend)
145 }
146 // Double the addend
147 addend = addend.Double()
148 // Shift scalar right by 1
149 scalar.Rsh(scalar, 1)
150 }
151
152 return result
153}
154
155// Negate returns -P (same x, negated y)
156func (p *Point) Negate() *Point {
157 if p.infinity {
158 return Infinity()
159 }
160 // -y mod P
161 negY := NewFieldElement(new(big.Int).Sub(P, p.y.value))
162 return &Point{x: p.x.Clone(), y: negY, infinity: false}
163}
164
165// String returns a readable representation
166func (p *Point) String() string {
167 if p.infinity {
168 return "Point(infinity)"
169 }
170 return fmt.Sprintf("Point(%s, %s)", p.x.String()[:8]+"...", p.y.String()[:8]+"...")
171}
diff --git a/point_test.go b/point_test.go
new file mode 100644
index 0000000..ba12977
--- /dev/null
+++ b/point_test.go
@@ -0,0 +1,195 @@
1package main
2
3import (
4 "math/big"
5 "testing"
6)
7
8func TestGeneratorIsOnCurve(t *testing.T) {
9 if !G.IsOnCurve() {
10 t.Error("generator point G should be on the curve")
11 }
12}
13
14func TestInfinityIsOnCurve(t *testing.T) {
15 inf := Infinity()
16 if !inf.IsOnCurve() {
17 t.Error("point at infinity should be considered on curve")
18 }
19}
20
21func TestPointNotOnCurve(t *testing.T) {
22 // (1, 1) is not on y² = x³ + 7
23 // y² = 1, x³ + 7 = 8, so 1 ≠ 8
24 x := NewFieldElementFromInt64(1)
25 y := NewFieldElementFromInt64(1)
26 _, err := NewPoint(x, y)
27 if err == nil {
28 t.Error("(1, 1) should not be on the curve")
29 }
30}
31
32func TestAddInfinity(t *testing.T) {
33 inf := Infinity()
34
35 // G + infinity = G
36 result := G.Add(inf)
37 if !result.Equal(G) {
38 t.Error("G + infinity should equal G")
39 }
40
41 // infinity + G = G
42 result = inf.Add(G)
43 if !result.Equal(G) {
44 t.Error("infinity + G should equal G")
45 }
46
47 // infinity + infinity = infinity
48 result = inf.Add(inf)
49 if !result.IsInfinity() {
50 t.Error("infinity + infinity should be infinity")
51 }
52}
53
54func TestAddInverseGivesInfinity(t *testing.T) {
55 // G + (-G) = infinity
56 negG := G.Negate()
57 result := G.Add(negG)
58 if !result.IsInfinity() {
59 t.Error("G + (-G) should be infinity")
60 }
61}
62
63func TestDoubleGenerator(t *testing.T) {
64 // 2G should be on the curve
65 twoG := G.Double()
66 if !twoG.IsOnCurve() {
67 t.Error("2G should be on the curve")
68 }
69
70 // 2G should not be infinity
71 if twoG.IsInfinity() {
72 t.Error("2G should not be infinity")
73 }
74
75 // 2G should not equal G
76 if twoG.Equal(G) {
77 t.Error("2G should not equal G")
78 }
79}
80
81func TestAddEqualsDouble(t *testing.T) {
82 // G + G should equal G.Double()
83 sum := G.Add(G)
84 doubled := G.Double()
85 if !sum.Equal(doubled) {
86 t.Error("G + G should equal 2G")
87 }
88}
89
90func TestScalarMulByOne(t *testing.T) {
91 // 1 * G = G
92 one := big.NewInt(1)
93 result := G.ScalarMul(one)
94 if !result.Equal(G) {
95 t.Error("1 * G should equal G")
96 }
97}
98
99func TestScalarMulByTwo(t *testing.T) {
100 // 2 * G = G + G
101 two := big.NewInt(2)
102 result := G.ScalarMul(two)
103 expected := G.Double()
104 if !result.Equal(expected) {
105 t.Error("2 * G should equal G.Double()")
106 }
107}
108
109func TestScalarMulByThree(t *testing.T) {
110 // 3 * G = G + G + G
111 three := big.NewInt(3)
112 result := G.ScalarMul(three)
113 expected := G.Double().Add(G)
114 if !result.Equal(expected) {
115 t.Error("3 * G should equal 2G + G")
116 }
117
118 // Result should be on curve
119 if !result.IsOnCurve() {
120 t.Error("3G should be on the curve")
121 }
122}
123
124func TestScalarMulByN(t *testing.T) {
125 // N * G = infinity (curve order)
126 result := G.ScalarMul(N)
127 if !result.IsInfinity() {
128 t.Error("N * G should be infinity (curve order)")
129 }
130}
131
132func TestScalarMulAssociative(t *testing.T) {
133 // (a * b) * G = a * (b * G)
134 a := big.NewInt(7)
135 b := big.NewInt(11)
136 ab := new(big.Int).Mul(a, b) // 77
137
138 left := G.ScalarMul(ab)
139 right := G.ScalarMul(b).ScalarMul(a)
140
141 if !left.Equal(right) {
142 t.Error("scalar multiplication should be associative")
143 }
144}
145
146func TestNegateOnCurve(t *testing.T) {
147 negG := G.Negate()
148 if !negG.IsOnCurve() {
149 t.Error("-G should be on the curve")
150 }
151}
152
153func TestDoubleNegateEqualsOriginal(t *testing.T) {
154 // -(-G) = G
155 result := G.Negate().Negate()
156 if !result.Equal(G) {
157 t.Error("-(-G) should equal G")
158 }
159}
160
161func TestPointEquality(t *testing.T) {
162 // Same point should be equal
163 if !G.Equal(G) {
164 t.Error("G should equal itself")
165 }
166
167 // Different points should not be equal
168 twoG := G.Double()
169 if G.Equal(twoG) {
170 t.Error("G should not equal 2G")
171 }
172
173 // Infinity equals infinity
174 inf1 := Infinity()
175 inf2 := Infinity()
176 if !inf1.Equal(inf2) {
177 t.Error("infinity should equal infinity")
178 }
179}
180
181// Known test vector from Bitcoin
182func TestKnownVector2G(t *testing.T) {
183 // 2G has known coordinates (verified against bitcoin wiki)
184 expectedX, _ := new(big.Int).SetString("C6047F9441ED7D6D3045406E95C07CD85C778E4B8CEF3CA7ABAC09B95C709EE5", 16)
185 expectedY, _ := new(big.Int).SetString("1AE168FEA63DC339A3C58419466CEAEEF7F632653266D0E1236431A950CFE52A", 16)
186
187 twoG := G.Double()
188
189 if twoG.x.value.Cmp(expectedX) != 0 {
190 t.Errorf("2G.x = %s, want %x", twoG.x.String(), expectedX)
191 }
192 if twoG.y.value.Cmp(expectedY) != 0 {
193 t.Errorf("2G.y = %s, want %x", twoG.y.String(), expectedY)
194 }
195}