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
  • wesh.gnogno
    1// Package wesh implements the publishable, non-secret half of the Wesh2// protocol (weshnet, the network layer behind Berty) as a pure gno library.3//4// # What this is, and what it deliberately is not5//6// Wesh is an off-grid, peer-to-peer, end-to-end-encrypted messaging protocol.7// A public blockchain is the opposite of that in almost every dimension:

    Functions

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

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

    8// globally replicated, permanent, publicly readable and totally ordered. So
    9// "put Wesh on chain" is the wrong goal, and this package does not pursue it.
    10//
    11// What a chain *is* good at is the one thing Wesh has no answer for: an
    12// authenticated, ordered, publicly auditable record that no single party owns.
    13// Wesh has three gaps of exactly that shape:
    14//
    15// 1. A Berty identity travels out of band, as a QR code or a
    16// https://berty.tech/id# link. There is no way to look one up, and the
    17// web prefix is a single host that can be blocked or spoofed.
    18// 2. Resetting the public rendezvous seed (ContactRequestResetReference)
    19// silently invalidates every link ever shared. There is no revocation
    20// channel, so a business card keeps pointing at a dead rendezvous point.
    21// 3. A device, once linked to an account, can never be revoked. The Wesh
    22// protocol documentation states this outright. The account metadata log is
    23// append-only and there is no authority that can mark an entry void.
    24//
    25// This package carries the pieces needed to address those on chain, and
    26// nothing else. Everything here is public by construction:
    27//
    28// - [Contact], the publishable identity, weshnet's ShareableContact
    29// (account public key + public rendezvous seed + display name).
    30// - The Berty link codec, byte-compatible with berty's own MarshalLink.
    31// - [RendezvousPoint], the rotating DHT address derivation.
    32// - The canonical statements an account signs to bind itself to a gno
    33// address, rotate its seed, and append to a device sigchain.
    34//
    35// # What must never reach a chain
    36//
    37// Group secrets (weshnet's Group.secret, Group.link_key), device chain keys,
    38// message keys, ciphertexts. Publishing a group secret hands the group to
    39// everyone; publishing ciphertext is permanent, expensive, and leaks the
    40// social graph through access patterns. This package has no type that can
    41// hold one, which is the point: there is no struct field here for a secret to
    42// accidentally land in.
    43//
    44// # The privacy trade-off, stated plainly
    45//
    46// Publishing a rendezvous seed is equivalent to printing your Berty QR code on
    47// a billboard. Anyone can then derive today's rendezvous point with
    48// [RendezvousPoint] and watch the DHT for who shows up. That is a real
    49// deanonymization surface, and it is why publication must be an explicit,
    50// opt-in act for an identity that *wants* to be found: a support line, a shop,
    51// a DAO's public channel.
    52//
    53// For an identity that does not want that, [SeedCommitment] publishes
    54// H(seed ‖ salt) instead. The chain then proves that a seed handed over out of
    55// band really does belong to the named account, without broadcasting the
    56// rendezvous point to the world.
    57//
    58// Live realm using this library: [r/moul/x/wesh](/r/moul/x/wesh/v0).
    59package wesh
    60
    61import (
    62 "crypto/sha256"
    63 "encoding/hex"
    64 "errors"
    65)
    66
    67const (
    68 // SeedLen is the length of a public rendezvous seed. Matches weshnet's
    69 // protocoltypes.RendezvousSeedLength.
    70 SeedLen = 32
    71
    72 // AccountPKLen is the length of an account public key. weshnet validates
    73 // it with libp2p's UnmarshalEd25519PublicKey, which accepts raw 32-byte
    74 // ed25519 public keys only.
    75 AccountPKLen = 32
    76
    77 // DevicePKLen is the length of a device public key: also a raw ed25519
    78 // public key.
    79 DevicePKLen = 32
    80
    81 // MaxDisplayNameLen bounds the display name so a link stays scannable and
    82 // gas stays predictable. Not a protocol constant.
    83 MaxDisplayNameLen = 64
    84)
    85
    86var (
    87 ErrBadSeedLen = errors.New("wesh: public rendezvous seed must be 32 bytes")
    88 ErrBadAccountPKLen = errors.New("wesh: account public key must be a raw 32-byte ed25519 key")
    89 ErrBadDevicePKLen = errors.New("wesh: device public key must be a raw 32-byte ed25519 key")
    90 ErrDisplayNameTooLong = errors.New("wesh: display name exceeds MaxDisplayNameLen")
    91 ErrBadCommitmentLen = errors.New("wesh: seed commitment must be 32 bytes")
    92 ErrBadHex = errors.New("wesh: value is not valid hex")
    93)
    94
    95// Contact is the publishable identity of a Wesh account: weshnet's
    96// ShareableContact. It is everything a stranger needs to send a contact
    97// request, and it contains no secret.
    98//
    99// AccountPK is the raw ed25519 account public key. Seed is the public
    100// rendezvous seed; together they derive the rotating rendezvous point the
    101// account announces on, see [RendezvousPoint]. DisplayName is free-form app
    102// metadata and is not authenticated by anything.
    103type Contact struct {
    104 AccountPK []byte
    105 Seed []byte
    106 DisplayName string
    107}
    108
    109// Validate applies weshnet's own ShareableContact.CheckFormat rules: the seed
    110// must be exactly SeedLen bytes, and the account key must be a raw ed25519
    111// public key. It deliberately does not verify that AccountPK is a valid curve
    112// point; that costs a scalar multiplication and the chain gets the same
    113// guarantee for free the first time it verifies a signature under the key.
    114func (c Contact) Validate() error {
    115 if len(c.Seed) != SeedLen {
    116 return ErrBadSeedLen
    117 }
    118 if len(c.AccountPK) != AccountPKLen {
    119 return ErrBadAccountPKLen
    120 }
    121 if len(c.DisplayName) > MaxDisplayNameLen {
    122 return ErrDisplayNameTooLong
    123 }
    124 return nil
    125}
    126
    127// SeedCommitment returns H(seed ‖ salt), the value published instead of the
    128// seed itself when an account wants the chain to attest the binding without
    129// broadcasting its rendezvous point.
    130//
    131// The salt is not optional: a seed is 32 bytes of entropy, but a commitment
    132// with no salt is a deterministic function of the seed, so two accounts that
    133// (impossibly, but still) shared a seed would be linkable, and a seed later
    134// disclosed out of band retroactively confirms every past commitment. The salt
    135// keeps disclosure a deliberate act.
    136func SeedCommitment(seed, salt []byte) ([]byte, error) {
    137 if len(seed) != SeedLen {
    138 return nil, ErrBadSeedLen
    139 }
    140 buf := make([]byte, 0, len(seed)+len(salt))
    141 buf = append(buf, seed...)
    142 buf = append(buf, salt...)
    143 sum := sha256.Sum256(buf)
    144 return sum[:], nil
    145}
    146
    147// OpenCommitment reports whether commitment is H(seed ‖ salt).
    148//
    149// The comparison is constant-time over the digest: a short-circuiting compare
    150// leaks, through gas, how many leading bytes of a guess were right.
    151func OpenCommitment(commitment, seed, salt []byte) bool {
    152 want, err := SeedCommitment(seed, salt)
    153 if err != nil {
    154 return false
    155 }
    156 return constantTimeEqual(commitment, want)
    157}
    158
    159// constantTimeEqual compares two byte slices without an early return.
    160func constantTimeEqual(a, b []byte) bool {
    161 if len(a) != len(b) {
    162 return false
    163 }
    164 var diff byte
    165 for i := 0; i < len(a); i++ {
    166 diff |= a[i] ^ b[i]
    167 }
    168 return diff == 0
    169}
    170
    171// Every key this package handles is 32 bytes, so a single length-checking
    172// helper cannot say which one was wrong. These four wrappers exist so the
    173// error a realm surfaces names the field the caller actually got wrong.
    174
    175// DecodeAccountPK parses a hex-encoded account public key.
    176func DecodeAccountPK(s string) ([]byte, error) {
    177 return decodeFixed(s, AccountPKLen, ErrBadAccountPKLen)
    178}
    179
    180// DecodeSeed parses a hex-encoded public rendezvous seed.
    181func DecodeSeed(s string) ([]byte, error) {
    182 return decodeFixed(s, SeedLen, ErrBadSeedLen)
    183}
    184
    185// DecodeDevicePK parses a hex-encoded device public key.
    186func DecodeDevicePK(s string) ([]byte, error) {
    187 return decodeFixed(s, DevicePKLen, ErrBadDevicePKLen)
    188}
    189
    190// DecodeCommitment parses a hex-encoded seed commitment.
    191func DecodeCommitment(s string) ([]byte, error) {
    192 return decodeFixed(s, DigestLen, ErrBadCommitmentLen)
    193}
    194
    195// decodeFixed parses hex and enforces an exact byte length. Realms take keys
    196// as hex strings because that is what a transaction argument can carry; this
    197// is the single place the length rule is applied.
    198func decodeFixed(s string, want int, badLen error) ([]byte, error) {
    199 b, err := hex.DecodeString(s)
    200 if err != nil {
    201 return nil, ErrBadHex
    202 }
    203 if len(b) != want {
    204 return nil, badLen
    205 }
    206 return b, nil
    207}
    208