@mysten/sealで分散暗号化を構築する:開発者向けチュートリアル
プライバシーは公共インフラになりつつあります。2025年、開発者にはデータ保存と同じくらい簡単に暗号化を行うツールが必要です。Mysten LabsのSealは、まさにそれを提供します—オンチェーンアクセス制御を備えた分散シークレット管理。このチュートリアルでは、アイデンティティベース暗号化、閾値セキュリティ、プログラマブルアクセスポリシーを使用して安全なWeb3アプリケーションを構築する方法を教えます。
導入:なぜSealがWeb3にとって重要なのか
従来のクラウドアプリケーションは、単一のプロバイダーが暗号化データへのアクセスを制御する集中型キー管理システムに依存しています。便利ですが、これは危険な単一障害点を作り出します。プロバイダ ーが侵害されたり、オフラインになったり、アクセスを制限することを決定した場合、あなたのデータはアクセス不可能または脆弱になります。
Sealは、このパラダイムを完全に変えます。Mysten LabsがSuiブロックチェーン向けに構築したSealは、分散シークレット管理(DSM)サービスで、以下を可能にします:
- アイデンティティベース暗号化:コンテンツが環境を離れる前に保護される
- 閾値暗号化:複数の独立したノード間でキーアクセスを分散
- オンチェーンアクセス制御:タイムロック、トークンゲーティング、カスタム認証ロジック
- ストレージ非依存設計:Walrus、IPFS、または任意のストレージソリューションと連携
安全なメッセージングアプリ、ゲート付きコンテンツプラットフォーム、タイムロック資産転送を構築する場合、Sealは必要な暗号プリミティブとアクセス制御インフラストラクチャを提供します。
はじめに
前提条件
開始する前に、以下があることを確認してください:
- Node.js 18+がインストールされていること
- TypeScript/JavaScriptの基本的な知識
- テスト用のSuiウォレット(Sui Walletなど)
- ブロックチェーンの概念の理解
インストール
npm経由でSeal SDKをインストールします:
npm install @mysten/seal
ブロックチェーンインタラクション用にSui SDKも必要です:
npm install @mysten/sui
プロジェクトセットアップ
新しいプロジェクトを作成し、初期化します:
mkdir seal-tutorial
cd seal-tutorial
npm init -y
npm install @mysten/seal @mysten/sui typescript @types/node
シンプルなTypeScript設定を作成します:
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
コアコンセプト:Sealの仕組み
コードを書く前に、Sealのアーキテクチャを理解しましょう:
1. アイデンティティベース暗号化(IBE)
公開キーに暗号化する従来の暗号化とは異なり、IBEではアイデンティティ(メールアドレスやSuiアドレスなど)に暗号化できます。受信者は、そのアイデンティティを制御していることを証明できる場合にのみ復号化できます。
2. 閾値暗号化
単一のキーサーバーを信頼する代わりに、Sealはt-of-n閾値スキームを使用します。3-of-5キーサーバーを設定することで、任意の3つのサーバーが協力して復号化キーを提供できますが、2つ以下では不可能です。
3. オンチェーンアクセス制御
アクセスポリシーはSuiスマートコントラクトによって強制されます。キーサーバーが復号化キーを提供する前に、要求者がオンチェーンポリシー要件(トークン所有権、時間制約など)を満たしていることを確認します。
4. キーサーバーネットワーク
分散キーサーバーはアクセスポリシーを検証し、復号化キーを生成します。これらのサーバーは、単一の制御点を確保しないよう、異なる当事者によって運営されます。
基本実装:最初のSealアプリケーション
Suiブロックチェーンポリシーを通じてアクセスを制御し、機密データを暗号化するシンプルなアプリケーションを構築しましょう。
ステップ1:Sealクライアントの初期化
// src/seal-client.ts
import { SealClient } from '@mysten/seal';
import { SuiClient } from '@mysten/sui/client';
export async function createSealClient() {
// テストネット用のSuiクライアントを初期化
const suiClient = new SuiClient({
url: 'https://fullnode.testnet.sui.io'
});
// テストネットキーサーバーでSealクライアントを設定
const sealClient = new SealClient({
suiClient,
keyServers: [
'https://keyserver1.seal-testnet.com',
'https://keyserver2.seal-testnet.com',
'https://keyserver3.seal-testnet.com'
],
threshold: 2, // 2-of-3閾値
network: 'testnet'
});
return { sealClient, suiClient };
}
ステップ2:シンプルな暗号化/復号化
// src/basic-encryption.ts
import { createSealClient } from './seal-client';
async function basicExample() {
const { sealClient } = await createSealClient();
// 暗号化するデータ
const sensitiveData = "これは私の秘密メッセージです!";
const recipientAddress = "0x742d35cc6d4c0c08c0f9bf3c9b2b6c64b3b4f5c6d7e8f9a0b1c2d3e4f5a6b7c8";
try {
// 特定のSuiアドレス用にデータを暗号化
const encryptedData = await sealClient.encrypt({
data: Buffer.from(sensitiveData, 'utf-8'),
recipientId: recipientAddress,
// オプション:メタデータを追加
metadata: {
contentType: 'text/plain',
timestamp: Date.now()
}
});
console.log('暗号化されたデータ:', {
ciphertext: encryptedData.ciphertext.toString('base64'),
encryptionId: encryptedData.encryptionId
});
// 後でデータを復号化(適切な認証が必要)
const decryptedData = await sealClient.decrypt({
ciphertext: encryptedData.ciphertext,
encryptionId: encryptedData.encryptionId,
recipientId: recipientAddress
});
console.log('復号化されたデータ:', decryptedData.toString('utf-8'));
} catch (error) {
console.error('暗号化/復号化に失敗しました:', error);
}
}
basicExample();
Suiスマートコントラクトによるアクセス制御
Sealの真の力は、プログラマブルアクセス制御にあります。特定の時間後にのみデータを復号化できるタイムロック暗号化の例を作成しましょう。
ステップ1:アクセス制御コントラクトのデプロイ
まず、アクセスポリシーを定義するMoveスマートコントラクトが必要です:
// contracts/time_lock.move
module time_lock::policy {
use sui::clock::{Self, Clock};
use sui::object::{Self, UID};
use sui::tx_context::{Self, TxContext};
public struct TimeLockPolicy has key, store {
id: UID,
unlock_time: u64,
authorized_user: address,
}
public fun create_time_lock(
unlock_time: u64,
authorized_user: address,
ctx: &mut TxContext
): TimeLockPolicy {
TimeLockPolicy {
id: object::new(ctx),
unlock_time,
authorized_user,
}
}
public fun can_decrypt(
policy: &TimeLockPolicy,
user: address,
clock: &Clock
): bool {
let current_time = clock::timestamp_ms(clock);
policy.authorized_user == user && current_time >= policy.unlock_time
}
}
ステップ2:Sealとの統合
// src/time-locked-encryption.ts
import { createSealClient } from './seal-client';
import { TransactionBlock } from '@mysten/sui/transactions';
async function createTimeLocked() {
const { sealClient, suiClient } = await createSealClient();
// Sui上でアクセスポリシーを作成
const txb = new TransactionBlock();
const unlockTime = Date.now() + 60000; // 1分後にアンロック
const authorizedUser = "0x742d35cc6d4c0c08c0f9bf3c9b2b6c64b3b4f5c6d7e8f9a0b1c2d3e4f5a6b7c8";
txb.moveCall({
target: 'time_lock::policy::create_time_lock',
arguments: [
txb.pure(unlockTime),
txb.pure(authorizedUser)
]
});
// ポリシーを作成するトランザクションを実行
const result = await suiClient.signAndExecuteTransactionBlock({
transactionBlock: txb,
signer: yourKeypair, // あなたのSuiキーペア
});
const policyId = result.objectChanges?.find(
change => change.type === 'created'
)?.objectId;
// このポリシーで暗号化
const sensitiveData = "これは1分後にアンロックされます!";
const encryptedData = await sealClient.encrypt({
data: Buffer.from(sensitiveData, 'utf-8'),
recipientId: authorizedUser,
accessPolicy: {
policyId,
policyType: 'time_lock'
}
});
console.log('タイムロックされたデータが作成されました。1分後に復号化を試してください。');
return {
encryptedData,
policyId,
unlockTime
};
}
実用的な例
例1:安全なメッセージングアプリケーション
// src/secure-messaging.ts
import { createSealClient } from './seal-client';
class SecureMessenger {
private sealClient: any;
constructor(sealClient: any) {
this.sealClient = sealClient;
}
async sendMessage(
message: string,
recipientAddress: string,
senderKeypair: any
) {
const messageData = {
content: message,
timestamp: Date.now(),
sender: senderKeypair.toSuiAddress(),
messageId: crypto.randomUUID()
};
const encryptedMessage = await this.sealClient.encrypt({
data: Buffer.from(JSON.stringify(messageData), 'utf-8'),
recipientId: recipientAddress,
metadata: {
type: 'secure_message',
sender: senderKeypair.toSuiAddress()
}
});
// 暗号化されたメッセージを分散ストレージ(Walrus)に保存
return this.storeOnWalrus(encryptedMessage);
}
async readMessage(encryptionId: string, recipientKeypair: any) {
// ストレージから取得
const encryptedData = await this.retrieveFromWalrus(encryptionId);
// Sealで復号化
const decryptedData = await this.sealClient.decrypt({
ciphertext: encryptedData.ciphertext,
encryptionId: encryptedData.encryptionId,
recipientId: recipientKeypair.toSuiAddress()
});
return JSON.parse(decryptedData.toString('utf-8'));
}
private async storeOnWalrus(data: any) {
// Walrusストレージとの統合
// 暗号化されたデータをWalrusにアップロードし
// 取得用のblob IDを返します
}
private async retrieveFromWalrus(blobId: string) {
// blob IDを使用してWalrusから暗号化されたデータを取得
}
}
例2:トークンゲート付きコンテンツプラットフォーム
// src/gated-content.ts
import { createSealClient } from './seal-client';
class ContentGating {
private sealClient: any;
private suiClient: any;
constructor(sealClient: any, suiClient: any) {
this.sealClient = sealClient;
this.suiClient = suiClient;
}
async createGatedContent(
content: string,
requiredNftCollection: string,
creatorKeypair: any
) {
// NFT所有権ポリシーを作成
const accessPolicy = await this.createNftPolicy(
requiredNftCollection,
creatorKeypair
);
// NFTアクセス要件でコンテンツを暗号化
const encryptedContent = await this.sealClient.encrypt({
data: Buffer.from(content, 'utf-8'),
recipientId: 'nft_holders', // NFTホルダー用の特別な受信者
accessPolicy: {
policyId: accessPolicy.policyId,
policyType: 'nft_ownership'
}
});
return {
contentId: encryptedContent.encryptionId,
accessPolicy: accessPolicy.policyId
};
}
async accessGatedContent(
contentId: string,
userAddress: string,
userKeypair: any
) {
// まずNFT所有権を確認
const hasAccess = await this.verifyNftOwnership(
userAddress,
contentId
);
if (!hasAccess) {
throw new Error('アクセスが拒否されました:必要なNFTが見つかりません');
}
// コンテンツを復号化
const decryptedContent = await this.sealClient.decrypt({
encryptionId: contentId,
recipientId: userAddress
});
return decryptedContent.toString('utf-8');
}
private async createNftPolicy(collection: string, creator: any) {
// NFT所有権をチェックするMoveコントラクトを作成
// ポリシーオブジェクトIDを返す
}
private async verifyNftOwnership(user: string, contentId: string) {
// ユーザーが必要なNFTを所有しているかチェック
// NFT所有権についてSuiにクエリ
}
}
例3:タイムロック資産転送
// src/time-locked-transfer.ts
import { createSealClient } from './seal-client';
async function createTimeLockTransfer(
assetData: any,
recipientAddress: string,
unlockTimestamp: number,
senderKeypair: any
) {
const { sealClient, suiClient } = await createSealClient();
// Sui上でタイムロックポリシーを作成
const timeLockPolicy = await createTimeLockPolicy(
unlockTimestamp,
recipientAddress,
senderKeypair,
suiClient
);
// 資産転送データを暗号化
const transferData = {
asset: assetData,
recipient: recipientAddress,
unlockTime: unlockTimestamp,
transferId: crypto.randomUUID()
};
const encryptedTransfer = await sealClient.encrypt({
data: Buffer.from(JSON.stringify(transferData), 'utf-8'),
recipientId: recipientAddress,
accessPolicy: {
policyId: timeLockPolicy.policyId,
policyType: 'time_lock'
}
});
console.log(`資産は${new Date(unlockTimestamp)}までロックされています`);
return {
transferId: encryptedTransfer.encryptionId,
unlockTime: unlockTimestamp,
policyId: timeLockPolicy.policyId
};
}
async function claimTimeLockTransfer(
transferId: string,
recipientKeypair: any
) {
const { sealClient } = await createSealClient();
try {
const decryptedData = await sealClient.decrypt({
encryptionId: transferId,
recipientId: recipientKeypair.toSuiAddress()
});
const transferData = JSON.parse(decryptedData.toString('utf-8'));
// 資産転送を処理
console.log('資産転送のロックが解除されました:', transferData);
return transferData;
} catch (error) {
console.error('転送がまだアンロックされていないか、アクセスが拒否されました:', error);
throw error;
}
}
Walrus分散ストレージとの統合
SealはSuiの分散ストレージソリューションであるWalrusとシームレスに連携します。両方を統合する方法は以下の通りです:
// src/walrus-integration.ts
import { createSealClient } from './seal-client';
class SealWalrusIntegration {
private sealClient: any;
private walrusClient: any;
constructor(sealClient: any, walrusClient: any) {
this.sealClient = sealClient;
this.walrusClient = walrusClient;
}
async storeEncryptedData(
data: Buffer,
recipientAddress: string,
accessPolicy?: any
) {
// Sealで暗号化
const encryptedData = await this.sealClient.encrypt({
data,
recipientId: recipientAddress,
accessPolicy
});
// 暗号化されたデータをWalrusに保存
const blobId = await this.walrusClient.store(
encryptedData.ciphertext
);
// SealとWalrusの両方の情報を含む参照を返す
return {
blobId,
encryptionId: encryptedData.encryptionId,
accessPolicy: encryptedData.accessPolicy
};
}
async retrieveAndDecrypt(
blobId: string,
encryptionId: string,
userKeypair: any
) {
// Walrusから取得
const encryptedData = await this.walrusClient.retrieve(blobId);
// Sealで復号化
const decryptedData = await this.sealClient.decrypt({
ciphertext: encryptedData,
encryptionId,
recipientId: userKeypair.toSuiAddress()
});
return decryptedData;
}
}
// 使用例
async function walrusExample() {
const { sealClient } = await createSealClient();
const walrusClient = new WalrusClient('https://walrus-testnet.sui.io');
const integration = new SealWalrusIntegration(sealClient, walrusClient);
const fileData = Buffer.from('重要なドキュメントの内容');
const recipientAddress = '0x...';
// 暗号化して保存
const result = await integration.storeEncryptedData(
fileData,
recipientAddress
);
console.log('Blob IDで保存されました:', result.blobId);
// 後で取得して復号化
const decrypted = await integration.retrieveAndDecrypt(
result.blobId,
result.encryptionId,
recipientKeypair
);
console.log('取得されたデータ:', decrypted.toString());
}
閾値暗号化の高度な設定
本番アプリケーションでは、複数のキーサーバーでカスタム閾値暗号化を設定したいでしょう:
// src/advanced-threshold.ts
import { SealClient } from '@mysten/seal';
async function setupProductionSeal() {
// 複数の独立したキーサーバーで設定
const keyServers = [
'https://keyserver-1.your-org.com',
'https://keyserver-2.partner-org.com',
'https://keyserver-3.third-party.com',
'https://keyserver-4.backup-provider.com',
'https://keyserver-5.fallback.com'
];
const sealClient = new SealClient({
keyServers,
threshold: 3, // 3-of-5閾値
network: 'mainnet',
// 高度なオプション
retryAttempts: 3,
timeoutMs: 10000,
backupKeyServers: [
'https://backup-1.emergency.com',
'https://backup-2.emergency.com'
]
});
return sealClient;
}
async function robustEncryption() {
const sealClient = await setupProductionSeal();
const criticalData = "ミッションクリティカルな暗号化データ";
// 高いセキュリティ保証で暗号化
const encrypted = await sealClient.encrypt({
data: Buffer.from(criticalData, 'utf-8'),
recipientId: '0x...',
// 最大セキュリティのため全5サーバーを要求
customThreshold: 5,
// 冗長性を追加
redundancy: 2,
accessPolicy: {
// 多要素要件
requirements: ['nft_ownership', 'time_lock', 'multisig_approval']
}
});
return encrypted;
}
セキュリティベストプラクティス
1. キー管理
// src/security-practices.ts
// 良い例:安全なキー導出を使用
import { generateKeypair } from '@mysten/sui/cryptography/ed25519';
const keypair = generateKeypair();
// 良い例:キーを安全に保存(環境変数の例)
const keypair = Ed25519Keypair.fromSecretKey(
process.env.PRIVATE_KEY
);
// 悪い例:キーをハードコードしない
const badKeypair = Ed25519Keypair.fromSecretKey(
"hardcoded-secret-key-12345" // これはしないでください!
);
2. アクセスポリシーの検証
// 暗号化前に必ずアクセスポリシーを検証
async function secureEncrypt(data: Buffer, recipient: string) {
const { sealClient } = await createSealClient();
// 受信者アドレスを検証
if (!isValidSuiAddress(recipient)) {
throw new Error('無効な受信者アドレスです');
}
// ポリシーが存在し有効であることをチェック
const policy = await validateAccessPolicy(policyId);
if (!policy.isValid) {
throw new Error('無効なアクセスポリシーです');
}
return sealClient.encrypt({
data,
recipientId: recipient,
accessPolicy: policy
});
}
3. エラーハンドリングとフォールバック
// 堅牢なエラーハンドリング
async function resilientDecrypt(encryptionId: string, userKeypair: any) {
const { sealClient } = await createSealClient();
try {
return await sealClient.decrypt({
encryptionId,
recipientId: userKeypair.toSuiAddress()
});
} catch (error) {
if (error.code === 'ACCESS_DENIED') {
throw new Error('アクセスが拒否されました:権限を確認してください');
} else if (error.code === 'KEY_SERVER_UNAVAILABLE') {
// バックアップ設定で再試行
return await retryWithBackupServers(encryptionId, userKeypair);
} else if (error.code === 'THRESHOLD_NOT_MET') {
throw new Error('利用可能なキーサーバーが不十分です');
} else {
throw new Error(`復号化に失敗しました: ${error.message}`);
}
}
}