# 暗号笛 dApp（Polygon Amoy）設計メモ

2026-07-29 起案。笛から復号した秘密を、そのままブロックチェーン上の鍵として使う仕組みの検討。
サーバを持たず、ブラウザだけで完結させることを前提にする。

---

## 1. 何を作るか（3段階）

段階を分けて考えると、どこまでが契約なしで成り立ち、どこから契約が要るのかがはっきりする。

### 段階0：笛そのものが口座になる（契約は不要）

笛の秘密から決まった手順で秘密鍵を作り、そのアドレスを口座として使う。ブラウザで笛を吹くと
鍵が手元に戻り、その場で送金に署名できる。**ブロックチェーンには何も特別なものを載せない**。
ふつうの外部所有アカウント（EOA）が1つあるだけである。

これが「暗号笛＝ハードウェアウォレット」の最小の形であり、いちばん強い主張になる。契約を
書かなくても成立する点が重要で、**契約の欠陥という攻撃面をそもそも持たない**。

### 段階1：2枚そろって初めて動く金庫（契約が要る）

カード2枚の 2-of-2 を、そのままチェーン上の 2-of-2 に対応させる。金庫の契約が資産を預かり、
**2枚から復元した秘密で作った鍵の署名**があるときだけ引き出せる。物の形（半分のハートが2枚で
そろう）と、契約の条件（2つそろって初めて解ける）が一致する。

### 段階2：時間と相続（契約が要る）

「この日以降でなければ開かない」「一定期間だれも触らなければ、指定した相手が開けられる」と
いった条件を契約に書く。物理の笛は時間を知らないので、ここは契約でなければできない。
遺言やデジタル遺産の引き継ぎという用途に直結する。

---

## 2. セキュリティの検討

### 2.1 いちばん大事な制約：載せた瞬間に世界中へ公開され、消せない

ブロックチェーンは公開の台帳である。暗号文もハッシュも、書き込んだ瞬間に誰でも取得でき、
**あとから消せない**。したがって「秘密のハッシュを載せて照合する」という素直な設計は、
**秘密のエントロピーが小さいと、その場で破られる**。

### 2.2 担体ごとのエントロピーと、総当たりの現実性

| 担体 | エントロピー | 素のハッシュで総当たり | 遅い鍵導出（PBKDF2 60万回相当）で総当たり |
|---|---|---|---|
| カード1枚 | 20.8 bit | 一瞬 | 約1.5分 |
| カード2枚（2-of-2） | 20.8 bit | 一瞬 | 約1.5分 |
| 本立て | 64 bit | 約59年 | 実質不可能 |
| スプール2枚 | 128 bit | 実質不可能 | 実質不可能 |

（GPU 1台を 1e10回/秒、遅い鍵導出を 2e4回/秒と仮定した目安）

ここから導かれる結論は明快である。

* **価値のある資産を預けてよいのは、スプール（128 bit）だけ**である。本立て（64 bit）も
  遅い鍵導出と併用すれば当面は持ちこたえるが、余裕を見るなら128 bitを使う。
* **カード（20.8 bit）は、そのままでは公開台帳に載せてはいけない**。アドレスを1つ登録した
  時点で、総当たりで秘密鍵まで到達される。カードを使いたい場合は次のどちらかにする。
  1. デモに徹する（Amoyのテスト用トークンには金銭価値が無いので、破られても損害がない）。
  2. **2要素にする**。笛の秘密に、利用者が覚えている合言葉を混ぜて鍵を作る。合言葉が
     40 bit あれば合計60 bit級になり、総当たりは現実的でなくなる。

### 2.3 鍵の作り方

```
seed = KDF(secret_bytes, salt = "cipherflute/v1|" + object_id, iterations = 600,000)
privkey = seed（32バイト、secp256k1の範囲へ縮約）
address = keccak(pubkey)[12:]
```

* **遅い鍵導出を必ず通す**。素のハッシュだと、上表のとおり小さい秘密が即座に割れる。
  ブラウザ標準の WebCrypto に PBKDF2 があるので、追加の依存なしで書ける。より強くするなら
  Argon2id（WebAssembly）を使う。
