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