From c8e81cd9f0e7374c4ef5be4a2f1f860ff2df8c8a Mon Sep 17 00:00:00 2001 From: Ivan Date: Sat, 29 Mar 2025 18:12:47 -0500 Subject: [PATCH] Enhance node announcement handling and packet structure. Introduce node-specific metadata in the Reticulum struct, update announce packet creation to support new formats, and improve validation checks for announce data. Adjust minimum packet size requirements and refactor related functions for clarity and consistency. --- cmd/reticulum-go/main.go | 233 ++++++++++++++++++++++++++------------- pkg/announce/announce.go | 217 +++++++++++++++++++++++++----------- pkg/packet/constants.go | 2 +- pkg/packet/packet.go | 73 ++++++++++-- 4 files changed, 374 insertions(+), 151 deletions(-) diff --git a/cmd/reticulum-go/main.go b/cmd/reticulum-go/main.go index 4aa3987..bb494a1 100644 --- a/cmd/reticulum-go/main.go +++ b/cmd/reticulum-go/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/binary" "flag" "fmt" "log" @@ -48,7 +49,7 @@ const ( DEBUG_PACKETS = 6 // Packet-level details DEBUG_ALL = 7 // Everything including identity operations APP_NAME = "Go-Client" - APP_ASPECT = "node" + APP_ASPECT = "node" // Always use "node" for node announces ) type Reticulum struct { @@ -62,9 +63,16 @@ type Reticulum struct { announceHistoryMu sync.RWMutex identity *identity.Identity destination *destination.Destination + + // Node-specific information + maxTransferSize int16 // Max transfer size in KB + nodeEnabled bool // Whether this node is enabled + nodeTimestamp int64 // Last node announcement timestamp } type announceRecord struct { + timestamp int64 + appData []byte } func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) { @@ -74,10 +82,10 @@ func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) { // Set default app name and aspect if not provided if cfg.AppName == "" { - cfg.AppName = "Go Client" + cfg.AppName = APP_NAME } if cfg.AppAspect == "" { - cfg.AppAspect = "node" + cfg.AppAspect = APP_ASPECT // Always use "node" for node announcements } if err := initializeDirectories(); err != nil { @@ -100,23 +108,16 @@ func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) { identity, destination.IN, destination.SINGLE, - APP_NAME, - APP_ASPECT, + "reticulum", + "node", ) if err != nil { return nil, fmt.Errorf("failed to create destination: %v", err) } debugLog(DEBUG_INFO, "Created destination with hash: %x", dest.GetHash()) - // Set default app data for announces - appData := []byte(fmt.Sprintf(`{"app":"%s","aspect":"%s","version":"0.1.0"}`, APP_NAME, APP_ASPECT)) - dest.SetDefaultAppData(appData) - - // Enable destination features - dest.AcceptsLinks(true) - dest.EnableRatchets("") // Empty string for default path - dest.SetProofStrategy(destination.PROVE_APP) - debugLog(DEBUG_VERBOSE, "Configured destination features") + // Set node metadata + nodeTimestamp := time.Now().Unix() r := &Reticulum{ config: cfg, @@ -128,8 +129,19 @@ func NewReticulum(cfg *common.ReticulumConfig) (*Reticulum, error) { announceHistory: make(map[string]announceRecord), identity: identity, destination: dest, + + // Node-specific information + maxTransferSize: 500, // Default 500KB + nodeEnabled: true, // Enabled by default + nodeTimestamp: nodeTimestamp, } + // Enable destination features + dest.AcceptsLinks(true) + dest.EnableRatchets("") // Empty string for default path + dest.SetProofStrategy(destination.PROVE_APP) + debugLog(DEBUG_VERBOSE, "Configured destination features") + // Initialize interfaces from config for name, ifaceConfig := range cfg.Interfaces { if !ifaceConfig.Enabled { @@ -276,18 +288,6 @@ func main() { log.Fatalf("Failed to create Reticulum instance: %v", err) } - // Create announce using r.identity - announce, err := announce.NewAnnounce( - r.identity, - []byte("HELLO WORLD"), - nil, - false, - r.config, - ) - if err != nil { - log.Fatalf("Failed to create announce: %v", err) - } - // Start monitoring interfaces go r.monitorInterfaces() @@ -300,36 +300,45 @@ func main() { log.Fatalf("Failed to start Reticulum: %v", err) } - // Send initial announces after interfaces are ready - time.Sleep(2 * time.Second) // Give interfaces time to connect - for _, iface := range r.interfaces { - if netIface, ok := iface.(common.NetworkInterface); ok { - if netIface.IsEnabled() && netIface.IsOnline() { - debugLog(2, "Sending initial announce on interface %s", netIface.GetName()) - if err := announce.Propagate([]common.NetworkInterface{netIface}); err != nil { - debugLog(1, "Failed to propagate initial announce: %v", err) - } - } - } - } - // Start periodic announces go func() { - ticker := time.NewTicker(5 * time.Minute) + ticker := time.NewTicker(5 * time.Minute) // Adjust interval as needed defer ticker.Stop() for range ticker.C { debugLog(3, "Starting periodic announce cycle") + + // Create a new announce packet for this cycle + periodicAnnounce, err := announce.NewAnnounce( + r.identity, + r.createNodeAppData(), + nil, // No ratchet ID for now + false, + r.config, + ) + if err != nil { + debugLog(1, "Failed to create periodic announce: %v", err) + continue + } + + // Propagate announce to all online interfaces + var onlineInterfaces []common.NetworkInterface for _, iface := range r.interfaces { if netIface, ok := iface.(common.NetworkInterface); ok { if netIface.IsEnabled() && netIface.IsOnline() { - debugLog(2, "Sending periodic announce on interface %s", netIface.GetName()) - if err := announce.Propagate([]common.NetworkInterface{netIface}); err != nil { - debugLog(1, "Failed to propagate periodic announce: %v", err) - } + onlineInterfaces = append(onlineInterfaces, netIface) } } } + + if len(onlineInterfaces) > 0 { + debugLog(2, "Sending periodic announce on %d interfaces", len(onlineInterfaces)) + if err := periodicAnnounce.Propagate(onlineInterfaces); err != nil { + debugLog(1, "Failed to propagate periodic announce: %v", err) + } + } else { + debugLog(3, "No online interfaces for periodic announce") + } } }() @@ -440,7 +449,7 @@ func (r *Reticulum) Start() error { // Send initial announce once per interface initialAnnounce, err := announce.NewAnnounce( r.identity, - createAppData(r.config.AppName, r.config.AppAspect), + r.createNodeAppData(), nil, false, r.config, @@ -474,7 +483,7 @@ func (r *Reticulum) Start() error { periodicAnnounce, err := announce.NewAnnounce( r.identity, - createAppData(r.config.AppName, r.config.AppAspect), + r.createNodeAppData(), nil, false, r.config, @@ -559,25 +568,82 @@ func (h *AnnounceHandler) ReceivedAnnounce(destHash []byte, id interface{}, appD debugLog(DEBUG_INFO, "Received announce from %x", destHash) debugLog(DEBUG_PACKETS, "Raw announce data: %x", appData) + var isNode bool + var nodeEnabled bool + var nodeTimestamp int64 + var nodeMaxSize int16 + // Parse msgpack array if len(appData) > 0 { - if appData[0] == 0x92 { // msgpack array of 2 elements + if appData[0] == 0x92 { + // Format [name, ticket] for standard peers + debugLog(DEBUG_VERBOSE, "Received standard peer announce") + isNode = false var pos = 1 - // Parse first element (name) - if appData[pos] == 0xc4 { // bin 8 format + // Parse first element (NameBytes) + if pos+1 < len(appData) && appData[pos] == 0xc4 { nameLen := int(appData[pos+1]) - name := string(appData[pos+2 : pos+2+nameLen]) - pos += 2 + nameLen - debugLog(DEBUG_VERBOSE, "Announce name: %s", name) + if pos+2+nameLen <= len(appData) { + nameBytes := appData[pos+2 : pos+2+nameLen] + name := string(nameBytes) + pos += 2 + nameLen + debugLog(DEBUG_VERBOSE, "Peer name: %s (bytes: %x)", name, nameBytes) - // Parse second element (app data) - if pos < len(appData) && appData[pos] == 0xc4 { // bin 8 format - dataLen := int(appData[pos+1]) - data := appData[pos+2 : pos+2+dataLen] - debugLog(DEBUG_VERBOSE, "Announce app data: %s", string(data)) + // Parse second element (TicketValue) + if pos < len(appData) { + ticketValue := appData[pos] // Assuming fixint for now + debugLog(DEBUG_VERBOSE, "Peer ticket value: %d", ticketValue) + } else { + debugLog(DEBUG_ERROR, "Could not parse ticket value from announce appData") + } + } else { + debugLog(DEBUG_ERROR, "Could not parse name bytes from announce appData") + } + } else { + debugLog(DEBUG_ERROR, "Announce appData name is not in expected bin 8 format") + } + } else if appData[0] == 0x93 { + // Format [enable, timestamp, maxsize] for nodes + debugLog(DEBUG_VERBOSE, "Received node announce") + isNode = true + var pos = 1 + + // Parse first element (Boolean enable/disable) + if pos < len(appData) { + if appData[pos] == 0xc3 { + nodeEnabled = true + } else if appData[pos] == 0xc2 { + nodeEnabled = false + } else { + debugLog(DEBUG_ERROR, "Unexpected format for node enabled status: %x", appData[pos]) + } + pos++ + debugLog(DEBUG_VERBOSE, "Node enabled: %v", nodeEnabled) + + // Parse second element (Int32 timestamp) + if pos+4 < len(appData) && appData[pos] == 0xd2 { + pos++ + timestamp := binary.BigEndian.Uint32(appData[pos : pos+4]) + nodeTimestamp = int64(timestamp) + pos += 4 + debugLog(DEBUG_VERBOSE, "Node timestamp: %d (%s)", timestamp, time.Unix(nodeTimestamp, 0)) + + // Parse third element (Int16 max transfer size) + if pos+2 < len(appData) && appData[pos] == 0xd1 { + pos++ + maxSize := binary.BigEndian.Uint16(appData[pos : pos+2]) + nodeMaxSize = int16(maxSize) + debugLog(DEBUG_VERBOSE, "Node max transfer size: %d KB", nodeMaxSize) + } else { + debugLog(DEBUG_ERROR, "Could not parse max transfer size from node announce") + } + } else { + debugLog(DEBUG_ERROR, "Could not parse timestamp from node announce") } } + } else { + debugLog(DEBUG_VERBOSE, "Unknown announce data format: %x", appData) } } @@ -598,14 +664,22 @@ func (h *AnnounceHandler) ReceivedAnnounce(destHash []byte, id interface{}, appD } } - // Store announce in history + // Create a better record with more info + recordType := "peer" + if isNode { + recordType = "node" + debugLog(DEBUG_INFO, "Storing node in announce history: enabled=%v, timestamp=%d, maxsize=%dKB", + nodeEnabled, nodeTimestamp, nodeMaxSize) + } + h.reticulum.announceHistoryMu.Lock() h.reticulum.announceHistory[identity.GetHexHash()] = announceRecord{ - // You can add fields here to store relevant announce data + timestamp: time.Now().Unix(), + appData: appData, } h.reticulum.announceHistoryMu.Unlock() - debugLog(DEBUG_VERBOSE, "Stored announce in history for identity %s", identity.GetHexHash()) + debugLog(DEBUG_VERBOSE, "Stored %s announce in history for identity %s", recordType, identity.GetHexHash()) } return nil @@ -619,24 +693,33 @@ func (r *Reticulum) GetDestination() *destination.Destination { return r.destination } -func createAppData(appName, appAspect string) []byte { - nameString := fmt.Sprintf("%s.%s", appName, appAspect) +func (r *Reticulum) createNodeAppData() []byte { + // Create a msgpack array with 3 elements + // [Bool, Int32, Int16] for [enable, timestamp, max_transfer_size] + appData := []byte{0x93} // Array with 3 elements - // Create MessagePack array with 2 elements - appData := []byte{0x92} // Fix array with 2 elements + // Element 0: Boolean for enable/disable peer + if r.nodeEnabled { + appData = append(appData, 0xc3) // true + } else { + appData = append(appData, 0xc2) // false + } - // First element: name string (always use str 8 format for consistency) - nameBytes := []byte(nameString) - appData = append(appData, 0xd9) // str 8 format - appData = append(appData, byte(len(nameBytes))) // length - appData = append(appData, nameBytes...) // string data + // Element 1: Int32 timestamp (current time) + // Update the timestamp when creating new announcements + r.nodeTimestamp = time.Now().Unix() + appData = append(appData, 0xd2) // int32 format + timeBytes := make([]byte, 4) + binary.BigEndian.PutUint32(timeBytes, uint32(r.nodeTimestamp)) + appData = append(appData, timeBytes...) - // Second element: version string (always use str 8 format for consistency) - version := "0.1.0" - versionBytes := []byte(version) - appData = append(appData, 0xd9) // str 8 format - appData = append(appData, byte(len(versionBytes))) // length - appData = append(appData, versionBytes...) // string data + // Element 2: Int16 max transfer size in KB + appData = append(appData, 0xd1) // int16 format + sizeBytes := make([]byte, 2) + binary.BigEndian.PutUint16(sizeBytes, uint16(r.maxTransferSize)) + appData = append(appData, sizeBytes...) + log.Printf("[DEBUG-7] Created node appData (msgpack [enable=%v, timestamp=%d, maxsize=%d]): %x", + r.nodeEnabled, r.nodeTimestamp, r.maxTransferSize, appData) return appData } diff --git a/pkg/announce/announce.go b/pkg/announce/announce.go index 5022acf..e1bcdc2 100644 --- a/pkg/announce/announce.go +++ b/pkg/announce/announce.go @@ -169,44 +169,108 @@ func (a *Announce) HandleAnnounce(data []byte) error { log.Printf("[DEBUG-7] Handling announce packet of %d bytes", len(data)) - // Minimum packet size validation (header(2) + desthash(16) + enckey(32) + signkey(32) + namehash(10) + - // randomhash(10) + signature(64) + min app data(3)) - if len(data) < 169 { - log.Printf("[DEBUG-7] Invalid announce data length: %d bytes", len(data)) + // Minimum packet size validation + // header(2) + desthash(16) + context(1) + enckey(32) + signkey(32) + namehash(10) + + // randomhash(10) + signature(64) + min app data(3) + if len(data) < 170 { + log.Printf("[DEBUG-7] Invalid announce data length: %d bytes (minimum 170)", len(data)) return errors.New("invalid announce data length") } - // Parse fields + // Extract header and check packet type header := data[:2] + if header[0]&0x03 != PACKET_TYPE_ANNOUNCE { + return errors.New("not an announce packet") + } + + // Get hop count hopCount := header[1] - destHash := data[2:18] - encKey := data[18:50] - signKey := data[50:82] - nameHash := data[82:92] - randomHash := data[92:102] - signature := data[102:166] - appData := data[166:] - - log.Printf("[DEBUG-7] Announce fields: destHash=%x, encKey=%x, signKey=%x", - destHash, encKey, signKey) - log.Printf("[DEBUG-7] Name hash=%x, random hash=%x", nameHash, randomHash) - - // Validate hop count if hopCount > MAX_HOPS { log.Printf("[DEBUG-7] Announce exceeded max hops: %d", hopCount) return errors.New("announce exceeded maximum hop count") } - // Create announced identity from public keys + // Parse the packet based on header type + headerType := (header[0] & 0b01000000) >> 6 + var contextByte byte + var packetData []byte + + if headerType == HEADER_TYPE_2 { + // Header type 2 format: header(2) + desthash(16) + transportid(16) + context(1) + data + if len(data) < 35 { + return errors.New("header type 2 packet too short") + } + destHash := data[2:18] + transportID := data[18:34] + contextByte = data[34] + packetData = data[35:] + + log.Printf("[DEBUG-7] Header type 2 announce: destHash=%x, transportID=%x, context=%d", + destHash, transportID, contextByte) + } else { + // Header type 1 format: header(2) + desthash(16) + context(1) + data + if len(data) < 19 { + return errors.New("header type 1 packet too short") + } + destHash := data[2:18] + contextByte = data[18] + packetData = data[19:] + + log.Printf("[DEBUG-7] Header type 1 announce: destHash=%x, context=%d", + destHash, contextByte) + } + + // Now parse the data portion according to the spec + // Public Key (32) + Signing Key (32) + Name Hash (10) + Random Hash (10) + [Ratchet] + Signature (64) + App Data + + if len(packetData) < 148 { // 32 + 32 + 10 + 10 + 64 + return errors.New("announce data too short") + } + + // Extract the components + encKey := packetData[:32] + signKey := packetData[32:64] + nameHash := packetData[64:74] + randomHash := packetData[74:84] + + // The next field could be a ratchet (32 bytes) or signature (64 bytes) + // We need to detect this somehow or use a flag + // For now, assume no ratchet + + signature := packetData[84:148] + appData := packetData[148:] + + log.Printf("[DEBUG-7] Announce fields: encKey=%x, signKey=%x", encKey, signKey) + log.Printf("[DEBUG-7] Name hash=%x, random hash=%x", nameHash, randomHash) + log.Printf("[DEBUG-7] Signature=%x, appDataLen=%d", signature[:8], len(appData)) + + // Get the destination hash from header + var destHash []byte + if headerType == HEADER_TYPE_2 { + destHash = data[2:18] + } else { + destHash = data[2:18] + } + + // Combine public keys pubKey := append(encKey, signKey...) + + // Create announced identity from public keys announcedIdentity := identity.FromPublicKey(pubKey) if announcedIdentity == nil { return errors.New("invalid identity public key") } // Verify signature - signData := append(destHash, appData...) - if !announcedIdentity.Verify(signData, signature) { + signedData := make([]byte, 0) + signedData = append(signedData, destHash...) + signedData = append(signedData, encKey...) + signedData = append(signedData, signKey...) + signedData = append(signedData, nameHash...) + signedData = append(signedData, randomHash...) + signedData = append(signedData, appData...) + + if !announcedIdentity.Verify(signedData, signature) { return errors.New("invalid announce signature") } @@ -257,8 +321,8 @@ func (a *Announce) CreatePacket() []byte { // Create header header := CreateHeader( IFAC_NONE, - HEADER_TYPE_1, - 0, // No context flag + HEADER_TYPE_2, // Use header type 2 for announces + 0, // No context flag PROP_TYPE_BROADCAST, DEST_TYPE_SINGLE, PACKET_TYPE_ANNOUNCE, @@ -270,38 +334,85 @@ func (a *Announce) CreatePacket() []byte { // Add destination hash (16 bytes) packet = append(packet, a.destinationHash...) + // If using header type 2, add transport ID (16 bytes) + // For broadcast announces, this is filled with zeroes + transportID := make([]byte, 16) + packet = append(packet, transportID...) + + // Add context byte + packet = append(packet, byte(0)) // Context byte, 0 for announces + // Add public key parts (32 bytes each) pubKey := a.identity.GetPublicKey() - packet = append(packet, pubKey[:32]...) // Encryption key - packet = append(packet, pubKey[32:]...) // Signing key + encKey := pubKey[:32] // Encryption key + signKey := pubKey[32:] // Signing key + + // Start building data portion according to spec + data := make([]byte, 0, 32+32+10+10+32+64+len(a.appData)) + data = append(data, encKey...) // Encryption key (32 bytes) + data = append(data, signKey...) // Signing key (32 bytes) + + // Determine if this is a node announce based on appData format + var appName string + if len(a.appData) > 2 && a.appData[0] == 0x93 { + // This is a node announcement + appName = "reticulum.node" + } else if len(a.appData) > 3 && a.appData[0] == 0x92 && a.appData[1] == 0xc4 { + nameLen := int(a.appData[2]) + if 3+nameLen <= len(a.appData) { + appName = string(a.appData[3 : 3+nameLen]) + } else { + appName = fmt.Sprintf("%s.%s", a.config.AppName, a.config.AppAspect) + } + } else { + // Default fallback using config values + appName = fmt.Sprintf("%s.%s", a.config.AppName, a.config.AppAspect) + } // Add name hash (10 bytes) - nameHash := sha256.Sum256([]byte(fmt.Sprintf("%s.%s", a.config.AppName, a.config.AppAspect))) - packet = append(packet, nameHash[:10]...) + nameHash := sha256.Sum256([]byte(appName)) + nameHash10 := nameHash[:10] + log.Printf("[DEBUG-6] Using name hash for '%s': %x", appName, nameHash10) + data = append(data, nameHash10...) - // Add random hash (10 bytes) - randomBytes := make([]byte, 10) - rand.Read(randomBytes) - packet = append(packet, randomBytes...) + // Add random hash (10 bytes) - 5 bytes random + 5 bytes time + randomHash := make([]byte, 10) + rand.Read(randomHash[:5]) + timeBytes := make([]byte, 8) + binary.BigEndian.PutUint64(timeBytes, uint64(time.Now().Unix())) + copy(randomHash[5:], timeBytes[:5]) + data = append(data, randomHash...) + + // Add ratchet ID (32 bytes) - required in the packet format + if a.ratchetID != nil { + data = append(data, a.ratchetID...) + } else { + // If there's no ratchet, add 32 zero bytes as placeholder + data = append(data, make([]byte, 32)...) + } // Create validation data for signature + // Signature consists of destination hash, public keys, name hash, random hash, and app data validationData := make([]byte, 0) validationData = append(validationData, a.destinationHash...) - validationData = append(validationData, pubKey[:32]...) // Encryption key - validationData = append(validationData, pubKey[32:]...) // Signing key - validationData = append(validationData, nameHash[:10]...) - validationData = append(validationData, randomBytes...) + validationData = append(validationData, encKey...) + validationData = append(validationData, signKey...) + validationData = append(validationData, nameHash10...) + validationData = append(validationData, randomHash...) validationData = append(validationData, a.appData...) // Add signature (64 bytes) signature := a.identity.Sign(validationData) - packet = append(packet, signature...) + data = append(data, signature...) // Add app data if len(a.appData) > 0 { - packet = append(packet, a.appData...) + data = append(data, a.appData...) } + // Combine header and data + packet = append(packet, data...) + return packet } @@ -398,36 +509,8 @@ func (a *Announce) GetPacket() []byte { defer a.mutex.Unlock() if a.packet == nil { - // Generate hash from announce data - h := sha256.New() - h.Write(a.destinationHash) - h.Write(a.identity.GetPublicKey()) - h.Write([]byte{a.hops}) - h.Write(a.appData) - if a.ratchetID != nil { - h.Write(a.ratchetID) - } - - // Construct packet - packet := make([]byte, 0) - packet = append(packet, PACKET_TYPE_ANNOUNCE) - packet = append(packet, a.destinationHash...) - packet = append(packet, a.identity.GetPublicKey()...) - packet = append(packet, a.hops) - packet = append(packet, a.appData...) - if a.ratchetID != nil { - packet = append(packet, a.ratchetID...) - } - - // Add signature - signData := append(a.destinationHash, a.appData...) - if a.ratchetID != nil { - signData = append(signData, a.ratchetID...) - } - signature := a.identity.Sign(signData) - packet = append(packet, signature...) - - a.packet = packet + // Use CreatePacket to generate the packet + a.packet = a.CreatePacket() } return a.packet diff --git a/pkg/packet/constants.go b/pkg/packet/constants.go index ba5c0be..f911e54 100644 --- a/pkg/packet/constants.go +++ b/pkg/packet/constants.go @@ -16,6 +16,6 @@ const ( DestinationLink = 3 // Minimum packet sizes - MinAnnounceSize = 169 // header(2) + desthash(16) + enckey(32) + signkey(32) + + MinAnnounceSize = 170 // header(2) + desthash(16) + context(1) + enckey(32) + signkey(32) + // namehash(10) + randomhash(10) + signature(64) + min appdata(3) ) diff --git a/pkg/packet/packet.go b/pkg/packet/packet.go index fb460b8..453bec5 100644 --- a/pkg/packet/packet.go +++ b/pkg/packet/packet.go @@ -1,7 +1,9 @@ package packet import ( + "crypto/rand" "crypto/sha256" + "encoding/binary" "errors" "fmt" "log" @@ -217,14 +219,58 @@ func (p *Packet) Serialize() ([]byte, error) { } func NewAnnouncePacket(destHash []byte, identity *identity.Identity, appData []byte, transportID []byte) (*Packet, error) { - log.Printf("[DEBUG-7] Creating new announce packet: destHash=%x, appData=%s", destHash, string(appData)) + log.Printf("[DEBUG-7] Creating new announce packet: destHash=%x, appData=%s", destHash, fmt.Sprintf("%x", appData)) - // Create combined public key + // Get public key separated into encryption and signing keys pubKey := identity.GetPublicKey() - log.Printf("[DEBUG-6] Using public key: %x", pubKey) + encKey := pubKey[:32] + signKey := pubKey[32:] + log.Printf("[DEBUG-6] Using public keys: encKey=%x, signKey=%x", encKey, signKey) - // Create signed data - signedData := append(destHash, pubKey...) + // Parse app name from first msgpack element if possible + // For nodes, we'll use "reticulum.node" as the name hash + var appName string + if len(appData) > 2 && appData[0] == 0x93 { + // This is a node announce, use standard node name + appName = "reticulum.node" + } else if len(appData) > 3 && appData[0] == 0x92 && appData[1] == 0xc4 { + // Try to extract name from peer announce appData + nameLen := int(appData[2]) + if 3+nameLen <= len(appData) { + appName = string(appData[3 : 3+nameLen]) + } else { + // Default fallback + appName = "reticulum-go.node" + } + } else { + // Default fallback + appName = "reticulum-go.node" + } + + // Create name hash (10 bytes) + nameHash := sha256.Sum256([]byte(appName)) + nameHash10 := nameHash[:10] + log.Printf("[DEBUG-6] Using name hash for '%s': %x", appName, nameHash10) + + // Create random hash (10 bytes) - 5 bytes random + 5 bytes time + randomHash := make([]byte, 10) + rand.Read(randomHash[:5]) + timeBytes := make([]byte, 8) + binary.BigEndian.PutUint64(timeBytes, uint64(time.Now().Unix())) + copy(randomHash[5:], timeBytes[:5]) + log.Printf("[DEBUG-6] Generated random hash: %x", randomHash) + + // Prepare ratchet ID if available (not yet implemented) + var ratchetID []byte + + // Prepare data for signature + // Signature consists of destination hash, public keys, name hash, random hash, and app data + signedData := make([]byte, 0, len(destHash)+len(encKey)+len(signKey)+len(nameHash10)+len(randomHash)+len(appData)) + signedData = append(signedData, destHash...) + signedData = append(signedData, encKey...) + signedData = append(signedData, signKey...) + signedData = append(signedData, nameHash10...) + signedData = append(signedData, randomHash...) signedData = append(signedData, appData...) log.Printf("[DEBUG-5] Created signed data (%d bytes)", len(signedData)) @@ -232,11 +278,22 @@ func NewAnnouncePacket(destHash []byte, identity *identity.Identity, appData []b signature := identity.Sign(signedData) log.Printf("[DEBUG-6] Generated signature: %x", signature) - // Combine all data - data := append(pubKey, appData...) - data = append(data, signature...) + // Combine all fields according to spec + // Data structure: Public Key (32) + Signing Key (32) + Name Hash (10) + Random Hash (10) + Ratchet (optional) + Signature (64) + App Data + data := make([]byte, 0, 32+32+10+10+64+len(appData)) + data = append(data, encKey...) // Encryption key (32 bytes) + data = append(data, signKey...) // Signing key (32 bytes) + data = append(data, nameHash10...) // Name hash (10 bytes) + data = append(data, randomHash...) // Random hash (10 bytes) + if ratchetID != nil { + data = append(data, ratchetID...) // Ratchet ID (32 bytes if present) + } + data = append(data, signature...) // Signature (64 bytes) + data = append(data, appData...) // Application data (variable) + log.Printf("[DEBUG-5] Combined packet data (%d bytes)", len(data)) + // Create the packet with header type 2 (two address fields) p := &Packet{ HeaderType: HeaderType2, PacketType: PacketTypeAnnounce,