* **salt に版と物の識別子を入れる**。同じ笛から常に同じ口座が出ること（決定性）と、
  別の物・別の用途で鍵が衝突しないことの両方を満たす。salt は公開してよい。
* **版を名前に入れる**。将来 KDF を変えたとき、古い笛の口座を失わないようにするため。

### 2.4 署名の横取り（front-running）

契約に「正しい署名を出した人へ払う」と書くと、送信された取引を見た第三者が**同じ署名を
コピーして手数料を上乗せし、先に取り込ませる**ことができる。ブロックに入る前の取引は
公開の待ち行列に並ぶためである。

対策は、**署名の中に受取人のアドレスを含める**ことである。EIP-712 の型付きデータとして

```
Claim(address vault, uint256 chainId, address claimer, uint256 nonce, uint256 deadline)
```

に署名すれば、他人がコピーしても claimer が自分ではないので使えない。nonce で再利用を防ぎ、
deadline で古い署名を無効にする。chainId と契約アドレスを含めることで、別のチェーンや
別の契約への使い回しも防げる。

### 2.5 ブラウザが信頼境界になる

笛の秘密も秘密鍵も、ブラウザのメモリ上に現れる。したがって次を守る。

* **静的ページを HTTPS で配る**（GitHub Pages でよい）。第三者のスクリプトを読み込まない。
  読み込むなら Subresource Integrity を付ける。
* 秘密鍵を localStorage に保存しない。ページを離れたら消える状態にする。
* 何に署名しようとしているのかを、必ず人間が読める形で画面に出す。
* 拡張機能による盗み見は防げない。金額の大きい用途では専用の端末を使うべきである、と明記する。

### 2.6 物理面のリスクは変わらない

笛は静的な形状なので、**保管場所を知られたら、吹かなくても計測や透過撮影で読める**。これは
論文の脅威モデルと同じで、チェーンを使っても変わらない。守るのは秘密分散と保管の分離である。

### 2.7 テストネットである意味

Amoy のトークンには金銭価値がない。したがって**設計の誤りが金銭の損失に直結しない**。
仕組みを見せる目的にはむしろ適している。本番のチェーンへ移すかどうかは、上の
エントロピーの条件（128 bit を使う、2要素にする）を満たしてから判断する。

---

## 3. ブロックチェーンに何を載せるか

### 載せてよいもの

* 笛から導いた口座の**アドレス**（公開鍵のハッシュ。ただし2.2の条件を満たす場合に限る）
* 金庫の**状態**（未開封・開封済み、開封した時刻、受取人）
* **開封の記録**（イベントログ）。「この時刻に、この物を吹いて開けた」という証跡が公開台帳に
  残ることは、相続や複数人での開封式では利点になる。
* 時限や条件などの**規則そのもの**
* 128 bit の秘密で暗号化した**小さな暗号文**（遷移先URLなど）。エントロピーが足りている
  ときに限る。

### 載せてはいけないもの

* **秘密そのもの**（言うまでもない）
* **小さい秘密のハッシュ**。載せた瞬間に総当たりで割られる。ハッシュは秘匿ではない。
* **笛の音の並びや写真、寸法**。これらは秘密そのものである。
* 個人を特定できる情報。公開台帳は消せない。

---

## 4. 技術スタックの検討

サーバを持たない前提で、次の構成を推す。

| 層 | 選択 | 理由 |
|---|---|---|
| 配布 | GitHub Pages（既存の docs/ をそのまま使う） | すでに復号ページを置いており、追加費用も運用も要らない |
| 音の復号 | 既存の `cipher_codec.js` と pitchy | 実機で動いている資産をそのまま使う |
| 鍵導出 | WebCrypto の PBKDF2（必要なら Argon2id の WebAssembly） | 追加依存なしで始められる |
| チェーン接続 | **viem**（または ethers v6） | 型が付き、ツリーシェイキングが効いて配布が軽い。Amoy を標準で持つ |
| 署名 | EIP-712 型付きデータ | 人が読める形で内容が出る。横取り対策の項目を型に入れられる |
| 財布 | MetaMask（手数料の支払い用）／または笛から導いた鍵で直接署名 | 段階0では財布すら要らない |
| 契約 | Solidity 0.8.x ＋ OpenZeppelin（ECDSA, EIP712） | 実績のある実装を使い、自作の暗号処理を書かない |
| 開発 | Foundry（`forge test`, `forge script`） | テストが速く、鍵の扱いを含めた検証を書きやすい |
| ネットワーク | Polygon Amoy（chainId 80002） | 指定どおり。RPC は公開のもので足りる |

