From 3ff2bc0530bb98da139a5f68202c8e119f9d4775 Mon Sep 17 00:00:00 2001 From: bndw Date: Sun, 8 Mar 2026 22:07:14 -0700 Subject: feat: implement Phase 1 Axon protocol core package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the foundational Go package implementing the full Axon protocol signing and crypto spec per PROTOCOL.md: - Event/Tag structs and all kind constants (KindProfile through KindJobFeedback) - Byte-exact canonical_payload construction per the PROTOCOL.md layout table - Tag sorting and canonical_tags SHA256 hash (duplicate detection included) - Ed25519 sign/verify, challenge sign/verify - X25519 key conversion from Ed25519 keypair (RFC 8032 §5.1.5 clamping + birational Edwards→Montgomery map for pubkeys) - ChaCha20-Poly1305 encrypt/decrypt for DMs (nonce prepended) - MessagePack encode/decode for events and wire messages Test vectors written first in testdata/vectors.json covering canonical_tags, canonical_payload, event_id, and signature verification — all deterministic known-input → known-output pairs for cross-language validation in Phase 4. 13 tests, all passing. --- encoding.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 encoding.go (limited to 'encoding.go') diff --git a/encoding.go b/encoding.go new file mode 100644 index 0000000..4ed9da6 --- /dev/null +++ b/encoding.go @@ -0,0 +1,54 @@ +package axon + +import ( + "fmt" + + "github.com/vmihailenco/msgpack/v5" +) + +// MarshalEvent encodes an Event to MessagePack. Fields are encoded in the +// canonical struct order using the msgpack struct tags. Binary fields (id, +// pubkey, sig, content) are encoded as msgpack bin type ([]byte). +func MarshalEvent(e *Event) ([]byte, error) { + b, err := msgpack.Marshal(e) + if err != nil { + return nil, fmt.Errorf("axon: marshal event: %w", err) + } + return b, nil +} + +// UnmarshalEvent decodes a MessagePack blob into an Event. +func UnmarshalEvent(data []byte) (*Event, error) { + var e Event + if err := msgpack.Unmarshal(data, &e); err != nil { + return nil, fmt.Errorf("axon: unmarshal event: %w", err) + } + return &e, nil +} + +// MarshalMessage encodes a wire message as a msgpack array: [type, payload]. +// messageType is a uint16; payload is any msgpack-serializable value. +func MarshalMessage(messageType uint16, payload interface{}) ([]byte, error) { + b, err := msgpack.Marshal([]interface{}{messageType, payload}) + if err != nil { + return nil, fmt.Errorf("axon: marshal message: %w", err) + } + return b, nil +} + +// UnmarshalMessageType reads only the first element of a [type, payload] +// msgpack array, returning the message type without decoding the payload. +func UnmarshalMessageType(data []byte) (uint16, error) { + var arr []msgpack.RawMessage + if err := msgpack.Unmarshal(data, &arr); err != nil { + return 0, fmt.Errorf("axon: unmarshal message: %w", err) + } + if len(arr) < 1 { + return 0, fmt.Errorf("axon: message array is empty") + } + var t uint16 + if err := msgpack.Unmarshal(arr[0], &t); err != nil { + return 0, fmt.Errorf("axon: unmarshal message type: %w", err) + } + return t, nil +} -- cgit v1.2.3