summaryrefslogtreecommitdiffstats
path: root/internal/secp256k1/point.go
diff options
context:
space:
mode:
authorClawd <ai@clawd.bot>2026-02-20 18:52:08 -0800
committerClawd <ai@clawd.bot>2026-02-20 18:52:08 -0800
commitd641f4566f051656bae79e406155c4f7f65ec338 (patch)
tree4cd01f3fa4585cf719116a303473e792ea67e82a /internal/secp256k1/point.go
parent84709fd67c02058334519ebee9c110f68b33d9b4 (diff)
embed secp256k1: replace btcec with internal pure-go implementation
This removes all external dependencies by embedding the secp256k1-learn implementation into internal/secp256k1. Changes: - Add internal/secp256k1 with field arithmetic, curve ops, keys, schnorr - Update keys.go to use internal secp256k1 package - Remove btcec/btcutil dependencies (go.mod is now clean) - All tests pass Tradeoffs: - ~10x slower crypto ops vs btcec (acceptable for nostr use case) - Not constant-time (documented limitation) - Zero external dependencies Refs: code.northwest.io/secp256k1-learn
Diffstat (limited to 'internal/secp256k1/point.go')
-rw-r--r--internal/secp256k1/point.go171
1 files changed, 171 insertions, 0 deletions
diff --git a/internal/secp256k1/point.go b/internal/secp256k1/point.go
new file mode 100644
index 0000000..1def176
--- /dev/null
+++ b/internal/secp256k1/point.go
@@ -0,0 +1,171 @@
1package secp256k1
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}