手数料の支払いだけは、どうしても資金のある口座が要る。選択肢は2つある。

1. **利用者の MetaMask で払う**。笛から導いた鍵は署名だけに使い、取引の送信は財布が行う。
   サーバ不要。いちばん素直である。
2. **笛の口座に直接テスト用トークンを入れておく**。財布すら不要になり、「笛が財布そのもの」
   という筋が最も鮮明になる。蛇口（faucet）から入れておく運用。

段階0のデモは2、段階1以降は1が向いている。

---

## 5. UX の検討

### 段階0（笛が財布）

1. ページを開く。「笛を吹いて口座を開く」とだけ書いてある。
2. 笛を吹く（既存の復号ページと同じ操作）。
3. 復元できると、**口座のアドレスと残高**が出る。秘密鍵は画面に出さない。
4. 送金の画面。宛先と金額を入れて「吹いた鍵で署名して送る」。
5. 取引が通ったら、その番号（ハッシュ）と、台帳での確認先へのリンクを出す。

要点は、**財布の接続や鍵の管理を利用者に見せないこと**である。笛を吹くという物理の動作が、
そのまま口座を開く動作になっている状態を目指す。

### 段階1（2枚そろって開く金庫）

1. 「1枚目を吹く」→ 記号が読める。
2. 「2枚目を吹く」→ その場で合成し、鍵ができる。
3. 画面に**何に署名するのか**（金庫の住所、受取人＝自分の財布、期限）が日本語で出る。
4. 「開ける」を押すと、財布が手数料を払って取引を送る。
5. 金庫が開き、**開封の記録が台帳に残る**。「2026年7月29日、この2枚がそろって開かれた」と
   誰でも確認できる。

ここでの体験の核心は、**2枚がそろうまで画面が何も教えない**ことである。1枚目を吹いた時点では
「読み取った」としか出さず、秘密の値も残高も見せない。物の性質（片方だけでは何も分からない）
を画面の挙動でも守る。

### 失敗したときの案内

* 1本鳴らなかった → 「もう一度吹いてください」。誤り訂正の余裕を数字で出す。
* 秘密が違う → 「この笛では開きません」。何ビット違うといった手掛かりは出さない。
* 手数料が足りない → 蛇口への案内を出す。

---

## 6. 最小の実装計画

1. **段階0の口座デモ**（契約なし・半日）
   `docs/dapp/index.html` に、笛→鍵導出→アドレス表示→残高照会まで。viem を CDN ではなく
   同梱（Subresource Integrity か自前ビルド）。送金は次段で足す。
2. **鍵導出の仕様確定とテスト**（Node のテストで決定性を固定する）
   同じ秘密から常に同じアドレスが出ること、版を変えると別の口座になることを検査する。
3. **段階1の金庫契約**（Foundry・1日）
   `FluteVault.sol`：登録アドレス、EIP-712 の Claim 検証、nonce、deadline、開封イベント。
   `forge test` で、横取り（claimer が違う署名）と再利用（同じ nonce）が弾かれることを検査。
4. **Amoy へデプロイし、実際に笛で開ける**
5. 段階2（時限・相続）は、必要になってから。

---

## 7. 未解決の論点

* カード（20.8 bit）を実用として扱うなら、合言葉との2要素にするかどうか。UXは重くなる。
* 笛の口座に価値を置く場合、**誰かが総当たりで先に取る**危険を、どう利用者へ説明するか。
* 128 bit のスプールは吹くのに49本かかる。実用の速度として許容できるか（実測で確かめる）。
* 本番チェーンへ移すか、テストネットに留めるか。論文の主張としてはテストネットで十分である。
