Go client
The Go client package connects directly to the Zentalk relay network: anonymous registration and connection via zero-knowledge proofs, encrypted peer-to-peer messaging, username lookup, and group messaging. It speaks the relay wire protocol — for plain HTTP integrations, call the REST API with net/http instead.
import "github.com/ZentaChain/zentalk-node/pkg/client"The package is importable today from the zentalk-node module.
Concepts
Section titled “Concepts”| Concept | Description |
|---|---|
| Identity | A local ZK identity: a 32-byte secret, a public commitment, and an RSA key pair, stored as a JSON file. The secret never leaves the device. |
| Registration | The identity’s commitment is registered with a relay and assigned a Merkle tree index. |
| Anonymous connection | On connect, the client generates a Groth16 proof of tree membership. The relay learns that you are registered, not who you are, and assigns an anonymous address. |
| Proving key | A Groth16 proving key required to generate connection proofs, produced by the zkp-setup tool. |
| Ephemeral key | A per-connection RSA key pair used for encrypted messaging, deliberately unlinked from the identity. |
See Security overview for the broader security model.
Core types
Section titled “Core types”All constructors and methods below are defined in pkg/client.
Client
Section titled “Client”Client is the main entry point; it composes the identity, registration, ZKP, messaging, address, and key managers.
type ClientConfig struct { RelayAddr string // Relay server address, e.g. "localhost:8080" IdentityPath string // Where the identity JSON file is stored ProvingKey groth16.ProvingKey // Proving key for ZK proofs}
func NewClient(config *ClientConfig) *Client| Method | Description |
|---|---|
Initialize() error |
Loads the identity file, or creates a new identity if none exists |
Register(metadata map[string]string) error |
Registers the identity’s commitment with the relay (optional metadata such as "username") |
Connect() error |
Fetches the Merkle tree, generates a ZK handshake proof, and connects anonymously |
Disconnect() error |
Closes the relay connection |
IsConnected() bool / IsRegistered() bool |
Connection and registration state |
SendMessage(recipientAddr protocol.Address, recipientPubKey *rsa.PublicKey, message string) error |
Sends an encrypted message |
ReceiveMessage() (*ReceivedMessage, error) |
Blocks until one message is received and decrypted |
StartMessageListener(handler func(*ReceivedMessage)) error |
Delivers incoming messages to a handler in the background |
RegisterUsername(username string) error |
Registers a username for address lookup |
LookupAddress(username string) (protocol.Address, error) |
Resolves a username to a protocol address |
AdvertiseKey() error |
Publishes the ephemeral public key so others can send you encrypted messages |
LookupKey(addr protocol.Address) (*rsa.PublicKey, error) |
Fetches a peer’s advertised public key |
GetIdentity() *Identity / GetAnonymousAddress() protocol.Address / GetEphemeralPublicKey() *rsa.PublicKey |
Accessors |
InitializeGroups(dbPath, password string) error |
Opens the encrypted local group database and enables group support |
Received messages have this shape:
type ReceivedMessage struct { From protocol.Address To protocol.Address Content string Timestamp time.Time}Identity and IdentityManager
Section titled “Identity and IdentityManager”func NewIdentityManager(identityPath string) *IdentityManagerIdentityManager persists identities as JSON: GenerateIdentity(), SaveIdentity(), LoadIdentity(), HasIdentity(), GetOrCreateIdentity() (*Identity, bool, error) (the bool reports whether a new identity was created), and DeleteIdentity().
type Identity struct { Secret [32]byte // Private: never share Commitment zkp.Commitment // Public: registered with the relay TreeIndex uint64 // Assigned by the relay after registration IsRegistered bool PrivateKeyPEM []byte // RSA private key (PEM) PublicKeyPEM []byte // RSA public key (PEM)}Identity also exposes GetPrivateKey(), GetPublicKey(), and GetPublicKeyDER().
GroupClient
Section titled “GroupClient”Group messaging is layered on top of a connected Client and a local encrypted message database:
func NewGroupClient(client *Client, db *storage.MessageDB) *GroupClient| Method | Description |
|---|---|
CreateGroup(name, description string, groupType group.GroupType) (*group.Group, error) |
Create a group; group.GroupTypePrivate (invite-only) or group.GroupTypePublic (joinable via link) |
JoinGroup(joinCode string) (*group.Group, error) |
Join a public group by join code |
InviteMember(groupID string, memberAddr protocol.Address, memberUsername string, memberPubKey *rsa.PublicKey) error |
Invite a member |
SendGroupMessage(groupID string, message string) error |
Send a message to all members |
GetGroupMessages(groupID string, limit, offset int) ([]*group.GroupMessage, error) |
Read stored group messages |
GetMyGroups() ([]*group.Group, error) / GetGroupMembers(groupID string) ([]*group.Member, error) |
Listings |
UpdateGroupInfo(groupID, name, description string) error |
Update metadata |
PromoteMember / DemoteMember / KickMember |
Admin operations (each takes groupID string, memberAddr protocol.Address) |
LeaveGroup(groupID string) error |
Leave a group |
For the group cryptography design, see Group messaging.
TreeSyncClient and ZKPClient
Section titled “TreeSyncClient and ZKPClient”Lower-level building blocks, used internally by Client.Connect() and available directly:
func NewTreeSyncClient(relayAddr string) *TreeSyncClient// FetchTree() (*zkp.MerkleTree, error)// FetchTreeWithTimeout(timeout time.Duration) (*zkp.MerkleTree, error)// VerifyTreeIntegrity(tree *zkp.MerkleTree) error
func NewZKPClient(provingKey groth16.ProvingKey, tree *zkp.MerkleTree, identity *Identity, ephemeralPubKey *rsa.PublicKey) (*ZKPClient, error)// GenerateHandshakeProof() (*protocol.ZKHandshakeMessage, error)// GetEpoch() uint64, GetCommitment() zkp.Commitment, GetTreeIndex() uint64Usage example
Section titled “Usage example”The flow below mirrors the zentalk-client CLI: register once, then connect, look up a recipient, and send an encrypted message.
package main
import ( "log"
"github.com/ZentaChain/zentalk-node/pkg/client" "github.com/ZentaChain/zentalk-node/pkg/zkp")
func main() { // Load the Groth16 proving key (generated by the zkp-setup tool) pk, err := zkp.LoadProvingKey("./zkp-keys/proving_key.bin") if err != nil { log.Fatalf("load proving key: %v", err) }
cl := client.NewClient(&client.ClientConfig{ RelayAddr: "localhost:8080", IdentityPath: "./zkp-identity.json", ProvingKey: pk, })
// Load or create the local ZK identity if err := cl.Initialize(); err != nil { log.Fatalf("initialize: %v", err) }
// One-time registration of the identity's commitment with the relay if !cl.IsRegistered() { if err := cl.Register(map[string]string{"username": "alice"}); err != nil { log.Fatalf("register: %v", err) } }
// Connect anonymously (generates a ZK membership proof) if err := cl.Connect(); err != nil { log.Fatalf("connect: %v", err) } defer cl.Disconnect()
// Resolve the recipient and fetch their advertised public key addr, err := cl.LookupAddress("bob") if err != nil { log.Fatalf("lookup address: %v", err) } pubKey, err := cl.LookupKey(addr) if err != nil { log.Fatalf("lookup key: %v", err) // recipient must have run AdvertiseKey() }
// Send an encrypted message if err := cl.SendMessage(addr, pubKey, "hello over the relay"); err != nil { log.Fatalf("send: %v", err) }}To receive messages, the peer advertises its key and listens:
if err := cl.AdvertiseKey(); err != nil { log.Fatal(err)}
err := cl.StartMessageListener(func(msg *client.ReceivedMessage) { log.Printf("from %x: %s (%s)", msg.From[:8], msg.Content, msg.Timestamp)})if err != nil { log.Fatal(err)}select {} // keep listeningFor a runnable reference to the X3DH key-agreement primitives themselves, see the tests in pkg/protocol (go test ./pkg/protocol -run X3DH -v). These cover the classical handshake and a post-quantum hybrid variant, but note that the Go hybrid path is not reachable from any production code path: protocol.HybridInitialMessage has neither an Encode nor a Decode method and no caller outside its own file, so no Go binary can send or receive one. The hybrid handshake that matters on the wire is the TypeScript one (post-quantum hybrid encryption).
The zentalk-client CLI
Section titled “The zentalk-client CLI”The CLI in cmd/zentalk-client runs the ZKP messaging flow end to end without requiring any code.
go build -o zentalk-client ./cmd/zentalk-clientFull flag and command reference: CLI reference.
End-to-end walkthrough
Section titled “End-to-end walkthrough”Proving keys must exist before connecting; generate them once with the setup tool:
go run cmd/zkp-setup/main.go -output ./zkp-keysThen, with a relay running on localhost:8080:
# Terminal 1 — Bob registers, advertises his key, and listens./zentalk-client -cmd register -username bob./zentalk-client -cmd register-username -username bob./zentalk-client -cmd advertise-key./zentalk-client -cmd listen
# Terminal 2 — Alice registers and sends Bob a message./zentalk-client -cmd register -username alice -identity ./alice-identity.json./zentalk-client -cmd send -identity ./alice-identity.json \ -username bob -message "hello bob"Related pages
Section titled “Related pages”- SDK overview — how this package fits alongside the REST API
- REST API — for HTTP integrations in Go via
net/http - Security overview — ZKP and E2EE model
- X3DH — the key agreement used for message encryption
