PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/moul/x/wesh/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / x / wesh
Files
12 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/x/wesh/v0
gno
0.9

Files (12)

  • README.mdmarkdown
  • gnomod.tomltoml
  • hmac.gnogno
  • link.gnogno
  • rendezvous.gnogno
  • statement.gnogno
  • wesh.gnogno
  • hmac_test.gnogno
  • link_test.gnogno
  • rendezvous_test.gnogno
  • statement_test.gnogno
  • wesh_test.gnogno
  • link.gnogno
    1package wesh23import (4	"encoding/binary"5	"errors"6	"net/url"7	"strings"8

    Functions

    not supported for pure packages by the node (vm/qfuncs)

    Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

    9 "gno.land/p/moul/x/daily/b58/v0"
    10)
    11
    12// WebLinkPrefix is the prefix of a shareable Berty web link. The fragment
    13// marker is part of it on purpose: everything after '#' stays in the browser
    14// and is never sent to berty.tech, so the web host never learns the identity
    15// being shared.
    16const WebLinkPrefix = "https://berty.tech/id#"
    17
    18// contactKind is the human-readable link kind for a contact invite,
    19// berty's BertyLink_ContactInviteV1Kind.
    20const contactKind = "contact"
    21
    22// Protobuf field numbers, from berty's api/messengertypes/messengertypes.proto
    23// and weshnet's api/protocol/protocoltypes.proto. They are wire-format
    24// constants: changing one silently produces a link no Berty client can read.
    25const (
    26 fieldBertyLinkBertyID = 2 // BertyLink.berty_id
    27 fieldBertyIDSeed = 1 // BertyID.public_rendezvous_seed
    28 fieldBertyIDAccountPK = 2 // BertyID.account_pk
    29)
    30
    31var (
    32 ErrNotAWebLink = errors.New("wesh: not a berty web link")
    33 ErrNotAContactLink = errors.New("wesh: link is not a contact invite")
    34 ErrBadLinkPayload = errors.New("wesh: link payload is not a valid contact")
    35)
    36
    37// Blob returns the protobuf-encoded machine payload of a contact invite link:
    38// a BertyLink carrying only a BertyID with the rendezvous seed and account key.
    39//
    40// The kind and the display name are deliberately absent. berty's MarshalLink
    41// puts the kind in the human-readable path segment and the display name in the
    42// query string, so a blob that carried them would not match a link produced by
    43// a real Berty client. The encoding here is verified against berty's own
    44// golden test vector; see link_test.gno.
    45func (c Contact) Blob() ([]byte, error) {
    46 if err := c.Validate(); err != nil {
    47 return nil, err
    48 }
    49 id := appendBytesField(nil, fieldBertyIDSeed, c.Seed)
    50 id = appendBytesField(id, fieldBertyIDAccountPK, c.AccountPK)
    51 return appendBytesField(nil, fieldBertyLinkBertyID, id), nil
    52}
    53
    54// WebLink returns the shareable https://berty.tech/id# link for the contact.
    55// Scanning or opening it in Berty starts a contact request.
    56func (c Contact) WebLink() (string, error) {
    57 blob, err := c.Blob()
    58 if err != nil {
    59 return "", err
    60 }
    61 link := WebLinkPrefix + contactKind + "/" + b58.Encode(blob)
    62 if c.DisplayName != "" {
    63 link += "/name=" + url.QueryEscape(c.DisplayName)
    64 }
    65 return link, nil
    66}
    67
    68// ParseWebLink decodes a Berty contact web link back into a [Contact].
    69//
    70// It accepts links with or without a trailing query segment, and ignores query
    71// keys other than "name", matching berty's own UnmarshalLink leniency.
    72func ParseWebLink(link string) (Contact, error) {
    73 var c Contact
    74
    75 if !strings.HasPrefix(link, WebLinkPrefix) {
    76 return c, ErrNotAWebLink
    77 }
    78 parts := strings.Split(strings.TrimPrefix(link, WebLinkPrefix), "/")
    79 if len(parts) < 2 {
    80 return c, ErrNotAWebLink
    81 }
    82 if parts[0] != contactKind {
    83 return c, ErrNotAContactLink
    84 }
    85 if !b58.IsValid(parts[1]) {
    86 return c, ErrBadLinkPayload
    87 }
    88
    89 inner, ok := lookupBytesField(b58.Decode(parts[1]), fieldBertyLinkBertyID)
    90 if !ok {
    91 return c, ErrBadLinkPayload
    92 }
    93 seed, ok := lookupBytesField(inner, fieldBertyIDSeed)
    94 if !ok {
    95 return c, ErrBadLinkPayload
    96 }
    97 pk, ok := lookupBytesField(inner, fieldBertyIDAccountPK)
    98 if !ok {
    99 return c, ErrBadLinkPayload
    100 }
    101 c.AccountPK = pk
    102 c.Seed = seed
    103
    104 if len(parts) > 2 {
    105 c.DisplayName = queryName(parts[2])
    106 }
    107 if err := c.Validate(); err != nil {
    108 return Contact{}, err
    109 }
    110 return c, nil
    111}
    112
    113// queryName pulls the "name" key out of an encoded query segment, returning ""
    114// when it is absent or malformed. A bad display name must never fail a link
    115// that is otherwise valid: the name is decoration, the keys are the payload.
    116func queryName(q string) string {
    117 for _, pair := range strings.Split(q, "&") {
    118 if !strings.HasPrefix(pair, "name=") {
    119 continue
    120 }
    121 name, err := url.QueryUnescape(strings.TrimPrefix(pair, "name="))
    122 if err != nil || len(name) > MaxDisplayNameLen {
    123 return ""
    124 }
    125 return name
    126 }
    127 return ""
    128}
    129
    130// appendBytesField appends a length-delimited (wire type 2) protobuf field.
    131// An empty value encodes to nothing, which is what proto3 does for a zero
    132// bytes field, and is why an invalid contact can never produce a short-but-
    133// plausible blob: Validate has already rejected it.
    134func appendBytesField(dst []byte, num int, val []byte) []byte {
    135 if len(val) == 0 {
    136 return dst
    137 }
    138 dst = append(dst, byte(num<<3|2))
    139 dst = binary.AppendUvarint(dst, uint64(len(val)))
    140 return append(dst, val...)
    141}
    142
    143// lookupBytesField scans a protobuf message for the first length-delimited
    144// field with the given number, skipping every other field whatever its wire
    145// type. Unknown fields are skipped rather than rejected so a link produced by
    146// a newer Berty client still parses.
    147func lookupBytesField(buf []byte, num int) ([]byte, bool) {
    148 i := 0
    149 for i < len(buf) {
    150 tag, n := binary.Uvarint(buf[i:])
    151 if n <= 0 {
    152 return nil, false
    153 }
    154 i += n
    155 fieldNum := int(tag >> 3)
    156 wireType := int(tag & 0x7)
    157
    158 switch wireType {
    159 case 0: // varint
    160 _, n := binary.Uvarint(buf[i:])
    161 if n <= 0 {
    162 return nil, false
    163 }
    164 i += n
    165 case 1: // 64-bit
    166 if i+8 > len(buf) {
    167 return nil, false
    168 }
    169 i += 8
    170 case 2: // length-delimited
    171 l, n := binary.Uvarint(buf[i:])
    172 if n <= 0 {
    173 return nil, false
    174 }
    175 i += n
    176 end := i + int(l)
    177 if end < i || end > len(buf) {
    178 return nil, false
    179 }
    180 if fieldNum == num {
    181 return buf[i:end], true
    182 }
    183 i = end
    184 case 5: // 32-bit
    185 if i+4 > len(buf) {
    186 return nil, false
    187 }
    188 i += 4
    189 default:
    190 return nil, false
    191 }
    192 }
    193 return nil, false
    194}
    195