Skip to main content

cloudiamond-scratch

Encrypted Scratch ⇄ Python communication that stays secure even though every Scratch project's code is public. Key exchange X25519, encryption ChaCha20-Poly1305 — the Scratch side is generated as an .sb3 and has been run on the real Scratch VM (scratch-vm) against the real Python server.

Scratch ⇄ Python サーバー間の通信を、コードが全員に見える前提で守るためのパッケージです。

cloudiamond-scratch は PyPI 上の別プロジェクト cloudiamond とは無関係です(import 名は衝突しないよう cloudiamond_scratch にしています)。

発想

Scratch のコードは誰でも読めるので、「アルゴリズムや鍵を隠す」設計は成立しません。そこで 秘密をコードに一切置かない ようにしています。

  • サーバーの公開鍵だけをプロジェクトに埋め込む(公開情報)。
  • クライアントは接続のたびに使い捨ての秘密鍵を作る。その鍵は誰にも送られない。
  • プロジェクトのコード・公開鍵・通信の全記録を知っていても、鍵は計算できない(X25519 の離散対数問題)。

暗号は標準のもの(X25519 / ChaCha20-Poly1305 RFC 8439 / HChaCha20)だけで、独自の暗号は作っていません。Scratch にはビット演算も 64bit 整数も無いので、+ - * floor mod とリストだけで書き直してあります。

インストールと最短手順

pip install cloudiamond-scratch
cloudiamond-scratch selftest                       # 自己診断(数秒)
cloudiamond-scratch keygen server.key              # サーバーの長期鍵。秘密鍵は公開しない
cloudiamond-scratch build --key server.key -o client.sb3

client.sb3 を Scratch / TurboWarp で開くと、🔒 connect / 🔒 send text / 🔒 wait for message などのブロックが入っています(緑の旗でデモが動きます)。埋め込まれるのは公開鍵だけです(--pub <64桁の16進> でも生成できます)。

サーバー側(通信手段の部分は使う人が用意します):

from cloudiamond_scratch import CloudBridge, SecureServer, load_or_create_static_key

server = SecureServer(load_or_create_static_key("server.key"),
                      on_message=lambda sid, data: b"echo:" + data)   # None を返せば無返信
bridge = CloudBridge(server, write=my_cloud_write)   # write(name, value): クラウド変数を書く関数

# クラウド変数が更新されるたびに(名前と値を)渡す。slot を全部書いた後で HDR が書かれる順序を保つこと
bridge.on_variable(name, value)
bridge.push(sid, b"server-initiated message")        # サーバー起点の送信(任意)

CloudBridge は WebSocket も scratchattach も知りません。接続層(Scratch / TurboWarp の クラウド変数サーバーへの接続)は含まれておらず、実際のサーバーに対しては未検証です。 その代わり、クラウド変数と同じ挙動(slot → HDR の順に書く)で、本物の Scratch VM とつないだ通しテストが付いています。

プロトコル

          Scratch (client)                              Python (server)
  e ← 使い捨て乱数(32 byte)                      s = 長期秘密鍵(サーバーだけが持つ)
  E = e·G                                          S = s·G  ← .sb3 に埋め込む(公開情報)

  sid | 01 | E ───────────────────────────────▶    r ← 使い捨て乱数, R = r·G
                                                   dh_es = X25519(s, E)   dh_ee = X25519(r, E)
                                                   r を破棄
  ◀──────────────  sid | 02 | R | AEAD("READY")    (AAD = sid‖02‖E‖R)
  dh_es = X25519(e, S)   dh_ee = X25519(e, R)
  AEAD を検証できたら = 相手は s を持つサーバー

  鍵: master = HChaCha20(dh_ee, HChaCha20(dh_es,"master")[:16]);  k_c2s / k_s2c = HChaCha20(master, ラベル)
  以降: sid | 03(c2s)/04(s2c) | ctr(6 byte) | ChaCha20-Poly1305( 長さ‖データ‖0 を 32 byte 単位にパディング )

(ラベル中の SCRSEC1 はプロトコルのバージョン識別子で、旧称の名残です。変更すると互換性が切れるのでそのままにしています。)

性質 どう実現しているか
盗聴されても読めない X25519 + ChaCha20-Poly1305。通信に平文も鍵も乗らない
サーバーのなりすまし防止 dh_es は s を持つ者しか計算できない。偽サーバーの返信は AEAD 検証で落ちる
改ざん検出 Poly1305 タグ。1 bit でも変わればサーバーは黙って捨てる
再送(リプレイ)防止 方向別の単調増加カウンタ
前方秘匿性 サーバー側の一時鍵 r を使用後に破棄。後で s が漏れても過去の通信は読めない
長さの秘匿 平文を 32 byte 単位にパディング
認証前に送らない 接続に失敗したら、そのために作った鍵・乱数をすべて消す。未接続のときは送信ブロックが何もしない

