p256k1_signer_test.go raw
1 //go:build !js && !wasm && !tinygo && !wasm32
2
3 package signer
4
5 import (
6 "testing"
7
8 "next.orly.dev/pkg/p256k1"
9 )
10
11 func TestP256K1Signer_Generate(t *testing.T) {
12 s := NewP256K1Signer()
13 if err := s.Generate(); err != nil {
14 t.Fatalf("Generate failed: %v", err)
15 }
16
17 // Check that we have a secret key
18 sec := s.Sec()
19 if sec == nil || len(sec) != 32 {
20 t.Error("secret key should be 32 bytes")
21 }
22
23 // Check that we have a public key
24 pub := s.Pub()
25 if pub == nil || len(pub) != 32 {
26 t.Error("public key should be 32 bytes")
27 }
28
29 // Check that we can sign
30 msg := make([]byte, 32)
31 sig, err := s.Sign(msg)
32 if err != nil {
33 t.Fatalf("Sign failed: %v", err)
34 }
35 if len(sig) != 64 {
36 t.Error("signature should be 64 bytes")
37 }
38
39 // Check that we can verify
40 valid, err := s.Verify(msg, sig)
41 if err != nil {
42 t.Fatalf("Verify failed: %v", err)
43 }
44 if !valid {
45 t.Error("signature should be valid")
46 }
47
48 // Test with wrong message
49 wrongMsg := make([]byte, 32)
50 wrongMsg[0] = 1
51 valid, err = s.Verify(wrongMsg, sig)
52 if err != nil {
53 t.Fatalf("Verify failed: %v", err)
54 }
55 if valid {
56 t.Error("signature should be invalid for wrong message")
57 }
58
59 s.Zero()
60 }
61
62 func TestP256K1Signer_InitSec(t *testing.T) {
63 // Generate a secret key
64 seckey := make([]byte, 32)
65 for i := range seckey {
66 seckey[i] = byte(i + 1)
67 }
68
69 s := NewP256K1Signer()
70 if err := s.InitSec(seckey); err != nil {
71 t.Fatalf("InitSec failed: %v", err)
72 }
73
74 // Check secret key matches
75 sec := s.Sec()
76 for i := 0; i < 32; i++ {
77 if sec[i] != seckey[i] {
78 t.Errorf("secret key mismatch at byte %d", i)
79 }
80 }
81
82 // Check we can sign
83 msg := make([]byte, 32)
84 sig, err := s.Sign(msg)
85 if err != nil {
86 t.Fatalf("Sign failed: %v", err)
87 }
88 if len(sig) != 64 {
89 t.Error("signature should be 64 bytes")
90 }
91
92 s.Zero()
93 }
94
95 func TestP256K1Signer_InitPub(t *testing.T) {
96 // Generate a keypair first to get a valid x-only pubkey
97 kp, err := p256k1.KeyPairGenerate()
98 if err != nil {
99 t.Fatalf("KeyPairGenerate failed: %v", err)
100 }
101
102 xonly, err := kp.XOnlyPubkey()
103 if err != nil {
104 t.Fatalf("XOnlyPubkey failed: %v", err)
105 }
106
107 pubBytes := xonly.Serialize()
108
109 // Create signer with only public key
110 s := NewP256K1Signer()
111 if err := s.InitPub(pubBytes[:]); err != nil {
112 t.Fatalf("InitPub failed: %v", err)
113 }
114
115 // Check public key matches
116 pub := s.Pub()
117 for i := 0; i < 32; i++ {
118 if pub[i] != pubBytes[i] {
119 t.Errorf("public key mismatch at byte %d", i)
120 }
121 }
122
123 // Should not be able to sign
124 msg := make([]byte, 32)
125 _, err = s.Sign(msg)
126 if err == nil {
127 t.Error("should not be able to sign with only public key")
128 }
129
130 // Should be able to verify (create a signature with the original keypair)
131 var sig [64]byte
132 if err := p256k1.SchnorrSign(sig[:], msg, kp, nil); err != nil {
133 t.Fatalf("SchnorrSign failed: %v", err)
134 }
135
136 valid, err := s.Verify(msg, sig[:])
137 if err != nil {
138 t.Fatalf("Verify failed: %v", err)
139 }
140 if !valid {
141 t.Error("signature should be valid")
142 }
143
144 s.Zero()
145 }
146
147 func TestP256K1Signer_ECDH(t *testing.T) {
148 // Generate two keypairs
149 s1 := NewP256K1Signer()
150 if err := s1.Generate(); err != nil {
151 t.Fatalf("Generate failed: %v", err)
152 }
153 defer s1.Zero()
154
155 s2 := NewP256K1Signer()
156 if err := s2.Generate(); err != nil {
157 t.Fatalf("Generate failed: %v", err)
158 }
159 defer s2.Zero()
160
161 // Compute shared secrets
162 pub1 := s1.Pub()
163 pub2 := s2.Pub()
164
165 secret1, err := s1.ECDH(pub2)
166 if err != nil {
167 t.Fatalf("ECDH failed: %v", err)
168 }
169
170 secret2, err := s2.ECDH(pub1)
171 if err != nil {
172 t.Fatalf("ECDH failed: %v", err)
173 }
174
175 // Shared secrets should match
176 if len(secret1) != 32 || len(secret2) != 32 {
177 t.Error("shared secrets should be 32 bytes")
178 }
179
180 for i := 0; i < 32; i++ {
181 if secret1[i] != secret2[i] {
182 t.Errorf("shared secrets mismatch at byte %d", i)
183 }
184 }
185 }
186
187 func TestP256K1Gen_Generate(t *testing.T) {
188 g := NewP256K1Gen()
189
190 pubBytes, err := g.Generate()
191 if err != nil {
192 t.Fatalf("Generate failed: %v", err)
193 }
194
195 if len(pubBytes) != 33 {
196 t.Errorf("compressed pubkey should be 33 bytes, got %d", len(pubBytes))
197 }
198
199 // Check prefix is 0x02 or 0x03
200 if pubBytes[0] != 0x02 && pubBytes[0] != 0x03 {
201 t.Errorf("invalid compressed pubkey prefix: 0x%02x", pubBytes[0])
202 }
203 }
204
205 func TestP256K1Gen_Negate(t *testing.T) {
206 g := NewP256K1Gen()
207
208 pubBytes1, err := g.Generate()
209 if err != nil {
210 t.Fatalf("Generate failed: %v", err)
211 }
212
213 // Store the original prefix
214 originalPrefix := pubBytes1[0]
215
216 // Negate and check prefix changes
217 g.Negate()
218
219 // Get compressed pubkey from the keypair (don't generate new one)
220 if g.compressedPub == nil {
221 t.Fatal("compressedPub should not be nil after Generate")
222 }
223
224 var compressedPub [33]byte
225 n := p256k1.ECPubkeySerialize(compressedPub[:], g.compressedPub, p256k1.ECCompressed)
226 if n != 33 {
227 t.Fatal("failed to serialize compressed pubkey")
228 }
229
230 // Prefixes should be different (02 vs 03)
231 if originalPrefix == compressedPub[0] {
232 t.Error("Negate should flip the Y coordinate parity")
233 }
234
235 // X coordinates should be the same
236 for i := 1; i < 33; i++ {
237 if pubBytes1[i] != compressedPub[i] {
238 t.Errorf("X coordinate should not change, mismatch at byte %d", i)
239 }
240 }
241 }
242
243 func TestP256K1Gen_KeyPairBytes(t *testing.T) {
244 g := NewP256K1Gen()
245
246 compressedPub, err := g.Generate()
247 if err != nil {
248 t.Fatalf("Generate failed: %v", err)
249 }
250
251 secBytes, pubBytes := g.KeyPairBytes()
252
253 if len(secBytes) != 32 {
254 t.Errorf("secret key should be 32 bytes, got %d", len(secBytes))
255 }
256
257 if len(pubBytes) != 32 {
258 t.Errorf("x-only pubkey should be 32 bytes, got %d", len(pubBytes))
259 }
260
261 // Verify the pubkey matches the compressed pubkey X coordinate
262 // (compressedPub[1:] is the X coordinate)
263 for i := 0; i < 32; i++ {
264 if pubBytes[i] != compressedPub[i+1] {
265 t.Errorf("x-only pubkey mismatch at byte %d", i)
266 }
267 }
268 }
269