Add HandleDelete and GetSerialsFromFilter methods, integrate admin keys handling, and enhance constraints API. Include a new CLI convert tool for key translation.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -107,3 +107,4 @@ pkg/database/testrealy
|
|||||||
/.idea/go.imports.xml
|
/.idea/go.imports.xml
|
||||||
/.idea/inspectionProfiles/Project_Default.xml
|
/.idea/inspectionProfiles/Project_Default.xml
|
||||||
/.idea/.name
|
/.idea/.name
|
||||||
|
/ctxproxy.config.yml
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type C struct {
|
|||||||
DBLogLevel string `env:"ORLY_DB_LOG_LEVEL" default:"info" usage:"database log level: fatal error warn info debug trace"`
|
DBLogLevel string `env:"ORLY_DB_LOG_LEVEL" default:"info" usage:"database log level: fatal error warn info debug trace"`
|
||||||
Pprof string `env:"ORLY_PPROF" usage:"enable pprof in modes: cpu,memory,allocation"`
|
Pprof string `env:"ORLY_PPROF" usage:"enable pprof in modes: cpu,memory,allocation"`
|
||||||
IPWhitelist []string `env:"ORLY_IP_WHITELIST" usage:"comma-separated list of IP addresses to allow access from, matches on prefixes to allow private subnets, eg 10.0.0 = 10.0.0.0/8"`
|
IPWhitelist []string `env:"ORLY_IP_WHITELIST" usage:"comma-separated list of IP addresses to allow access from, matches on prefixes to allow private subnets, eg 10.0.0 = 10.0.0.0/8"`
|
||||||
|
Admins []string `env:"ORLY_ADMINS" usage:"comma-separated list of admin npubs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates and initializes a new configuration object for the relay
|
// New creates and initializes a new configuration object for the relay
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ import (
|
|||||||
// HandleClose processes a CLOSE envelope by unmarshalling the request,
|
// HandleClose processes a CLOSE envelope by unmarshalling the request,
|
||||||
// validates the presence of an <id> field, and signals cancellation for
|
// validates the presence of an <id> field, and signals cancellation for
|
||||||
// the associated listener through the server's publisher mechanism.
|
// the associated listener through the server's publisher mechanism.
|
||||||
func (l *Listener) HandleClose(
|
func (l *Listener) HandleClose(req []byte) (err error) {
|
||||||
req []byte,
|
|
||||||
) (err error) {
|
|
||||||
var rem []byte
|
var rem []byte
|
||||||
env := closeenvelope.New()
|
env := closeenvelope.New()
|
||||||
if rem, err = env.Unmarshal(req); chk.E(err) {
|
if rem, err = env.Unmarshal(req); chk.E(err) {
|
||||||
|
|||||||
180
app/handle-delete.go
Normal file
180
app/handle-delete.go
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
database "database.orly"
|
||||||
|
"database.orly/indexes/types"
|
||||||
|
"encoders.orly/envelopes/eventenvelope"
|
||||||
|
"encoders.orly/event"
|
||||||
|
"encoders.orly/filter"
|
||||||
|
"encoders.orly/hex"
|
||||||
|
"encoders.orly/ints"
|
||||||
|
"encoders.orly/kind"
|
||||||
|
"encoders.orly/tag"
|
||||||
|
"encoders.orly/tag/atag"
|
||||||
|
"lol.mleku.dev/chk"
|
||||||
|
"lol.mleku.dev/log"
|
||||||
|
utils "utils.orly"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *Listener) GetSerialsFromFilter(f *filter.F) (
|
||||||
|
sers types.Uint40s, err error,
|
||||||
|
) {
|
||||||
|
var idxs []database.Range
|
||||||
|
if idxs, err = database.GetIndexesFromFilter(f); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, idx := range idxs {
|
||||||
|
var s types.Uint40s
|
||||||
|
if s, err = l.GetSerialsByRange(idx); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sers = append(sers, s...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) HandleDelete(env *eventenvelope.Submission) {
|
||||||
|
log.T.C(
|
||||||
|
func() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"delete event\n%s", env.E.Serialize(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
var ownerDelete bool
|
||||||
|
for _, pk := range l.Admins {
|
||||||
|
if utils.FastEqual(pk, env.E.Pubkey) {
|
||||||
|
ownerDelete = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// process the tags in the delete event
|
||||||
|
var err error
|
||||||
|
for _, t := range *env.E.Tags {
|
||||||
|
// first search for a tags, as these are the simplest to process
|
||||||
|
if utils.FastEqual(t.Key(), []byte("a")) {
|
||||||
|
at := new(atag.T)
|
||||||
|
if _, err = at.Unmarshal(t.Value()); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ownerDelete || utils.FastEqual(env.E.Pubkey, at.Pubkey) {
|
||||||
|
// find the event and delete it
|
||||||
|
f := &filter.F{
|
||||||
|
Authors: tag.NewFromBytesSlice(at.Pubkey),
|
||||||
|
Kinds: kind.NewS(at.Kind),
|
||||||
|
}
|
||||||
|
if len(at.DTag) > 0 {
|
||||||
|
f.Tags = tag.NewS(
|
||||||
|
tag.NewFromAny("d", at.DTag),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var sers types.Uint40s
|
||||||
|
if sers, err = l.GetSerialsFromFilter(f); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// if found, delete them
|
||||||
|
if len(sers) > 0 {
|
||||||
|
for _, s := range sers {
|
||||||
|
var ev *event.E
|
||||||
|
if ev, err = l.FetchEventBySerial(s); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !(kind.IsReplaceable(ev.Kind) && len(at.DTag) == 0) {
|
||||||
|
// skip a tags with no dtag if the kind is not
|
||||||
|
// replaceable.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = l.DeleteEventBySerial(
|
||||||
|
l.Ctx, s, ev,
|
||||||
|
); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// if e tags are found, delete them if the author is signer, or one of
|
||||||
|
// the owners is signer
|
||||||
|
if utils.FastEqual(t.Key(), []byte("e")) {
|
||||||
|
var dst []byte
|
||||||
|
if _, err = hex.DecBytes(dst, t.Value()); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f := &filter.F{
|
||||||
|
Ids: tag.NewFromBytesSlice(dst),
|
||||||
|
}
|
||||||
|
var sers types.Uint40s
|
||||||
|
if sers, err = l.GetSerialsFromFilter(f); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// if found, delete them
|
||||||
|
if len(sers) > 0 {
|
||||||
|
// there should be only one event per serial, so we can just
|
||||||
|
// delete them all
|
||||||
|
for _, s := range sers {
|
||||||
|
var ev *event.E
|
||||||
|
if ev, err = l.FetchEventBySerial(s); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check that the author is the same as the signer of the
|
||||||
|
// delete, for the k tag case the author is the signer of
|
||||||
|
// the event.
|
||||||
|
if !utils.FastEqual(env.E.Pubkey, ev.Pubkey) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// exclude delete events
|
||||||
|
if ev.Kind == kind.EventDeletion.K {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = l.DeleteEventBySerial(l.Ctx, s, ev); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if k tags are found, check they are replaceable
|
||||||
|
if utils.FastEqual(t.Key(), []byte("k")) {
|
||||||
|
ki := ints.New(0)
|
||||||
|
if _, err = ki.Unmarshal(t.Value()); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kn := ki.Uint16()
|
||||||
|
// skip events that are delete events or that are not replaceable
|
||||||
|
if !kind.IsReplaceable(kn) || kn != kind.EventDeletion.K {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f := &filter.F{
|
||||||
|
Authors: tag.NewFromBytesSlice(env.E.Pubkey),
|
||||||
|
Kinds: kind.NewS(kind.New(kn)),
|
||||||
|
}
|
||||||
|
var sers types.Uint40s
|
||||||
|
if sers, err = l.GetSerialsFromFilter(f); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// if found, delete them
|
||||||
|
if len(sers) > 0 {
|
||||||
|
// there should be only one event per serial because replaces
|
||||||
|
// delete old ones, so we can just delete them all
|
||||||
|
for _, s := range sers {
|
||||||
|
var ev *event.E
|
||||||
|
if ev, err = l.FetchEventBySerial(s); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check that the author is the same as the signer of the
|
||||||
|
// delete, for the k tag case the author is the signer of
|
||||||
|
// the event.
|
||||||
|
if !utils.FastEqual(env.E.Pubkey, ev.Pubkey) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -12,9 +11,7 @@ import (
|
|||||||
utils "utils.orly"
|
utils "utils.orly"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *Listener) HandleEvent(c context.Context, msg []byte) (
|
func (l *Listener) HandleEvent(msg []byte) (err error) {
|
||||||
err error,
|
|
||||||
) {
|
|
||||||
// decode the envelope
|
// decode the envelope
|
||||||
env := eventenvelope.NewSubmission()
|
env := eventenvelope.NewSubmission()
|
||||||
if msg, err = env.Unmarshal(msg); chk.E(err) {
|
if msg, err = env.Unmarshal(msg); chk.E(err) {
|
||||||
@@ -57,24 +54,22 @@ func (l *Listener) HandleEvent(c context.Context, msg []byte) (
|
|||||||
}
|
}
|
||||||
// if the event is a delete, process the delete
|
// if the event is a delete, process the delete
|
||||||
if env.E.Kind == kind.EventDeletion.K {
|
if env.E.Kind == kind.EventDeletion.K {
|
||||||
|
l.HandleDelete(env)
|
||||||
}
|
} else {
|
||||||
// check if the event was deleted
|
// check if the event was deleted
|
||||||
//
|
if err = l.CheckForDeleted(env.E, l.Admins); err != nil {
|
||||||
// todo: the list of admin pubkeys should go in the second parameter when it
|
if strings.HasPrefix(err.Error(), "blocked:") {
|
||||||
// is implemented to enable admins to delete events of other users.
|
errStr := err.Error()[len("blocked: "):len(err.Error())]
|
||||||
if err = l.CheckForDeleted(env.E, nil); err != nil {
|
if err = Ok.Error(
|
||||||
if strings.HasPrefix(err.Error(), "blocked:") {
|
l, env, errStr,
|
||||||
errStr := err.Error()[len("blocked: "):len(err.Error())]
|
); chk.E(err) {
|
||||||
if err = Ok.Error(
|
return
|
||||||
l, env, errStr,
|
}
|
||||||
); chk.E(err) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// store the event
|
// store the event
|
||||||
if _, _, err = l.SaveEvent(c, env.E); chk.E(err) {
|
if _, _, err = l.SaveEvent(l.Ctx, env.E); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.publishers.Deliver(env.E)
|
l.publishers.Deliver(env.E)
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ func (l *Listener) HandleMessage(msg []byte, remote string) {
|
|||||||
switch t {
|
switch t {
|
||||||
case eventenvelope.L:
|
case eventenvelope.L:
|
||||||
log.D.F("eventenvelope: %s", rem)
|
log.D.F("eventenvelope: %s", rem)
|
||||||
err = l.HandleEvent(l.ctx, rem)
|
err = l.HandleEvent(rem)
|
||||||
case reqenvelope.L:
|
case reqenvelope.L:
|
||||||
log.D.F("reqenvelope: %s", rem)
|
log.D.F("reqenvelope: %s", rem)
|
||||||
err = l.HandleReq(l.ctx, rem)
|
err = l.HandleReq(rem)
|
||||||
case closeenvelope.L:
|
case closeenvelope.L:
|
||||||
log.D.F("closeenvelope: %s", rem)
|
log.D.F("closeenvelope: %s", rem)
|
||||||
err = l.HandleClose(rem)
|
err = l.HandleClose(rem)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"encoders.orly/envelopes/closedenvelope"
|
"encoders.orly/envelopes/closedenvelope"
|
||||||
@@ -18,7 +17,7 @@ import (
|
|||||||
"utils.orly/pointers"
|
"utils.orly/pointers"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *Listener) HandleReq(c context.Context, msg []byte) (
|
func (l *Listener) HandleReq(msg []byte) (
|
||||||
err error,
|
err error,
|
||||||
) {
|
) {
|
||||||
var rem []byte
|
var rem []byte
|
||||||
@@ -36,7 +35,7 @@ func (l *Listener) HandleReq(c context.Context, msg []byte) (
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if events, err = l.QueryEvents(c, f); chk.E(err) {
|
if events, err = l.QueryEvents(l.Ctx, f); chk.E(err) {
|
||||||
if errors.Is(err, badger.ErrDBClosed) {
|
if errors.Is(err, badger.ErrDBClosed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
13
app/main.go
13
app/main.go
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
database "database.orly"
|
database "database.orly"
|
||||||
|
"encoders.orly/bech32encoding"
|
||||||
"lol.mleku.dev/chk"
|
"lol.mleku.dev/chk"
|
||||||
"lol.mleku.dev/log"
|
"lol.mleku.dev/log"
|
||||||
"next.orly.dev/app/config"
|
"next.orly.dev/app/config"
|
||||||
@@ -23,12 +24,24 @@ func Run(
|
|||||||
close(quit)
|
close(quit)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
// get the admins
|
||||||
|
var err error
|
||||||
|
var adminKeys [][]byte
|
||||||
|
for _, admin := range cfg.Admins {
|
||||||
|
var pk []byte
|
||||||
|
if pk, err = bech32encoding.NpubOrHexToPublicKeyBinary(admin); chk.E(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
adminKeys = append(adminKeys, pk)
|
||||||
|
}
|
||||||
|
|
||||||
// start listener
|
// start listener
|
||||||
l := &Server{
|
l := &Server{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
D: db,
|
D: db,
|
||||||
publishers: publish.New(NewPublisher(ctx)),
|
publishers: publish.New(NewPublisher(ctx)),
|
||||||
|
Admins: adminKeys,
|
||||||
}
|
}
|
||||||
addr := fmt.Sprintf("%s:%d", cfg.Listen, cfg.Port)
|
addr := fmt.Sprintf("%s:%d", cfg.Listen, cfg.Port)
|
||||||
log.I.F("starting listener on http://%s", addr)
|
log.I.F("starting listener on http://%s", addr)
|
||||||
|
|||||||
@@ -12,12 +12,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
mux *http.ServeMux
|
mux *http.ServeMux
|
||||||
Config *config.C
|
Config *config.C
|
||||||
Ctx context.Context
|
Ctx context.Context
|
||||||
remote string
|
remote string
|
||||||
*database.D
|
|
||||||
publishers *publish.S
|
publishers *publish.S
|
||||||
|
Admins [][]byte
|
||||||
|
*database.D
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
143
cmd/convert/convert.go
Normal file
143
cmd/convert/convert.go
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"crypto.orly/ec/schnorr"
|
||||||
|
"crypto.orly/ec/secp256k1"
|
||||||
|
b32 "encoders.orly/bech32encoding"
|
||||||
|
"encoders.orly/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: convert [--secret] <key>\n")
|
||||||
|
fmt.Fprintf(
|
||||||
|
os.Stderr, " <key> can be hex (64 chars) or bech32 (npub/nsec).\n",
|
||||||
|
)
|
||||||
|
fmt.Fprintf(
|
||||||
|
os.Stderr,
|
||||||
|
" --secret: interpret input key as a secret key; print both nsec and npub in hex and bech32.\n"+
|
||||||
|
" --secret is implied if <key> starts with nsec.\n",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var isSecret bool
|
||||||
|
flag.BoolVar(
|
||||||
|
&isSecret, "secret", false, "interpret the input as a secret key",
|
||||||
|
)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if flag.NArg() < 1 {
|
||||||
|
usage()
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
input := strings.TrimSpace(flag.Arg(0))
|
||||||
|
|
||||||
|
// Auto-detect secret if input starts with nsec
|
||||||
|
if strings.HasPrefix(input, string(b32.SecHRP)) {
|
||||||
|
isSecret = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isSecret {
|
||||||
|
if err := handleSecret(input); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := handlePublic(input); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSecret(input string) error {
|
||||||
|
// Accept nsec bech32 or 64-char hex as secret key
|
||||||
|
var sk *secp256k1.SecretKey
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if strings.HasPrefix(input, string(b32.SecHRP)) { // nsec...
|
||||||
|
if sk, err = b32.NsecToSecretKey([]byte(input)); err != nil {
|
||||||
|
return fmt.Errorf("failed to decode nsec: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Expect hex
|
||||||
|
if len(input) != b32.HexKeyLen {
|
||||||
|
return fmt.Errorf("secret key hex must be %d chars", b32.HexKeyLen)
|
||||||
|
}
|
||||||
|
var b []byte
|
||||||
|
if b, err = hex.Dec(input); err != nil {
|
||||||
|
return fmt.Errorf("invalid secret hex: %w", err)
|
||||||
|
}
|
||||||
|
sk = secp256k1.SecKeyFromBytes(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare outputs for secret
|
||||||
|
nsec, err := b32.SecretKeyToNsec(sk)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encode nsec: %w", err)
|
||||||
|
}
|
||||||
|
secHex := hex.EncAppend(nil, sk.Serialize())
|
||||||
|
|
||||||
|
// Derive public key
|
||||||
|
pk := sk.PubKey()
|
||||||
|
npub, err := b32.PublicKeyToNpub(pk)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encode npub: %w", err)
|
||||||
|
}
|
||||||
|
pkBytes := schnorr.SerializePubKey(pk)
|
||||||
|
pkHex := hex.EncAppend(nil, pkBytes)
|
||||||
|
|
||||||
|
// Print results
|
||||||
|
fmt.Printf("nsec (hex): %s\n", string(secHex))
|
||||||
|
fmt.Printf("nsec (bech32): %s\n", string(nsec))
|
||||||
|
fmt.Printf("npub (hex): %s\n", string(pkHex))
|
||||||
|
fmt.Printf("npub (bech32): %s\n", string(npub))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePublic(input string) error {
|
||||||
|
// Accept npub bech32, nsec bech32 (derive pub), or 64-char hex pubkey
|
||||||
|
var pubBytes []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if strings.HasPrefix(input, string(b32.PubHRP)) { // npub...
|
||||||
|
if pubBytes, err = b32.NpubToBytes([]byte(input)); err != nil {
|
||||||
|
return fmt.Errorf("failed to decode npub: %w", err)
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(
|
||||||
|
input, string(b32.SecHRP),
|
||||||
|
) { // nsec without --secret: show pub only
|
||||||
|
var sk *secp256k1.SecretKey
|
||||||
|
if sk, err = b32.NsecToSecretKey([]byte(input)); err != nil {
|
||||||
|
return fmt.Errorf("failed to decode nsec: %w", err)
|
||||||
|
}
|
||||||
|
pubBytes = schnorr.SerializePubKey(sk.PubKey())
|
||||||
|
} else {
|
||||||
|
// Expect hex pubkey
|
||||||
|
if len(input) != b32.HexKeyLen {
|
||||||
|
return fmt.Errorf("public key hex must be %d chars", b32.HexKeyLen)
|
||||||
|
}
|
||||||
|
if pubBytes, err = hex.Dec(input); err != nil {
|
||||||
|
return fmt.Errorf("invalid public hex: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute encodings
|
||||||
|
npub, err := b32.BinToNpub(pubBytes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encode npub: %w", err)
|
||||||
|
}
|
||||||
|
pubHex := hex.EncAppend(nil, pubBytes)
|
||||||
|
|
||||||
|
// Print only pubkey representations
|
||||||
|
fmt.Printf("npub (hex): %s\n", string(pubHex))
|
||||||
|
fmt.Printf("npub (bech32): %s\n", string(npub))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ func (d *D) CheckForDeleted(ev *event.E, admins [][]byte) (err error) {
|
|||||||
t := ev.Tags.GetFirst([]byte("d"))
|
t := ev.Tags.GetFirst([]byte("d"))
|
||||||
a := atag.T{
|
a := atag.T{
|
||||||
Kind: kind.New(ev.Kind),
|
Kind: kind.New(ev.Kind),
|
||||||
PubKey: ev.Pubkey,
|
Pubkey: ev.Pubkey,
|
||||||
DTag: t.Value(),
|
DTag: t.Value(),
|
||||||
}
|
}
|
||||||
at := a.Marshal(nil)
|
at := a.Marshal(nil)
|
||||||
@@ -135,7 +135,7 @@ func (d *D) CheckForDeleted(ev *event.E, admins [][]byte) (err error) {
|
|||||||
// construct a tag
|
// construct a tag
|
||||||
a := atag.T{
|
a := atag.T{
|
||||||
Kind: kind.New(ev.Kind),
|
Kind: kind.New(ev.Kind),
|
||||||
PubKey: ev.Pubkey,
|
Pubkey: ev.Pubkey,
|
||||||
}
|
}
|
||||||
at := a.Marshal(nil)
|
at := a.Marshal(nil)
|
||||||
if idxs, err = GetIndexesFromFilter(
|
if idxs, err = GetIndexesFromFilter(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package bech32encoding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"crypto.orly/ec"
|
"crypto.orly/ec"
|
||||||
"crypto.orly/ec/bech32"
|
"crypto.orly/ec/bech32"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"lol.mleku.dev/chk"
|
"lol.mleku.dev/chk"
|
||||||
"lol.mleku.dev/log"
|
"lol.mleku.dev/log"
|
||||||
"utils.orly"
|
"utils.orly"
|
||||||
|
"utils.orly/constraints"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -67,7 +69,9 @@ func PublicKeyToNpub(pk *secp256k1.PublicKey) (encoded []byte, err error) {
|
|||||||
|
|
||||||
// NsecToSecretKey decodes a nostr secret key (nsec) and returns the secp256k1
|
// NsecToSecretKey decodes a nostr secret key (nsec) and returns the secp256k1
|
||||||
// secret key.
|
// secret key.
|
||||||
func NsecToSecretKey(encoded []byte) (sk *secp256k1.SecretKey, err error) {
|
func NsecToSecretKey[V constraints.Bytes](encoded V) (
|
||||||
|
sk *secp256k1.SecretKey, err error,
|
||||||
|
) {
|
||||||
var b8 []byte
|
var b8 []byte
|
||||||
if b8, err = NsecToBytes(encoded); chk.E(err) {
|
if b8, err = NsecToBytes(encoded); chk.E(err) {
|
||||||
return
|
return
|
||||||
@@ -77,9 +81,9 @@ func NsecToSecretKey(encoded []byte) (sk *secp256k1.SecretKey, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NsecToBytes converts a nostr bech32 encoded secret key to raw bytes.
|
// NsecToBytes converts a nostr bech32 encoded secret key to raw bytes.
|
||||||
func NsecToBytes(encoded []byte) (sk []byte, err error) {
|
func NsecToBytes[V constraints.Bytes](encoded V) (sk []byte, err error) {
|
||||||
var b5, hrp []byte
|
var b5, hrp []byte
|
||||||
if hrp, b5, err = bech32.Decode(encoded); chk.E(err) {
|
if hrp, b5, err = bech32.Decode([]byte(encoded)); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !utils.FastEqual(hrp, SecHRP) {
|
if !utils.FastEqual(hrp, SecHRP) {
|
||||||
@@ -97,9 +101,9 @@ func NsecToBytes(encoded []byte) (sk []byte, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NpubToBytes converts a bech32 encoded public key to raw bytes.
|
// NpubToBytes converts a bech32 encoded public key to raw bytes.
|
||||||
func NpubToBytes(encoded []byte) (pk []byte, err error) {
|
func NpubToBytes[V constraints.Bytes](encoded V) (pk []byte, err error) {
|
||||||
var b5, hrp []byte
|
var b5, hrp []byte
|
||||||
if hrp, b5, err = bech32.Decode(encoded); chk.E(err) {
|
if hrp, b5, err = bech32.Decode([]byte(encoded)); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !utils.FastEqual(hrp, PubHRP) {
|
if !utils.FastEqual(hrp, PubHRP) {
|
||||||
@@ -118,9 +122,11 @@ func NpubToBytes(encoded []byte) (pk []byte, err error) {
|
|||||||
|
|
||||||
// NpubToPublicKey decodes an nostr public key (npub) and returns an secp256k1
|
// NpubToPublicKey decodes an nostr public key (npub) and returns an secp256k1
|
||||||
// public key.
|
// public key.
|
||||||
func NpubToPublicKey(encoded []byte) (pk *secp256k1.PublicKey, err error) {
|
func NpubToPublicKey[V constraints.Bytes](encoded V) (
|
||||||
|
pk *secp256k1.PublicKey, err error,
|
||||||
|
) {
|
||||||
var b5, b8, hrp []byte
|
var b5, b8, hrp []byte
|
||||||
if hrp, b5, err = bech32.Decode(encoded); chk.E(err) {
|
if hrp, b5, err = bech32.Decode([]byte(encoded)); chk.E(err) {
|
||||||
err = log.E.Err("ERROR: '%s'", err)
|
err = log.E.Err("ERROR: '%s'", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -134,14 +140,13 @@ func NpubToPublicKey(encoded []byte) (pk *secp256k1.PublicKey, err error) {
|
|||||||
if b8, err = ConvertFromBech32(b5); chk.E(err) {
|
if b8, err = ConvertFromBech32(b5); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return schnorr.ParsePubKey(b8[:schnorr.PubKeyBytesLen])
|
return schnorr.ParsePubKey(b8[:schnorr.PubKeyBytesLen])
|
||||||
}
|
}
|
||||||
|
|
||||||
// HexToPublicKey decodes a string that should be a 64 character long hex
|
// 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
|
// encoded public key into a btcec.PublicKey that can be used to verify a
|
||||||
// signature or encode to Bech32.
|
// signature or encode to Bech32.
|
||||||
func HexToPublicKey(pk string) (p *btcec.PublicKey, err error) {
|
func HexToPublicKey[V constraints.Bytes](pk V) (p *btcec.PublicKey, err error) {
|
||||||
if len(pk) != HexKeyLen {
|
if len(pk) != HexKeyLen {
|
||||||
err = log.E.Err(
|
err = log.E.Err(
|
||||||
"secret key is %d bytes, must be %d", len(pk),
|
"secret key is %d bytes, must be %d", len(pk),
|
||||||
@@ -150,7 +155,7 @@ func HexToPublicKey(pk string) (p *btcec.PublicKey, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var pb []byte
|
var pb []byte
|
||||||
if pb, err = hex.Dec(pk); chk.D(err) {
|
if pb, err = hex.Dec(string(pk)); chk.D(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if p, err = schnorr.ParsePubKey(pb); chk.D(err) {
|
if p, err = schnorr.ParsePubKey(pb); chk.D(err) {
|
||||||
@@ -159,17 +164,30 @@ func HexToPublicKey(pk string) (p *btcec.PublicKey, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func NpubOrHexToPublicKey(encoded []byte) (pk *btcec.PublicKey, err error) {
|
func NpubOrHexToPublicKey[V constraints.Bytes](encoded V) (
|
||||||
if !bytes.HasPrefix([]byte("npub"), encoded) && len(encoded) == HexKeyLen {
|
pk *btcec.PublicKey, err error,
|
||||||
return HexToPublicKey(string(encoded))
|
) {
|
||||||
|
if !strings.HasPrefix(
|
||||||
|
"npub", string(encoded),
|
||||||
|
) && len(encoded) == HexKeyLen {
|
||||||
|
return HexToPublicKey(encoded)
|
||||||
}
|
}
|
||||||
return NpubToPublicKey(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
|
// 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
|
// encoded public key into a btcec.PublicKey that can be used to verify a
|
||||||
// signature or encode to Bech32.
|
// signature or encode to Bech32.
|
||||||
func HexToSecretKey(sk []byte) (s *btcec.SecretKey, err error) {
|
func HexToSecretKey[V constraints.Bytes](sk V) (s *btcec.SecretKey, err error) {
|
||||||
if len(sk) != HexKeyLen {
|
if len(sk) != HexKeyLen {
|
||||||
err = log.E.Err(
|
err = log.E.Err(
|
||||||
"secret key is %d bytes, must be %d", len(sk),
|
"secret key is %d bytes, must be %d", len(sk),
|
||||||
@@ -178,7 +196,7 @@ func HexToSecretKey(sk []byte) (s *btcec.SecretKey, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
pb := make([]byte, schnorr.PubKeyBytesLen)
|
pb := make([]byte, schnorr.PubKeyBytesLen)
|
||||||
if _, err = hex.DecBytes(pb, sk); chk.D(err) {
|
if _, err = hex.DecBytes(pb, []byte(sk)); chk.D(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if s = secp256k1.SecKeyFromBytes(pb); chk.D(err) {
|
if s = secp256k1.SecKeyFromBytes(pb); chk.D(err) {
|
||||||
@@ -189,9 +207,9 @@ func HexToSecretKey(sk []byte) (s *btcec.SecretKey, err error) {
|
|||||||
|
|
||||||
// HexToNpub converts a raw 64 character hex encoded public key (as used in
|
// HexToNpub converts a raw 64 character hex encoded public key (as used in
|
||||||
// standard nostr json events) to a bech32 encoded npub.
|
// standard nostr json events) to a bech32 encoded npub.
|
||||||
func HexToNpub(publicKeyHex []byte) (s []byte, err error) {
|
func HexToNpub[V constraints.Bytes](publicKeyHex V) (s []byte, err error) {
|
||||||
b := make([]byte, schnorr.PubKeyBytesLen)
|
b := make([]byte, schnorr.PubKeyBytesLen)
|
||||||
if _, err = hex.DecBytes(b, publicKeyHex); chk.D(err) {
|
if _, err = hex.DecBytes(b, []byte(publicKeyHex)); chk.D(err) {
|
||||||
err = log.E.Err("failed to decode public key hex: %w", err)
|
err = log.E.Err("failed to decode public key hex: %w", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -212,7 +230,7 @@ func BinToNpub(b []byte) (s []byte, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HexToNsec converts a hex encoded secret key to a bech32 encoded nsec.
|
// HexToNsec converts a hex encoded secret key to a bech32 encoded nsec.
|
||||||
func HexToNsec(sk []byte) (nsec []byte, err error) {
|
func HexToNsec[V constraints.Bytes](sk V) (nsec []byte, err error) {
|
||||||
var s *btcec.SecretKey
|
var s *btcec.SecretKey
|
||||||
if s, err = HexToSecretKey(sk); chk.E(err) {
|
if s, err = HexToSecretKey(sk); chk.E(err) {
|
||||||
return
|
return
|
||||||
@@ -235,13 +253,12 @@ func BinToNsec(sk []byte) (nsec []byte, err error) {
|
|||||||
|
|
||||||
// SecretKeyToHex converts a secret key to the hex encoding.
|
// SecretKeyToHex converts a secret key to the hex encoding.
|
||||||
func SecretKeyToHex(sk *btcec.SecretKey) (hexSec []byte) {
|
func SecretKeyToHex(sk *btcec.SecretKey) (hexSec []byte) {
|
||||||
hex.EncBytes(hexSec, sk.Serialize())
|
return hex.EncAppend(nil, sk.Serialize())
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NsecToHex converts a bech32 encoded nostr secret key to a raw hexadecimal
|
// NsecToHex converts a bech32 encoded nostr secret key to a raw hexadecimal
|
||||||
// string.
|
// string.
|
||||||
func NsecToHex(nsec []byte) (hexSec []byte, err error) {
|
func NsecToHex[V constraints.Bytes](nsec V) (hexSec []byte, err error) {
|
||||||
var sk *secp256k1.SecretKey
|
var sk *secp256k1.SecretKey
|
||||||
if sk, err = NsecToSecretKey(nsec); chk.E(err) {
|
if sk, err = NsecToSecretKey(nsec); chk.E(err) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ func TestEncodeDecodeNEventTestEncodeDecodeNEvent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustDecode[V string | []byte](s V) (b []byte) {
|
func MustDecode[V constraints.Bytes](s V) (b []byte) {
|
||||||
var err error
|
var err error
|
||||||
if _, err = hex.Dec(string(s)); chk.E(err) {
|
if _, err = hex.Dec(string(s)); chk.E(err) {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"lol.mleku.dev/errorf"
|
"lol.mleku.dev/errorf"
|
||||||
"lol.mleku.dev/log"
|
"lol.mleku.dev/log"
|
||||||
"utils.orly/units"
|
"utils.orly/units"
|
||||||
|
"utils.orly/constraints"
|
||||||
)
|
)
|
||||||
|
|
||||||
// L is the label associated with this type of codec.Envelope.
|
// L is the label associated with this type of codec.Envelope.
|
||||||
@@ -30,7 +31,7 @@ var _ codec.Envelope = (*Challenge)(nil)
|
|||||||
func NewChallenge() *Challenge { return &Challenge{} }
|
func NewChallenge() *Challenge { return &Challenge{} }
|
||||||
|
|
||||||
// NewChallengeWith creates a new authenvelope.Challenge with provided bytes.
|
// NewChallengeWith creates a new authenvelope.Challenge with provided bytes.
|
||||||
func NewChallengeWith[V string | []byte](challenge V) *Challenge {
|
func NewChallengeWith[V constraints.Bytes](challenge V) *Challenge {
|
||||||
return &Challenge{[]byte(challenge)}
|
return &Challenge{[]byte(challenge)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"interfaces.orly/codec"
|
"interfaces.orly/codec"
|
||||||
"lol.mleku.dev/chk"
|
"lol.mleku.dev/chk"
|
||||||
"lol.mleku.dev/errorf"
|
"lol.mleku.dev/errorf"
|
||||||
|
"utils.orly/constraints"
|
||||||
)
|
)
|
||||||
|
|
||||||
// L is the label associated with this type of codec.Envelope.
|
// L is the label associated with this type of codec.Envelope.
|
||||||
@@ -118,7 +119,7 @@ func NewResponse() *Response { return new(Response) }
|
|||||||
// NewResponseFrom creates a new countenvelope.Response with provided string for the
|
// NewResponseFrom creates a new countenvelope.Response with provided string for the
|
||||||
// subscription.Id, a count and optional variadic approximate flag, which is
|
// subscription.Id, a count and optional variadic approximate flag, which is
|
||||||
// otherwise false and does not get rendered into the JSON.
|
// otherwise false and does not get rendered into the JSON.
|
||||||
func NewResponseFrom[V string | []byte](
|
func NewResponseFrom[V constraints.Bytes](
|
||||||
s V, cnt int,
|
s V, cnt int,
|
||||||
approx ...bool,
|
approx ...bool,
|
||||||
) (res *Response, err error) {
|
) (res *Response, err error) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"lol.mleku.dev/errorf"
|
"lol.mleku.dev/errorf"
|
||||||
"utils.orly/bufpool"
|
"utils.orly/bufpool"
|
||||||
"utils.orly/units"
|
"utils.orly/units"
|
||||||
|
"utils.orly/constraints"
|
||||||
)
|
)
|
||||||
|
|
||||||
// L is the label associated with this type of codec.Envelope.
|
// L is the label associated with this type of codec.Envelope.
|
||||||
@@ -103,7 +104,7 @@ func NewResult() *Result { return &Result{} }
|
|||||||
|
|
||||||
// NewResultWith creates a new eventenvelope.Result with a provided
|
// NewResultWith creates a new eventenvelope.Result with a provided
|
||||||
// subscription.Id string and event.E.
|
// subscription.Id string and event.E.
|
||||||
func NewResultWith[V string | []byte](s V, ev *event.E) (
|
func NewResultWith[V constraints.Bytes](s V, ev *event.E) (
|
||||||
res *Result, err error,
|
res *Result, err error,
|
||||||
) {
|
) {
|
||||||
if len(s) < 0 || len(s) > 64 {
|
if len(s) < 0 || len(s) > 64 {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"encoders.orly/text"
|
"encoders.orly/text"
|
||||||
"interfaces.orly/codec"
|
"interfaces.orly/codec"
|
||||||
"lol.mleku.dev/chk"
|
"lol.mleku.dev/chk"
|
||||||
|
"utils.orly/constraints"
|
||||||
)
|
)
|
||||||
|
|
||||||
// L is the label associated with this type of codec.Envelope.
|
// L is the label associated with this type of codec.Envelope.
|
||||||
@@ -28,7 +29,7 @@ var _ codec.Envelope = (*T)(nil)
|
|||||||
func New() *T { return &T{} }
|
func New() *T { return &T{} }
|
||||||
|
|
||||||
// NewFrom creates a new noticeenvelope.T with a provided message.
|
// NewFrom creates a new noticeenvelope.T with a provided message.
|
||||||
func NewFrom[V string | []byte](msg V) *T { return &T{Message: []byte(msg)} }
|
func NewFrom[V constraints.Bytes](msg V) *T { return &T{Message: []byte(msg)} }
|
||||||
|
|
||||||
// Label returns the label of a NOTICE envelope.
|
// Label returns the label of a NOTICE envelope.
|
||||||
func (en *T) Label() string { return L }
|
func (en *T) Label() string { return L }
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"lol.mleku.dev/chk"
|
"lol.mleku.dev/chk"
|
||||||
"lol.mleku.dev/errorf"
|
"lol.mleku.dev/errorf"
|
||||||
"lol.mleku.dev/log"
|
"lol.mleku.dev/log"
|
||||||
|
"utils.orly/constraints"
|
||||||
)
|
)
|
||||||
|
|
||||||
// L is the label associated with this type of codec.Envelope.
|
// L is the label associated with this type of codec.Envelope.
|
||||||
@@ -34,7 +35,7 @@ func New() *T { return &T{} }
|
|||||||
|
|
||||||
// NewFrom creates a new okenvelope.T with a string for the subscription.Id and
|
// NewFrom creates a new okenvelope.T with a string for the subscription.Id and
|
||||||
// the optional reason.
|
// the optional reason.
|
||||||
func NewFrom[V string | []byte](eid V, ok bool, msg ...V) *T {
|
func NewFrom[V constraints.Bytes](eid V, ok bool, msg ...V) *T {
|
||||||
var m []byte
|
var m []byte
|
||||||
if len(msg) > 0 {
|
if len(msg) > 0 {
|
||||||
m = []byte(msg[0])
|
m = []byte(msg[0])
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"encoders.orly/text"
|
"encoders.orly/text"
|
||||||
"interfaces.orly/codec"
|
"interfaces.orly/codec"
|
||||||
"lol.mleku.dev/chk"
|
"lol.mleku.dev/chk"
|
||||||
|
"utils.orly/constraints"
|
||||||
)
|
)
|
||||||
|
|
||||||
// L is the label associated with this type of codec.Envelope.
|
// L is the label associated with this type of codec.Envelope.
|
||||||
@@ -39,7 +40,7 @@ func NewFrom(id []byte, ff *filter.S) *T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWithId[V string | []byte](id V, ff *filter.S) (sub *T) {
|
func NewWithId[V constraints.Bytes](id V, ff *filter.S) (sub *T) {
|
||||||
return &T{
|
return &T{
|
||||||
Subscription: []byte(id),
|
Subscription: []byte(id),
|
||||||
Filters: ff,
|
Filters: ff,
|
||||||
|
|||||||
5
pkg/utils/constraints/constraints.go
Normal file
5
pkg/utils/constraints/constraints.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package constraints
|
||||||
|
|
||||||
|
type Bytes interface {
|
||||||
|
~string | ~[]byte
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
func FastEqual[A string | []byte, B string | []byte](a A, b B) (same bool) {
|
import "utils.orly/constraints"
|
||||||
|
|
||||||
|
func FastEqual[A constraints.Bytes, B constraints.Bytes](a A, b B) (same bool) {
|
||||||
if len(a) != len(b) {
|
if len(a) != len(b) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"encoders.orly/ints"
|
"encoders.orly/ints"
|
||||||
"lol.mleku.dev/chk"
|
"lol.mleku.dev/chk"
|
||||||
"lol.mleku.dev/log"
|
"lol.mleku.dev/log"
|
||||||
|
"utils.orly/constraints"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -29,7 +30,7 @@ var (
|
|||||||
// - Adds ws:// to addresses with any other port
|
// - Adds ws:// to addresses with any other port
|
||||||
//
|
//
|
||||||
// - Converts http/s to ws/s
|
// - Converts http/s to ws/s
|
||||||
func URL[V string | []byte](v V) (b []byte) {
|
func URL[V constraints.Bytes](v V) (b []byte) {
|
||||||
u := []byte(v)
|
u := []byte(v)
|
||||||
if len(u) == 0 {
|
if len(u) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user