SolarOS

SolarOS 4.17.7 manual · api

Python SC2 end-to-end chat security API

API overview · Lua SC2 · Commands

solaros.sc2 exposes the device's SC2 (SolarOS Chat v2, suite SC2-1) session layer: X3DH key agreement, the double ratchet for direct messages, epoch keys with per-sender chains for private rooms, and signed posts for public rooms. The module exists in builds with the sc2 group (enabled in the full flavor on boards with PSRAM).

Keys never enter the Python heap. Scripts pass plaintext and relay frames in and get frames, plaintext and public metadata out. Identity keys, prekeys, ratchet states and room epoch keys stay in the C service. Messages that carry keys (t:"epoch_key", t:"history") are consumed inside C and only reported as events; scripts cannot send them (dm_encrypt and room_encrypt refuse epoch_key, history and removed inner types).

Plaintext is the inner JSON of the protocol, an object with a t field, for example {"t":"text","id":"…","body":"hello","ts":1760889600}. Build it with json.dumps and read it back with json.loads. Inner JSON is limited to 8191 bytes; it is padded to 256…8192-byte buckets before sealing.

Frames are complete relay lines (dm.send, room.send, room.post, keys.publish) ready to write to a relay connection, and the decrypt and verify calls take the relay's dm.msg, room.msg and room.pub lines. This module does not open network connections.

Identity and peers

Direct messages

Private rooms

Private rooms use one random epoch key per epoch, distributed by the admin over pairwise DMs, and one sending chain per member. Every room message is also Ed25519-signed by its sender.

This API does not check membership commits (room.commit); the sc2-sync worker verifies them for the rooms it manages. A device therefore accepts epoch keys for a room it does not know from whoever sends them first, and afterwards only newer epochs from that admin.

Public rooms

Public rooms (pub: names) are signed, not encrypted.

Selftest

solaros.sc2.selftest() returns {"passed", "failed"} after the same checks as the sc2 selftest shell command. It uses throwaway in-memory devices and never touches stored keys.

Example

import json, solaros
sc2 = solaros.sc2
me = sc2.identity()["device_id"]
publish = sc2.keys_publish()          # send to the relay
peer = sc2.add_bundle(bundle_line)    # bundle_line from keys.claim
frame = sc2.dm_encrypt(peer, json.dumps({"t": "text", "body": "hi"}))
msg = sc2.dm_decrypt(incoming_line)
if msg["kind"] == "message":
    print(msg["from"], json.loads(msg["body"])["body"])
print(sc2.safety_number(peer)["words"])

Keys at rest

Keys live on internal flash in /.sc2/ (/flash/.sc2/ on boards with an SD slot), never on removable cards. Each record is versioned, CRC-checked and kept in two alternating copies, so an interrupted write falls back to the previous copy. Flash encryption is off by default: anyone who holds the device can read its flash, copy the identity and session keys, impersonate it and read messages that arrive afterwards until contacts block it. Deleted records are overwritten before removal, but the flash wear-levelling layer can keep older sector copies until they are erased.

The hardware RNG is only a true RNG while Wi-Fi or Bluetooth is running. SC2 mixes it with a seed carried across boots, and the identity is created on first use rather than at boot; create it with Wi-Fi connected.

Quick reference

Use solaros.sc2 for SC2 end-to-end encrypted DMs, private rooms and signed public posts. Keys stay in C: pass inner JSON and relay lines, get frames and plaintext back. Call keys_publish() once and add_bundle() for each contact, then dm_encrypt/dm_decrypt, room_* and post_sign/ post_verify. Errors raise OSError("sc2: …"). See man python for runtime conventions.