1
1
package c32
2
2
3
3
import (
4
- "encoding/base32"
4
+ "bytes"
5
+ "crypto/sha256"
5
6
"errors"
6
7
"fmt"
7
8
"math/big"
@@ -12,9 +13,35 @@ import (
12
13
13
14
var crockfordAlphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
14
15
15
- func C32Encode (input []byte ) string {
16
- encoder := base32 .NewEncoding (crockfordAlphabet ).WithPadding (base32 .NoPadding )
17
- return encoder .EncodeToString (input )
16
+ func C32Encode (data []byte ) string {
17
+ // Convert bytes to big.Int
18
+ bi := new (big.Int ).SetBytes (data )
19
+
20
+ // Convert big.Int to base32 string
21
+ var encoded strings.Builder
22
+ for bi .Cmp (big .NewInt (0 )) > 0 {
23
+ mod := new (big.Int )
24
+ bi .DivMod (bi , big .NewInt (32 ), mod )
25
+ encoded .WriteByte (crockfordAlphabet [mod .Int64 ()])
26
+ }
27
+
28
+ // Reverse the string
29
+ encodedStr := reverseString (encoded .String ())
30
+
31
+ // Handle leading zeros
32
+ leadingZeros := 0
33
+ for _ , b := range data {
34
+ if b == 0 {
35
+ leadingZeros ++
36
+ } else {
37
+ break
38
+ }
39
+ }
40
+ for i := 0 ; i < leadingZeros ; i ++ {
41
+ encodedStr = "0" + encodedStr
42
+ }
43
+
44
+ return encodedStr
18
45
}
19
46
20
47
func C32Decode (input string ) ([]byte , error ) {
@@ -64,51 +91,101 @@ func DecodeC32Address(address string) (version byte, hash160 [20]byte, err error
64
91
return version , hash160 , nil
65
92
}
66
93
67
- func SerializeAddress (address string ) ([]byte , error ) {
68
- if len (address ) != 1 + 20 * 2 { // 'S' + version + 40 hex chars
69
- return nil , fmt .Errorf ("invalid address length: %d" , len (address ))
94
+ func DecodeWithChecksum (c32addr string ) (byte , []byte , error ) {
95
+ if len (c32addr ) < 2 {
96
+ return 0 , nil , errors .New ("address too short" )
97
+ }
98
+ if c32addr [0 ] != 'S' {
99
+ return 0 , nil , errors .New ("address must start with 'S'" )
70
100
}
71
101
72
- var version byte
73
- switch address [0 ] {
74
- case 'S' :
75
- version = byte (stacks .AddressVersionMainnetSingleSig )
76
- case 'T' :
77
- version = byte (stacks .AddressVersionTestnetSingleSig )
78
- default :
79
- return nil , fmt .Errorf ("invalid address version: %c" , address [0 ])
102
+ // Extract and decode the version character
103
+ versionChar := c32addr [1 ]
104
+ version := byte (strings .IndexRune (crockfordAlphabet , rune (versionChar )))
105
+ if version == 255 { // strings.IndexRune returns -1 if not found, which becomes 255 as byte
106
+ return 0 , nil , errors .New ("invalid version character" )
80
107
}
81
108
82
- hashBytes , err := C32Decode (address [1 :])
109
+ // Decode the remaining C32 string
110
+ c32str := c32addr [2 :]
111
+ data , err := C32Decode (c32str )
83
112
if err != nil {
84
- return nil , fmt .Errorf ("invalid address hash: %v" , err )
113
+ return 0 , nil , err
114
+ }
115
+
116
+ // Expected length: data (20 bytes) + checksum (4 bytes)
117
+ expectedLength := stacks .AddressHashLength + 4
118
+ if len (data ) != expectedLength {
119
+ return 0 , nil , fmt .Errorf ("invalid decoded length: expected %d, got %d" , expectedLength , len (data ))
120
+ }
121
+
122
+ payload := data [:stacks .AddressHashLength ]
123
+ checksum := data [stacks .AddressHashLength :]
124
+
125
+ // Recompute checksum
126
+ versionedData := append ([]byte {version }, payload ... )
127
+ computedChecksum := sha256 .Sum256 (versionedData )
128
+ computedChecksum = sha256 .Sum256 (computedChecksum [:])
129
+ computedChecksumBytes := computedChecksum [:4 ]
130
+
131
+ // Compare checksums
132
+ if ! bytes .Equal (checksum , computedChecksumBytes ) {
133
+ return 0 , nil , errors .New ("checksum mismatch" )
85
134
}
86
135
87
- result := make ([]byte , 1 + len (hashBytes ))
88
- result [0 ] = version
89
- copy (result [1 :], hashBytes )
136
+ return version , payload , nil
137
+ }
138
+
139
+ func EncodeWithChecksum (version byte , data []byte ) (string , error ) {
140
+ if len (data ) != stacks .AddressHashLength {
141
+ return "" , errors .New ("data must be 20 bytes for P2PKH" )
142
+ }
143
+
144
+ // Version byte + data
145
+ versionedData := append ([]byte {version }, data ... )
146
+
147
+ // Compute checksum: double SHA256, first 4 bytes
148
+ checksum := sha256 .Sum256 (versionedData )
149
+ checksum = sha256 .Sum256 (checksum [:])
150
+ checksumBytes := checksum [:4 ]
90
151
91
- return result , nil
152
+ // Append checksum
153
+ fullData := append (data , checksumBytes ... )
154
+
155
+ // Encode to c32
156
+ c32str := C32Encode (fullData )
157
+
158
+ // Add prefix 'S'
159
+ return "S" + string (crockfordAlphabet [version ]) + c32str , nil
160
+ }
161
+
162
+ func SerializeAddress (version stacks.AddressVersion , hash160 []byte ) (string , error ) {
163
+ return EncodeWithChecksum (byte (version ), hash160 )
92
164
}
93
165
94
- func DeserializeAddress (data []byte ) (string , int , error ) {
95
- if len (data ) < 1 + stacks .AddressHashLength {
96
- return "" , 0 , errors .New ("insufficient data for address" )
166
+ func DeserializeAddress (address string ) (stacks.AddressVersion , []byte , error ) {
167
+ version , payload , err := DecodeWithChecksum (address )
168
+ if err != nil {
169
+ return 0 , nil , err
97
170
}
98
171
99
- version := stacks .AddressVersion (data [0 ])
100
- var prefix string
172
+ var addrVersion stacks.AddressVersion
101
173
switch version {
102
- case stacks .AddressVersionMainnetSingleSig :
103
- prefix = "S"
104
- case stacks .AddressVersionTestnetSingleSig :
105
- prefix = "T"
174
+ case byte ( stacks .AddressVersionMainnetSingleSig ) :
175
+ addrVersion = stacks . AddressVersionMainnetSingleSig
176
+ case byte ( stacks .AddressVersionTestnetSingleSig ) :
177
+ addrVersion = stacks . AddressVersionTestnetSingleSig
106
178
default :
107
- return "" , 0 , fmt .Errorf ("invalid address version: %d" , version )
179
+ return 0 , nil , fmt .Errorf ("unknown address version: %d" , version )
108
180
}
109
181
110
- c32hash := C32Encode ( data [ 1 : 1 + stacks . AddressHashLength + 5 ])
111
- address := fmt . Sprintf ( "%s%s" , prefix , c32hash )
182
+ return addrVersion , payload , nil
183
+ }
112
184
113
- return address , 1 + stacks .AddressHashLength + 5 , nil
185
+ func reverseString (s string ) string {
186
+ runes := []rune (s )
187
+ for i , j := 0 , len (runes )- 1 ; i < j ; i , j = i + 1 , j - 1 {
188
+ runes [i ], runes [j ] = runes [j ], runes [i ]
189
+ }
190
+ return string (runes )
114
191
}
0 commit comments