
PromptVault 구축하기: 완전 동형 암호화 (Fully Homomorphic Encryption)를 활용한 암호화된 AI 프롬프트
요약
완전 동형 암호화(FHE)를 활용하여 프롬프트 제작자의 지적 재산을 보호하면서 구매자에게 안전하게 전달하는 PromptVault 구축 방법을 소개합니다. AES-256과 FHE를 결합한 하이브리드 암호화 모델을 통해 데이터 크기 제한을 극복하고 신뢰할 수 있는 프롬프트 거래 환경을 구현합니다.
핵심 포인트
- FHE를 활용해 복호화 없이 암호화된 데이터 연산 가능
- AES-256과 FHE를 결합한 2계층 하이브리드 암호화 모델 적용
- 스마트 컨트랙트를 통한 원자적(Atomic) 권한 부여 및 결제 프로세스
- IPFS와 블록체인을 활용한 프롬프트 데이터 및 키 관리
문제점
AI 프롬프트는 디지털 자산이 되었습니다. 정교하게 제작된 프롬프트는 수 시간의 반복 작업을 줄여줄 수 있으며, 이는 일반적인 ChatGPT 응답과 진정으로 유용한 응답 사이의 차이를 만듭니다. 사람들은 이러한 프롬프트를 엔지니어링하는 데 실제 시간을 투자하며, 마켓플레이스는 자연스러운 다음 단계입니다.
하지만 근본적인 긴장 관계가 존재합니다: 구매자는 프롬프트의 가치를 알기 위해 이를 확인해야 하지만, 제작자는 대가를 받지 않고서는 이를 공개할 수 없습니다.
전통적인 마켓플레이스는 "나를 믿으세요"라는 방식으로 이 문제를 해결합니다. 즉, 당신이 결제하면 판매자가 콘텐츠를 보내주는 방식입니다. 이는 작동하는 듯 보이지만, 결국 문제가 발생합니다. 차지백(Chargebacks), 가짜 프롬프트, 판매 후 사라지는 판매자 등이 그 예입니다.
PromptVault는 신뢰를 암호학(Cryptography)으로 대체합니다.
왜 완전 동형 암호화 (Fully Homomorphic Encryption)인가?
완전 동형 암호화 (Fully Homomorphic Encryption, FHE)는 데이터를 복호화하지 않고도 암호화된 데이터에 대해 연산을 수행할 수 있게 해주는 암호학적 프리미티브 (Cryptographic primitive)입니다. Fhenix의 CoFHE 코프로세서 (Coprocessor)는 이를 Ethereum으로 가져옵니다. 스마트 컨트랙트 (Smart contracts)는 FHE.add(), FHE.mul(), 그리고 우리의 사용 사례에서 가장 중요한 FHE.allow()와 같은 연산을 사용하여 암호화된 값을 조작할 수 있습니다.
FHE.allow(encryptedValue, address)는 특정 지갑에 암호문 (Ciphertext)을 복호화할 수 있는 권한을 부여합니다. 스마트 컨트랙트는 이를 원자적(Atomically)으로 수행할 수 있습니다. 즉, 구매자가 결제하면 컨트랙트가 동일한 트랜잭션 내에서 FHE.allow()를 호출합니다. 별도의 단계도 필요 없고, 신뢰도 필요하지 않습니다.
이것이 핵심적인 가능 기술입니다: 암호화 키는 온체인 (On-chain), API 응답, 또는 그 어떤 데이터베이스에서도 평문 (Plaintext) 상태로 절대 나타나지 않습니다. 키는 온체인에 저장된 FHE 암호문으로부터 재구성되어, 권한을 부여받은 구매자 또는 제작자의 브라우저 내에서만 일시적으로 존재합니다.
아키텍처 결정: AES + FHE 하이브리드
순수 FHE에는 제약 사항이 있습니다: 각 암호화된 값은 128비트(euint128)로 제한됩니다. 일반적인 AI 프롬프트는 200~2,000바이트입니다. 이를 FHE로 직접 암호화하는 것은 비실용적입니다.
해결책은 2계층 암호화 모델입니다:
| 계층 (Layer) | 알고리즘 (Algorithm) | 암호화 대상 | 저장 위치 |
|---|---|---|---|
| 1 | AES-256-GCM | 프롬프트 본문 (길이 제한 없음) | IPFS (Pinata) |
| 2 | FHE (CoFHE) | AES-256 키 (2×128b 절반으로 분할) | Sepolia 블록체인 |
이 하이브리드 (Hybrid) 접근 방식은 크기 제한을 완전히 제거합니다. 빠른 대칭 암호 (Symmetric cipher)인 AES-256-GCM으로 프롬프트 본문을 암호화한 다음, FHE를 사용하여 대칭 키에 대한 접근을 제어 (Gate) 합니다. IPFS에 있는 프롬프트 암호문 (Ciphertext)은 공개되어 있지만, 온체인(On-chain)에서 FHE로 제어되는 AES 키 없이는 무용지물입니다.
256비트 AES 키는 두 개의 128비트 절반(key0 및 key1)으로 분할되며, 각각 온체인에 euint128로 저장됩니다. 이를 재구성하려면 빅 엔디언 (Big-endian) 바이트 순서로 bytes32(key0 << 128 | key1)를 수행합니다.
스마트 컨트랙트 (Smart Contract)
PromptVault.sol
Solidity 컨트랙트는 시스템의 핵심입니다. 이 컨트랙트는 FHE.sol 라이브러리를 위해 @fhenixprotocol/cofhe-contracts를 사용합니다.
배포된 주소 (Deployed address): Sepolia 상의 0xc369bd84AE4a4468DA5635619e62F942BeaF5DA3.
리스팅 흐름 (Listing Flow)
function listPrompt(
InEuint128 calldata encryptedKey0,
InEuint128 calldata encryptedKey1,
...
생성자는 브라우저에서 CoFHE SDK를 사용하여 AES 키의 절반을 암호화하고, InEuint128 구조체 (암호문 + 영지식 증명 (ZK proof))를 컨트랙트로 전송합니다. 컨트랙트는 FHE.asEuint128()을 통해 영지식 증명을 검증하고, 암호화된 핸들 (Handles)을 저장하며, 두 개의 ACL (액세스 제어 목록) 항목을 설정합니다:
FHE.allowThis()— 컨트랙트가 접근 권한을 유지하여 나중에 구매자에게 접근 권한을 부여할 수 있도록 합니다.FHE.allowSender()— 생성자가 자신의 리스팅을 검증할 수 있도록 합니다.
프롬프트 본문 (AES 암호문)은 Pinata를 통해 IPFS로 전송됩니다. CID는 온체인에 문자열로 저장됩니다.
구매 흐름 (Purchase Flow)
function purchasePrompt(uint256 listingId) external payable
구매자는 priceWei와 동일한 양의 ETH를 전송합니다. 컨트랙트는:
- 리스팅이 활성 상태인지, 구매자가 이미 구매하지 않았는지, 그리고 구매자가 생성자가 아닌지 확인합니다.
FHE.allow(keyPart0, buyer)및FHE.allow(keyPart1, buyer)를 호출하여 복호화 권한을 영구적으로 부여합니다.- 구매를 기록합니다.
- 결제 금액을 분할합니다: 생성자에게 97.5%, 플랫폼 수수료로 2.5%를 배분합니다.
핵심 설계 특성: FHE.allow()는 **영구적 (permanent)**입니다. 일단 구매자가 권한을 얻으면 이를 취소할 수 없습니다. 이는 생성자가 리스팅을 삭제하더라도 구매자가 계속해서 접근 권한을 유지함을 의미하며, 이는 원자성 (atomicity)을 위한 의도적인 트레이드오프입니다 (권한 취소가 불가능해야 접근 제어에 대한 경합 조건 (race conditions)이 발생하지 않습니다).
수익 모델 (Revenue Model)
플랫폼은 250 베이시스 포인트 (2.5%)를 수취합니다. feeRecipient는 생성자 (constructor)에서 배포자 (deployer)로 설정됩니다. 수익은 매핑 (mapping)에 누적되며, withdrawEarnings()를 통해 언제든지 인출할 수 있습니다. 컨트랙트는 재진입 공격 (re-entrancy)을 방지하기 위해 외부 호출 전에 잔액을 0으로 만드는 checks-effects-interactions 패턴을 따릅니다.
주요 뷰 함수 (Key View Functions)
getListing(listingId)— 모든 메타데이터 (제목, 카테カテゴリ, 가격, 판매 횟수 등)를 반환하지만, 키 핸들 (key handles)은 반환하지 않습니다.getKeyHandles(listingId)—euint128핸들을 반환하지만, 생성자 또는 인증된 구매자에게만 허용됩니다 (그 외의 경우NotAuthorised와 함께 리버트 (revert)됩니다).getListings(offset, limit)— 페이지네이션 (paginated) 처리된 리스팅 ID들을 반환합니다.hasPurchased(listingId, buyer)— 구매 상태를 확인합니다.pendingEarnings(account)— 미지급된 수익 잔액을 확인합니다.
에러 핸들링 (Error Handling)
컨트랙트는 가스 효율성 (gas efficiency)을 위해 문자열 메시지 대신 커스텀 에러 (Solidity revert)를 사용합니다: ListingDoesNotExist, AlreadyPurchased, IncorrectPayment, CreatorCannotBuyOwnPrompt를 포함하여 모든 예외 상황을 다루는 10개 이상의 에러가 정의되어 있습니다.
테스트 커버리지 (Test Coverage)
9개의 describe 블록에 걸쳐 41개의 테스트를 수행하며, 배포, 모든 검증 경로를 포함한 리스팅 생성, 구매 흐름 (성공 및 모든 에러 상태), 리스팅 삭제, 수익 및 인출, 권한이 있는 키 핸들 접근, 그리고 페이지네이션된 열거 (enumeration)를 다룹니다.
프론트엔드 (The Frontend)
스택 (Stack)
| 계층 (Layer) | 선택 (Choice) |
|---|---|
| 프레임워크 (Framework) | Next.js 14 (App Router) |
| ... |
데이터 흐름 (Data Flow): 프롬프트 목록화 (Listing a Prompt)
이것은 애플리케이션에서 가장 복잡한 작업으로, 서로 다른 시스템에 걸쳐 여러 암호화 연산 (cryptographic operations)을 포함합니다:
브라우저 (browser) → AES-256 키 생성 (keygen) → 2개의 절반으로 분할 → AES로 프롬프트 암호화
→ IPFS에 암호문 (ciphertext) 업로드 → CID 획득
→ CoFHE SDK로 키 절반을 암호화 → InEuint128 구조체 획득
...
useListPrompt 훅이 이 전체 파이프라인을 조율합니다. 핵심 세부 사항: AES 키는 Signal, WhatsApp, 그리고 모든 현대적 브라우저에서 사용하는 것과 동일한 프리미티브(primitive)인 { name: "AES-GCM", length: 256 }를 사용하여 브라우저의 crypto.subtle.generateKey()로 생성됩니다. 키는 로우 바이트 (raw bytes)로 내보내진 후, 두 개의 bigint 값(각 128비트)으로 분할되며, 컨트랙트로 전송되기 전에 CoFHE SDK에 의해 암호화됩니다.
데이터 흐름 (Data Flow): 프롬프트 구매 (Purchasing a Prompt)
구매자 (buyer) → wallet.writeContract("purchasePrompt", id, { value })
→ FHE.allow(key0, buyer) + FHE.allow(key1, buyer) [원자적 트랜잭션 (atomic tx)]
→ contract.read.getKeyHandles(id) → euint128 핸들 (handles)
...
중요한 세부 사항: getKeyHandles()는 view 함수이므로 가스 (gas) 비용이 발생하지 않습니다. 구매자는 purchasePrompt 트랜잭션에 대해서만 가스를 지불합니다. 키 핸들 (key handles)은 트랜잭션이 확정된 후에 가져오며, CoFHE SDK가 브라우저 내에서 로컬로 이를 복호화합니다. AES 키는 브라우저 네이티브 CryptoKey 객체로 실체화되며 절대 저장되지 않습니다 (never persisted) — localStorage, 쿠키, 네트워크 전송 모두 사용하지 않습니다. 사용자가 페이지를 벗어나면 키는 사라집니다.
SSR 안전성 (SSR Safety) 및 CoFHE SDK
CoFHE SDK는 내부적으로 IndexedDB를 사용하는데(캐시된 FHE 키 및 WASM 모듈용), 이는 서버에는 존재하지 않습니다. 클라이언트는 typeof window === "undefined"로 보호되는 동적 import("@cofhe/sdk/web")를 통해 초기화됩니다. cofhe.ts의 싱글톤 패턴 (singleton pattern)은 지연 초기화 (lazy initialisation)를 통해 단일 클라이언트 인스턴스를 보장합니다:
async function initClient() {
if (typeof window === "undefined") return;
const [{ createCofheClient, createCofheConfig }, { sepolia }] =
...
버전 디버깅: 0.4.0 → 0.5.2 마이그레이션
개발 과정에서 encryptInputs()를 호출할 때 다음과 같은 난해한 오류를 마주했습니다:
Failed to fetch FHE key and CRS
Caused by: Error serializing FHE publicKey
Error: Custom("invalid value: integer `7809075072243073024`, expected usize")
16진수(hex)로 변환한 7809075072243073024 값은 0x6C6F6F6B이며, 이는 ASCII 코드로 "look"을 의미합니다. CoFHE SDK가 이진(binary) FHE 키 데이터 대신 텍스트 응답(아마도 오류 페이지나 리다이렉트 페이지)을 받았고, bincode 역직렬화기(deserializer)가 해당 텍스트를 usize로 해석하려고 시도했던 것입니다.
근본 원인은 @cofhe/sdk 0.4.0 버전에 오래된 테스트넷 코디네이터(testnet coordinator) URL이 포함되어 있었기 때문입니다. SDK가 이전 엔드포인트에서 FHE 공개 키(public key)를 가져오려 할 때 예상치 못한 응답을 받은 것입니다. (업데이트된 체인 설정이 포함된) @cofhe/sdk 0.5.2 버전으로 업그레이드함으로써 이 문제가 해결되었습니다.
WYSIWYG 마크다운 에디터
프롬프트 내용 필드는 실시간 미리보기(live-preview) 모드의 @uiw/react-md-editor를 사용합니다. 사용자는 전체 도구 모음(굵게, 기울임꼴, 제목, 목록, 코드 블록)을 사용할 수 있으며, 에디터 아래에서 렌더링된 마크다운을 실시간으로 확인할 수 있습니다. 프롬프트는 가공되지 않은(raw) 마크다운 텍스트로 제출됩니다. AES 암호화는 이 원시 텍스트에 대해 수행되며, 구매자는 복호화 후 react-markdown을 통해 렌더링된 결과를 보게 됩니다.
보안 모델
신뢰 가정 (Trust assumptions):
├── 제작자는 프롬프트 내용에 대해 정직함
├── 구매자는 정당한 지갑을 보유함 (Sybil 방어 기제 없음)
...
위협 모델(threat model)은 설계상 신뢰가 필요 없는(trustless) 컨트랙트 자체를 제외한 모든 당사자에 대해 '정직하지만 호기심 많은(honest-but-curious)' 모델을 따릅니다. AES+FHE 하이브리드 아키텍처를 사용하므로 IPFS 게이트웨이, RPC 제공자 또는 Vercel 배포 환경이 침해되더라도 프롬프트 내용은 유출되지 않습니다. 복호화할 수 있는 유일한 방법은 컨트랙트가 FHE.allow()를 통해 권한을 부여한 지갑을 보유하는 것뿐입니다.
이것은 DRM (Digital Rights Management)이 아닙니다. 구매자가 프롬프트를 복호화(Decrypt)하면 이를 공유할 수 있습니다. 우리는 불법 복제를 방지하는 것이 아니라, 발견 및 결제(Discovery-and-payment) 문제를 해결하고 있습니다.
내가 다르게 했을 점
-
CoFHE SDK 버전 고정 (version pinning): 정확한 버전에 고정하고 실제 테스트넷(Testnet)을 대상으로 더 일찍 테스트했다면 엔드포인트 불일치 문제를 더 빨리 발견했을 것입니다. 0.4.0에서 0.5.2로의 점프는 viem 업그레이드와 타입 캐스팅 (Type casting)을 필요로 했습니다.
-
필드 크기 고려 (Field size consideration):
euint128은 AES-128에 충분하지만, AES-256을 위해서는 두 개의 핸들(Handle)이 필요합니다. 만약 CoFHE 프로토콜이euint256지원을 추가한다면, 스마트 컨트랙트는 단일 핸들을 저장하도록 단순화될 수 있습니다. -
로컬 허가 캐싱 (Local permit caching): CoFHE SDK의 허가(Permit) 시스템은 세션마다 MetaMask 서명을 요구합니다.
sessionStorage에 허가 해시(Permit hash)를 캐싱하면 페이지 새로고침 시 발생하는 중복 서명 단계를 제거할 수 있습니다.
코드
모든 코드는 GitHub에 오픈 소스로 공개되어 있습니다: harishkotra/promptvault
contracts/PromptVault.sol— ACL(Access Control List) 기반의 키 게이팅(Key gating) 기능이 포함된 FHE 지원 스마트 컨트랙트frontend/src/lib/aes.ts— Web Crypto API를 사용한 AES-256-GCM 키 생성, 분할, 재구성, 암호화 및 복호화frontend/src/lib/cofhe.ts— SSR(Server-Side Rendering) 안전성을 갖춘 CoFHE 클라이언트 싱글톤(Singleton)frontend/src/lib/ipfs.ts— Pinata 기반의 IPFS 업로드 및 다운로드frontend/src/hooks/useListPrompt.ts— 엔드 투 엔드 암호화 (End-to-end encryption) 파이프라인frontend/src/hooks/usePurchasePrompt.ts— 온체인(On-chain) 구매 및 로컬 복호화 흐름frontend/src/lib/contract.ts— 완전한 함수 및 에러 시그니처를 포함한 전체 ABI
사용된 기술
- Fhenix CoFHE — Ethereum을 위한 FHE 코프로세서 (Coprocessor)
- Hardhat — 스마트 컨트랙트 개발
- Next.js 14 — React 프레임워크
- Wagmi — React를 위한 Ethereum 훅 (Hooks)
- shadcn/ui — 컴포넌트 라이브러리
- Pinata — IPFS 피닝 (Pinning) 서비스
스크린샷
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기