gomatter

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 17, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

README

                                       __  __
          __ _  ___  _ __ ___   __ _ _| |_| |_ ___ _ __
         / _` |/ _ \| '_ ` _ \ / _` |_   _|  _/ _ \ '__|
        | (_| | (_) | | | | | | (_| | | | | ||  __/ |
         \__, |\___/|_| |_| |_|\__,_| |_|  \__\___|_|
         |___/        a from-scratch Matter, in pure Go

gomatter(7) — a Matter (CSA) device stack in pure Go

Status: operational · Deps: none (Go stdlib only) · Spec: Matter Core 1.x Validated: commissioned + controlled end-to-end by a real Yandex station (Alisa)

Full disclosure. This was largely written by an LLM (Claude) from prompts over a few evenings, to bridge my Home Assistant to a Yandex station. I drove the design and it's validated end-to-end on real hardware — but it is not security-audited and not CSA-certified. It hand-rolls cryptography and parses untrusted network traffic, and it ships the CSA test vendor id (0xFFF1). Run it on your own LAN, at your own risk — not where security matters. No warranty.

A real Yandex station (Alisa) pairs and drives a from-scratch Go Matter light
A real Yandex station pairing the onoff-light example — discovery → PASE → attestation → CASE.


ABSTRACT

gomatter implements the device half of Matter — discovery, the security handshakes, the Interaction Model, and a Bridge/Aggregator data model — with no third-party dependencies. Every wire format and cryptographic primitive (TLV, SPAKE2+, AES-CCM, the attestation chain, CASE) is written from the spec against the Go standard library. The result is a single node a commissioner can discover, authenticate, certify onto its fabric, and drive.

This repository is a library. The reference consumer — a Home Assistant → Yandex (Alisa) bridge — lives in gomatter-bridge and is what keeps this half dependency-free. See SEE ALSO.

1. DESCRIPTION

A Matter commissioner expects a device to answer a fixed choreography: be found over mDNS, complete a PAKE, prove its identity, receive an operational certificate, re-establish a mutually-authenticated session, and then expose a tree of endpoints/clusters/attributes it can read, subscribe and command. gomatter answers all of it.

Non-goals. Not a controller/commissioner. Not Thread/BLE transport (operates over IP/UDP). Not a certified product — it presents the public CSA test attestation, exactly as open SDKs do (see §7 ATTESTATION).

2. ARCHITECTURE

Packages form four layers; a consumer touches only the public API (top).

┌─────────────────────────────────────────────────────────────────────┐
│ consumer   (gomatter-bridge / your app)                             │
╞═════════════════════════════════════════════════════════════════════╡  public API
│ L4   transport    UDP/5540, MRP, session demux, keepalive, reload   │
├─────────────────────────────────────────────────────────────────────┤
│ L3   im       Read, Subscribe, Invoke, Write, ReportData            │
│      model    endpoint/cluster tree, Aggregator + bridged devices   │
├─────────────────────────────────────────────────────────────────────┤
│ L2   pase, casesession, attest, opcreds, session, mdns              │
│      SPAKE2+   CASE   DAC/PAI/CD   NOC/CSR   AEAD-nonce   _matter*  │
├─────────────────────────────────────────────────────────────────────┤
│ L1   tlv, message, ccm, spake2p, onboarding, qr, console            │
│      TLV   hdr/MRP   AES-CCM   P-256   MT:/PIN   ISO-18004   logger │
└─────────────────────────────────────────────────────────────────────┘
                no third-party imports - Go stdlib only                

Package map

The public API is deliberately small — a root facade plus the few types you hand it. Everything else is internal/, free to change without breaking consumers.

Package Vis. Role
gomatter pub facade: NewDevice(name, payload), Add(handle), Run(ctx)
device pub typed device handles: NewLight, NewTempSensor, NewComposite, … (On/Off/Set…)
onboarding pub setup payload: MT: QR string + manual pairing code
qr pub QR encoder (ISO/IEC 18004) + terminal/PNG renderer
console pub colored per-subsystem log/slog handler + pairing-console UI
cluster int cluster contract + implementations + the Endpoint they sit on
transport int UDP/5540 loop, MRP, session demux, keepalive, hot-reload
im int Interaction Model responder + endpoint/cluster encoder
mdns int _matterc._udp + _matter._tcp responder
pase int PASE responder state machine
tlv int Matter Tag-Length-Value codec
message int message + protocol/exchange headers, counters, MRP acks
ccm int AES-CCM (RFC 3610) — stdlib has no CCM mode
spake2p int SPAKE2+ over P-256 (Prover/Verifier, Ke, confirmations)
session int encrypt/decrypt + Matter AEAD nonce construction
attest int DAC/PAI chain + signed CD (Device Attestation)
opcreds int NOC/CSR/cert parsing, compressed fabric id
casesession int CASE Sigma1/2/3 → operational session
state int persisted commissioning identity (op key, NOC, fabric)

3. COMMISSIONING

The full join, as gomatter answers it. The same flow re-runs from CASE on a restart (identity is persisted), so reconnection needs no re-pairing.

       commissioner (Alisa)                                           gomatter
      ┌────────────────────────────────────────────────────────────┐
disc  │── browse  _matterc._udp ──────────────────────────────────▶│ mdns
      │◀─────────────────────────────── SRV + TXT {D,CM=1,VP,DN} ──│ 
      │                                                            │
PASE  │── PBKDFParamRequest ──────────────────────────────────────▶│ pase
      │◀───────────────────────────────────── PBKDFParamResponse ──│ spake2p
      │── Pake1 ──────────────────────────────────────────────────▶│ 
      │◀────────────────────────────────────────────────── Pake2 ──│ 
      │── Pake3 ──────────────────────────────────────────────────▶│ 
      │◀────────────────────────────────────── StatusReport (OK) ──│ → keys
      │                                                            │
comm  │── Read Basic/GenComm/NetComm, ArmFailSafe ────────────────▶│ im
att   │── CertChainReq, AttestationReq, CSRReq ───────────────────▶│ attest
      │── AddTrustedRootCert, AddNOC ─────────────────────────────▶│ opcreds
      │                                                            │
CASE  │── Sigma1 ─────────────────────────────────────────────────▶│ casesession
      │◀───────────────────────────────────────────────── Sigma2 ──│ 
      │── Sigma3 ─────────────────────────────────────────────────▶│ 
      │── CommissioningComplete  (joins fabric) ──────────────────▶│ 
      │                                                            │
oper  │── Read, Subscribe, Invoke, Write  (encrypted) ────────────▶│ transport+im
      └────────────────────────────────────────────────────────────┘

  every message is MRP-acked; traffic after the session keys exist is encrypted

4. DATA MODEL

Topology is derived from how many handles you Add: a single endpoint is a flat device; more than one makes the node present an Aggregator with N bridged devices, each its own roomable device in the controller. The commissioner reads ep0.PartsList as the device list. The multi-endpoint (bridge) case:

ep0 ── Root Node 0x0016
 │       Descriptor  BasicInformation  GeneralCommissioning
 │       NetworkCommissioning  GeneralDiagnostics  OperationalCredentials
 │
 └─ ep1 ── Aggregator 0x000E              ("the bridge")
     │       Descriptor (PartsList)  Identify
     │
     ├─ ep2 ── Bridged Node 0x0013 + BridgedDeviceBasicInformation
     │           └── On/Off Light 0x0100        OnOff  Groups  Identify
     ├─ ep3 ── Bridged Node 0x0013 + ...
     │           └── Ext. Color Light 0x010D    OnOff  LevelControl  ColorControl
     ├─ ep4 ── Bridged Node 0x0013 + ...
     │           └── Temperature Sensor 0x0302   TemperatureMeasurement
     └─ epN ── Bridged Node 0x0013 + ...
                 └── composed endpoint        Plug + Temp + Humidity + Contact

Each endpoint is just a bag of clusters (like USB interfaces or PCI functions on a device). You build one with a typed handle from package deviceNewLight, NewColorLight, NewTempSensor, … for a single-function device, or a generic NewComposite(name).Add(parts…) from capability parts (NewPower, NewTemperature, NewContact, …) for a multifunction device — then dev.Add(handle). A new capability is a new part + a cluster type in internal/cluster implementing the Cluster/Invoker interface — no central switch to edit.

Devices may be added/removed at runtime with the same dev.Add / dev.Remove: the model rebuilds the tree on the serving goroutine, im bumps the data version and pushes the new PartsList on the live subscription — no re-pairing.

5. CONFORMANCE

What of the device surface is implemented. All items below are exercised by a real controller (see §8).

Surface Standard Package(s) State
Onboarding payload + manual PIN Matter §5 · base38/Verhoeff onboarding
QR encoding + render ISO/IEC 18004 qr
Commissionable discovery Matter Secure Channel mdns
PASE (SPAKE2+) Matter §4 · NIST P-256 pase, spake2p
Encrypted session AES-CCM, RFC 3610 ccm, session
Reliable messaging (MRP) Matter §4 message, transport
Interaction Model Matter §8 im
Device attestation Matter §6 (DAC/PAI/CD) attest
Operational creds (NOC/CSR) Matter §6/§11 opcreds
CASE (Sigma1/2/3) Matter §4 casesession
Operational discovery Matter Secure Channel mdns
Bridge + bridged devices Matter Device Library §9.12 gomatter, im
Identity persistence state

6. SYNOPSIS

Create a Device, plug typed handles into it, drive them by reference, then Run.

import (
    "github.com/slepogin/gomatter"
    "github.com/slepogin/gomatter/console"
    "github.com/slepogin/gomatter/device"
    "github.com/slepogin/gomatter/onboarding"
)

log := console.New()
dev, err := gomatter.NewDevice("Desk Lamp", onboarding.SetupPayload{
    VendorID: 0xFFF1, ProductID: 0x8000,
    Discovery: onboarding.DiscoveryOnNetwork,
    Discriminator: 3840, Passcode: 20202021,
}, gomatter.WithLogger(log.Slog()))
if err != nil { log.Error("%v", err); return }

lamp := device.NewLight("Desk Lamp")
lamp.OnSet(driveRelay) // OnSet: controller → hardware
dev.Add(lamp)

dev.Run(ctx)   // prints QR, advertises, then: discovery → PASE → attestation → CASE → operational
// elsewhere: lamp.On(), lamp.Off() reflect a hardware change back to the controller

Add more handles and the node becomes an Aggregator/bridge automatically — the topology is derived from what you add, no mode to set. The handle keeps a back-reference to its device, so a state change pushes itself; there's no separate update call.

The whole public surface: gomatter (NewDevice, Add/Remove, Run) and device (the typed handles), plus onboarding, console and qr. Everything that implements the protocol — cluster, transport, im, pase, mdns, the crypto — is internal/, so it can evolve without breaking your code.

go test ./... runs the payload/QR vectors plus PASE, CASE, IM and transport round-trips. The full reference wiring (flags, HA, hot-reload) is gomatter-bridge/cmd/gomatter.

6.1 Runnable examples
Example What it exposes
examples/onoff-light one On/Off light — one endpoint, so a flat device
examples/hub four On/Off lights — many endpoints, so a derived Aggregator/bridge
examples/weather one composite endpoint: temperature + humidity + pressure, pushing live updates
go run ./examples/onoff-light            # pair from your app, then toggle
go run ./examples/hub -v                 # + debug-level wire logging

Protocol logging is built in: every package logs through one *console.Logger with colored per-subsystem tags; log.SetLevel(console.LevelDebug) (the -v flag) surfaces every wire event.

7. ATTESTATION

Attestation acceptance is the commissioner's policy, not a global trusted list — the spec permits proceeding on failure (typically behind an "uncertified device, add anyway?" prompt). gomatter therefore presents the public CSA test attestation — a DAC/PAI chaining to the CSA test PAA plus a test-signed Certification Declaration, in the 0xFFF1 test-vendor range — exactly the bundle mainstream open SDKs ship. This is not "get certified"; it is answering CertificateChainRequest / AttestationRequest / CSRRequest with bounded, deterministic crypto over public test roots. Residual risk: a commissioner later tightening policy to reject the test PAA.

8. VALIDATION

A real Yandex station (Alisa) commissions and drives the device end to end — discovery → PASE → attestation → CASE → CommissioningComplete → operational read/subscribe/invoke — entirely on this stdlib-only stack. Lights, sensors and composite devices appear as individually roomable devices and respond to control both ways (controller ↔ device). Removing the device in the controller (RemoveFabric) wipes the identity and reopens commissioning for a fresh pairing.

Limitation — single fabric. The device commissions onto one fabric at a time; multi-admin (shared across several controllers) is out of scope for 1.0. The untrusted-input decoders (tlv, message, im) are fuzzed. See docs/conformance.md § Known limitations.

9. SEE ALSO

  • gomatter-bridge — the Home Assistant → Alisa application built on this library (owns the HA WebSocket client + config; the third-party deps live there).

10. NOTICE

Independent, unofficial implementation of the Matter protocol. Not affiliated with, endorsed by, or certified by the Connectivity Standards Alliance. 'Matter' is a trademark of the CSA. No CSA specification text is reproduced here. Not a certified product (uses CSA test attestation for development).

Documentation

Overview

Package gomatter is a from-scratch, dependency-free Matter (CSA) device stack in pure Go. Create a Device, plug typed device handles into it (package device), and Run a node a controller can pair and drive — discovery, PASE, attestation, CASE and the Interaction Model are wired for you.

log := console.New() // or any *slog.Logger
dev, err := gomatter.NewDevice("Desk Lamp", onboarding.SetupPayload{
    VendorID: 0xFFF1, ProductID: 0x8000,
    Discovery: onboarding.DiscoveryOnNetwork,
    Discriminator: 3840, Passcode: 20202021,
}, gomatter.WithLogger(log.Slog()))
if err != nil { ... }

lamp := device.NewLight("Desk Lamp")
dev.Add(lamp)
dev.Run(ctx)   // later: lamp.On(), lamp.Off() drive the controller

Topology is derived from what you add: one endpoint is a flat node, many are an Aggregator/bridge — there is no mode to set.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Component

type Component = cluster.Component

Component is anything that can be added to a Device — the device handles from package device (NewLight, NewComposite, …) satisfy it. Re-exported so callers can store handles (e.g. to Remove them later) without naming an internal type.

type Device

type Device struct {
	// contains filtered or unexported fields
}

Device is a runnable Matter node: mDNS discovery, the commissioning handshakes, and the Interaction Model over the device handles you Add. It implements im.Model.

func NewDevice

func NewDevice(name string, payload onboarding.SetupPayload, opts ...Option) (*Device, error)

NewDevice creates a device from a product name and an onboarding payload. Plug in device handles with Add, then Run; the topology (flat vs Aggregator/bridge) is derived from what you added. It binds no sockets; call Run to advertise and serve.

func (*Device) Add

func (d *Device) Add(c Component)

Add plugs a device handle (device.NewLight, device.NewTempSensor, …) into the node and wires its push callback. Safe before or during Run: if the device is already serving, the structure change is applied on the serving goroutine and pushed on the controller's subscription — no re-pairing.

func (*Device) Endpoints

func (d *Device) Endpoints() []*cluster.Endpoint

Endpoints returns the added endpoints (the encoder's view).

func (*Device) IsBridge

func (d *Device) IsBridge() bool

IsBridge derives the topology: more than one endpoint ⇒ an Aggregator (bridge).

func (*Device) PrintPairing

func (d *Device) PrintPairing()

PrintPairing renders the pairing QR and the manual setup code to stderr.

func (*Device) ProductName

func (d *Device) ProductName() string

ProductName is the device's product name (Basic Information / mDNS).

func (*Device) Push

func (d *Device) Push()

Push re-reports the current attribute values on active subscriptions. Handle Set… methods push automatically once added; call this only to force a report.

func (*Device) Remove

func (d *Device) Remove(c Component)

Remove detaches a previously-added handle.

func (*Device) Run

func (d *Device) Run(ctx context.Context) error

Run advertises the device over mDNS and serves commissioning + operational traffic until ctx is cancelled. It blocks. Unless suppressed, it prints the pairing console first. Add handles before calling Run.

func (*Device) VendorName

func (d *Device) VendorName() string

VendorName is the device's vendor name (Basic Information).

type Option

type Option func(*config)

Option configures a Device.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the logger (default: a colored console logger to stderr). Any *slog.Logger works; console.New().Slog() is the batteries-included choice.

func WithStatePath

func WithStatePath(path string) Option

WithStatePath persists the commissioning identity to path, so a restart reconnects (CASE) without re-pairing. Empty (default) = in-memory only.

func WithoutPairingConsole

func WithoutPairingConsole() Option

WithoutPairingConsole suppresses printing the QR + manual code on Run.

Directories

Path Synopsis
Package console is a small, dependency-free colored logger for the terminal, built on log/slog.
Package console is a small, dependency-free colored logger for the terminal, built on log/slog.
Package device provides the typed device handles you plug into a gomatter.Device.
Package device provides the typed device handles you plug into a gomatter.Device.
examples
hub command
Command hub exposes four On/Off lights as separate, individually-roomable devices.
Command hub exposes four On/Off lights as separate, individually-roomable devices.
onoff-light command
Command onoff-light is the smallest useful gomatter device: a single On/Off light that a real Matter controller can discover, pair and toggle.
Command onoff-light is the smallest useful gomatter device: a single On/Off light that a real Matter controller can discover, pair and toggle.
weather command
Command weather exposes a single composite device with three read-only measurements on one endpoint — temperature, humidity and barometric pressure.
Command weather exposes a single composite device with three read-only measurements on one endpoint — temperature, humidity and barometric pressure.
internal
attest
Package attest provides Matter device attestation using the public CSA test credentials (route B): the test DAC/PAI for VID 0xFFF1 / PID 0x8000 (which chain to the CSA test PAA that commissioners trust) and a Certification Declaration signed with the CSA test CD-signing key.
Package attest provides Matter device attestation using the public CSA test credentials (route B): the test DAC/PAI for VID 0xFFF1 / PID 0x8000 (which chain to the CSA test PAA that commissioners trust) and a Certification Declaration signed with the CSA test CD-signing key.
casesession
Package casesession implements the device (responder) side of Matter CASE — the certificate-authenticated session the commissioner opens over the operational network after AddNOC (spec §4.14):
Package casesession implements the device (responder) side of Matter CASE — the certificate-authenticated session the commissioner opens over the operational network after AddNOC (spec §4.14):
ccm
Package ccm implements AES-CCM as Matter uses it for message encryption: 128-bit key, 13-byte nonce, 16-byte tag (spec §4.7 / RFC 3610 / NIST SP 800-38C).
Package ccm implements AES-CCM as Matter uses it for message encryption: 128-bit key, 13-byte nonce, 16-byte tag (spec §4.7 / RFC 3610 / NIST SP 800-38C).
cluster
Package cluster is gomatter's device model engine: the Matter cluster contract and the concrete cluster implementations, plus the Endpoint that holds them.
Package cluster is gomatter's device model engine: the Matter cluster contract and the concrete cluster implementations, plus the Endpoint that holds them.
im
Package im is a minimal Matter Interaction Model responder — enough of the commissioning surface to answer what a commissioner reads/invokes right after PASE: a ReadRequest for the General Commissioning / Basic Information / Network Commissioning attributes, and the ArmFailSafe command.
Package im is a minimal Matter Interaction Model responder — enough of the commissioning surface to answer what a commissioner reads/invokes right after PASE: a ReadRequest for the General Commissioning / Basic Information / Network Commissioning attributes, and the ArmFailSafe command.
logger
Package logger is a thin printf-style adapter over *slog.Logger for the library internals.
Package logger is a thin printf-style adapter over *slog.Logger for the library internals.
mdns
Package mdns is a tiny, dependency-free multicast-DNS responder — just enough to advertise a Matter commissionable node (_matterc._udp) so a commissioner can discover the device on the LAN.
Package mdns is a tiny, dependency-free multicast-DNS responder — just enough to advertise a Matter commissionable node (_matterc._udp) so a commissioner can discover the device on the LAN.
message
Package message encodes/decodes the Matter message + protocol (exchange) headers for the unsecured session used during PASE (spec §4.4).
Package message encodes/decodes the Matter message + protocol (exchange) headers for the unsecured session used during PASE (spec §4.4).
opcreds
Package opcreds reads the operational identity Matter assigns at AddNOC and derives the values needed for operational discovery and CASE: the operational Node ID and Fabric ID (from the NOC), the root public key (from the trusted root cert), and the Compressed Fabric ID.
Package opcreds reads the operational identity Matter assigns at AddNOC and derives the values needed for operational discovery and CASE: the operational Node ID and Fabric ID (from the NOC), the root public key (from the trusted root cert), and the Compressed Fabric ID.
pase
Package pase implements the device (responder) side of Matter PASE — the Secure Channel exchange that turns the setup passcode into an encrypted session, using SPAKE2+ (spec §4.13 / §3.10):
Package pase implements the device (responder) side of Matter PASE — the Secure Channel exchange that turns the setup passcode into an encrypted session, using SPAKE2+ (spec §4.13 / §3.10):
session
Package session is the Matter secure (encrypted) message layer that takes over once PASE establishes keys.
Package session is the Matter secure (encrypted) message layer that takes over once PASE establishes keys.
spake2p
Package spake2p implements SPAKE2+ over NIST P-256 as used by Matter's PASE (Passcode-Authenticated Session Establishment), spec §3.10 / RFC 9383.
Package spake2p implements SPAKE2+ over NIST P-256 as used by Matter's PASE (Passcode-Authenticated Session Establishment), spec §3.10 / RFC 9383.
state
Package state persists the device's commissioning result so it survives restarts — a real Matter device keeps its operational credentials in non-volatile storage so the commissioner can reconnect (CASE) without re-commissioning.
Package state persists the device's commissioning result so it survives restarts — a real Matter device keeps its operational credentials in non-volatile storage so the commissioner can reconnect (CASE) without re-commissioning.
tlv
Package tlv is a small Matter TLV (Tag-Length-Value) codec covering the Appendix A element types: signed/unsigned integers (1–8 octet), booleans, single/double floats, UTF-8 and octet strings (1/2/4/8-octet length), null, and the struct/array/list containers.
Package tlv is a small Matter TLV (Tag-Length-Value) codec covering the Appendix A element types: signed/unsigned integers (1–8 octet), booleans, single/double floats, UTF-8 and octet strings (1/2/4/8-octet length), null, and the struct/array/list containers.
transport
Package transport is the Matter UDP message layer.
Package transport is the Matter UDP message layer.
Package onboarding builds the Matter setup payload: the "MT:" QR-code string and the 11-digit manual pairing code, per the Matter Core spec §5.1.
Package onboarding builds the Matter setup payload: the "MT:" QR-code string and the 11-digit manual pairing code, per the Matter Core spec §5.1.
Package qr is a small, dependency-free QR Code encoder good enough to render a Matter "MT:" onboarding payload in the terminal.
Package qr is a small, dependency-free QR Code encoder good enough to render a Matter "MT:" onboarding payload in the terminal.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL