aboutsummaryrefslogtreecommitdiffstats
path: root/bech32_test.go
diff options
context:
space:
mode:
authorClawd <ai@clawd.bot>2026-02-19 21:24:07 -0800
committerClawd <ai@clawd.bot>2026-02-19 21:24:07 -0800
commit5778c288e1f61af9dee23967c62871c4c31b0feb (patch)
tree5838473fc3be94d64176a1b6303def7da933b03e /bech32_test.go
parent6f3902d09504838e4e486825f073df971c412595 (diff)
Add bech32 encoding (npub/nsec for Nostr)
Diffstat (limited to 'bech32_test.go')
-rw-r--r--bech32_test.go193
1 files changed, 193 insertions, 0 deletions
diff --git a/bech32_test.go b/bech32_test.go
new file mode 100644
index 0000000..276d998
--- /dev/null
+++ b/bech32_test.go
@@ -0,0 +1,193 @@
1package secp256k1
2
3import (
4 "strings"
5 "testing"
6)
7
8func TestBech32EncodeBasic(t *testing.T) {
9 data := []byte{0x00, 0x01, 0x02}
10 encoded, err := Bech32Encode("test", data)
11 if err != nil {
12 t.Fatalf("encoding failed: %v", err)
13 }
14
15 // Should start with hrp + "1"
16 if !strings.HasPrefix(encoded, "test1") {
17 t.Errorf("encoded should start with 'test1', got %s", encoded)
18 }
19}
20
21func TestBech32RoundTrip(t *testing.T) {
22 data := []byte{0xde, 0xad, 0xbe, 0xef}
23 encoded, err := Bech32Encode("test", data)
24 if err != nil {
25 t.Fatalf("encoding failed: %v", err)
26 }
27
28 hrp, decoded, err := Bech32Decode(encoded)
29 if err != nil {
30 t.Fatalf("decoding failed: %v", err)
31 }
32
33 if hrp != "test" {
34 t.Errorf("hrp mismatch: got %s, want test", hrp)
35 }
36
37 if len(decoded) != len(data) {
38 t.Fatalf("length mismatch: got %d, want %d", len(decoded), len(data))
39 }
40
41 for i := range data {
42 if decoded[i] != data[i] {
43 t.Errorf("byte %d mismatch: got %x, want %x", i, decoded[i], data[i])
44 }
45 }
46}
47
48func TestBech32DecodeInvalidChecksum(t *testing.T) {
49 // Valid encoding, then corrupt it
50 data := []byte{0x01, 0x02, 0x03}
51 encoded, _ := Bech32Encode("test", data)
52
53 // Corrupt last character
54 corrupted := encoded[:len(encoded)-1] + "q"
55
56 _, _, err := Bech32Decode(corrupted)
57 if err == nil {
58 t.Error("should reject invalid checksum")
59 }
60}
61
62func TestBech32DecodeInvalidCharacter(t *testing.T) {
63 _, _, err := Bech32Decode("test1invalid!")
64 if err == nil {
65 t.Error("should reject invalid character")
66 }
67}
68
69func TestNsecEncode(t *testing.T) {
70 priv, _ := NewPrivateKeyFromHex("0000000000000000000000000000000000000000000000000000000000000001")
71 nsec := priv.Nsec()
72
73 if !strings.HasPrefix(nsec, "nsec1") {
74 t.Errorf("nsec should start with 'nsec1', got %s", nsec)
75 }
76}
77
78func TestNpubEncode(t *testing.T) {
79 priv, _ := NewPrivateKeyFromHex("0000000000000000000000000000000000000000000000000000000000000001")
80 pub := priv.PublicKey()
81 npub := pub.Npub()
82
83 if !strings.HasPrefix(npub, "npub1") {
84 t.Errorf("npub should start with 'npub1', got %s", npub)
85 }
86}
87
88func TestNsecRoundTrip(t *testing.T) {
89 priv1, _ := GeneratePrivateKey()
90 nsec := priv1.Nsec()
91
92 priv2, err := PrivateKeyFromNsec(nsec)
93 if err != nil {
94 t.Fatalf("failed to parse nsec: %v", err)
95 }
96
97 if priv1.D.Cmp(priv2.D) != 0 {
98 t.Error("private key should survive nsec round-trip")
99 }
100}
101
102func TestNpubRoundTrip(t *testing.T) {
103 priv, _ := GeneratePrivateKey()
104 pub1 := priv.PublicKey()
105 npub := pub1.Npub()
106
107 pub2, err := PublicKeyFromNpub(npub)
108 if err != nil {
109 t.Fatalf("failed to parse npub: %v", err)
110 }
111
112 // X coordinates should match (y might differ in sign)
113 if pub1.Point.x.value.Cmp(pub2.Point.x.value) != 0 {
114 t.Error("public key x should survive npub round-trip")
115 }
116}
117
118func TestPrivateKeyFromNsecInvalid(t *testing.T) {
119 // Wrong prefix
120 _, err := PrivateKeyFromNsec("npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xjaeh")
121 if err == nil {
122 t.Error("should reject npub as nsec")
123 }
124}
125
126func TestPublicKeyFromNpubInvalid(t *testing.T) {
127 // Wrong prefix
128 _, err := PublicKeyFromNpub("nsec1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq0dcpx3")
129 if err == nil {
130 t.Error("should reject nsec as npub")
131 }
132}
133
134// Test with known private key
135func TestKnownNostrKeyPair(t *testing.T) {
136 // Private key = 1, should give G as public key
137 priv, _ := NewPrivateKeyFromHex("0000000000000000000000000000000000000000000000000000000000000001")
138
139 nsec := priv.Nsec()
140 // Verify it starts with nsec and round-trips
141 if nsec[:5] != "nsec1" {
142 t.Errorf("nsec should start with nsec1, got %s", nsec)
143 }
144
145 // Parse it back
146 priv2, err := PrivateKeyFromNsec(nsec)
147 if err != nil {
148 t.Fatalf("failed to parse nsec: %v", err)
149 }
150 if priv.D.Cmp(priv2.D) != 0 {
151 t.Error("nsec round-trip failed")
152 }
153
154 // Public key should be G
155 pub := priv.PublicKey()
156 npub := pub.Npub()
157
158 if npub[:5] != "npub1" {
159 t.Errorf("npub should start with npub1, got %s", npub)
160 }
161
162 // Parse it back and verify x matches G
163 pub2, err := PublicKeyFromNpub(npub)
164 if err != nil {
165 t.Fatalf("failed to parse npub: %v", err)
166 }
167 if pub2.Point.x.value.Cmp(Gx) != 0 {
168 t.Error("npub should decode to G.x")
169 }
170}
171
172func TestSignAndVerifyWithNostrKeys(t *testing.T) {
173 // Create keys
174 priv, _ := GeneratePrivateKey()
175 nsec := priv.Nsec()
176 npub := priv.PublicKey().Npub()
177
178 // Parse them back
179 priv2, _ := PrivateKeyFromNsec(nsec)
180 pub2, _ := PublicKeyFromNpub(npub)
181
182 // Sign with parsed private key
183 message := []byte("hello nostr")
184 sig, err := Sign(priv2, message)
185 if err != nil {
186 t.Fatalf("signing failed: %v", err)
187 }
188
189 // Verify with parsed public key
190 if !Verify(pub2, message, sig) {
191 t.Error("signature should verify with parsed keys")
192 }
193}