Skip to content

Message Signing

How Ed25519 signing and canonical JSON ensure message integrity.

Overview

Starweft applies Ed25519 digital signatures to every message on the P2P network. Because there is no central server, message authenticity and integrity are guaranteed solely through cryptographic signatures.

The library used for signing is ed25519-dalek. Both the secret key and public key are 32 bytes in length, and signatures are 64 bytes.

Signing Flow

Messages are signed through the following steps.

2

The sending node creates an UnsignedEnvelope<T>. The envelope contains metadata such as the protocol version, message ID, source/destination actor IDs, and Lamport timestamp, along with a typed message body (body).

3
rust
let envelope = UnsignedEnvelope::new(actor_id, Some(to_actor_id), body)
    .with_vision_id(vision_id)
    .with_project_id(project_id)
    .with_lamport_ts(lamport_ts);
5

The signable fields (SignableEnvelope) are serialized to Canonical JSON. The signature field is excluded from the signing target.

6
rust
let signable_bytes = canonical_json(&SignableEnvelope {
    protocol, msg_id, msg_type, from_actor_id, to_actor_id,
    vision_id, project_id, task_id, lamport_ts, created_at,
    expires_at, body,
})?;
8

A signature is generated over the Canonical JSON byte sequence using the Ed25519 secret key. The resulting signature is Base64-encoded and stored in a MessageSignature struct.

9
rust
let signature = keypair.sign_bytes(&signable_bytes)?;
// MessageSignature { alg: "ed25519", key_id: "...", sig: "<base64>" }
11

The signed Envelope<T> is now complete and ready to be transmitted over the network.

12
rust
let signed_envelope = unsigned_envelope.sign(&keypair)?;

Signature Verification

The receiving side verifies the signature using Envelope::verify_with_key().

rust
envelope.verify_with_key(&verifying_key)?;

The verification process works as follows.

  1. Confirm that signature.alg is "ed25519"
  2. Confirm that msg_type matches body.msg_type()
  3. Reconstruct a SignableEnvelope from the signable fields and serialize it to Canonical JSON
  4. Verify the Ed25519 signature using the sender's public key

Wire Envelope

During network transmission, the WireEnvelope format is used. It differs from Envelope<T> in that the body field is held as a serde_json::Value (untyped JSON) rather than a typed struct.

WireEnvelope also implements verify_with_key(), enabling signature verification even when body remains a Value type. This means relay nodes can verify signatures without deserializing the message body.

Canonical JSON

Serialization Rules

Canonical JSON deterministically produces a byte sequence according to the following rules.

  • Object keys are sorted alphabetically (applied recursively to all nested levels)
  • Array element order is preserved
  • Scalar values (numbers, strings, booleans, null) use standard JSON representation
  • No extraneous whitespace is included
json
// Input (key order undefined)
{"z": 1, "a": {"c": 3, "b": 2}, "m": [{"y": 4, "x": 5}]}
 
// Canonical JSON output (keys sorted)
{"a":{"b":2,"c":3},"m":[{"x":5,"y":4}],"z":1}

Why Canonical JSON Is Needed

Ed25519 signs over byte sequences. Even if two JSON documents are logically identical, differences in key ordering or formatting will produce different byte sequences, causing signature verification to fail.

Canonical JSON addresses the following problems.

  • Field insertion order differences -- Key ordering can vary due to serde_json's preserve_order feature or platform-specific behavior
  • Cross-platform determinism -- Produces identical byte sequences across nodes running on different operating systems and architectures
  • Cross-language compatibility -- In the future, even if non-Rust clients participate, they can verify signatures as long as they follow the same Canonical JSON rules

mDNS and the Security Model

In Starweft v0.3.0, automatic peer discovery on the local network via mDNS is supported when using the libp2p transport. It can be enabled with discovery.mdns = true in the configuration file.

mDNS Is Not Authenticated

The mDNS protocol itself has no authentication mechanism. Any node on the same LAN may be discovered as a peer.

Ed25519 Signatures Protect the Message Layer

Even though mDNS is unauthenticated, Starweft's security is not compromised. All protocol messages are signed with Ed25519, maintaining the following guarantees.

  • Tamper detection -- Signature verification fails if message content has been altered
  • Impersonation prevention -- Nodes without the secret key cannot generate valid signatures
  • Peer identification via public keys -- Peer identity can be verified through public keys registered via peer add or exchanged through CapabilityQuery / CapabilityAdvertisement
Key Management

Ed25519 key generation, storage, and exchange methods

Audit

Signature verification and integrity checks for event logs