Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

v0 source realm

Package wesh is an on-chain directory and device sigchain for Wesh protocol (weshnet / Berty) identities.

Readme View source

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

An on-chain directory and device sigchain for Wesh protocol (weshnet / Berty) identities, built on p/moul/x/wesh.

gnokey maketx call -pkgpath gno.land/r/moul/x/wesh/v0 -func Register \
  -args alice -args <accountPK hex> -args <seed hex> -args "Alice" -args <sig hex>

Render then emits a link a Berty client can actually open:

https://berty.tech/id#contact/oZBLFpzghxrATkepWvDPNX9pHYqi6BWgP45x…/name=Alice

What it is for

Wesh is peer-to-peer and end-to-end encrypted, and it has three gaps that only an authenticated, ordered, publicly auditable record can close:

  1. Resolution. A Berty identity travels out of band and cannot be looked up. Here a name resolves to a contact, and the realm renders the real link.
  2. Rotation with a paper trail. Resetting the rendezvous seed silently kills every link ever shared. Here every rotation is numbered, signed and kept, so a stale link is recognisably stale rather than merely dead.
  3. Device revocation. Wesh cannot revoke a device at all. This realm hosts the missing log: hash-chained, account-signed, and verified on chain with crypto/ed25519 before an entry is accepted.

API

call what it does
Register(cur, name, accountPK, seed, displayName, sig) claim a name, publishing the rendezvous seed
RegisterCommitted(cur, name, accountPK, commitment, displayName, sig) claim a name, publishing only H(seed ‖ salt)
Rotate(cur, payload, sig) publish a new seed/commitment at revision+1
AppendDevice(cur, prev, op, devicePK, sig) append add or revoke to the sigchain
SetDisplayName(cur, name) · Release(cur) update the label · free the name
Resolve · Link · RendezvousPointAt · DeviceStatus · SigchainHead · NameOf · Count read-only, for gnoweb and other realms

Authentication

Every state-changing call carries an ed25519 signature made with the Wesh account key, over a canonical statement naming the kind, the chain id, the caller's gno address, and a monotonic revision or sequence number. Each field is there to stop one attack:

  • without the kind, a binding signature could be replayed as a device one;
  • without the chain id, a testnet binding replays on mainnet;
  • without the gno address, anyone could publish someone else's account key next to a seed they control and harvest the contact requests;
  • without the revision / sequence, a superseded statement could roll a rotation back or fork the sigchain.

All five are covered by a negative test apiece.

What it never stores

No group secret, no device chain key, no message key, no ciphertext: the library it is built on has no type that can hold one. And it does not pretend to enforce revocation inside weshnet: a revoked device's ratchets are already distributed. What revocation buys here is that it becomes public, ordered and attributable, which is strictly more than weshnet has today.

Publishing is opt-in exposure

A published seed lets anyone derive the account's rotating rendezvous point. That is the right trade for a support line, a shop or a public channel, and the wrong one for a private account, which is what RegisterCommitted is for.

Library: p/moul/x/wesh.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

Dependency graph:

gno.land/r/moul/x/wesh/v0 dependency graph

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.

Overview

Package wesh is an on-chain directory and device sigchain for Wesh protocol (weshnet / Berty) identities.

It exists to give weshnet the three things a peer-to-peer network cannot give itself, and which a chain is uniquely good at:

  1. Resolution. A Berty identity travels out of band as a QR code or a https://berty.tech/id# link, and there is no way to look one up. Here a name resolves to a contact, and Render emits the real, scannable link.
  2. Rotation with a paper trail. Resetting the public rendezvous seed silently kills every link ever shared; there is no revocation channel. Here every rotation is a numbered, timestamped, signed entry, and the superseded seeds stay visible, so a stale link resolves to a redirect instead of to nothing.
  3. Device revocation. The Wesh protocol documentation states plainly that a device, once linked to an account, can never be revoked: the account metadata log is append-only and no authority can void an entry. This realm hosts the log that was missing: a hash-chained, account-signed roster whose every entry the chain verifies with ed25519 before accepting it.

What this realm does not do, and will not

It never stores a secret. Group secrets, device chain keys, message keys and ciphertexts stay off chain, permanently. The library this realm is built on, p/moul/x/wesh(/p/moul/x/wesh/v0), has no type that can hold one.

It also does not pretend to enforce revocation inside weshnet. A revoked device's ratchets are already distributed and forward secrecy is a local property; no chain can reach into a group and forget them. What revocation buys here is that it becomes public, ordered, and attributable to the account key, which is strictly more than weshnet has today.

Publishing is a deliberate act

A published rendezvous seed lets anyone derive the account's rotating rendezvous point and watch the DHT for it. That is the same exposure as printing your Berty QR code on a billboard, and it is the right trade only for an identity that wants to be found: a support line, a shop, a public channel.

RegisterCommitted is the alternative. It publishes H(seed ‖ salt) instead, so the chain attests that a seed handed over out of band really belongs to the named account, without broadcasting where that account listens.

