Replaced legacy `*.orly` module imports with `next.orly.dev/pkg` paths across the codebase for consistency. Removed legacy `go.mod` files from sub-packages, consolidating dependency management. Added Dockerfiles and configurations for benchmarking environments.
269 lines
7.0 KiB
Go
269 lines
7.0 KiB
Go
package bech32encoding
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
|
|
"lol.mleku.dev/chk"
|
|
"lol.mleku.dev/log"
|
|
"next.orly.dev/pkg/crypto/ec"
|
|
"next.orly.dev/pkg/crypto/ec/bech32"
|
|
"next.orly.dev/pkg/crypto/ec/schnorr"
|
|
"next.orly.dev/pkg/crypto/ec/secp256k1"
|
|
"next.orly.dev/pkg/encoders/hex"
|
|
"next.orly.dev/pkg/utils"
|
|
"next.orly.dev/pkg/utils/constraints"
|
|
)
|
|
|
|
const (
|
|
// MinKeyStringLen is 56 because Bech32 needs 52 characters plus 4 for the HRP,
|
|
// any string shorter than this cannot be a nostr key.
|
|
MinKeyStringLen = 56
|
|
// HexKeyLen is the length of a nostr key in hexadecimal.
|
|
HexKeyLen = 64
|
|
// Bech32HRPLen is the length of the standard nostr keys, nsec and npub.
|
|
Bech32HRPLen = 4
|
|
)
|
|
|
|
var (
|
|
// SecHRP is the standard Human Readable Prefix (HRP) for a nostr secret key in bech32 encoding - nsec
|
|
SecHRP = []byte("nsec")
|
|
// PubHRP is the standard Human Readable Prefix (HRP) for a nostr public key in bech32 encoding - nsec
|
|
PubHRP = []byte("npub")
|
|
)
|
|
|
|
// ConvertForBech32 performs the bit expansion required for encoding into Bech32.
|
|
func ConvertForBech32(b8 []byte) (b5 []byte, err error) {
|
|
return bech32.ConvertBits(
|
|
b8, 8, 5,
|
|
true,
|
|
)
|
|
}
|
|
|
|
// ConvertFromBech32 collapses together the bit expanded 5 bit numbers encoded in bech32.
|
|
func ConvertFromBech32(b5 []byte) (b8 []byte, err error) {
|
|
return bech32.ConvertBits(
|
|
b5, 5, 8,
|
|
true,
|
|
)
|
|
}
|
|
|
|
// SecretKeyToNsec encodes an secp256k1 secret key as a Bech32 string (nsec).
|
|
func SecretKeyToNsec(sk *secp256k1.SecretKey) (encoded []byte, err error) {
|
|
var b5 []byte
|
|
if b5, err = ConvertForBech32(sk.Serialize()); chk.E(err) {
|
|
return
|
|
}
|
|
return bech32.Encode(SecHRP, b5)
|
|
}
|
|
|
|
// PublicKeyToNpub encodes a public key as a bech32 string (npub).
|
|
func PublicKeyToNpub(pk *secp256k1.PublicKey) (encoded []byte, err error) {
|
|
var bits5 []byte
|
|
pubKeyBytes := schnorr.SerializePubKey(pk)
|
|
if bits5, err = ConvertForBech32(pubKeyBytes); chk.E(err) {
|
|
return
|
|
}
|
|
return bech32.Encode(PubHRP, bits5)
|
|
}
|
|
|
|
// NsecToSecretKey decodes a nostr secret key (nsec) and returns the secp256k1
|
|
// secret key.
|
|
func NsecToSecretKey[V constraints.Bytes](encoded V) (
|
|
sk *secp256k1.SecretKey, err error,
|
|
) {
|
|
var b8 []byte
|
|
if b8, err = NsecToBytes(encoded); chk.E(err) {
|
|
return
|
|
}
|
|
sk = secp256k1.SecKeyFromBytes(b8)
|
|
return
|
|
}
|
|
|
|
// NsecToBytes converts a nostr bech32 encoded secret key to raw bytes.
|
|
func NsecToBytes[V constraints.Bytes](encoded V) (sk []byte, err error) {
|
|
var b5, hrp []byte
|
|
if hrp, b5, err = bech32.Decode([]byte(encoded)); chk.E(err) {
|
|
return
|
|
}
|
|
if !utils.FastEqual(hrp, SecHRP) {
|
|
err = log.E.Err(
|
|
"wrong human readable part, got '%s' want '%s'",
|
|
hrp, SecHRP,
|
|
)
|
|
return
|
|
}
|
|
if sk, err = ConvertFromBech32(b5); chk.E(err) {
|
|
return
|
|
}
|
|
sk = sk[:secp256k1.SecKeyBytesLen]
|
|
return
|
|
}
|
|
|
|
// NpubToBytes converts a bech32 encoded public key to raw bytes.
|
|
func NpubToBytes[V constraints.Bytes](encoded V) (pk []byte, err error) {
|
|
var b5, hrp []byte
|
|
if hrp, b5, err = bech32.Decode([]byte(encoded)); chk.E(err) {
|
|
return
|
|
}
|
|
if !utils.FastEqual(hrp, PubHRP) {
|
|
err = log.E.Err(
|
|
"wrong human readable part, got '%s' want '%s'",
|
|
hrp, SecHRP,
|
|
)
|
|
return
|
|
}
|
|
if pk, err = ConvertFromBech32(b5); chk.E(err) {
|
|
return
|
|
}
|
|
pk = pk[:schnorr.PubKeyBytesLen]
|
|
return
|
|
}
|
|
|
|
// NpubToPublicKey decodes an nostr public key (npub) and returns an secp256k1
|
|
// public key.
|
|
func NpubToPublicKey[V constraints.Bytes](encoded V) (
|
|
pk *secp256k1.PublicKey, err error,
|
|
) {
|
|
var b5, b8, hrp []byte
|
|
if hrp, b5, err = bech32.Decode([]byte(encoded)); chk.E(err) {
|
|
err = log.E.Err("ERROR: '%s'", err)
|
|
return
|
|
}
|
|
if !utils.FastEqual(hrp, PubHRP) {
|
|
err = log.E.Err(
|
|
"wrong human readable part, got '%s' want '%s'",
|
|
hrp, PubHRP,
|
|
)
|
|
return
|
|
}
|
|
if b8, err = ConvertFromBech32(b5); chk.E(err) {
|
|
return
|
|
}
|
|
return schnorr.ParsePubKey(b8[:schnorr.PubKeyBytesLen])
|
|
}
|
|
|
|
// HexToPublicKey decodes a string that should be a 64 character long hex
|
|
// encoded public key into a btcec.PublicKey that can be used to verify a
|
|
// signature or encode to Bech32.
|
|
func HexToPublicKey[V constraints.Bytes](pk V) (p *btcec.PublicKey, err error) {
|
|
if len(pk) != HexKeyLen {
|
|
err = log.E.Err(
|
|
"secret key is %d bytes, must be %d", len(pk),
|
|
HexKeyLen,
|
|
)
|
|
return
|
|
}
|
|
var pb []byte
|
|
if pb, err = hex.Dec(string(pk)); chk.D(err) {
|
|
return
|
|
}
|
|
if p, err = schnorr.ParsePubKey(pb); chk.D(err) {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func NpubOrHexToPublicKey[V constraints.Bytes](encoded V) (
|
|
pk *btcec.PublicKey, err error,
|
|
) {
|
|
if !strings.HasPrefix(
|
|
"npub", string(encoded),
|
|
) && len(encoded) == HexKeyLen {
|
|
return HexToPublicKey(encoded)
|
|
}
|
|
return NpubToPublicKey(encoded)
|
|
}
|
|
|
|
func NpubOrHexToPublicKeyBinary[V constraints.Bytes](enc V) (
|
|
pkb []byte, err error,
|
|
) {
|
|
if bytes.HasPrefix([]byte(enc), []byte("npub")) {
|
|
return NpubToBytes(enc)
|
|
}
|
|
return hex.Dec(string(enc))
|
|
}
|
|
|
|
// HexToSecretKey decodes a string that should be a 64 character long hex
|
|
// encoded public key into a btcec.PublicKey that can be used to verify a
|
|
// signature or encode to Bech32.
|
|
func HexToSecretKey[V constraints.Bytes](sk V) (s *btcec.SecretKey, err error) {
|
|
if len(sk) != HexKeyLen {
|
|
err = log.E.Err(
|
|
"secret key is %d bytes, must be %d", len(sk),
|
|
HexKeyLen,
|
|
)
|
|
return
|
|
}
|
|
pb := make([]byte, schnorr.PubKeyBytesLen)
|
|
if _, err = hex.DecBytes(pb, []byte(sk)); chk.D(err) {
|
|
return
|
|
}
|
|
if s = secp256k1.SecKeyFromBytes(pb); chk.D(err) {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// HexToNpub converts a raw 64 character hex encoded public key (as used in
|
|
// standard nostr json events) to a bech32 encoded npub.
|
|
func HexToNpub[V constraints.Bytes](publicKeyHex V) (s []byte, err error) {
|
|
b := make([]byte, schnorr.PubKeyBytesLen)
|
|
if _, err = hex.DecBytes(b, []byte(publicKeyHex)); chk.D(err) {
|
|
err = log.E.Err("failed to decode public key hex: %w", err)
|
|
return
|
|
}
|
|
var bits5 []byte
|
|
if bits5, err = bech32.ConvertBits(b, 8, 5, true); chk.D(err) {
|
|
return nil, err
|
|
}
|
|
return bech32.Encode(NpubHRP, bits5)
|
|
}
|
|
|
|
// BinToNpub converts a raw 32 byte public key to nostr bech32 encoded npub.
|
|
func BinToNpub(b []byte) (s []byte, err error) {
|
|
var bits5 []byte
|
|
if bits5, err = bech32.ConvertBits(b, 8, 5, true); chk.D(err) {
|
|
return nil, err
|
|
}
|
|
return bech32.Encode(NpubHRP, bits5)
|
|
}
|
|
|
|
// HexToNsec converts a hex encoded secret key to a bech32 encoded nsec.
|
|
func HexToNsec[V constraints.Bytes](sk V) (nsec []byte, err error) {
|
|
var s *btcec.SecretKey
|
|
if s, err = HexToSecretKey(sk); chk.E(err) {
|
|
return
|
|
}
|
|
if nsec, err = SecretKeyToNsec(s); chk.E(err) {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// BinToNsec converts a binary secret key to a bech32 encoded nsec.
|
|
func BinToNsec(sk []byte) (nsec []byte, err error) {
|
|
var s *btcec.SecretKey
|
|
s, _ = btcec.SecKeyFromBytes(sk)
|
|
if nsec, err = SecretKeyToNsec(s); chk.E(err) {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// SecretKeyToHex converts a secret key to the hex encoding.
|
|
func SecretKeyToHex(sk *btcec.SecretKey) (hexSec []byte) {
|
|
return hex.EncAppend(nil, sk.Serialize())
|
|
}
|
|
|
|
// NsecToHex converts a bech32 encoded nostr secret key to a raw hexadecimal
|
|
// string.
|
|
func NsecToHex[V constraints.Bytes](nsec V) (hexSec []byte, err error) {
|
|
var sk *secp256k1.SecretKey
|
|
if sk, err = NsecToSecretKey(nsec); chk.E(err) {
|
|
return
|
|
}
|
|
hexSec = SecretKeyToHex(sk)
|
|
return
|
|
}
|