This commit is contained in:
Sudo-Ivan
2025-01-01 03:12:26 -06:00
parent 3ffd5b72a1
commit 6cdc02346f
6 changed files with 522 additions and 186 deletions

View File

@@ -50,7 +50,7 @@ type Destination struct {
destType byte
appName string
aspects []string
hash []byte
hashValue []byte
acceptsLinks bool
proofStrategy byte
@@ -95,12 +95,12 @@ func New(id *identity.Identity, direction byte, destType byte, appName string, a
}
// Generate destination hash
d.hash = d.Hash()
d.hashValue = d.calculateHash()
return d, nil
}
func (d *Destination) Hash() []byte {
func (d *Destination) calculateHash() []byte {
nameHash := sha256.Sum256([]byte(d.ExpandName()))
identityHash := sha256.Sum256(d.identity.GetPublicKey())
@@ -134,8 +134,8 @@ func (d *Destination) Announce(appData []byte) error {
packet := make([]byte, 0)
// Add destination hash
packet = append(packet, d.hash...)
log.Printf("[DEBUG-4] Added destination hash %x to announce", d.hash[:8])
packet = append(packet, d.hashValue...)
log.Printf("[DEBUG-4] Added destination hash %x to announce", d.hashValue[:8])
// Add identity public key
pubKey := d.identity.GetPublicKey()
@@ -386,3 +386,21 @@ func (d *Destination) GetPublicKey() []byte {
func (d *Destination) GetIdentity() *identity.Identity {
return d.identity
}
func (d *Destination) GetType() byte {
return d.destType
}
func (d *Destination) GetHash() []byte {
d.mutex.RLock()
defer d.mutex.RUnlock()
if d.hashValue == nil {
d.mutex.RUnlock()
d.mutex.Lock()
defer d.mutex.Unlock()
if d.hashValue == nil {
d.hashValue = d.calculateHash()
}
}
return d.hashValue
}

View File

@@ -17,6 +17,8 @@ import (
"encoding/hex"
"log"
"github.com/Sudo-Ivan/reticulum-go/pkg/common"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
@@ -35,6 +37,9 @@ const (
AES128_BLOCKSIZE = 16
HASHLENGTH = 256
SIGLENGTH = KEYSIZE
RATCHET_ROTATION_INTERVAL = 1800 // Default 30 minutes in seconds
MAX_RETAINED_RATCHETS = 512 // Maximum number of retained ratchet keys
)
type Identity struct {
@@ -116,28 +121,33 @@ func New() (*Identity, error) {
i := &Identity{
ratchets: make(map[string][]byte),
ratchetExpiry: make(map[string]int64),
mutex: &sync.RWMutex{},
}
// Generate X25519 key pair
i.privateKey = make([]byte, curve25519.ScalarSize)
if _, err := io.ReadFull(rand.Reader, i.privateKey); err != nil {
log.Printf("[DEBUG-1] Failed to generate X25519 private key: %v", err)
return nil, err
}
var err error
i.publicKey, err = curve25519.X25519(i.privateKey, curve25519.Basepoint)
if err != nil {
log.Printf("[DEBUG-1] Failed to generate X25519 public key: %v", err)
return nil, err
}
// Generate Ed25519 signing keypair
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
log.Printf("[DEBUG-1] Failed to generate Ed25519 keypair: %v", err)
return nil, err
}
i.signingKey = privKey
i.verificationKey = pubKey
log.Printf("[DEBUG-7] Created new identity with hash: %x", i.Hash())
return i, nil
}
@@ -162,9 +172,15 @@ func (i *Identity) Verify(data []byte, signature []byte) bool {
func (i *Identity) Encrypt(plaintext []byte, ratchet []byte) ([]byte, error) {
if i.publicKey == nil {
log.Printf("[DEBUG-1] Encryption failed: identity has no public key")
return nil, errors.New("encryption failed: identity does not hold a public key")
}
log.Printf("[DEBUG-7] Starting encryption for identity %s", i.GetHexHash())
if ratchet != nil {
log.Printf("[DEBUG-7] Using ratchet for encryption")
}
// Generate ephemeral keypair
ephemeralPrivKey := make([]byte, curve25519.ScalarSize)
if _, err := io.ReadFull(rand.Reader, ephemeralPrivKey); err != nil {
@@ -232,6 +248,7 @@ func (i *Identity) Encrypt(plaintext []byte, ratchet []byte) ([]byte, error) {
token = append(token, ciphertext...)
token = append(token, mac...)
log.Printf("[DEBUG-7] Encryption completed successfully")
return token, nil
}
@@ -270,12 +287,11 @@ func Remember(packet []byte, destHash []byte, publicKey []byte, appData []byte)
func ValidateAnnounce(packet []byte, destHash []byte, publicKey []byte, signature []byte, appData []byte) bool {
if len(publicKey) != KEYSIZE/8 {
log.Printf("[DEBUG-7] Invalid public key length: %d", len(publicKey))
return false
}
if len(destHash) > TRUNCATED_HASHLENGTH/8 {
destHash = destHash[:TRUNCATED_HASHLENGTH/8]
}
log.Printf("[DEBUG-7] Validating announce for destination hash: %x", destHash)
announced := &Identity{}
announced.publicKey = publicKey[:KEYSIZE/16]
@@ -285,10 +301,12 @@ func ValidateAnnounce(packet []byte, destHash []byte, publicKey []byte, signatur
signedData = append(signedData, appData...)
if !announced.Verify(signedData, signature) {
log.Printf("[DEBUG-7] Signature verification failed")
return false
}
Remember(packet, destHash, publicKey, appData)
log.Printf("[DEBUG-7] Announce validated and remembered successfully")
return true
}
@@ -369,9 +387,15 @@ func (i *Identity) GetCurrentRatchetKey() []byte {
func (i *Identity) Decrypt(ciphertextToken []byte, ratchets [][]byte, enforceRatchets bool, ratchetIDReceiver *common.RatchetIDReceiver) ([]byte, error) {
if i.privateKey == nil {
log.Printf("[DEBUG-1] Decryption failed: identity has no private key")
return nil, errors.New("decryption failed because identity does not hold a private key")
}
log.Printf("[DEBUG-7] Starting decryption for identity %s", i.GetHexHash())
if len(ratchets) > 0 {
log.Printf("[DEBUG-7] Attempting decryption with %d ratchets", len(ratchets))
}
if len(ciphertextToken) <= KEYSIZE/8/2 {
return nil, errors.New("decryption failed because the token size was invalid")
}
@@ -450,6 +474,7 @@ func (i *Identity) Decrypt(ciphertextToken []byte, ratchets [][]byte, enforceRat
ratchetIDReceiver.LatestRatchetID = nil
}
log.Printf("[DEBUG-7] Decryption completed successfully")
return plaintext[:len(plaintext)-padding], nil
}
@@ -461,13 +486,17 @@ func (i *Identity) tryRatchetDecryption(peerPubBytes, ciphertext, ratchet []byte
// Get ratchet ID
ratchetPubBytes, err := curve25519.X25519(ratchetPriv, curve25519.Basepoint)
if err != nil {
log.Printf("[DEBUG-7] Failed to generate ratchet public key: %v", err)
return nil, nil, err
}
ratchetID := i.GetRatchetID(ratchetPubBytes)
log.Printf("[DEBUG-7] Decrypting with ratchet ID: %x", ratchetID)
// Generate shared key
sharedKey, err := curve25519.X25519(ratchetPriv, peerPubBytes)
if err != nil {
log.Printf("[DEBUG-7] Failed to generate shared key: %v", err)
return nil, nil, err
}
@@ -475,17 +504,20 @@ func (i *Identity) tryRatchetDecryption(peerPubBytes, ciphertext, ratchet []byte
hkdfReader := hkdf.New(sha256.New, sharedKey, i.GetSalt(), i.GetContext())
derivedKey := make([]byte, 32)
if _, err := io.ReadFull(hkdfReader, derivedKey); err != nil {
log.Printf("[DEBUG-7] Failed to derive key: %v", err)
return nil, nil, err
}
// Create AES cipher
block, err := aes.NewCipher(derivedKey)
if err != nil {
log.Printf("[DEBUG-7] Failed to create cipher: %v", err)
return nil, nil, err
}
// Extract IV and decrypt
if len(ciphertext) < aes.BlockSize {
log.Printf("[DEBUG-7] Ciphertext too short")
return nil, nil, errors.New("ciphertext too short")
}
@@ -493,6 +525,7 @@ func (i *Identity) tryRatchetDecryption(peerPubBytes, ciphertext, ratchet []byte
actualCiphertext := ciphertext[aes.BlockSize:]
if len(actualCiphertext)%aes.BlockSize != 0 {
log.Printf("[DEBUG-7] Ciphertext is not a multiple of block size")
return nil, nil, errors.New("ciphertext is not a multiple of block size")
}
@@ -504,15 +537,18 @@ func (i *Identity) tryRatchetDecryption(peerPubBytes, ciphertext, ratchet []byte
// Remove padding
padding := int(plaintext[len(plaintext)-1])
if padding > aes.BlockSize || padding == 0 {
log.Printf("[DEBUG-7] Invalid padding")
return nil, nil, errors.New("invalid padding")
}
for i := len(plaintext) - padding; i < len(plaintext); i++ {
if plaintext[i] != byte(padding) {
log.Printf("[DEBUG-7] Invalid padding")
return nil, nil, errors.New("invalid padding")
}
}
log.Printf("[DEBUG-7] Decrypted successfully")
return plaintext[:len(plaintext)-padding], ratchetID, nil
}
@@ -555,6 +591,8 @@ func (i *Identity) DecryptWithHMAC(data []byte, key []byte) ([]byte, error) {
}
func (i *Identity) ToFile(path string) error {
log.Printf("[DEBUG-7] Saving identity %s to file: %s", i.GetHexHash(), path)
data := map[string]interface{}{
"private_key": i.privateKey,
"public_key": i.publicKey,
@@ -565,26 +603,36 @@ func (i *Identity) ToFile(path string) error {
file, err := os.Create(path)
if err != nil {
log.Printf("[DEBUG-1] Failed to create identity file: %v", err)
return err
}
defer file.Close()
return json.NewEncoder(file).Encode(data)
if err := json.NewEncoder(file).Encode(data); err != nil {
log.Printf("[DEBUG-1] Failed to encode identity data: %v", err)
return err
}
log.Printf("[DEBUG-7] Identity saved successfully")
return nil
}
func RecallIdentity(path string) (*Identity, error) {
log.Printf("[DEBUG-7] Attempting to recall identity from: %s", path)
file, err := os.Open(path)
if err != nil {
log.Printf("[DEBUG-1] Failed to open identity file: %v", err)
return nil, err
}
defer file.Close()
var data map[string]interface{}
if err := json.NewDecoder(file).Decode(&data); err != nil {
log.Printf("[DEBUG-1] Failed to decode identity data: %v", err)
return nil, err
}
// Reconstruct identity from saved data
id := &Identity{
privateKey: data["private_key"].([]byte),
publicKey: data["public_key"].([]byte),
@@ -593,8 +641,10 @@ func RecallIdentity(path string) (*Identity, error) {
appData: data["app_data"].([]byte),
ratchets: make(map[string][]byte),
ratchetExpiry: make(map[string]int64),
mutex: &sync.RWMutex{},
}
log.Printf("[DEBUG-7] Successfully recalled identity with hash: %s", id.GetHexHash())
return id, nil
}
@@ -686,3 +736,98 @@ func NewIdentity() (*Identity, error) {
return i, nil
}
func (i *Identity) RotateRatchet() ([]byte, error) {
i.mutex.Lock()
defer i.mutex.Unlock()
log.Printf("[DEBUG-7] Rotating ratchet for identity %s", i.GetHexHash())
// Generate new ratchet key
newRatchet := make([]byte, RATCHETSIZE/8)
if _, err := io.ReadFull(rand.Reader, newRatchet); err != nil {
log.Printf("[DEBUG-1] Failed to generate new ratchet: %v", err)
return nil, err
}
// Get public key for ratchet ID
ratchetPub, err := curve25519.X25519(newRatchet, curve25519.Basepoint)
if err != nil {
log.Printf("[DEBUG-1] Failed to generate ratchet public key: %v", err)
return nil, err
}
ratchetID := i.GetRatchetID(ratchetPub)
expiry := time.Now().Unix() + RATCHET_EXPIRY
// Store new ratchet
i.ratchets[string(ratchetID)] = newRatchet
i.ratchetExpiry[string(ratchetID)] = expiry
log.Printf("[DEBUG-7] New ratchet generated with ID: %x, expiry: %d", ratchetID, expiry)
// Cleanup old ratchets if we exceed max retained
if len(i.ratchets) > MAX_RETAINED_RATCHETS {
var oldestID string
oldestTime := time.Now().Unix()
for id, exp := range i.ratchetExpiry {
if exp < oldestTime {
oldestTime = exp
oldestID = id
}
}
delete(i.ratchets, oldestID)
delete(i.ratchetExpiry, oldestID)
log.Printf("[DEBUG-7] Cleaned up oldest ratchet with ID: %x", []byte(oldestID))
}
log.Printf("[DEBUG-7] Current number of active ratchets: %d", len(i.ratchets))
return newRatchet, nil
}
func (i *Identity) GetRatchets() [][]byte {
i.mutex.RLock()
defer i.mutex.RUnlock()
log.Printf("[DEBUG-7] Getting ratchets for identity %s", i.GetHexHash())
ratchets := make([][]byte, 0, len(i.ratchets))
now := time.Now().Unix()
expired := 0
// Return only non-expired ratchets
for id, expiry := range i.ratchetExpiry {
if expiry > now {
ratchets = append(ratchets, i.ratchets[id])
} else {
// Clean up expired ratchets
delete(i.ratchets, id)
delete(i.ratchetExpiry, id)
expired++
}
}
log.Printf("[DEBUG-7] Retrieved %d active ratchets, cleaned up %d expired", len(ratchets), expired)
return ratchets
}
func (i *Identity) CleanupExpiredRatchets() {
i.mutex.Lock()
defer i.mutex.Unlock()
log.Printf("[DEBUG-7] Starting ratchet cleanup for identity %s", i.GetHexHash())
now := time.Now().Unix()
cleaned := 0
for id, expiry := range i.ratchetExpiry {
if expiry <= now {
delete(i.ratchets, id)
delete(i.ratchetExpiry, id)
cleaned++
}
}
log.Printf("[DEBUG-7] Cleaned up %d expired ratchets, %d remaining", cleaned, len(i.ratchets))
}

View File

@@ -5,7 +5,7 @@ import (
"crypto/cipher"
"crypto/ed25519"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
@@ -16,6 +16,8 @@ import (
"github.com/Sudo-Ivan/reticulum-go/pkg/destination"
"github.com/Sudo-Ivan/reticulum-go/pkg/identity"
"github.com/Sudo-Ivan/reticulum-go/pkg/packet"
"github.com/Sudo-Ivan/reticulum-go/pkg/pathfinder"
"github.com/Sudo-Ivan/reticulum-go/pkg/resolver"
"github.com/Sudo-Ivan/reticulum-go/pkg/resource"
"github.com/Sudo-Ivan/reticulum-go/pkg/transport"
)
@@ -38,10 +40,6 @@ const (
STATUS_CLOSED = 0x02
STATUS_FAILED = 0x03
PACKET_TYPE_DATA = 0x00
PACKET_TYPE_LINK = 0x01
PACKET_TYPE_IDENTIFY = 0x02
PROVE_NONE = 0x00
PROVE_ALL = 0x01
PROVE_APP = 0x02
@@ -56,6 +54,7 @@ type Link struct {
lastOutbound time.Time
lastDataReceived time.Time
lastDataSent time.Time
pathFinder *pathfinder.PathFinder
remoteIdentity *identity.Identity
sessionKey []byte
@@ -97,6 +96,7 @@ func NewLink(dest *destination.Destination, transport *transport.Transport, esta
lastOutbound: time.Time{},
lastDataReceived: time.Time{},
lastDataSent: time.Time{},
pathFinder: pathfinder.NewPathFinder(),
}
}
@@ -117,16 +117,21 @@ func (l *Link) Establish() error {
log.Printf("[DEBUG-4] Creating link request packet for destination %x", destPublicKey[:8])
// Create link request packet
p, err := packet.NewPacket(
packet.PACKET_TYPE_LINK,
0x00,
0x00,
destPublicKey,
l.linkID,
)
if err != nil {
log.Printf("[DEBUG-3] Failed to create link request packet: %v", err)
p := &packet.Packet{
HeaderType: packet.HeaderType1,
PacketType: packet.PacketTypeLinkReq,
TransportType: 0,
Context: packet.ContextLinkIdentify,
ContextFlag: packet.FlagUnset,
Hops: 0,
DestinationType: l.destination.GetType(),
DestinationHash: l.destination.GetHash(),
Data: l.linkID,
CreateReceipt: true,
}
if err := p.Pack(); err != nil {
log.Printf("[DEBUG-3] Failed to pack link request packet: %v", err)
return err
}
@@ -139,15 +144,20 @@ func (l *Link) Identify(id *identity.Identity) error {
return errors.New("link not active")
}
// Create identify packet
p, err := packet.NewPacket(
packet.PACKET_TYPE_IDENTIFY,
0x00,
0x00,
l.destination.GetPublicKey(),
id.GetPublicKey(),
)
if err != nil {
p := &packet.Packet{
HeaderType: packet.HeaderType1,
PacketType: packet.PacketTypeData,
TransportType: 0,
Context: packet.ContextLinkIdentify,
ContextFlag: packet.FlagUnset,
Hops: 0,
DestinationType: l.destination.GetType(),
DestinationHash: l.destination.GetHash(),
Data: id.GetPublicKey(),
CreateReceipt: true,
}
if err := p.Pack(); err != nil {
return err
}
@@ -466,11 +476,28 @@ func (l *Link) SendPacket(data []byte) error {
return err
}
p := &packet.Packet{
HeaderType: packet.HeaderType1,
PacketType: packet.PacketTypeData,
TransportType: 0,
Context: packet.ContextNone,
ContextFlag: packet.FlagUnset,
Hops: 0,
DestinationType: l.destination.GetType(),
DestinationHash: l.destination.GetHash(),
Data: encrypted,
CreateReceipt: false,
}
if err := p.Pack(); err != nil {
return err
}
log.Printf("[DEBUG-4] Sending encrypted packet of %d bytes", len(encrypted))
l.lastOutbound = time.Now()
l.lastDataSent = time.Now()
return nil
return l.transport.SendPacket(p)
}
func (l *Link) HandleInbound(data []byte) error {
@@ -482,55 +509,24 @@ func (l *Link) HandleInbound(data []byte) error {
return errors.New("link not active")
}
log.Printf("[DEBUG-7] Received encrypted packet of %d bytes", len(data))
// Decode and log packet details
l.decodePacket(data)
// Decrypt data using session key
decryptedData, err := l.decrypt(data)
if err != nil {
log.Printf("[DEBUG-3] Failed to decrypt packet: %v", err)
return err
}
// Split message and HMAC
if len(decryptedData) < sha256.Size {
log.Printf("[DEBUG-3] Received data too short: %d bytes", len(decryptedData))
return errors.New("received data too short")
}
message := decryptedData[:len(decryptedData)-sha256.Size]
messageHMAC := decryptedData[len(decryptedData)-sha256.Size:]
// Log packet details
log.Printf("[DEBUG-7] Decrypted packet details:")
log.Printf("[DEBUG-7] - Size: %d bytes", len(message))
log.Printf("[DEBUG-7] - First 16 bytes: %x", message[:min(16, len(message))])
if len(message) > 0 {
log.Printf("[DEBUG-7] - Type: 0x%02x", message[0])
switch message[0] {
case packet.PacketData:
log.Printf("[DEBUG-7] - Type: Data Packet")
case packet.PacketAnnounce:
log.Printf("[DEBUG-7] - Type: Announce Packet")
case packet.PacketLinkRequest:
log.Printf("[DEBUG-7] - Type: Link Request")
case packet.PacketProof:
log.Printf("[DEBUG-7] - Type: Proof Request")
default:
log.Printf("[DEBUG-7] - Type: Unknown (0x%02x)", message[0])
// Decrypt if we have a session key
if l.sessionKey != nil {
decrypted, err := l.decrypt(data)
if err != nil {
log.Printf("[DEBUG-3] Failed to decrypt packet: %v", err)
return err
}
}
// Verify HMAC
if !l.destination.GetIdentity().ValidateHMAC(l.hmacKey, message, messageHMAC) {
log.Printf("[DEBUG-3] Invalid HMAC for packet")
return errors.New("invalid message authentication code")
data = decrypted
}
l.lastInbound = time.Now()
l.lastDataReceived = time.Now()
if l.packetCallback != nil {
l.packetCallback(message, nil)
l.packetCallback(data, nil)
}
return nil
@@ -737,6 +733,69 @@ func (l *Link) HandleProofRequest(packet *packet.Packet) bool {
}
}
func (l *Link) decodePacket(data []byte) {
if len(data) < 1 {
log.Printf("[DEBUG-7] Invalid packet: zero length")
return
}
packetType := data[0]
log.Printf("[DEBUG-7] Packet Analysis:")
log.Printf("[DEBUG-7] - Size: %d bytes", len(data))
log.Printf("[DEBUG-7] - Type: 0x%02x", packetType)
switch packetType {
case packet.PacketTypeData:
log.Printf("[DEBUG-7] - Type Description: Data Packet")
if len(data) > 1 {
log.Printf("[DEBUG-7] - Payload Size: %d bytes", len(data)-1)
}
case packet.PacketTypeLinkReq:
log.Printf("[DEBUG-7] - Type Description: Link Management")
if len(data) > 32 {
log.Printf("[DEBUG-7] - Link ID: %x", data[1:33])
}
case packet.PacketTypeAnnounce:
log.Printf("[DEBUG-7] - Type Description: RNS Announce")
if len(data) > 33 {
destHash := data[1:17]
pubKey := data[17:49]
log.Printf("[DEBUG-7] - Destination Hash: %x", destHash)
log.Printf("[DEBUG-7] - Public Key: %x", pubKey)
if len(data) > 81 {
signature := data[49:81]
appData := data[81:]
if identity.ValidateAnnounce(data, destHash, pubKey, signature, appData) {
log.Printf("[DEBUG-7] - Announce signature valid")
if path, ok := l.pathFinder.GetPath(hex.EncodeToString(destHash)); ok {
log.Printf("[DEBUG-7] - Updated path: Interface=%s, Hops=%d",
path.Interface, path.HopCount)
}
}
}
}
case packet.PacketTypeProof:
log.Printf("[DEBUG-7] - Type Description: RNS Discovery")
if len(data) > 17 {
searchHash := data[1:17]
log.Printf("[DEBUG-7] - Searching for Hash: %x", searchHash)
if id, err := resolver.ResolveIdentity(hex.EncodeToString(searchHash)); err == nil {
log.Printf("[DEBUG-7] - Found matching identity: %s", id.GetHexHash())
}
}
default:
log.Printf("[DEBUG-7] - Type Description: Unknown (0x%02x)", packetType)
log.Printf("[DEBUG-7] - Raw Hex: %x", data)
}
}
// Helper function for min of two ints
func min(a, b int) int {
if a < b {

View File

@@ -5,10 +5,6 @@ const (
EncryptedMDU = 383 // Maximum size of payload data in encrypted packet
PlainMDU = 464 // Maximum size of payload data in unencrypted packet
// Header Types
HeaderType1 = 0 // Two byte header, one 16 byte address field
HeaderType2 = 1 // Two byte header, two 16 byte address fields
// Propagation Types
PropagationBroadcast = 0
PropagationTransport = 1

View File

@@ -1,116 +1,208 @@
package packet
import (
"crypto/sha256"
"errors"
"fmt"
"time"
)
const (
// Packet Types
PacketTypeData = 0x00
PacketTypeAnnounce = 0x01
PacketTypeLink = 0x02
PacketTypeProof = 0x03
PACKET_TYPE_DATA = 0x00
PACKET_TYPE_LINK = 0x01
PACKET_TYPE_IDENTIFY = 0x02
PacketTypeData = 0x00
PacketTypeAnnounce = 0x01
PacketTypeLinkReq = 0x02
PacketTypeProof = 0x03
// Sizes
HeaderSize = 2
AddressSize = 16
ContextSize = 1
MaxDataSize = 465
RandomBlobSize = 16
// Header Types
HeaderType1 = 0x00
HeaderType2 = 0x01
// Context Types
ContextNone = 0x00
ContextResource = 0x01
ContextResourceAdv = 0x02
ContextResourceReq = 0x03
ContextResourceHMU = 0x04
ContextResourcePRF = 0x05
ContextResourceICL = 0x06
ContextResourceRCL = 0x07
ContextCacheReq = 0x08
ContextRequest = 0x09
ContextResponse = 0x0A
ContextPathResponse = 0x0B
ContextCommand = 0x0C
ContextCmdStatus = 0x0D
ContextChannel = 0x0E
ContextKeepalive = 0xFA
ContextLinkIdentify = 0xFB
ContextLinkClose = 0xFC
ContextLinkProof = 0xFD
ContextLRRTT = 0xFE
ContextLRProof = 0xFF
// Flag Values
FlagSet = 0x01
FlagUnset = 0x00
// Header sizes
HeaderMaxSize = 64
MTU = 500
AddressSize = 32 // Size of address/hash fields in bytes
)
// Header flags and types
const (
// First byte flags
IFACFlag = 0x80
HeaderTypeFlag = 0x40
ContextFlag = 0x20
PropagationFlags = 0x18
DestinationFlags = 0x06
PacketTypeFlags = 0x01
// Second byte
HopsField = 0xFF
)
// Packet represents a network packet in the Reticulum protocol
type Packet struct {
Header [2]byte
Addresses []byte
Context byte
Data []byte
AccessCode []byte
RandomBlob []byte
Timestamp time.Time
HeaderType byte
PacketType byte
TransportType byte
Context byte
ContextFlag byte
Hops byte
DestinationType byte
DestinationHash []byte
TransportID []byte
Data []byte
Raw []byte
Packed bool
Sent bool
CreateReceipt bool
FromPacked bool
SentAt time.Time
PacketHash []byte
RatchetID []byte
RSSI *float64
SNR *float64
Q *float64
Addresses []byte // Add this field for address storage
}
// NewPacket creates a new packet with the specified parameters
func NewPacket(packetType byte, flags byte, hops byte, destKey []byte, data []byte) (*Packet, error) {
if len(destKey) != AddressSize {
return nil, errors.New("invalid destination key length")
func NewPacket(destType byte, data []byte, packetType byte, context byte,
transportType byte, headerType byte, transportID []byte, createReceipt bool,
contextFlag byte) *Packet {
return &Packet{
HeaderType: headerType,
PacketType: packetType,
TransportType: transportType,
Context: context,
ContextFlag: contextFlag,
Hops: 0,
DestinationType: destType,
Data: data,
TransportID: transportID,
CreateReceipt: createReceipt,
Packed: false,
Sent: false,
FromPacked: false,
}
p := &Packet{
Header: [2]byte{flags, hops},
Addresses: make([]byte, AddressSize),
Data: data,
Timestamp: time.Now(),
}
// Set packet type in flags
p.Header[0] |= packetType & PacketTypeFlags
// Copy destination address
copy(p.Addresses, destKey)
return p, nil
}
// Serialize converts the packet into a byte slice
func (p *Packet) Pack() error {
if p.Packed {
return nil
}
flags := (p.HeaderType << 6) | (p.ContextFlag << 5) |
(p.TransportType << 4) | (p.DestinationType << 2) | p.PacketType
header := make([]byte, 0)
header = append(header, flags)
header = append(header, p.Hops)
if p.HeaderType == HeaderType2 && p.TransportID != nil {
header = append(header, p.TransportID...)
header = append(header, p.DestinationHash...)
} else if p.HeaderType == HeaderType1 {
header = append(header, p.DestinationHash...)
} else {
return errors.New("invalid header configuration")
}
header = append(header, p.Context)
p.Raw = append(header, p.Data...)
if len(p.Raw) > MTU {
return errors.New("packet size exceeds MTU")
}
p.Packed = true
p.updateHash()
return nil
}
func (p *Packet) Unpack() error {
if len(p.Raw) < 3 {
return errors.New("packet too short")
}
flags := p.Raw[0]
p.Hops = p.Raw[1]
p.HeaderType = (flags & 0b01000000) >> 6
p.ContextFlag = (flags & 0b00100000) >> 5
p.TransportType = (flags & 0b00010000) >> 4
p.DestinationType = (flags & 0b00001100) >> 2
p.PacketType = flags & 0b00000011
dstLen := 16 // Truncated hash length
if p.HeaderType == HeaderType2 {
if len(p.Raw) < 2*dstLen+3 {
return errors.New("packet too short for header type 2")
}
p.TransportID = p.Raw[2 : dstLen+2]
p.DestinationHash = p.Raw[dstLen+2 : 2*dstLen+2]
p.Context = p.Raw[2*dstLen+2]
p.Data = p.Raw[2*dstLen+3:]
} else {
if len(p.Raw) < dstLen+3 {
return errors.New("packet too short for header type 1")
}
p.TransportID = nil
p.DestinationHash = p.Raw[2 : dstLen+2]
p.Context = p.Raw[dstLen+2]
p.Data = p.Raw[dstLen+3:]
}
p.Packed = false
p.updateHash()
return nil
}
func (p *Packet) GetHash() []byte {
hashable := p.getHashablePart()
hash := sha256.Sum256(hashable)
return hash[:]
}
func (p *Packet) getHashablePart() []byte {
hashable := []byte{p.Raw[0] & 0b00001111}
if p.HeaderType == HeaderType2 {
hashable = append(hashable, p.Raw[18:]...)
} else {
hashable = append(hashable, p.Raw[2:]...)
}
return hashable
}
func (p *Packet) updateHash() {
p.PacketHash = p.GetHash()
}
func (p *Packet) Serialize() ([]byte, error) {
totalSize := HeaderSize + len(p.Addresses) + ContextSize + len(p.Data)
if p.AccessCode != nil {
totalSize += len(p.AccessCode)
if !p.Packed {
if err := p.Pack(); err != nil {
return nil, fmt.Errorf("failed to pack packet: %w", err)
}
}
buffer := make([]byte, totalSize)
offset := 0
p.Addresses = p.DestinationHash
// Write header
copy(buffer[offset:], p.Header[:])
offset += HeaderSize
// Write access code if present
if p.AccessCode != nil {
copy(buffer[offset:], p.AccessCode)
offset += len(p.AccessCode)
}
// Write addresses
copy(buffer[offset:], p.Addresses)
offset += len(p.Addresses)
// Write context
buffer[offset] = p.Context
offset += ContextSize
// Write data
copy(buffer[offset:], p.Data)
return buffer, nil
}
type AnnouncePacket struct {
Header [2]byte
DestHash []byte
PublicKey []byte
AppData []byte
RandomBlob []byte
Signature []byte
HopCount byte
Timestamp time.Time
return p.Raw, nil
}