防げないもの(正直に)

  • クライアントの本人確認。 コードが公開なので、誰でも正規クライアントと同じものを作れます。ユーザー認証が必要なら、パスワード等を暗号化されたメッセージの中で送り、サーバー側で検証してください。
  • 妨害(DoS)。 クラウド変数は誰でも書けます。壊れた通信は捨てられる(安全側)だけで、届かなくすることは防げません。
  • メタデータ。 通信の有無・回数・タイミング、32 byte 刻みの長さ、セッション ID は見えます。
  • クライアント端末の乗っ取り(本人の端末内のメモリは守れません)。
  • 弱い乱数。 Scratch の pick random は暗号用ではありません(下記)。

乱数

接続時に約 3 秒かけて、pick random・timer の細かい桁・マウス座標・days since 2000 の小数下位桁を HChaCha20 で混ぜ込んで使い捨て秘密鍵を作ります。マウスを動かしてもらうほど安全側です(動かさなくても動作はしますが、その場合の強さはブラウザの Math.random に依存します)。

保存・共有のときの注意

Scratch は変数・リストの現在値をプロジェクトに保存します。接続した後のプロジェクトをそのまま保存・共有すると、通信用の鍵が入ったままになります。保存・公開の前に必ず 🔒 wipe secrets(または 🔒 disconnect)ブロックを実行してください。生成直後の .sb3 には秘密は入っていません(テストで確認)。サーバーの秘密鍵(server.key)は絶対に Scratch に入れない・コミットしないでください。

GitHub などで .sb3 を配布するときも同じです。生成直後のもの、または wipe してから保存したものだけを置いてください。接続したあとに保存した .sb3 は置かないでください。同じ公開鍵・同じバージョン・同じ --name(省略時は Cloudiamond)なら、cloudiamond-scratch build --pub <公開鍵> で同じバイト列を再生成できます。配布物には sha256・公開鍵・バージョンを添えておくと、受け取った人が検証できます。

プロジェクトのパレットには、内部で使う変数・リストが多数並びます(関数ごとのローカル変数を含む)。邪魔でも消さないでください。

クラウド変数の約束

  • プロジェクトのクラウド変数は 10 個ちょうど: ☁ C2S_HDR ☁ C2S_0..3(クライアント→サーバー)、☁ S2C_HDR ☁ S2C_0..3(サーバー→クライアント)。
  • 1 slot = 84 byte(数字だけの文字列。1 byte = 3 桁 + 先頭に 1、253 桁)。1 フレーム最大 336 byte、メッセージ本文は最大 286 byte。
  • slot を全部書いた後で HDR を書く。HDR の値が変わったことが「slot が揃った」合図。 HDR は書くたびに必ず変わる(seq を増やす)。サーバー側の seq はサーバー全体で単調増加にすること(CloudBridge がそうしています)。
  • クラウド変数は全員で共有です。同時に複数人が使うと衝突します。衝突は暗号が「捨てる」だけなので安全は保たれますが、再送が必要です(HELLO の自動再送はまだありません)。
  • 数字だけ・256 桁まで・1 プロジェクト 10 個、という制約に合わせた設計です。書き込み頻度の制限や、公式の Scratch サーバーでの動作は、実機では確認していません。

検証したこと

テストは tests/ にあります(pip install "cloudiamond-scratch[test]" + ソースツリーで pytest)。

  • 暗号部品: RFC 7748 / RFC 8439 の公式ベクトル、cryptography との照合、巨大整数の参照実装との境界値照合(Poly1305 の最終還元など)。テストの感度は、定数を 1 つ壊した版で落ちることで確認しています。
  • Scratch の制約: 全プリミティブが「+ − × floor mod とリストだけ」で動き、途中の値が 2^53 未満に収まること(ビット演算を禁止した検査クラス経由で実行)。
  • 本物の Scratch VM 上での実行: .sb3 を scratch-vm で実行し、X25519 / HChaCha20 / ChaCha20-Poly1305 / Poly1305 の出力が標準実装と一致。さらに VM 上のクライアントと実物のサーバーを、クラウド変数と同じ挙動でつなぎ、接続 → サーバー認証 → 暗号化メッセージの往復を確認。鍵の違うサーバーは拒否され、鍵素材が残らないことも確認。
  • 攻撃シナリオ: 盗聴 / ビット反転改ざん / 再送 / 反射 / 返信の差し替え(R := S など)/ 偽サーバー / 位数の小さい点 / ゴミ入力 / 返信フラッド / レート制限。
  • 決定的ビルド: 同じ公開鍵からは、バイト単位で同じ .sb3 ができます。配布された .sb3 は、公開鍵・このパッケージの同じバージョン・同じスプライト名(--name)から再生成して sha256 を突き合わせれば検証できます。

