sigil (0.4.0-rc.0)

Published 2026-07-14 22:35:33 +00:00 by faicel

Installation

[registries.forgejo]
index = "sparse+" # Sparse index
# index = "" # Git

[net]
git-fetch-with-cli = true
cargo add sigil@0.4.0-rc.0 --registry forgejo

About this package

sigil

no_std Rust library for secure device communication over LoRa: messaging, encryption, install/renew handshakes, key derivation, and events.


What is a Sigil?

Sigil (from Latin sigillum, “seal”) traditionally denotes a symbol or mark that represents an identity, intent, or authority—like a seal on a document. In this project, sigil is the “seal” of your device stack: the layer that defines how devices identify, authenticate, and exchange encrypted messages over LoRa.


Features

  • com — Communication over LoRa: read/write of encrypted messages, header validation, key callbacks.
  • models — Shared message and protocol models (MessageSchema, events, handshake types).

Handshake flows

Install (first pairing)

Clear-text 3-message exchange with ephemeral-to-ephemeral ECDH + ECDSA for identity proof:

HandshakeRequest → HandshakeChallenge {gw_eph_pubkey, …} → HandshakeConfirm {sig_gateway_nonce, sensor_eph_pubkey}
→ session_key = HKDF(ECDH(eph↔eph), sensor_nonce ‖ gateway_nonce)

Sensor: install_session() — callback ecdh_ephemeral_with_peer(gw_eph_pubkey) → (sensor_eph_pubkey, shared_secret)
Gateway: handle_install_request() — callbacks generate_gateway_ephemeral() → gw_eph_pubkey and ecdh_with_sensor_ephemeral(sensor_eph_pubkey) → shared_secret (plus ECDSA verify/sign callbacks)

Renew (session key rotation)

Encrypted 3-message exchange on the existing session keyephemeral-to-ephemeral ECDH (no ECDSA, no ECIES):

KeyRenewRequest → KeyRenewChallenge {gw_eph_pubkey} → KeyRenewAck {sensor_eph_pubkey}
→ session_key_new = HKDF(ECDH(eph↔eph), sensor_nonce ‖ gateway_nonce)

Sensor: com::handshake::renew::sensor::renew_session() — callback ecdh_ephemeral_with_peer(gw_eph_pubkey) → (sensor_eph_pubkey, shared_secret)
Gateway: com::handshake::renew::gateway::handle_renew_request() — callbacks generate_gateway_ephemeral() → gw_eph_pubkey and ecdh_with_sensor_ephemeral(sensor_eph_pubkey) → shared_secret

Trigger renew when ValidatedKey::should_renew() returns true (~75% of key lifetime elapsed).

Session resume (re-pair session without full install)

Clear-text 3-message exchange for already-enrolled sensors when the session key is lost but long-term identity is still trusted (no AllGateways broadcast, no re-enrollment):

SessionResumeRequest {sig_sensor_nonce} → SessionResumeChallenge {gw_eph_pubkey, …} → SessionResumeConfirm {sig_gateway_nonce, sensor_eph_pubkey}
→ session_key = HKDF(ECDH(eph↔eph), sensor_nonce ‖ gateway_nonce)

Sensor: com::handshake::resume::sensor::resume_session() — sign sensor_nonce with long-term key + ecdh_ephemeral_with_peer
Gateway: com::handshake::resume::gateway::handle_resume_request() — lookup enrolled sensor_pubkey, verify resume signature, then same eph-eph callbacks as install

Use when: paired sensor lost session key in flash but gateway still has enrollment. Prefer renew when the old session key is still available.

See diagramme/install.mermaid, diagramme/renew.mermaid, and diagramme/session_resume.mermaid.


Usage

Add to your Cargo.toml (adjust registry and version as needed):

[dependencies]
sigil = { version = "0.3", registry = "forgejo", default-features = false, features = ["com"] }

Read and decrypt a message with Sigil::read:

use sigil::Sigil;

let msg = Sigil::read(
    &mut lora,
    &mut delay,
    |header| { /* validate header */ true },
    |msg_type, source| { /* return Some(session_key) or None */ None },
    timestamp,
)?;

Renew example (sensor, already paired):

use sigil::com::handshake::renew::sensor::renew_session;

let session = renew_session(
    &mut lora,
    &mut delay,
    sensor_mac,
    battery_level,
    gateway_mac,
    &current_session_key,
    &chacha_nonce,
    new_sensor_nonce,
    |gw_eph_pubkey| {
        /* ATECC608: generate sensor ephemeral keypair + ECDH with gw_eph_pubkey */
        Ok((sensor_eph_pubkey, shared_secret))
    },
    timestamp,
)?;
// session.keys.key is the new session key

Session resume example (sensor, enrolled but session key lost):

use sigil::com::handshake::resume::sensor::resume_session;

let session = resume_session(
    &mut lora,
    &mut delay,
    sensor_mac,
    battery_level,
    gateway_mac,
    new_sensor_nonce,
    sig_sensor_nonce, // ECDSA(sensor_nonce) with long-term ATECC key
    |gw_eph_pubkey| {
        Ok((sensor_eph_pubkey, shared_secret))
    },
    |gateway_pubkey, sig| { /* verify gateway sig on sensor_nonce */ true },
    |gateway_nonce| { /* sign gateway_nonce */ sig_gateway_nonce },
    timestamp,
)?;

Project structure

sigil/
├── diagramme/           # Mermaid sequence diagrams (install, renew, session_resume)
├── src/
│   ├── com/
│   │   ├── handshake/
│   │   │   ├── primitives/     # Shared crypto helpers (HKDF inputs, payloads)
│   │   │   ├── lora_helpers/   # send_encrypted, try_receive_encrypted, …
│   │   │   ├── install_core/   # Ephemeral-to-ephemeral ECDH + ECDSA (install)
│   │   │   ├── renew_core/     # Lightweight renew (ephemeral-to-ephemeral ECDH)
│   │   │   ├── resume_core/    # Session resume (ECDSA + eph-eph, no old session key)
│   │   │   ├── install/        # install_session, handle_install_request
│   │   │   ├── renew/          # renew_session, handle_renew_request
│   │   │   └── resume/         # resume_session, handle_resume_request
│   │   ├── keys/               # KeyManager, ValidatedKey
│   │   ├── event/
│   │   └── mod.rs              # Sigil::read()
│   ├── models/common/
│   └── crypto/                 # ChaCha20-Poly1305
├── scripts/test.sh
└── Cargo.toml

Status

Active development. Install API is stable; renew API was simplified in the unreleased refactor (see CHANGELOG).


License

Personal Use Only — Non-Commercial. See LICENSE for full terms and disclaimers.

Dependencies

ID Version
bincode ^2.0.0
chacha20poly1305 ^0.10.1
embedded-hal ^1.0.0
heapless ^0.9.2
hkdf ^0.12
log ^0.4
lora ^0.3.0-rc.0
sha2 ^0.10
embedded-hal-mock ^0.11.1
lora ^0.3.0-rc.0
Details
Cargo
2026-07-14 22:35:33 +00:00
11
53 KiB
Assets (1)
Versions (6) View all
0.4.0-rc.0 2026-07-14
0.3.0-rc.1 2026-07-13
0.2.2-rc.0 2026-07-02
0.2.1-rc.7 2026-06-29
0.2.1-rc.5 2026-03-17