Skip to content

Commit cc7f0ed

Browse files
committed
feat: add base58 output format and escape sequence processing
- Add base58 output format for Solana signature compatibility - Implement escape sequence processing (\n, \t, \r, \, ", \') - Fix newline handling in messages for proper wallet verification - Add comprehensive tests for escape sequence processing - Remove duplicate base58Encode function from tests This fixes signature verification issues where backends expect base58 format and properly handles multiline messages with actual newlines instead of literal \n
1 parent 8197ebb commit cc7f0ed

File tree

3 files changed

+83
-51
lines changed

3 files changed

+83
-51
lines changed

go-sol-sign

2.96 MB
Binary file not shown.

main.go

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
// sign messages using Ed25519 private keys in the standard Solana keypair format.
55
//
66
// Usage:
7-
// go-sol-sign -keypair <path> -message <message> [-format base64|hex]
7+
// go-sol-sign -keypair <path> -message <message> [-format base64|hex|base58]
88
//
99
// Examples:
1010
// go-sol-sign -keypair ~/.config/solana/id.json -message "Hello World"
1111
// go-sol-sign -keypair ./keypair.json -message "Test" -format hex
12+
// go-sol-sign -private-key "base58key" -message "Test" -format base58
1213
package main
1314

1415
import (
@@ -40,7 +41,7 @@ func main() {
4041
privateKey = flag.String("private-key", "", "Private key as base58 string (alternative to -keypair)")
4142
message = flag.String("message", "", "Message to sign")
4243
messageFile = flag.String("message-file", "", "Path to file containing message to sign")
43-
outputFormat = flag.String("format", "base64", "Output format: base64, hex")
44+
outputFormat = flag.String("format", "base64", "Output format: base64, hex, base58")
4445
version = flag.Bool("version", false, "Show version information")
4546
verbose = flag.Bool("verbose", false, "Enable verbose output")
4647
)
@@ -76,7 +77,7 @@ func main() {
7677
}
7778

7879
if *message != "" {
79-
messageText = *message
80+
messageText = processEscapeSequences(*message)
8081
} else if *messageFile != "" {
8182
data, err := os.ReadFile(*messageFile)
8283
if err != nil {
@@ -148,11 +149,26 @@ func main() {
148149
fmt.Println(base64.StdEncoding.EncodeToString(signature))
149150
case "hex":
150151
fmt.Println(hex.EncodeToString(signature))
152+
case "base58":
153+
fmt.Println(base58Encode(signature))
151154
default:
152-
log.Fatalf("Unknown format: %s. Supported formats: base64, hex", *outputFormat)
155+
log.Fatalf("Unknown format: %s. Supported formats: base64, hex, base58", *outputFormat)
153156
}
154157
}
155158

159+
// processEscapeSequences converts escape sequences like \n, \t, etc. to actual characters
160+
func processEscapeSequences(input string) string {
161+
// Handle common escape sequences
162+
result := input
163+
result = strings.ReplaceAll(result, "\\n", "\n")
164+
result = strings.ReplaceAll(result, "\\t", "\t")
165+
result = strings.ReplaceAll(result, "\\r", "\r")
166+
result = strings.ReplaceAll(result, "\\\\", "\\")
167+
result = strings.ReplaceAll(result, "\\\"", "\"")
168+
result = strings.ReplaceAll(result, "\\'", "'")
169+
return result
170+
}
171+
156172
// printUsage displays usage information
157173
func printUsage() {
158174
fmt.Printf("%s v%s - %s\n\n", ToolName, Version, ToolDescription)
@@ -167,14 +183,15 @@ func printUsage() {
167183
fmt.Println(" -message-file string Path to file containing message")
168184
fmt.Println("")
169185
fmt.Println("Other Options:")
170-
fmt.Println(" -format string Output format: base64, hex (default: base64)")
186+
fmt.Println(" -format string Output format: base64, hex, base58 (default: base64)")
171187
fmt.Println(" -verbose Enable verbose output")
172188
fmt.Println(" -version Show version information")
173189
fmt.Println("")
174190
fmt.Println("Examples:")
175191
fmt.Printf(" %s -keypair ~/.config/solana/id.json -message \"Hello World\"\n", ToolName)
176192
fmt.Printf(" %s -private-key 3yD2... -message \"Test\" -format hex\n", ToolName)
177193
fmt.Printf(" %s -keypair ./keypair.json -message-file ./message.txt\n", ToolName)
194+
fmt.Printf(" %s -private-key 3yD2... -message \"Test\" -format base58\n", ToolName)
178195
fmt.Printf(" %s -version\n", ToolName)
179196
}
180197

@@ -294,3 +311,46 @@ func base58Decode(s string) ([]byte, error) {
294311

295312
return result, nil
296313
}
314+
315+
// Simple base58 encoder for Solana signatures
316+
func base58Encode(input []byte) string {
317+
const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
318+
319+
if len(input) == 0 {
320+
return ""
321+
}
322+
323+
// Count leading zeros
324+
leadingZeros := 0
325+
for i := 0; i < len(input) && input[i] == 0; i++ {
326+
leadingZeros++
327+
}
328+
329+
// Convert to base58
330+
var result []byte
331+
for i := leadingZeros; i < len(input); i++ {
332+
carry := int(input[i])
333+
for j := 0; j < len(result); j++ {
334+
carry += int(result[j]) * 256
335+
result[j] = byte(carry % 58)
336+
carry /= 58
337+
}
338+
339+
for carry > 0 {
340+
result = append(result, byte(carry%58))
341+
carry /= 58
342+
}
343+
}
344+
345+
// Convert to alphabet characters
346+
var encoded []byte
347+
for i := 0; i < leadingZeros; i++ {
348+
encoded = append(encoded, '1')
349+
}
350+
351+
for i := len(result) - 1; i >= 0; i-- {
352+
encoded = append(encoded, alphabet[result[i]])
353+
}
354+
355+
return string(encoded)
356+
}

main_test.go

Lines changed: 18 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -294,54 +294,26 @@ func TestBase58Decode(t *testing.T) {
294294
}
295295
}
296296

297-
// Helper function to encode bytes to base58 (for testing)
298-
func base58Encode(data []byte) string {
299-
const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
300-
301-
if len(data) == 0 {
302-
return ""
303-
}
304-
305-
// Convert to big integer
306-
var result []byte
307-
308-
// Count leading zeros
309-
leadingZeros := 0
310-
for i := 0; i < len(data) && data[i] == 0; i++ {
311-
leadingZeros++
297+
// TestEscapeSequenceProcessing tests that escape sequences are properly converted
298+
func TestEscapeSequenceProcessing(t *testing.T) {
299+
tests := []struct {
300+
input string
301+
expected string
302+
}{
303+
{"Hello\\nWorld", "Hello\nWorld"},
304+
{"Hello\\tWorld", "Hello\tWorld"},
305+
{"Hello\\rWorld", "Hello\rWorld"},
306+
{"Hello\\\\World", "Hello\\World"},
307+
{"Hello\\\"World", "Hello\"World"},
308+
{"Hello\\'World", "Hello'World"},
309+
{"No escapes", "No escapes"},
310+
{"Multiple\\nlines\\nhere", "Multiple\nlines\nhere"},
312311
}
313312

314-
// Convert
315-
input := make([]byte, len(data))
316-
copy(input, data)
317-
318-
for len(input) > 0 {
319-
// Find first non-zero
320-
i := 0
321-
for i < len(input) && input[i] == 0 {
322-
i++
313+
for _, test := range tests {
314+
result := processEscapeSequences(test.input)
315+
if result != test.expected {
316+
t.Errorf("processEscapeSequences(%q) = %q, want %q", test.input, result, test.expected)
323317
}
324-
input = input[i:]
325-
326-
if len(input) == 0 {
327-
break
328-
}
329-
330-
// Divide by 58
331-
remainder := 0
332-
for i := 0; i < len(input); i++ {
333-
temp := remainder*256 + int(input[i])
334-
input[i] = byte(temp / 58)
335-
remainder = temp % 58
336-
}
337-
338-
result = append([]byte{alphabet[remainder]}, result...)
339318
}
340-
341-
// Add leading 1s for leading zeros
342-
for i := 0; i < leadingZeros; i++ {
343-
result = append([]byte{'1'}, result...)
344-
}
345-
346-
return string(result)
347319
}

0 commit comments

Comments
 (0)