🚀
分散型ストリーミングに挑むEthereumとIPFS連携手法
1. はじめに
近年、中央集権的なプラットフォームに依存しない、透明性・検閲耐性・所有権の担保された分散型サービスが注目されています。その中でも動画ストリーミングは、帯域・コスト・権利管理など高度な課題の多い領域です。本記事では、Ethereumスマートコントラクトと**IPFS(InterPlanetary File System)**を連携させることで、中央サーバーに頼らず動画を配信・管理する分散型動画ストリーミングプラットフォームの設計・実装手法を徹底解説します。
この記事は、個人開発者・スタートアップのテクニカルリーダーを対象に、公式ドキュメントには載っていない実践的な設計パターン・コーディング例・ベストプラクティスを豊富に盛り込みます。
読者が得られるメリット:
- EthereumとIPFSの連携構築ノウハウと実用的なコード例
- 分散型ストリーミングの設計思想とアーキテクチャ理解
- セキュリティ・パフォーマンス・コスト最適化の具体的Tips
- 他の技術との比較・将来性に関する深い知見
参考
2. 基礎:分散型ストリーミングのアーキテクチャと技術スタック
2.1 主要な技術要素
- Ethereumスマートコントラクト:動画メタデータ・所有権・アクセス制御の管理
- IPFS:動画ファイルの分散保存と配信
- フロントエンド:ユーザーインターフェース(例:React + web3.js/ethers.js)
- バックエンドAPI(任意):トランザクション補助・ノード管理用
- ウォレット連携(MetaMask等)
2.2 アーキテクチャ全体像
- 動画本体はIPFSに保存され、**CID(Content Identifier)**で参照
- 動画メタデータ・所有権情報はEthereumコントラクトに記録
- フロントエンドは両者を仲介してストリーミング体験を提供
参考
3. 実践的実装ガイド
3.1 環境準備とセットアップ
必要ツール・バージョン
- Node.js (v18.x推奨)
- Hardhat (v2.20.x)
- ipfs-http-client (v60.x)
- ethers.js (v6.x)
- React (v18.x)
- MetaMask(拡張機能)
# 必要パッケージのインストール
npm init -y
npm install --save hardhat ethers ipfs-http-client
npx hardhat
IPFSノードはInfura等のパブリックゲートウェイ利用も可能ですが、ローカルノード(go-ipfs v0.22.x)を立てると開発がスムーズです。
参考
3.2 基本的な機能の実装
3.2.1 スマートコントラクト:動画メタデータ管理
contracts/VideoPortal.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract VideoPortal {
struct Video {
address owner;
string ipfsCid; // IPFSコンテンツID
string title;
string description;
uint256 createdAt;
}
Video[] public videos;
event VideoUploaded(address indexed owner, uint256 indexed videoId, string ipfsCid);
function uploadVideo(string memory _ipfsCid, string memory _title, string memory _description) public {
videos.push(Video({
owner: msg.sender,
ipfsCid: _ipfsCid,
title: _title,
description: _description,
createdAt: block.timestamp
}));
emit VideoUploaded(msg.sender, videos.length - 1, _ipfsCid);
}
function getVideo(uint256 _videoId) public view returns (Video memory) {
require(_videoId < videos.length, "Invalid videoId");
return videos[_videoId];
}
function getVideosCount() public view returns (uint256) {
return videos.length;
}
}
デプロイ例(scripts/deploy.js)
const hre = require("hardhat");
async function main() {
const VideoPortal = await hre.ethers.getContractFactory("VideoPortal");
const videoPortal = await VideoPortal.deploy();
await videoPortal.deployed();
console.log(`VideoPortal deployed to: ${videoPortal.address}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
参考
3.2.2 IPFSへ動画ファイルをアップロード
uploadVideo.js
const { create } = require('ipfs-http-client');
const fs = require('fs');
(async () => {
// Infura IPFSノード(APIキー必須)
const projectId = "YOUR_INFURA_PROJECT_ID";
const projectSecret = "YOUR_INFURA_PROJECT_SECRET";
const auth = 'Basic ' + Buffer.from(projectId + ':' + projectSecret).toString('base64');
const ipfs = create({
host: 'ipfs.infura.io',
port: 5001,
protocol: 'https',
headers: {
authorization: auth,
},
});
const file = fs.readFileSync('./sample.mp4');
const { cid } = await ipfs.add({ content: file });
console.log('IPFS CID:', cid.toString());
})();
参考
3.2.3 フロントエンド:動画アップロード&再生(React例)
src/App.js
import React, { useState } from "react";
import { ethers } from "ethers";
import { create } from "ipfs-http-client";
import VideoPortalABI from "./abi/VideoPortal.json";
const CONTRACT_ADDRESS = "YOUR_DEPLOYED_CONTRACT_ADDRESS";
const INFURA_PROJECT_ID = "YOUR_INFURA_PROJECT_ID";
const INFURA_PROJECT_SECRET = "YOUR_INFURA_PROJECT_SECRET";
const IPFS_GATEWAY = "https://ipfs.io/ipfs/";
const ipfs = create({
host: "ipfs.infura.io",
port: 5001,
protocol: "https",
headers: {
authorization:
"Basic " +
Buffer.from(INFURA_PROJECT_ID + ":" + INFURA_PROJECT_SECRET).toString(
"base64"
),
},
});
function App() {
const [file, setFile] = useState(null);
const [videos, setVideos] = useState([]);
const uploadToIPFS = async (file) => {
const { cid } = await ipfs.add(file);
return cid.toString();
};
const uploadVideo = async () => {
if (!window.ethereum || !file) return;
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const contract = new ethers.Contract(CONTRACT_ADDRESS, VideoPortalABI, signer);
const cid = await uploadToIPFS(file);
const tx = await contract.uploadVideo(cid, "タイトル例", "説明文例");
await tx.wait();
alert("アップロード完了");
};
const fetchVideos = async () => {
const provider = new ethers.BrowserProvider(window.ethereum);
const contract = new ethers.Contract(CONTRACT_ADDRESS, VideoPortalABI, provider);
const count = await contract.getVideosCount();
let items = [];
for (let i = 0; i < count; i++) {
const v = await contract.getVideo(i);
items.push(v);
}
setVideos(items);
};
return (
<div>
<h1>分散型動画ストリーミングPoC</h1>
<input type="file" accept="video/mp4" onChange={e => setFile(e.target.files[0])} />
<button onClick={uploadVideo}>動画アップロード</button>
<button onClick={fetchVideos}>動画一覧取得</button>
<ul>
{videos.map((v, i) => (
<li key={i}>
<h3>{v.title}</h3>
<video width="320" controls src={IPFS_GATEWAY + v.ipfsCid}></video>
<div>説明: {v.description}</div>
<div>所有者: {v.owner}</div>
</li>
))}
</ul>
</div>
);
}
export default App;
参考
3.3 応用的な機能の実装
3.3.1 アクセスコントロール付き動画配信(有料視聴/許可制)
スマートコントラクト(有料視聴例)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract PaidVideoPortal {
struct Video { /* 先述と同様 */ }
Video[] public videos;
mapping(uint256 => mapping(address => bool)) public paidUsers;
function uploadVideo(string memory _ipfsCid, string memory _title, string memory _desc) public { /* 省略 */ }
function payToView(uint256 _videoId) public payable {
require(msg.value >= 0.01 ether, "Fee not enough");
paidUsers[_videoId][msg.sender] = true;
// クリエイターへ分配(例: 95%)
payable(videos[_videoId].owner).transfer(msg.value * 95 / 100);
}
function canView(uint256 _videoId, address _user) public view returns (bool) {
return paidUsers[_videoId][_user] || videos[_videoId].owner == _user;
}
}
React(視聴権チェック)
const canView = await contract.canView(videoId, userAddress);
if (canView) {
// <video src=...>
} else {
// 「視聴料を支払ってください」ボタン表示
}
課題と対策:
- IPFSの動画自体は公開なので、オンチェーンでのCID暗号化/復号キー管理や分割配信+署名検証などを検討(詳細は考察セクション参照)
参考
4. Tips & ベストプラクティス
-
CIDの検証と改ざん対策
- コントラクトでCIDのフォーマットや重複登録をチェック
- メタデータのSHA256ハッシュも併記することで不正アップロード検出
-
動画ファイルの分割とストリーミング最適化
- HLS/m3u8等で動画をチャンク分割し、IPFSマニ
自動レビュー結果 (2025-07-27 00:59)
- 記事品質: 4.5/5.0
- トピック多様性: 5.0/5.0
- コードサンプル: 4.6/5.0
- 実用性・応用性: 4.4/5.0
- 総合評価: 4.6/5.0 (優秀)
Discussion