実測した速さ(参考値)

開発用サンドボックスの scratch-vm(Node.js、画面描画なし)での値です。

処理 所要時間
X25519 1 回 約 0.5 秒
接続 → 暗号化メッセージ 2 往復(乱数収集の約 3 秒を含む) 約 6〜10 秒

実際のブラウザの Scratch・スマートフォン・古い PC での時間は計測していません。 同じインタプリタで動くはずですが、より遅い可能性があります。接続時に X25519 を 3 回行います。

未検証・限界

  • 実際の Scratch 公式サーバー / TurboWarp のクラウド変数サーバー越しの動作(接続層は含まれていません)
  • 実ブラウザでの速度、Scratch のエディタ上での操作感
  • 第三者による暗号レビューは受けていません。 部品はすべて標準のものですが、唯一の「組み立て」は 2 段の鍵導出(HChaCha20 を PRF として連鎖。NaCl の crypto_box が HSalsa20 でやっているのと同じ発想で、dh_es / dh_ee の一方が秘密なら master は予測できません)です。単純な XOR 結合は R := S で鍵が崩壊するため採りませんでした。標準の HKDF-SHA256 が理想ですが、Scratch への SHA-256 の移植コストが大きいため見送っています。重要な情報を扱う前に、レビューを受けてください。

公開前チェックリスト(PyPI へ出す人向け)

ライセンスは MIT です(LICENSE。著作権表示は「cloudiamond-scratch contributors」にしてあるので、個人名・組織名にしたい場合は書き換えてください)。pyproject.toml には、公開する人が決めて書く項目がまだ残っています。意図的に入れていません。

  1. 作者名・連絡先(authors。メールアドレスは PyPI で公開されます)
  2. プロジェクト URL([project.urls]。GitHub の URL など)
  3. python -m build → twine check --strict dist/* → まず TestPyPI で pip install を試す
  4. アップロードは .github/workflows/publish.yml(PyPI の Trusted Publishing。API トークン不要)か、API トークンを使った手動の twine upload。トークンは他人に渡さない

GitHub Actions で公開する場合

.github/workflows/publish.yml は、Release を公開すると PyPI へ、Actions 画面からの手動実行で TestPyPI へアップロードします。PyPI / TestPyPI の Trusted Publisher 登録(Pending publisher)には次の値を入れます。

項目 PyPI TestPyPI
PyPI Project Name cloudiamond-scratch cloudiamond-scratch
Owner / Repository name GitHub のアカウント名 / リポジトリ名 同左
Workflow name publish.yml publish.yml
Environment name pypi testpypi

GitHub 側でも、リポジトリの Settings → Environments に pypi と testpypi を作ってください(pypi には Required reviewers を付けておくと、承認なしに公開されません)。Release のタグは v + バージョン(例: v0.1.0)にします。pyproject.toml のバージョンと違うと、ワークフローは公開前に止まります。

開発

pip install -e ".[test]"
pytest -m "not slow"          # Node.js 不要(Python 実行版の検証)

本物の Scratch VM 上のテストには Node.js と scratch-vm が必要です。scratch-vm を入れたディレクトリの node_modules を環境変数で渡すと実行されます(無ければ skip されます)。

npm install scratch-vm@5.0.300
CLOUDIAMOND_SCRATCH_VM=$PWD/node_modules pytest        # slow も含めて全部

scratch/program.py が Scratch に載るクライアント本体です。「SPy」という制限付き Python で書かれていて、同じファイルが (1) 普通の Python として動き(標準実装との照合に使う)、(2) scratch/spy.py で Scratch のブロックにコンパイルされます。

Metadata

Release files for cloudiamond-scratch 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cloudiamond-scratch 0.1.0
File Size Uploaded
cloudiamond_scratch-0.1.0.tar.gz 75.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cloudiamond-scratch 0.1.0
File Interpreter ABI Platform
cloudiamond_scratch-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 126.5 kB

Release files / cloudiamond_scratch-0.1.0.tar.gz

Download URL cloudiamond_scratch-0.1.0.tar.gz
Size 75.2 kB
Tags Source
SHA-256 checksum
How to use checksums
7917758d12256e1a609f246e0d2cd5b9284d25b9598eb09ea150182472771889
BLAKE2b-256 checksum
How to use checksums
d5dbaed2f681f0f786c2b190cbd5a2256563a7cfb7678a6b4989d82e6dcba684
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / cloudiamond_scratch-0.1.0-py3-none-any.whl

Download URL cloudiamond_scratch-0.1.0-py3-none-any.whl
Size 51.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
89cc7844b8a01772b172f61ab11157d6a469165ef289a73cce66f3ef88b45ce2
BLAKE2b-256 checksum
How to use checksums
ab5a980170d4b3d1069738929ef07b50d5411c89bab6946050a7371dcb56fe79
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page