Fully Open Decentralized Protocol — P2P Mesh Edition
Fodpr(ふぉどぷる)は、SNS のような「投稿」を、特定の会社やサービス・リレーサーバーに依存せずにやりとりするための完全 P2P メッシュプロトコルです。投稿(イベント)は WebRTC データチャネル上でピア間(F2F)に直接やりとりされます。
英語版は README.en.md をご覧ください。 English version is available at README.en.md
| 項目 | v0.5 以前 (Relay) | v0.6 "Mesh" |
|---|---|---|
| アーキテクチャ | リレーサーバー経由 / ホスト昇格型 P2P | 完全 P2P メッシュ (リレーなし、ホストなし) |
| 接続形態 | Client ↔ Relay / Star topology | F2F (Friend-to-Friend) メッシュ |
| IP 発見 | リレーが仲介 / 固定アドレス | Kademlia DHT over WebRTC データチャネル (SHA-256(pubkey) = nodeId) |
| 信頼モデル | リレー信頼 / ホスト信頼 | WoT (Web of Trust) スコアゲート (最小スコア開始、閾値到達でダイヤル) |
| ブートストラップ | リレーのシード / 招待 | 招待コード f2finv1... / 設定シードノード / 手動 IP / ビルトインアンカー |
| メッセージング | リレー経由 PUSH / WebRTC シグナリング | ゴシップ (MAX_HOPS=2, eventId 重複排除) / FodprData 直接 P2P |
| リレーサーバー | 必須 (FodprRelay) | 不要・廃止 |
| ホスト昇格 (RtcGroup) | 有り | 廃止 |
- だれもが自由に使える — オープンなルールで動くため、特定の企業やサービスに縛られません。
- なりすまし・改ざんを防げる — 投稿には「電子署名」が付いており、本人が書いたものか、書き換えられていないかをだれでも確認できます。
- サーバーが 1 台にも依存しない — リレーサーバーは存在しません。すべての接続はピア間 (F2F) の WebRTC データチャネルです。
- 投稿の形式を自由に選べる — JSON(構造化データ)/ 文字列 / バイナリ(画像など)の 3 種類から投稿者が自由に選べます。
- メタデータまで守れる(全体署名) — 「全体署名」イベント(
TransTypeSigned)を使うと、本文だけでなく送信日時・公開鍵・タグも含めて署名できます。イベント ID で特定イベントを一意に参照できます。 - 宛先別に暗号化できる(E2EE エンベロープ) — 暗号化イベント(
TransTypeEncrypted)は、本文を AES-256-GCM で暗号化した「エンベロープ」を content に載せます。受信者ごとに鍵をラップするので、宛先にした本人だけが本文を復号できます(gift-wrap 相当)。
- すべての接続はクライアント間 (F2F) の WebRTC データチャネル。
- ホスト/星形トポロジは廃止。すべてのピアはメッシュの隣接ピア群へ直接接続。
- 最大 50 接続まで連鎖的に拡大。
- ノード ID = SHA-256(compressed pubkey) (256 ビット)。
- k-buckets ルーティングテーブル、
PING/FIND_NODE/FIND_VALUE/STOREをデータチャネル上で RPC。 FIND_VALUEで公開鍵 → IPv6 アドレスを解決し直接ダイヤル。
- 各ピアに
identityTrust(身元) とreliabilityScore(接続実績) の 2 スコア (0.0 ~ 1.0)。新規/未検証ピアは 最小スコアから開始。 - スコアが 閾値 (デフォルト 0.0) に達したピアのみダイヤル。
- WoT 紹介 (
MsgTypeWoTIntroPush) は発見と来歴の記録のみ。スコアは紹介では変化しない。 - 接続成功で上昇、失敗で低下、時間経過で減衰 (
decayTrustScores)。 - ピアキャッシュは破棄しない — 取得した PeerList は既存キャッシュへマージ。削除は
reliabilityScoreが 0.1 未満のまま 7日経過したピアのみ (pruneStalePeers)。
- 署名済みイベントをメッシュ隣接へ
MsgTypeEvent(0x01)でフラッシュ。 - ホップ制限 (
MAX_HOPS=2)、Set<eventIdHex>で重複排除。 - 直接 P2P メッセージは
FodprData(TransTypeSigned等) で運ぶ。
- 招待コード (
f2finv1...Bech32) — 知人から受け取り即ダイヤル。 - 設定シードノード (
fpub1...@[ipv6]:port) —fodpr_bootstrap_nodesに登録。 - 手動 IP 入力 — 直接 IPv6 アドレスを指定。
- ビルトインコミュニティアンカー (
FODPR_BOOTSTRAP_ANCHORS) — 完全孤立時に自動フォールバック (Bitcoin DNS seed / IPFS bootnodes 方式)。
- IPv6 一時アドレス (RFC 4941 / RFC 8981) を
addressesに含め、接続ごとにローテーション。 /64プレフィクスで ISP 割り当て単位を判定し GeoIP で国コードを推定。
- ダイヤル候補選抜時に 国ごとに最大 2 本を優先確保。
- 残りはグローバル信頼順。GeoIP 失敗は 'XX' バケツで信頼順フォールバック。
重要: 一般的な家庭用インターネット接続では、IPv6 インバウンド接続が デフォルトでブロック されていることが多いです。
| 環境 | インバウンド IPv6 | 備考 |
|---|---|---|
| 一般家庭用ルーター (ISP 提供/市販) | デフォルト遮断 が多数 | Verizon, AT&T などの ISP 提供 CPE は IPv6 ファイアウォールでインバウンドを遮断。手動で「ピンホール/ポート開放」が必要。 |
| Unifi / pfSense / OPNsense 等 | デフォルト遮断 | 明示的な「External → Internal」IPv6 ファイアウォールルール追加まで到達不能。 |
| ISP 側での遮断 | 一部 ISP で遮断 | AT&T などはプロトコル 41 (6in4 トンネル) を遮断する事例あり。ネイティブ IPv6 でもインバウンド遮断のケースあり。 |
| モバイル回線 (4G/5G) | ほぼ遮断 | CGNAT 的な構成でインバウンド不可。IPv6 アドレスが割り当てられても到達不能。 |
| データセンター / VPS / クラウド | 到達可能 | 適切なファイアウォール設定さえあればネイティブ IPv6 インバウンドが通る。 |
- Fodpr メッシュでは「インバウンドを受け入れられるピアが最低 1 つ」必要です。完全に遮断された環境同士では直接接続できません。
- 現状、WebRTC の ICE/STUN で NAT トラバーサルを試みますが、ステートフルファイアウォールでインバウンドが遮断されている場合は シグナリング経由のフォールバック (既接続メッシュピアを中継) に頼ります。
- ルーターの IPv6 ファイアウォールで「ピンホール/ポート開放」 を設定 (対象ポート: WebRTC 用 UDP 範囲、または全 IPv6 UDP 一時許可)。
- Unifi 等の場合:
IPv6 Firewall→WAN_INにAction: Accept/Protocol: All/Source: Any/Destination: Internal Networkルールを追加。 - モバイル/遮断環境では: 到達可能なピア (VPS, データセンター, IPv6 開放済み自宅) を ブートストラップノード/アンカー として登録し、そこを経由してメッシュに参加。
- ICE/STUN サーバー をクライアント設定で指定 (Google
stun:stun.l.google.com:19302等) し、NAT トラバーサル成功率を上げる。 - ICMPv6 (Packet Too Big 等) はフィルタしない — PMTU 発見が動かなくなり、大容量転送が失敗する原因になります。
参考文献: "Where Have All the Firewalls Gone? Security Consequences of Residential IPv6 Transition" (arXiv:2509.04792, 2025) — 数百万の住宅 IPv6 ホストがステートフルファイアウォールなしで公開されている実態を大規模測定。NAT が事実上のファイアウォールだったことが判明。
v0.5 までは「郵便局 (リレーサーバー) 経由」でしたが、v0.6 からは 「知り合いの家を訪ねて手紙を直接渡す」 イメージです。
- 最初の 1 人 と繋ぐ — 招待コード / 設定シードノード / ビルトインアンカーのいずれかで最初のピアと F2F 接続確立。
- 署名付き PeerList を交換 — 相手の知り合い (最大 50 件) を教えてもらい、WoT スコア閾値を超える相手へ自動ダイヤル。取得リストは既存キャッシュへマージ (破棄しない)。
- メッシュ形成 — 最大 50 接続まで連鎖的に拡大。以降はリレーなしで直接通信。
- DHT で IP 解決 — 公開鍵を鍵に
FIND_VALUEを発行し、相手の IPv6 を解決して直接ダイヤル。 - ゴシップで同期 — 署名済みイベントを隣接へフラッシュ (MAX_HOPS=2)。eventId で重複排除。
- WoT で質維持 — 紹介で信頼を引き継ぎ、接続成功でスコア上昇、失敗で低下、時間で減衰。キャッシュは破棄せず維持し、スコア 0.1 未満のまま 7日経過したピアのみ削除。
git clone https://github.com/LunaYoineko/Fodpr
cd Fodpr
nimble install -ynim c -r examples/protocol_demo.nim鍵ペア生成、イベントの作成・署名、エンコード → デコード、署名検証までをオフラインで確認できます。
cd ../FodprWebClient
pnpm install
pnpm dev --port 5199ブラウザで http://localhost:5199 を開き、設定画面 (歯車アイコン) → 「ネットワーク」タブで:
- 招待コード (
f2finv1...) を入力して接続、または fpub1...@[ipv6]:port形式のブートストラップノードを追加、または- 何もしなければ ビルトインコミュニティアンカー へ自動接続を試行
# ビルド
pnpm build
# 静的ファイルを配信ディレクトリへ同期
cp -r dist/assets /var/www/fodpr/assets
cp dist/index.html /var/www/fodpr/index.html
cp public/docs.html /var/www/fodpr/docs.html
# 静的ファイルサーバー起動 (Node.js)
FODPR_STATIC_ROOT=/var/www/fodpr FODPR_API_PORT=8088 nohup node api/server.mjs > /tmp/fodpr-api.log 2>&1 &生成物 (build/, out/, .so, .apk, .ipa 等) は .gitignore で除外済みです。
ビルドに必要なのはソースのみで、依存は各ツールチェインが自動取得します。
# --- Android (Linux/macOS) ---
bash android/setup_toolchain.sh # 初回のみ: JDK / NDK / SDL2 を $HOME/fodpr-toolchain に取得
bash android/build_apk.sh # libmain.so + libSDL2.so をビルドし APK を生成
# オプション: APP_SRC=examples/chat_client.nim APP_PACKAGE=com.fodpr.chat APP_LABEL="Fodpr Chat" \
# bash android/build_apk.sh --install
# --- iOS (macOS + Xcode, 開発者アカウント不要) ---
bash ios/setup_toolchain.sh # 初回のみ: SDL2 ソースを取得
bash ios/build.sh # FodprChat.app (ad-hoc 署名) + FodprChat.ipa を生成
# シミュレータ: bash ios/build.sh --sim
# Xcode デバッグインストール: bash ios/make_xcodeproj.sh → 詳細は ios/README.mdFodpr/
├── Fodpr.nimble # Nimble パッケージ定義 (Library 型)
├── src/
│ ├── Fodpr.nim # ライブラリのメインモジュール (protocol / crypto / envelope / f2f/* を再エクスポート)
│ ├── protocol.nim # ワイヤプロトコル: メッセージ種別 / TransType / DhtOp / データ構造 (PeerInfo, PeerList, WoTIntro, InvitationCode, DhtMessage, FodprData)
│ ├── crypto.nim # secp256k1 (鍵生成・署名・検証・ECDH・Bech32)
│ ├── envelope.nim # 宛先別暗号化エンベロープ (TransTypeEncrypted)
│ └── f2f/ # F2F メッシュ実装
│ ├── dht.nim # Kademlia ルーティングテーブル (256bit ID, k-buckets, PING/FIND_NODE/FIND_VALUE/STORE)
│ ├── wot.nim # WoT グラフ (addWoTIntroduction / findTrustPath / recommendPeersByTrust / decayTrustScores)
│ ├── discovery.nim # WoT グラフ構築, getNextCandidates, requestPeerList/sendPeerList, WoT 紹介処理
│ ├── peer_cache.nim # ピアキャッシュ永続化 (50件, 破棄なしマージ + 7日ルール), pruneStalePeers / selectPeers
│ ├── invitation.nim # Bech32 招待コード encode/decode/verify (f2finv1...)
│ ├── signaling.nim # F2F SDP offer/answer/candidate サポート
│ ├── transport.nim # WebRTC データチャネル send/receive, IPv6 プレフィックス + IID 生成
│ └── bootstrap.nim # 設定シードノードからのブートストラップ
├── examples/
│ └── protocol_demo.nim # protocol.nim を使ったサンプル (サーバー不要)
├── LICENSES/ # サードパーティライブラリのライセンス情報
├── README.md # 日本語版 README (このファイル)
├── README.en.md # 英語版 README
└── data/ # ローカル実行時のピアキャッシュ等 (gitignore)
注意: 旧
FodprRelay/リポジトリ、src/server.nim、f2f/group.nim(RtcGroup) は v0.6 で 削除済み です。リレーサーバーは存在しません。
| 値 | 定数 | 方向 | 説明 |
|---|---|---|---|
| 0x01 | MsgTypeEvent |
ピア → ピア | 署名付きイベント (ゴシップ配信) |
| 0x05 | MsgTypeSignal |
ピア → ピア | WebRTC シグナリング (offer/answer/ICE) |
| 0x06 | MsgTypeData |
ピア → ピア | 直接 P2P メッセージ (FodprData) |
| 0x07 | MsgTypePeerListReq |
ピア → ピア | ピアリスト要求 |
| 0x87 | MsgTypePeerListPush |
ピア → ピア | ピアリスト配信 (WoTキャッシュ同期) |
| 0x08 | MsgTypeWoTIntro |
ピア → ピア | WoT 紹介要求 |
| 0x88 | MsgTypeWoTIntroPush |
ピア → ピア | WoT 紹介配信 |
| 0x09 | MsgTypeInvitationReq |
ピア → ピア | 招待コード要求 |
| 0x89 | MsgTypeInvitationPush |
ピア → ピア | 招待コード配信 |
| 0x0B | MsgTypeDht |
ピア → ピア | DHT RPC (PING/FIND_NODE/FIND_VALUE/STORE) |
| 0x8B | MsgTypeDhtNodes |
ピア → ピア | DHT 近傍ノード応答 |
| 0x8C | MsgTypeDhtValue |
ピア → ピア | DHT 値応答 (FIND_VALUE ヒット / STORE 完了) |
| 定数 | 値 | 説明 |
|---|---|---|
TransTypeJSON |
1 | JSON 構造化データ (UTF-8) |
TransTypeString |
2 | 文字列 (UTF-8) |
TransTypeBinary |
3 | バイナリデータ (任意バイト列) |
TransTypeSigned |
4 | 全体署名イベント (全フィールド署名、SHA-256 = eventId) |
TransTypeEncrypted |
5 | 暗号化イベント (エンベロープ、E2EE、gift-wrap 相当) |
TransTypeData |
7 | WebRTC データチャネル専用 (直接 P2P メッセージ) |
TransTypePeerList |
9 | F2F: ピアリスト交換 (WoTキャッシュ同期) 専用 |
TransTypeWoTIntro |
10 | F2F: WoT 紹介メッセージ専用 |
TransTypeInvitation |
11 | F2F: 招待コード専用 |
| 定数 | 値 | 説明 |
|---|---|---|
DhtOpPing |
0 | 生存確認 |
DhtOpPong |
1 | 生存応答 |
DhtOpFindNode |
2 | キー (nodeId) に最も近いノードを探す |
DhtOpFindValue |
3 | キーに対応する値を探す (IP アドレス解決) |
DhtOpStore |
4 | 値を保存する (自ノードの IP 記録等) |
PeerInfo = object
pubkey: SkPublicKey # 公開鍵 (圧縮形式 33 バイト)
addresses: seq[string] # 接続アドレス (IPv6 一時アドレス "[ipv6]:port" 等)
lastSeen: uint64 # 最後に見た時刻 (Unix秒)
identityTrust: float # 身元信頼 (0.0-1.0): 取得元 (issuer) での信頼度
reliabilityScore: float # ネットワーク信頼性 (0.0-1.0): 接続実績・安定性
country: string # GeoIP 国コード (ISO 3166-1 alpha-2)
zeroScoreSince: uint64 # ローカル専用 (ワイヤ非公開): スコアが 0.1 未満になった時刻 (7日ルール用, 0=未計測/健全)zeroScoreSince はワイヤ形式 (encodePeerInfo) では送受信せず、ローカルキャッシュの 7日ルール削除判定にのみ使用する。
version(8) | peerCount(2) | PeerInfo[] | signature(64) — 最大 50 件。
introducer: SkPublicKey | newPeer: PeerInfo | signature: FodprSignature
Bech32: f2finv1... — version(1) | issuer(33) | targetPeer: PeerInfo | expiresAt(8) | scope(1) | signature(64)
op(1) | msgId(16) | key(32) | nodes[] | value(string) | sender(33) | signature(64)
nodeId = SHA-256(compressed pubkey)(256 ビット)。XOR 距離で近傍判定。
sender | target | seq | timestamp | tags[] | content | signature — TransTypeSigned/Encrypted 等を content に包む。
- FodprWebClient (TypeScript/React) — ブラウザ向けメッシュクライアント (
../FodprWebClient) - FodprTSSDK (TypeScript) — 共通プロトコル/暗号 SDK (
../FodprTSSDK) - reference.md (
../FodprWebClient/reference.md) — イベント送信リファレンス (v0.6 ゴシップ版)
MIT License (see LICENSES/)
- 2026-08-12: v0.6 "Mesh" リリース — リレー廃止、完全 P2P メッシュ化。Kademlia DHT over WebRTC、WoT 信頼ゲート、ゴシップ、招待コード/ビルトインアンカーブートストラップ、GeoIP 接続多様性、IPv6 インバウンド遮断対策ドキュメント化。
- 2026-08-15: ピアキャッシュ方針変更 — 取得 PeerList による完全置換を廃止し破棄なしマージへ移行。既存ピアの
reliabilityScore/zeroScoreSinceをローカル実績として保持。削除はreliabilityScoreが 0.1 未満のまま 7日経過したピアのみ (pruneStalePeers)。PeerInfo.zeroScoreSinceはローカル専用でワイヤ形式には含めない。