Documentation
¶
Index ¶
- Constants
- Variables
- func EncodeMailboxPayload(senderAddr, recipientAddr a2al.Address, recipientPub ed25519.PublicKey, ...) ([]byte, error)
- func FindNodeResponseWireSize(resp *BodyFindNodeResp) (int, error)
- func FindValueResponseWireSize(resp *BodyFindValueResp) (int, error)
- func FormatObservedUDP(ip net.IP, port uint16) ([]byte, error)
- func IsPublicIP(ip net.IP) bool
- func MarshalAgentProfilePayload(p AgentProfilePayload) ([]byte, error)
- func MarshalSignedMessage(hdr Header, body any, priv ed25519.PrivateKey) ([]byte, error)
- func MarshalSignedMessageKeyStore(hdr Header, body any, ks acrypto.KeyStore, addr a2al.Address) ([]byte, error)
- func MarshalTopicPayload(p TopicPayload) ([]byte, error)
- func ParseObservedUDP(b []byte) (host string, port uint16, ok bool)
- func RecordCategory(recType uint8) uint8
- func RecordIsNewer(a, b SignedRecord) bool
- func TopicNodeID(topic string) a2al.NodeID
- func VerifySignedRecord(sr SignedRecord, now time.Time) error
- type AgentProfilePayload
- type BodyFindNode
- type BodyFindNodeResp
- type BodyFindValue
- type BodyFindValueResp
- type BodyNATProbeEcho
- type BodyNATProbeReq
- type BodyPing
- type BodyPong
- type BodyStore
- type BodyStoreResp
- type DecodedMessage
- type DiscoverFilter
- type EndpointPayload
- type EndpointRecord
- type Header
- type MailboxMessage
- type MailboxPayload
- type NodeInfo
- type SignedRecord
- func SignEndpointRecord(priv ed25519.PrivateKey, addr a2al.Address, ep EndpointPayload, ...) (SignedRecord, error)
- func SignEndpointRecordDelegated(opPriv ed25519.PrivateKey, delegationCBOR []byte, addr a2al.Address, ...) (SignedRecord, error)
- func SignRecord(priv ed25519.PrivateKey, addr a2al.Address, recType uint8, payload []byte, ...) (SignedRecord, error)
- func SignRecordDelegated(opPriv ed25519.PrivateKey, delegationCBOR []byte, addr a2al.Address, ...) (SignedRecord, error)
- type StoreReason
- type TopicEntry
- type TopicPayload
Constants ¶
const ( CategoryUnknown uint8 = 0 CategorySovereign uint8 = 1 // RecType 0x01–0x0F CategoryTopic uint8 = 2 // RecType 0x10–0x1F CategoryMailbox uint8 = 3 // RecType 0x80–0x8F )
DHT record categories (Phase 4 spec §2.1).
const ( RecTypeTopic uint8 = 0x10 RecTypeMailbox uint8 = 0x80 )
RecType constants beyond endpoint (Phase 4).
const ( // MaxMailboxPayloadCBOR is the spec §4.8 cap on SignedRecord.payload (MailboxPayload CBOR). MaxMailboxPayloadCBOR = 512 // DefaultMailboxTTL is the recommended mailbox record TTL in seconds (spec §4.8). DefaultMailboxTTL uint32 = 3600 )
const ( MailboxMsgConnectRequest uint8 = 0x01 MailboxMsgCandidates uint8 = 0x02 MailboxMsgText uint8 = 0x03 )
Mailbox message types (spec §4.3).
const ( MsgPing uint8 = 0x01 MsgPong uint8 = 0x02 MsgFindNode uint8 = 0x03 MsgFindNodeResp uint8 = 0x04 MsgFindValue uint8 = 0x05 MsgFindValueResp uint8 = 0x06 MsgStore uint8 = 0x07 MsgStoreResp uint8 = 0x08 MsgNATProbeReq uint8 = 0x09 // NAT probe request: ask peer to echo to our claimed address MsgNATProbeEcho uint8 = 0x0A // NAT probe echo: sent by responder directly to claimed address )
Message type IDs (spec §7.4).
const ( // RecTypeEndpoint is the Phase 1 endpoint advertisement (spec §7.6). RecTypeEndpoint uint8 = 0x01 // MaxEndpointSignalURLLen caps each URL in EndpointPayload.Signal / Signals. MaxEndpointSignalURLLen = 2048 // MaxSignalURLs caps EndpointPayload.Signals entry count (multi-center signal URLs). MaxSignalURLs = 8 // MaxTurnURLs caps EndpointPayload.Turns entry count (credential-free relay hints for DHT). MaxTurnURLs = 16 // MaxTurnURLEntryLen caps each turn:// string length in Turns. MaxTurnURLEntryLen = 512 )
const ( NATUnknown uint8 = iota NATFullCone NATRestricted NATPortRestricted NATSymmetric )
NAT types for EndpointPayload (spec §7.6).
const AgentProfilePayloadMaxSize = 700
AgentProfilePayloadMaxSize is the hard pre-publish rejection threshold (bytes). The FIND_VALUE response budget is ~1050 B; SignedRecord wrapper adds ~156 B, leaving ≈894 B for payload. We cap at 700 B to keep routing nodes comfortable.
const AgentProfilePayloadWarnSize = 350
AgentProfilePayloadWarnSize is a soft advisory threshold (bytes). Payloads approaching this size may compete with routing nodes for UDP packet space.
const MaxTopicBriefRunes = 140
MaxTopicBriefRunes is spec §5.3 one-line description limit.
const MaxTopicPayloadCBOR = 512
MaxTopicPayloadCBOR is the spec §5.9 cap on SignedRecord.payload for RecType 0x10.
const ProtocolVersion uint8 = 1
ProtocolVersion is the wire protocol version (spec §7.3).
const RecTypeAgentProfile uint8 = 0x02
RecTypeAgentProfile is the sovereign RecType for AgentProfilePayload (0x02).
Variables ¶
var ( // ErrInvalidMessage is returned for malformed wire data or failed verification. ErrInvalidMessage = errors.New("a2al/protocol: invalid message") // ErrUnknownMsgType is returned when msg_type is not defined in Phase 1. ErrUnknownMsgType = errors.New("a2al/protocol: unknown msg_type") )
var ( // ErrInvalidRecord is returned when structure, signature, or address binding fails. ErrInvalidRecord = errors.New("a2al/protocol: invalid record") // ErrRecordExpired means now is past Timestamp+TTL (VerifySignedRecord). ErrRecordExpired = errors.New("a2al/protocol: record expired") // ErrPayloadApproachingLimit is a non-fatal advisory: payload is valid but // approaching the soft size threshold and may compete with routing nodes. ErrPayloadApproachingLimit = errors.New("a2al/protocol: payload approaching size limit") )
Functions ¶
func EncodeMailboxPayload ¶
func EncodeMailboxPayload(senderAddr, recipientAddr a2al.Address, recipientPub ed25519.PublicKey, msgType uint8, body []byte) ([]byte, error)
EncodeMailboxPayload encrypts (msgType, body) and returns canonical CBOR MailboxPayload.
func FindNodeResponseWireSize ¶
func FindNodeResponseWireSize(resp *BodyFindNodeResp) (int, error)
FindNodeResponseWireSize returns the canonical CBOR size of a FIND_NODE_RESP body (UDP trim, spec §3.7).
func FindValueResponseWireSize ¶
func FindValueResponseWireSize(resp *BodyFindValueResp) (int, error)
FindValueResponseWireSize returns the canonical CBOR size of a FIND_VALUE_RESP body (UDP trim, spec §3.7).
func FormatObservedUDP ¶
FormatObservedUDP encodes IP:port to the same wire form as BodyPong.observed_addr. Both IPv4 (4+2 bytes = 6 total) and IPv6 (16+2 bytes = 18 total) are supported. No changes needed here for dual-stack.
func IsPublicIP ¶ added in v0.1.4
IsPublicIP reports whether ip is a routeable public WAN address — not loopback, link-local, private, multicast, unspecified, or RFC 6598 CGNAT (100.64/10). Used by both dht and host packages to validate observed / claimed addresses.
Works for both IPv4 and IPv6. For IPv6: GUA (2000::/3) passes; ULA (fc00::/7) and link-local (fe80::/10) are rejected. No changes needed here for dual-stack.
func MarshalAgentProfilePayload ¶ added in v0.2.0
func MarshalAgentProfilePayload(p AgentProfilePayload) ([]byte, error)
MarshalAgentProfilePayload canonical-encodes p and enforces the size limit. Returns ErrPayloadApproachingLimit (non-fatal) wrapped in the error when the payload is within the hard limit but above the advisory threshold.
func MarshalSignedMessage ¶
MarshalSignedMessage canonical-encodes header and body, signs, and returns the outer CBOR bytes.
func MarshalSignedMessageKeyStore ¶
func MarshalSignedMessageKeyStore(hdr Header, body any, ks acrypto.KeyStore, addr a2al.Address) ([]byte, error)
MarshalSignedMessageKeyStore signs with KeyStore (spec: same preimage as MarshalSignedMessage).
func MarshalTopicPayload ¶
func MarshalTopicPayload(p TopicPayload) ([]byte, error)
MarshalTopicPayload canonical-encodes p and enforces size / brief limits. If p.Version == 0, it is treated as 1 for encoding.
func ParseObservedUDP ¶
ParseObservedUDP decodes wire observed_addr (4+2 IPv4 or 16+2 IPv6, big-endian port). The wire format is address-family agnostic: 6 bytes = IPv4, 18 bytes = IPv6. Both IPv4 and IPv6 are fully supported; no changes needed here for dual-stack.
func RecordCategory ¶
RecordCategory maps RecType to a storage/query policy bucket. Reserved ranges (0x20–0x7F, 0x90–0xFF) return CategoryUnknown and are stored like sovereign (key must be NodeID(Address)) for forward compatibility.
func RecordIsNewer ¶
func RecordIsNewer(a, b SignedRecord) bool
RecordIsNewer reports whether a should replace b (spec: larger seq wins; tie-break by timestamp).
func TopicNodeID ¶
TopicNodeID returns SHA-256("topic:" || topicUTF8) as the DHT key for Topic rendezvous (Phase 4 / v2 §10.3). The formula is immutable on the wire.
func VerifySignedRecord ¶
func VerifySignedRecord(sr SignedRecord, now time.Time) error
VerifySignedRecord checks cryptographic integrity: signature validity, endpoint payload shape, and expiry. It does NOT enforce pubkey↔address authority (whether the signing key is allowed to publish for the given Address). Authority is a deployment policy; inject it via dht.Config.RecordAuth at the storage layer.
Types ¶
type AgentProfilePayload ¶ added in v0.2.0
type AgentProfilePayload struct {
Version uint8 `cbor:"1,keyasint"`
Name string `cbor:"2,keyasint,omitempty"`
Brief string `cbor:"3,keyasint,omitempty"`
Protocols []string `cbor:"4,keyasint,omitempty"`
Skills []string `cbor:"5,keyasint,omitempty"`
CardHash []byte `cbor:"6,keyasint,omitempty"`
Modalities []string `cbor:"8,keyasint,omitempty"`
Meta map[string]any `cbor:"9,keyasint,omitempty"`
}
AgentProfilePayload is the CBOR body of a RecType=0x02 sovereign record. All fields except Version are optional (omitempty). Integer map keys minimise wire footprint under the 700 B cap.
func ParseAgentProfilePayload ¶ added in v0.2.0
func ParseAgentProfilePayload(sr SignedRecord) (AgentProfilePayload, error)
ParseAgentProfilePayload decodes the payload from a RecType=0x02 SignedRecord.
type BodyFindNode ¶
type BodyFindNode struct {
Target []byte `cbor:"1,keyasint"` // 32 NodeID
}
type BodyFindNodeResp ¶
type BodyFindValue ¶
type BodyFindValueResp ¶
type BodyFindValueResp struct {
Nodes []NodeInfo `cbor:"1,keyasint"`
Record *SignedRecord `cbor:"2,keyasint,omitempty"`
ObservedAddr []byte `cbor:"3,keyasint"`
Records []SignedRecord `cbor:"4,keyasint,omitempty"`
}
type BodyNATProbeEcho ¶ added in v0.1.4
type BodyNATProbeEcho struct {
Token []byte `cbor:"1,keyasint"` // 8-byte nonce echoed from BodyNATProbeReq
}
BodyNATProbeEcho is sent by the responder directly to the requester's claimed address. Token echoes the nonce from the corresponding BodyNATProbeReq.
type BodyNATProbeReq ¶ added in v0.1.4
type BodyNATProbeReq struct {
Token []byte `cbor:"1,keyasint"` // 8-byte random nonce
ClaimedAddr []byte `cbor:"2,keyasint"` // wire-encoded UDP address (6 or 18 bytes)
}
BodyNATProbeReq asks the receiver to send a NATProbeEcho directly to ClaimedAddr. Token (8 bytes) correlates the echo back to the requester's wait channel.
type BodyStore ¶
type BodyStore struct {
Record SignedRecord `cbor:"1,keyasint"`
Key []byte `cbor:"2,keyasint,omitempty"` // 32-byte DHT key; omit = NodeID(Address)
}
type BodyStoreResp ¶
type BodyStoreResp struct {
Stored bool `cbor:"1,keyasint"`
AlreadyHad bool `cbor:"2,keyasint,omitempty"` // true when the node already held an equal-or-newer record
Reason StoreReason `cbor:"3,keyasint,omitempty"` // non-zero only on rejection; 0 from old nodes means unknown
}
type DecodedMessage ¶
type DecodedMessage struct {
Header Header
SenderPubkey ed25519.PublicKey
SenderAddr a2al.Address
Body any
}
DecodedMessage is a verified, parsed wire message.
func VerifyAndDecode ¶
func VerifyAndDecode(raw []byte) (*DecodedMessage, error)
VerifyAndDecode parses the outer message, re-canonicalizes header/body, verifies Ed25519, checks sender_pubkey matches body.address for PING and PONG (spec §Step 3).
type DiscoverFilter ¶
type DiscoverFilter struct {
Protocols []string `json:"protocols,omitempty"`
Tags []string `json:"tags,omitempty"`
}
DiscoverFilter is optional client-side filtering (spec §5.5).
type EndpointPayload ¶
type EndpointPayload struct {
Endpoints []string `cbor:"1,keyasint"`
NatType uint8 `cbor:"2,keyasint"`
// Signal is the primary WebSocket base URL for ICE trickle signaling (no query; room is
// appended by peers). Kept for backward compatibility with nodes that only read key 3.
Signal string `cbor:"3,keyasint,omitempty"`
// Turns lists optional turn:// URLs without credentials (public relay hints).
Turns []string `cbor:"4,keyasint,omitempty"`
// Signals lists additional WebSocket base URLs for ICE signaling (multi-center).
// New nodes prefer this list; old nodes that only read Signal (key 3) still work.
// When non-empty, Signal should equal Signals[0] for maximum compatibility.
Signals []string `cbor:"5,keyasint,omitempty"`
}
EndpointPayload is the CBOR inside SignedRecord.payload for rec_type=0x01 (spec §7.6).
type EndpointRecord ¶
type EndpointRecord struct {
Address a2al.Address
Endpoints []string
NatType uint8
Signal string
Turns []string
// Signals is the multi-center signal URL list from EndpointPayload.Signals (key 5).
// When non-empty, callers should prefer this over Signal. Signal is kept for compat.
Signals []string
Timestamp uint64
Seq uint64
TTL uint32
}
EndpointRecord is the decoded logical view (spec Step 4); no signature material.
func ParseEndpointRecord ¶
func ParseEndpointRecord(sr SignedRecord) (EndpointRecord, error)
ParseEndpointRecord decodes an endpoint record after verification.
type Header ¶
type Header struct {
Version uint8 `cbor:"1,keyasint"`
Features uint16 `cbor:"2,keyasint"`
MsgType uint8 `cbor:"3,keyasint"`
TxID []byte `cbor:"4,keyasint"`
}
Header is the CBOR map in field 1 of the outer message (spec §7.3).
type MailboxMessage ¶
type MailboxMessage struct {
Sender a2al.Address
MsgType uint8
Body []byte
// Seq is copied from the outer SignedRecord.Seq; it is monotonically
// increasing per sender and serves as a unique record identifier.
Seq uint64
// SenderPubkey is the sender's Ed25519 identity public key, taken from
// SignedRecord.Pubkey after signature verification. It is guaranteed to
// correspond to Sender (same key that derived the AID). Callers may use
// this to encrypt a reply without an extra DHT lookup.
SenderPubkey ed25519.PublicKey
}
MailboxMessage is one decrypted mailbox entry for the application (spec §4.6).
func OpenMailboxRecord ¶
func OpenMailboxRecord(recipientPriv ed25519.PrivateKey, recipientAddr a2al.Address, sr SignedRecord) (MailboxMessage, error)
OpenMailboxRecord decrypts a verified mailbox SignedRecord for recipientAddr.
type MailboxPayload ¶
type MailboxPayload struct {
Recipient []byte `cbor:"1,keyasint"`
SenderAddr []byte `cbor:"2,keyasint"`
EphemeralPK []byte `cbor:"3,keyasint"`
Ciphertext []byte `cbor:"4,keyasint"`
Nonce []byte `cbor:"5,keyasint"`
}
MailboxPayload is the CBOR inside SignedRecord.payload for rec_type=0x80 (spec §4.1).
type NodeInfo ¶
type NodeInfo struct {
Address []byte `cbor:"1,keyasint"` // 21 bytes
NodeID []byte `cbor:"2,keyasint"` // 32 bytes
IP []byte `cbor:"3,keyasint"` // 4 or 16 bytes
Port uint16 `cbor:"4,keyasint"`
}
NodeInfo is the DHT routing contact shape (spec §7.6).
type SignedRecord ¶
type SignedRecord struct {
Address []byte `cbor:"1,keyasint"`
RecType uint8 `cbor:"2,keyasint"`
Payload []byte `cbor:"3,keyasint"`
Seq uint64 `cbor:"4,keyasint"`
Timestamp uint64 `cbor:"5,keyasint"`
TTL uint32 `cbor:"6,keyasint"`
Pubkey []byte `cbor:"7,keyasint"`
Signature []byte `cbor:"8,keyasint"`
Delegation []byte `cbor:"9,keyasint,omitempty"`
}
SignedRecord is the on-wire record container (spec §7.6). Record signing uses prefix "a2al-rec\0" over CBOR of fields 1–6 (implemented in record.go, Step 4). Field 9 (Delegation) is optional and carries a CBOR-encoded DelegationProof when the signing key (Pubkey) is an operational key publishing on behalf of a master-derived Address.
func SignEndpointRecord ¶
func SignEndpointRecord(priv ed25519.PrivateKey, addr a2al.Address, ep EndpointPayload, seq, timestamp uint64, ttl uint32) (SignedRecord, error)
SignEndpointRecord builds a SignedRecord for a self-signed endpoint advertisement. The signing key must derive the same Address as addr (Phase 1/2 path).
func SignEndpointRecordDelegated ¶
func SignEndpointRecordDelegated(opPriv ed25519.PrivateKey, delegationCBOR []byte, addr a2al.Address, ep EndpointPayload, seq, timestamp uint64, ttl uint32) (SignedRecord, error)
SignEndpointRecordDelegated builds a SignedRecord where an operational key signs on behalf of a master-derived AID (Phase 3 delegation path). delegationCBOR is embedded verbatim in the record and will be verified by receivers via the RecordAuth policy injected into the DHT store. Callers must ensure delegationCBOR is a valid DelegationProof for addr authorized to opPriv.
func SignRecord ¶
func SignRecord(priv ed25519.PrivateKey, addr a2al.Address, recType uint8, payload []byte, seq, timestamp uint64, ttl uint32) (SignedRecord, error)
SignRecord builds a signed record for RecType sovereign custom (0x02–0x0F), topic (0x10–0x1F), or mailbox (0x80–0x8F). payload must be CBOR-encoded bytes.
func SignRecordDelegated ¶
func SignRecordDelegated(opPriv ed25519.PrivateKey, delegationCBOR []byte, addr a2al.Address, recType uint8, payload []byte, seq, timestamp uint64, ttl uint32) (SignedRecord, error)
SignRecordDelegated is SignRecord with an operational key and DelegationProof.
type StoreReason ¶ added in v0.1.8
type StoreReason uint8
StoreReason is the machine-readable cause carried in BodyStoreResp.Reason. Zero (StoreReasonOK) is the default and means no error information is present, which is also what older nodes send. New reasons must be assigned new non-zero values; existing values must never be reused for a different meaning.
const ( StoreReasonOK StoreReason = 0 // stored or already-had (see Stored/AlreadyHad) StoreReasonPolicy StoreReason = 1 // node-side policy rejected (ACL, whitelist, etc.) — stable refusal StoreReasonRecordInvalid StoreReason = 2 // record-level rejection (bad sig, key mismatch, delegation error) )
type TopicEntry ¶
type TopicEntry struct {
Address a2al.Address
Seq uint64
Timestamp uint64
TTL uint32
Version uint8
Topic string
Name string
Protocols []string
Tags []string
Brief string
Meta map[string]any
}
TopicEntry is the decoded logical view for discovery (spec §5.7), like EndpointRecord.
func FilterTopicEntries ¶
func FilterTopicEntries(in []TopicEntry, f *DiscoverFilter) []TopicEntry
FilterTopicEntries applies protocol/tag filters (AND: all filter tags must appear in entry.Tags; at least one protocol match if filter protocols non-empty).
func TopicEntryFromSignedRecord ¶
func TopicEntryFromSignedRecord(sr SignedRecord) (TopicEntry, error)
TopicEntryFromSignedRecord builds TopicEntry after caller verifies signature / expiry.
type TopicPayload ¶
type TopicPayload struct {
Version uint8 `cbor:"1,keyasint"`
Topic string `cbor:"2,keyasint"`
Name string `cbor:"3,keyasint"`
Protocols []string `cbor:"4,keyasint"`
Tags []string `cbor:"5,keyasint"`
Brief string `cbor:"6,keyasint"`
Meta map[string]any `cbor:"7,keyasint,omitempty"`
}
TopicPayload is the CBOR inside SignedRecord.payload for rec_type=0x10 (spec §5.3).
func ParseTopicRecord ¶
func ParseTopicRecord(sr SignedRecord) (TopicPayload, error)
ParseTopicRecord decodes a topic SignedRecord payload.