Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Modellen Tools & Services
Oplossingen
Organisaties Ontwikkelaars Internet Service Providers Managed Service Providers AI-in-a-Box
Kenniscentrum
Ondersteuning Documentation Blog Downloads
Bedrijf
Over ons Onderzoek Vacatures Investeren Contact
Inloggen

Quickstart

In five minutes you'll enrol two devices into a ScaiClip personal channel, publish an encrypted clip from one, and see it arrive on the other. Everything ciphered client-side; the grid holds only opaque bytes.

You need:

  • A ScaiGrid endpoint (managed or self-hosted) with the ScaiClip module enabled.
  • A user sgk_ key with scopes scaiclip:read and scaiclip:write, or a user JWT.
  • Python 3.10+ with pip install cbor2 pynacl httpx websockets.
bash
1
2
export SCAIGRID_HOST="https://scaigrid.scailabs.ai"
export SCAIGRID_API_KEY="sgk_..."

1. Register two devices#

Each client generates its own X25519 KEM keypair and Ed25519 signing keypair. Private keys never leave the device.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from nacl.public import PrivateKey
from nacl.signing import SigningKey
import base64, httpx

def gen_device():
    kem = PrivateKey.generate()
    sig = SigningKey.generate()
    return {
        "kem_priv": bytes(kem),
        "kem_pub":  bytes(kem.public_key),
        "sig_priv": bytes(sig),
        "sig_pub":  bytes(sig.verify_key),
    }

def b64u(b): return base64.urlsafe_b64encode(b).rstrip(b"=").decode()

alice_a, alice_b = gen_device(), gen_device()

import os
HOST = os.environ["SCAIGRID_HOST"]
KEY  = os.environ["SCAIGRID_API_KEY"]

def enrol(dev, name):
    r = httpx.post(f"{HOST}/v1/modules/scaiclip/devices",
        headers={"Authorization": f"Bearer {KEY}"},
        json={
            "kem_pub": b64u(dev["kem_pub"]),
            "sig_pub": b64u(dev["sig_pub"]),
            "kind": "desktop",
            "platform": "linux",
            "name": name,
        })
    r.raise_for_status()
    return r.json()["data"]["device_id"]

alice_a["id"] = enrol(alice_a, "Alice Laptop")
alice_b["id"] = enrol(alice_b, "Alice Phone")
print(f"laptop: {alice_a['id']}\nphone:  {alice_b['id']}")

2. Connect both devices to the personal channel#

Each device opens its own WebSocket on /v1/modules/scaiclip/ws. The personal channel is auto-created on first connect.

python
1
2
3
4
5
6
7
8
9
import asyncio, websockets

async def connect(dev):
    ws = await websockets.connect(
        f"{HOST.replace('https', 'wss')}/v1/modules/scaiclip/ws"
        f"?token={KEY}&device_id={dev['id']}",
        subprotocols=["scaiclip/1"],
    )
    return ws

Every client-originated frame carries a 16-byte header (ver | type | flags | request_id | body_length | reserved) followed by a canonical-CBOR body. See the SCWP reference for the wire format.

3. Publish a clip from the phone#

The phone encrypts a UTF-8 text clip with a fresh XChaCha20-Poly1305 content key, wraps that key for every enrolled device via HPKE, signs the envelope, and sends clip.publish.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from nacl.secret import SecretBox
import secrets, cbor2, struct

TYPE_CLIP_PUBLISH = 0x10

def frame(type_, req_id, body):
    return struct.pack("<BBHII", 0x01, type_, 0, req_id, len(body)) + b"\0"*4 + body

async def publish_text(ws, dev, text):
    content_key = secrets.token_bytes(32)
    nonce = secrets.token_bytes(24)
    ciphertext = SecretBox(content_key).encrypt(text.encode(), nonce)
    envelope = {
        1: uuid7_bytes(),       # clip_id
        2: personal_channel_id, # channel_id
        4: "usr_alice",         # sender subject
        5: bytes.fromhex(dev["id"]) if len(dev["id"]) == 32 else dev["id"],
        6: int(time.time()*1000),
        8: 1,                   # type_hint: text
        9: 0,                   # size_class: small
        10: 0,                  # cflags: none
        11: 60*60,              # ttl_s: 1 hour
    }
    envelope["sig"] = sign(envelope, dev["sig_priv"])
    body = cbor2.dumps({1: envelope, 2: ciphertext}, canonical=True)
    await ws.send(frame(TYPE_CLIP_PUBLISH, 1, body))
    resp = await ws.recv()
    print("published:", resp[16:])

Some helpers (uuid7_bytes, sign, personal_channel_id resolution via channel.list) are omitted for brevity — see the two-device tutorial for the complete runnable script.

4. Receive on the laptop#

The laptop's socket sees clip.new (0x90) with the same envelope. It verifies the signature against the phone's sig_pub (fetched via channel.devices), unwraps its own copy of the content key with its KEM private key, decrypts, hits the OS clipboard.

Total round-trip: under 250 ms warm.

What just happened#

  • The grid never saw plaintext. Ciphertext + wraps only.
  • The phone signed the envelope. The laptop verified before decrypting — spoofed envelopes are rejected.
  • Both wraps were pre-installed at enrolment. New devices trigger the onboarding flow with wraps.ready push events.

What's next#

Updated 2026-07-12 00:26:08 View source (.md) rev 6