aboutsummaryrefslogtreecommitdiffstats
path: root/internal/secp256k1/keys.go
blob: fde5d26632e9fb4314fabe7e98f25ddff07068df (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package secp256k1

import (
	"crypto/rand"
	"fmt"
	"math/big"
)

// PrivateKey is a scalar (1 to N-1) used for signing
type PrivateKey struct {
	D *big.Int // the secret scalar
}

// PublicKey is a point on the curve (D * G)
type PublicKey struct {
	Point *Point
}

// GeneratePrivateKey creates a random private key
func GeneratePrivateKey() (*PrivateKey, error) {
	// Generate random bytes
	bytes := make([]byte, 32)
	_, err := rand.Read(bytes)
	if err != nil {
		return nil, fmt.Errorf("failed to generate random bytes: %w", err)
	}

	// Convert to big.Int and reduce mod N
	d := new(big.Int).SetBytes(bytes)
	d.Mod(d, N)

	// Ensure it's not zero (extremely unlikely but must check)
	if d.Sign() == 0 {
		d.SetInt64(1)
	}

	return &PrivateKey{D: d}, nil
}

// NewPrivateKeyFromBytes creates a private key from 32 bytes
func NewPrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
	if len(b) != 32 {
		return nil, fmt.Errorf("private key must be 32 bytes, got %d", len(b))
	}

	d := new(big.Int).SetBytes(b)

	// Validate: must be in range [1, N-1]
	if d.Sign() == 0 {
		return nil, fmt.Errorf("private key cannot be zero")
	}
	if d.Cmp(N) >= 0 {
		return nil, fmt.Errorf("private key must be less than curve order N")
	}

	return &PrivateKey{D: d}, nil
}

// NewPrivateKeyFromHex creates a private key from a hex string
func NewPrivateKeyFromHex(hex string) (*PrivateKey, error) {
	d, ok := new(big.Int).SetString(hex, 16)
	if !ok {
		return nil, fmt.Errorf("invalid hex string")
	}

	// Validate range
	if d.Sign() == 0 {
		return nil, fmt.Errorf("private key cannot be zero")
	}
	if d.Cmp(N) >= 0 {
		return nil, fmt.Errorf("private key must be less than curve order N")
	}

	return &PrivateKey{D: d}, nil
}

// PublicKey derives the public key from the private key
// PublicKey = D * G
func (priv *PrivateKey) PublicKey() *PublicKey {
	point := G.ScalarMul(priv.D)
	return &PublicKey{Point: point}
}

// Bytes returns the private key as 32 bytes (big-endian, zero-padded)
func (priv *PrivateKey) Bytes() []byte {
	b := priv.D.Bytes()
	// Pad to 32 bytes
	if len(b) < 32 {
		padded := make([]byte, 32)
		copy(padded[32-len(b):], b)
		return padded
	}
	return b
}

// Hex returns the private key as a 64-character hex string
func (priv *PrivateKey) Hex() string {
	return fmt.Sprintf("%064x", priv.D)
}

// Bytes returns the public key in uncompressed format (65 bytes: 0x04 || x || y)
func (pub *PublicKey) Bytes() []byte {
	if pub.Point.IsInfinity() {
		return []byte{0x00} // shouldn't happen with valid keys
	}

	result := make([]byte, 65)
	result[0] = 0x04 // uncompressed prefix

	xBytes := pub.Point.x.value.Bytes()
	yBytes := pub.Point.y.value.Bytes()

	// Copy x (padded to 32 bytes)
	copy(result[1+(32-len(xBytes)):33], xBytes)
	// Copy y (padded to 32 bytes)
	copy(result[33+(32-len(yBytes)):65], yBytes)

	return result
}

// BytesCompressed returns the public key in compressed format (33 bytes: prefix || x)
// Prefix is 0x02 if y is even, 0x03 if y is odd
func (pub *PublicKey) BytesCompressed() []byte {
	if pub.Point.IsInfinity() {
		return []byte{0x00}
	}

	result := make([]byte, 33)

	// Prefix based on y parity
	if pub.Point.y.value.Bit(0) == 0 {
		result[0] = 0x02 // y is even
	} else {
		result[0] = 0x03 // y is odd
	}

	xBytes := pub.Point.x.value.Bytes()
	copy(result[1+(32-len(xBytes)):33], xBytes)

	return result
}

// Hex returns the public key as uncompressed hex (130 characters)
func (pub *PublicKey) Hex() string {
	return fmt.Sprintf("%x", pub.Bytes())
}

// HexCompressed returns the public key as compressed hex (66 characters)
func (pub *PublicKey) HexCompressed() string {
	return fmt.Sprintf("%x", pub.BytesCompressed())
}

// Equal checks if two public keys are the same
func (pub *PublicKey) Equal(other *PublicKey) bool {
	return pub.Point.Equal(other.Point)
}

// ParsePublicKeyXOnly parses a 32-byte x-only public key (BIP-340 format)
func ParsePublicKeyXOnly(xOnlyBytes []byte) (*PublicKey, error) {
	if len(xOnlyBytes) != 32 {
		return nil, fmt.Errorf("x-only public key must be 32 bytes, got %d", len(xOnlyBytes))
	}

	x := new(big.Int).SetBytes(xOnlyBytes)
	point, err := LiftX(x)
	if err != nil {
		return nil, fmt.Errorf("invalid x-only public key: %w", err)
	}

	return &PublicKey{Point: point}, nil
}