@mysten/seal로 탈중앙화 암호화 구축하기: 개발자 튜토리얼
프라이버시가 공공 인프라가 되고 있습니다. 2025년에 개발자들은 데이터 저장만큼 쉽게 암호화를 수행할 수 있는 도구가 필요합니다. Mysten Labs의 Seal은 바로 그것을 제공합니다—온체인 접근 제어가 있는 탈중앙화 비밀 관리입니다. 이 튜토리얼은 신원 기반 암호화, 임계값 보안, 프로그래밍 가능한 접근 정책을 사용하여 안전한 Web3 애플리케이션을 구축하는 방법을 알려드립니다.
소개: Web3에서 Seal이 중요한 이유
기존의 클라우드 애플리케이션은 단일 제공업체가 암호화된 데이터에 대한 접근을 제어하는 중앙화된 키 관리 시스템에 의존합니다. 편리하지만, 이는 위험한 단일 장애점을 만듭니다. 제공업체가 손상되거나, 오프라인이 되거나, 접근을 제한하기로 결정하면 데이터에 접근할 수 없거나 취약해집니다.
Seal은 이 패러다임을 완전히 바꿉니다. Sui 블록체인을 위해 Mysten Labs에서 구축한 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 임계값 체계를 사용합니다. 5개 중 3개 키 서버를 구성할 수 있으며, 이는 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, // 3개 중 2개 임계값
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 keypair
});
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;
}
}