README.md
gno.land/p/moul/x/wesh/v0
The publishable half of the Wesh protocol (weshnet, the network behind
Berty) as a pure gno library: Contact, WebLink,
ParseWebLink, RendezvousPoint, SeedCommitment, BindStatement,
DeviceStatement, VerifyBind, VerifyDevice, HMACSHA256.
1import "gno.land/p/moul/x/wesh/v0"
2
3c := wesh.Contact{AccountPK: pk, Seed: seed, DisplayName: "Alice"}
4link, _ := c.WebLink() // https://berty.tech/id#contact/<base58>/name=Alice
5pt, _ := c.RendezvousPointAt(now, wesh.DefaultRotationInterval)
Why a chain at all
Wesh is off-grid, peer-to-peer and end-to-end encrypted. A public chain is the opposite in nearly every dimension, so this is deliberately not "weshnet on chain". It carries only the parts that need an authenticated, ordered, publicly auditable record, the one thing a peer-to-peer network cannot give itself, and which Wesh is missing in three specific places:
- Identities do not resolve. A Berty ID travels out of band as a QR code or
a
https://berty.tech/id#link, and there is no lookup. - Seed resets are silent.
ContactRequestResetReferencegives the account a new rendezvous seed, which kills every link ever shared, with no channel to announce it. - Devices can never be revoked. The protocol documentation says so outright: the account metadata log is append-only and no authority can void an entry.
What is in here
| piece | what it is |
|---|---|
Contact |
weshnet's ShareableContact: account key + public rendezvous seed + display name, with weshnet's own CheckFormat length rules |
Blob, WebLink, ParseWebLink |
the Berty link codec, hand-rolled protobuf + base58 |
RendezvousPoint, RoundPeriod, NextPeriod |
weshnet's rotating DHT topic derivation |
SeedCommitment, OpenCommitment |
publish H(seed ‖ salt) instead of the seed |
BindStatement, DeviceStatement, VerifyBind, VerifyDevice |
the canonical texts an account signs, and their ed25519 verification |
HMACSHA256 |
RFC 2104, because gno has sha256.Sum256 but no crypto/hmac |
The link codec is conformance-tested, not guessed: TestBlobEncodingMatches BertyGoldenVector reproduces the exact base58 payload from berty's own
links_test.go, byte for byte. The rendezvous derivation is pinned against
vectors computed with weshnet's GenerateRendezvousPointForPeriod, and the HMAC
against RFC 4231, including case 6, the longer-than-block-size key.
What is deliberately absent
There is no type here that can hold a secret. Group secrets, device chain keys, message keys and ciphertexts never touch a chain: publishing a group secret hands the group to everyone, and publishing ciphertext is permanent, expensive, and leaks the social graph through access patterns.
The privacy trade-off
Publishing a rendezvous seed is equivalent to printing your Berty QR code on a
billboard: anyone can then derive today's rendezvous point and watch the DHT for
it. That is the right trade only for an identity that wants to be found.
SeedCommitment is the alternative: the chain attests the binding, the seed
travels out of band, and the rendezvous point stays private.
Cost
WebLink is base58 over a ~70-byte payload, which is big-integer work:
TestWebLink reports ~19M gas for one encode, TestWebLinkRoundTrip ~61M per
encode-plus-decode. That is fine in a Render (a query) and worth avoiding
per-row in a list. Signature verification is a native op and costs far less.
Live realm: r/moul/x/wesh
· render it at /r/moul/x/wesh/v0.
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:

🧪 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.