summaryrefslogtreecommitdiffstats
path: root/internal/nym/address.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/nym/address.go')
-rw-r--r--internal/nym/address.go131
1 files changed, 131 insertions, 0 deletions
diff --git a/internal/nym/address.go b/internal/nym/address.go
new file mode 100644
index 0000000..f8c251a
--- /dev/null
+++ b/internal/nym/address.go
@@ -0,0 +1,131 @@
+package nym
+
+import (
+ "fmt"
+ "math/big"
+ "strings"
+)
+
+const (
+ pubkeySize = 32
+ // RecipientLen is the wire length of a nym-client Recipient: identity ||
+ // encryption || gateway pubkeys, 32 bytes each.
+ RecipientLen = 3 * pubkeySize
+)
+
+var base58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
+
+// decodeBase58 decodes plain base58 (Bitcoin alphabet, no checksum), matching
+// the Rust `bs58` crate defaults used by nym-sphinx for identity/encryption keys.
+func decodeBase58(s string) ([]byte, error) {
+ if s == "" {
+ return nil, fmt.Errorf("empty base58 string")
+ }
+
+ num := new(big.Int)
+ base := big.NewInt(58)
+ for _, r := range s {
+ idx := strings.IndexRune(base58Alphabet, r)
+ if idx < 0 {
+ return nil, fmt.Errorf("invalid base58 character %q", r)
+ }
+ num.Mul(num, base)
+ num.Add(num, big.NewInt(int64(idx)))
+ }
+
+ decoded := num.Bytes()
+
+ // Each leading '1' encodes a leading zero byte.
+ leadingZeros := 0
+ for _, r := range s {
+ if r != '1' {
+ break
+ }
+ leadingZeros++
+ }
+
+ out := make([]byte, leadingZeros+len(decoded))
+ copy(out[leadingZeros:], decoded)
+ return out, nil
+}
+
+// ParseRecipient converts a nym address string "identity.encryption@gateway"
+// into the 96-byte wire encoding expected by the nym-client binary WS protocol
+// (identity_pubkey || encryption_pubkey || gateway_identity_pubkey).
+func ParseRecipient(addr string) ([]byte, error) {
+ atParts := strings.Split(addr, "@")
+ if len(atParts) != 2 {
+ return nil, fmt.Errorf("nym address must contain exactly one '@': %q", addr)
+ }
+ clientHalf, gatewayHalf := atParts[0], atParts[1]
+
+ dotParts := strings.Split(clientHalf, ".")
+ if len(dotParts) != 2 {
+ return nil, fmt.Errorf("nym address client part must contain exactly one '.': %q", addr)
+ }
+
+ identity, err := decodeBase58(dotParts[0])
+ if err != nil {
+ return nil, fmt.Errorf("decode identity key: %w", err)
+ }
+ encryption, err := decodeBase58(dotParts[1])
+ if err != nil {
+ return nil, fmt.Errorf("decode encryption key: %w", err)
+ }
+ gateway, err := decodeBase58(gatewayHalf)
+ if err != nil {
+ return nil, fmt.Errorf("decode gateway key: %w", err)
+ }
+
+ if len(identity) != pubkeySize || len(encryption) != pubkeySize || len(gateway) != pubkeySize {
+ return nil, fmt.Errorf("nym address %q: expected 3x%d byte keys, got %d/%d/%d",
+ addr, pubkeySize, len(identity), len(encryption), len(gateway))
+ }
+
+ out := make([]byte, 0, RecipientLen)
+ out = append(out, identity...)
+ out = append(out, encryption...)
+ out = append(out, gateway...)
+ return out, nil
+}
+
+// FormatRecipient is the inverse of ParseRecipient: it encodes 96 raw
+// recipient bytes back into "identity.encryption@gateway" base58 form.
+func FormatRecipient(b []byte) (string, error) {
+ if len(b) != RecipientLen {
+ return "", fmt.Errorf("expected %d recipient bytes, got %d", RecipientLen, len(b))
+ }
+ identity := encodeBase58(b[:pubkeySize])
+ encryption := encodeBase58(b[pubkeySize : 2*pubkeySize])
+ gateway := encodeBase58(b[2*pubkeySize:])
+ return identity + "." + encryption + "@" + gateway, nil
+}
+
+// encodeBase58 is the inverse of decodeBase58 (plain base58, Bitcoin alphabet,
+// no checksum).
+func encodeBase58(data []byte) string {
+ leadingZeros := 0
+ for _, b := range data {
+ if b != 0 {
+ break
+ }
+ leadingZeros++
+ }
+
+ num := new(big.Int).SetBytes(data)
+ base := big.NewInt(58)
+ mod := new(big.Int)
+ var out []byte
+ for num.Sign() > 0 {
+ num.DivMod(num, base, mod)
+ out = append(out, base58Alphabet[mod.Int64()])
+ }
+ for i := 0; i < leadingZeros; i++ {
+ out = append(out, '1')
+ }
+ // out was built least-significant-digit first; reverse it.
+ for i, j := 0, len(out)-1; i < j; i, j = i+1, j-1 {
+ out[i], out[j] = out[j], out[i]
+ }
+ return string(out)
+}