Authentication

Every state-changing call carries an ed25519 signature made with the Wesh account private key over a canonical statement that names this chain, the caller's gno address, and a monotonic revision or sequence number. Without it, anyone could publish anyone else's account key next to a seed of their choosing and harvest the contact requests that followed.

Constants 1

const MinNameLen, MaxNameLen, MaxDevices, MaxRotations

 1const (
 2	// MinNameLen and MaxNameLen bound a directory handle.
 3	MinNameLen = 3
 4	MaxNameLen = 32
 5
 6	// MaxDevices bounds one account's sigchain so iteration and Render stay
 7	// affordable. weshnet accounts hold a handful of devices, not hundreds.
 8	MaxDevices = 64
 9
10	// MaxRotations bounds the retained rotation history. Older entries are
11	// dropped from the front: the point of the history is to let a stale link
12	// be recognised, not to keep a permanent archive.
13	MaxRotations = 32
14)
source

Functions 14

func AppendDevice

crossing Action
1func AppendDevice(cur realm, prevHex, op, devicePKHex, sigHex string)
source

AppendDevice adds one entry to the caller's device sigchain.

prevHex must be the digest of the current head (all zeros for the first entry), and the signature must cover the whole statement including the sequence number and that digest. Chaining every entry to its predecessor is what stops the log being reordered or having an entry quietly dropped: any gap changes every digest after it.

op is "add" or "revoke". Adding a device that is already active, or revoking one that is not, is refused: an append-only log is only useful if its entries are meaningful.

func Count

Action
1func Count() int
source

Count returns the number of registered identities.

func DeviceStatus

Action
1func DeviceStatus(name, devicePKHex string) string
source

DeviceStatus reports "active", "revoked" or "unknown" for a device under a name. "unknown" also covers an unregistered name: a caller must not be able to tell those apart by status alone.

func NameOf

Action
1func NameOf(owner address) string
source

NameOf returns the handle owned by an address, or "".

func Register

crossing Action
1func Register(cur realm, name, accountPKHex, seedHex, displayName, sigHex string)
source

Register publishes an identity whose rendezvous seed is public.

sigHex must be an ed25519 signature by the account private key over wesh.BindStatement(chainID, caller, accountPK, seed, 1). Revision 1 is fixed at registration so a signature captured from a later rotation cannot be replayed to re-register the name after it is released.

func RegisterCommitted

crossing Action
1func RegisterCommitted(cur realm, name, accountPKHex, commitmentHex, displayName, sigHex string)
source

RegisterCommitted publishes an identity that keeps its rendezvous point off chain: commitmentHex is wesh.SeedCommitment(seed, salt), and the seed itself is shared out of band.

The chain still attests the binding (this account key really did claim this commitment from this gno address), so a seed later disclosed privately can be checked against it. What it does not do is tell the world where the account listens.

func Release

crossing Action
1func Release(cur realm)
source

Release removes the caller's identity and frees its name and account key.

The sigchain goes with it. That is the honest behaviour: a directory entry is a live claim, not an archive, and keeping a dangling roster for a name somebody else can now take would be worse than keeping nothing.

func Render

1func Render(path string) string
source

Render serves the directory index at "" and one identity card at "<name>".

The index deliberately does not compute a Berty link per row: base58 over a 70-byte payload is big-integer work, and it belongs on the page that a human actually asked for.

func RendezvousPointAt

Action
1func RendezvousPointAt(name string, unixSec int64) string
source

RendezvousPointAt returns the hex rendezvous point the named account announces on during the rotation period containing unixSec, or "" when the name is unknown or committed.

This is what makes a directory entry checkable rather than merely claimed: anyone can compare it against the DHT without trusting this realm.

func Resolve

Action
1func Resolve(name string) (accountPKHex, payloadHex string, committed, ok bool)
source

Resolve returns the account key and current payload of a name, both hex, and whether the payload is a commitment rather than a seed. ok is false when the name is not registered.

func Rotate

crossing Action
1func Rotate(cur realm, payloadHex, sigHex string)
source

Rotate publishes a new seed (or commitment) for the caller's identity.

This is the operation weshnet's ContactRequestResetReference has no counterpart for. There, resetting the seed silently invalidates every link ever shared. Here the new value is numbered and the old one stays in the history, so a holder of a stale link can see that it was superseded and when.

The signature must be over revision+1, which is what stops a superseded binding being replayed to roll a rotation back.

func SetDisplayName

crossing Action
1func SetDisplayName(cur realm, displayName string)
source

SetDisplayName updates the identity's free-form label.

It carries no signature because it authenticates nothing: the display name is app metadata, and berty's own link format keeps it outside the signed payload for the same reason. Only the owning address may change it.

func SigchainHead

Action
1func SigchainHead(name string) string
source

SigchainHead returns the hex digest the next sigchain entry must chain to, or "" when the name is unknown. A client builds its next statement from this.

Imports 7

Source Files 6