convert.go raw
1 package main
2
3 import (
4 "flag"
5 "fmt"
6 "os"
7 "strings"
8
9 "next.orly.dev/pkg/nostr/crypto/ec/schnorr"
10 "next.orly.dev/pkg/nostr/crypto/ec/secp256k1"
11 b32 "next.orly.dev/pkg/nostr/encoders/bech32encoding"
12 "next.orly.dev/pkg/nostr/encoders/hex"
13 )
14
15 func usage() {
16 fmt.Fprintf(os.Stderr, "Usage: convert [--secret] <key>\n")
17 fmt.Fprintf(
18 os.Stderr, " <key> can be hex (64 chars) or bech32 (npub/nsec).\n",
19 )
20 fmt.Fprintf(
21 os.Stderr,
22 " --secret: interpret input key as a secret key; print both nsec and npub in hex and bech32.\n"+
23 " --secret is implied if <key> starts with nsec.\n",
24 )
25 }
26
27 func main() {
28 var isSecret bool
29 flag.BoolVar(
30 &isSecret, "secret", false, "interpret the input as a secret key",
31 )
32 flag.Parse()
33
34 if flag.NArg() < 1 {
35 usage()
36 os.Exit(2)
37 }
38
39 input := strings.TrimSpace(flag.Arg(0))
40
41 // Auto-detect secret if input starts with nsec
42 if strings.HasPrefix(input, string(b32.SecHRP)) {
43 isSecret = true
44 }
45
46 if isSecret {
47 if err := handleSecret(input); err != nil {
48 fmt.Fprintf(os.Stderr, "error: %v\n", err)
49 os.Exit(1)
50 }
51 return
52 }
53
54 if err := handlePublic(input); err != nil {
55 fmt.Fprintf(os.Stderr, "error: %v\n", err)
56 os.Exit(1)
57 }
58 }
59
60 func handleSecret(input string) error {
61 // Accept nsec bech32 or 64-char hex as secret key
62 var sk *secp256k1.SecretKey
63 var err error
64
65 if strings.HasPrefix(input, string(b32.SecHRP)) { // nsec...
66 if sk, err = b32.NsecToSecretKey([]byte(input)); err != nil {
67 return fmt.Errorf("failed to decode nsec: %w", err)
68 }
69 } else {
70 // Expect hex
71 if len(input) != b32.HexKeyLen {
72 return fmt.Errorf("secret key hex must be %d chars", b32.HexKeyLen)
73 }
74 var b []byte
75 if b, err = hex.Dec(input); err != nil {
76 return fmt.Errorf("invalid secret hex: %w", err)
77 }
78 sk = secp256k1.SecKeyFromBytes(b)
79 }
80
81 // Prepare outputs for secret
82 nsec, err := b32.SecretKeyToNsec(sk)
83 if err != nil {
84 return fmt.Errorf("encode nsec: %w", err)
85 }
86 secHex := hex.EncAppend(nil, sk.Serialize())
87
88 // Derive public key
89 pk := sk.PubKey()
90 npub, err := b32.PublicKeyToNpub(pk)
91 if err != nil {
92 return fmt.Errorf("encode npub: %w", err)
93 }
94 pkBytes := schnorr.SerializePubKey(pk)
95 pkHex := hex.EncAppend(nil, pkBytes)
96
97 // Print results
98 fmt.Printf("nsec (hex): %s\n", string(secHex))
99 fmt.Printf("nsec (bech32): %s\n", string(nsec))
100 fmt.Printf("npub (hex): %s\n", string(pkHex))
101 fmt.Printf("npub (bech32): %s\n", string(npub))
102 return nil
103 }
104
105 func handlePublic(input string) error {
106 // Accept npub bech32, nsec bech32 (derive pub), or 64-char hex pubkey
107 var pubBytes []byte
108 var err error
109
110 if strings.HasPrefix(input, string(b32.PubHRP)) { // npub...
111 if pubBytes, err = b32.NpubToBytes([]byte(input)); err != nil {
112 return fmt.Errorf("failed to decode npub: %w", err)
113 }
114 } else if strings.HasPrefix(
115 input, string(b32.SecHRP),
116 ) { // nsec without --secret: show pub only
117 var sk *secp256k1.SecretKey
118 if sk, err = b32.NsecToSecretKey([]byte(input)); err != nil {
119 return fmt.Errorf("failed to decode nsec: %w", err)
120 }
121 pubBytes = schnorr.SerializePubKey(sk.PubKey())
122 } else {
123 // Expect hex pubkey
124 if len(input) != b32.HexKeyLen {
125 return fmt.Errorf("public key hex must be %d chars", b32.HexKeyLen)
126 }
127 if pubBytes, err = hex.Dec(input); err != nil {
128 return fmt.Errorf("invalid public hex: %w", err)
129 }
130 }
131
132 // Compute encodings
133 npub, err := b32.BinToNpub(pubBytes)
134 if err != nil {
135 return fmt.Errorf("encode npub: %w", err)
136 }
137 pubHex := hex.EncAppend(nil, pubBytes)
138
139 // Print only pubkey representations
140 fmt.Printf("npub (hex): %s\n", string(pubHex))
141 fmt.Printf("npub (bech32): %s\n", string(npub))
142 return nil
143 }
144