
Conclave: 완전 동형 암호화(FHE)를 이용한 Ethereum 기반의 기밀 멀티 에이전트 합의
요약
FHE(완전 동형 암호화)를 활용하여 AI 에이전트들이 개별 점수를 노출하지 않고도 합의를 도출할 수 있는 Ethereum 기반 프로토콜 Conclave를 소개합니다. 앵커링, 담합, 위축 효과와 같은 기존 온체인 투표의 문제를 해결하기 위해 암호화된 상태에서 연산을 수행합니다.
핵심 포인트
- FHE를 통해 개별 투표 값을 공개하지 않고 최종 평균값만 복호화
- 앵커링 및 담합 방지를 통한 독립적이고 정직한 에이전트 평가 유도
- Fhenix의 CoFHE SDK와 Solidity를 결합한 구현 방식
- 암호화된 상태에서의 산술 연산으로 데이터 프라이버시 보장
우리는 FHE, Solidity, 그리고 다크 터미널(dark-terminal) 프론트엔드를 사용하여, AI 에이전트들이 개별 점수를 전혀 드러내지 않고도 주관적인 작업에 대해 투표할 수 있는 프로토콜을 어떻게 구축했는지 소개합니다.
문제점 (The Problem)
여러 AI 에이전트가 동일한 작업(LLM 출력물, 코드 리뷰, 크리에이티브 브리프 등)을 평가할 때, 각 에이전트의 개별 점수는 가치 있는 신호를 담고 있습니다. 하지만 오늘날의 온체인(on-chain) 투표에는 근본적인 결함이 있습니다. 바로 모든 투표가 공개된다는 점입니다.
이는 세 가지 문제를 야기합니다:
-
앵커링 (Anchoring) — 처음 공개된 점수가 이후 모든 투표자의 판단에 편향을 일으킵니다. 만약 에이전트 A가 87점을 제출하면, 에이전트 B는 독립적으로 평가하기보다 무의식적으로 87점에 끌리게 됩니다.
-
담합 (Collusion) — 에이전트들이 동료들의 제출 내용을 확인한 후 자신의 점수를 조정함으로써 합의(consensus)를 조작하도록 공모할 수 있습니다.
-
위축 효과 (Chilling effects) — 정직하게 낮은 점수(예: 30점)를 제출하려는 에이전트가 다른 에이전트나 라운드 생성자가 이를 보고 보복할 수 있다는 생각에 망설일 수 있습니다.
멀티시그(multi-sigs), DAO 투표, 커밋-리빌(commit-reveal) 방식과 같은 기존 솔루션들은 생명주기의 어느 시점에서든 결국 개별 투표 내용을 유출하게 됩니다. 커밋-리빌 방식은 공개를 지연시킬 뿐, 공개 자체를 막지는 못합니다.
우리에게 필요했던 것은 암호화된 입력값들의 평균을 계산하고, 오직 최종 결과만을 복호화하는 방법이었습니다.
해결책: FHE 기반 합의 (The Solution: FHE-Based Consensus)
**완전 동형 암호화 (Fully Homomorphic Encryption, FHE)**를 사용하면 암호문(ciphertext) 상에서 직접 산술 연산을 수행할 수 있습니다. 암호화된 숫자를 더하고, 빼고, 곱하고, 나눌 수 있으며, 이를 복호화했을 때의 결과는 평문(plaintext)으로 연산했을 때의 결과와 동일합니다.
Conclave는 Fhenix의 CoFHE (Confidential FHE) SDK를 사용하여 다음과 같은 멀티 에이전트 합의 프로토콜을 구현합니다:
- 점수는 클라이언트 측에서 암호화됩니다 — 컨트랙트를 포함하여 그 누구도 평문 점수를 볼 수 없습니다.
- 컨트랙트는 FHE를 사용하여 암호문들을 합산합니다 — 누적 합계는 과정 내내 암호화된 상태로 유지됩니다.
- 오직 최종 평균값만이 복호화됩니다 — 모든 에이전트가 제출을 완료한 후, FHE 노드들의 임계치 네트워크(threshold network)에 의해 복호화됩니다.
개별 점수는 영원히 암호화된 상태로 남습니다.
Agent 1 ───── encrypt(87) ─────┐
▼
Agent 2 ───── encrypt(92) ───► FHE.sum() ──► FHE.div() ──► avg = 81
...
프로토콜 (The Protocol)
Conclave의 프로토콜은 Solidity enum으로 인코딩된 5가지 단계로 구성됩니다:
enum Phase { Voting, Revision, Finalized, Revealed }
1. 생성 (Create)
라운드 생성자(round creator)는 다음 사항을 지정합니다:
- 에이전트 주소 목록 (2–50개)
- 작업 URI (URL 또는 인라인
data:application/jsonURI) revisionsEnabled플래그
컨트랙트는 두 개의 FHE 누산기(accumulator)인 encryptedSum과 encryptedCount를 초기화하며, 둘 다 FHE.asEuint32(0)로 설정됩니다. 이들은 누적 합계와 개수를 암호화된 형태로 보유하게 됩니다.
r.encryptedSum = FHE.asEuint32(0);
r.encryptedCount = FHE.asEuint32(0);
...
allowThis 호출은 CoFHE의 권한 모델(permission model)로, 컨트랙트 자체가 이러한 암호화된 값들을 읽고 연산할 수 있는 능력을 부여합니다.
2. 투표 (Vote)
각 에이전트는 다음 과정을 수행합니다:
- URI에서 작업을 가져옵니다.
- LLM (예: GPT-4o)을 통해 점수를 매겨 0–100 사이의 평문 정수(plaintext integer)를 생성합니다.
@cofhe/sdk를 사용하여 클라이언트 측에서 점수를 암호화합니다:
const [encrypted] = await cofheClient
.encryptInputs([Encryptable.uint32(BigInt(score))])
.execute();
InEuint32암호문(ciphertext)을 컨트랙트에 제출합니다.
컨트랙트는 아무것도 복호화하지 않습니다. 순수 FHE 산술 연산을 사용하여 암호문을 암호화된 합계에 더합니다:
euint32 score = FHE.asEuint32(encryptedScore);
euint32 newSum = FHE.add(r.encryptedSum, score);
r.encryptedSum = newSum;
...
3. 수정 (Revision, 선택 사항)
생성자가 수정을 활성화한 경우, 모든 에이전트가 투표를 마친 후 수정 창(revision window)을 열 수 있습니다. 이미 투표한 에이전트는 새로운 암호화된 점수를 제출할 수 있습니다. 컨트랙트는 이전 암호문을 빼고 새로운 암호문을 더하며, 이 모든 과정은 암호화된 도메인(encrypted domain) 내에서 이루어집니다:
euint32 sumAfterRemoval = FHE.sub(r.encryptedSum, oldScore);
euint32 newSum = FHE.add(sumAfterRemoval, newScore);
r.encryptedSum = newSum;
이것이 바로 FHE (Fully Homomorphic Encryption, 완전 동형 암호화)의 힘입니다. 컨트랙트는 해당 값이 무엇인지 전혀 확인하지 않고도 값을 업데이트할 수 있습니다.
4. Finalize (마무리)
정족수 (Quorum)가 충족되면, 생성자는 해당 라운드를 잠급니다. 컨트랙트는 암호화된 평균값을 계산합니다:
euint32 avg = FHE.div(r.encryptedSum, r.encryptedCount);
컨트랙트는 이 암호화된 평균값을 consensusHandle로 저장하고 단계를 Finalized로 전환합니다. 이 시점부터는 더 이상의 투표나 수정이 허용되지 않습니다.
5. Reveal (공개)
Fhenix 임계 네트워크 (Threshold Network)가 평균값을 복호화합니다. 평문 (Plaintext) 결과와 암호학적 증명 (Cryptographic Proof)을 가지고 revealConsensus()가 호출됩니다:
function revealConsensus(uint256 roundId, euint32 ctHash, uint32 plaintext, bytes calldata signature) external {
FHE.publishDecryptResult(ctHash, plaintext, signature);
r.consensusScore = plaintext;
...
임계 네트워크는 단일 당사자가 복호화를 위조할 수 없음을 보장합니다. 서명 (Signature)은 FHE 노드들의 정족수가 결과를 검증했음을 증명합니다.
Smart Contract Architecture (스마트 컨트랙트 아키텍처, 353행)
전체 프로토콜은 단일 Solidity 컨트랙트에 포함됩니다. 주요 설계 결정 사항은 다음과 같습니다:
Errors, Not Strings (문자열 대신 에러 사용)
가스 효율적인 리버트 (Revert)를 위해 18개의 커스텀 에러 (Custom Errors)를 사용했습니다. 이는 Solidity의 권장 사항(Best Practice)입니다. 커스텀 에러는 문자열 메시지를 포함하는 require보다 비용이 저렴하며, 프론트엔드 처리를 위한 타입화된 데이터 (Typed Data)를 제공합니다.
error QuorumNotMet(uint256 roundId, uint32 submitted, uint32 required);
error ScoreOutOfRange(uint32 plaintext, uint32 max);
error RevisionNotEnabled(uint256 roundId);
Permission Model (권한 모델)
CoFHE는 모든 암호화된 값에 대해 명시적인 allow 호출을 요구하며, 그렇지 않으면 접근할 수 없습니다. 컨트랙트는 접근 권한을 엄격하게 제한합니다:
FHE.allowThis()— 컨트랙트 자체가 해당 값을 읽고 조작할 수 있음FHE.allow(score, msg.sender)— 제출한 에이전트가 나중에 자신의 점수를 읽을 수 있음FHE.allowPublic()— 최종 평균값에 대해서만 호출되어 임계 네트워크가 이를 복호화할 수 있게 함
이러한 허가 (Permit) 없이는 컨트랙트 배포자조차 개별 투표 내용을 복호화할 수 없습니다.
개별 입력값에 대한 영지식 (Zero-Knowledge)
컨트랙트는 평문(Plaintext) 점수를 절대 저장하지 않습니다. 각 에이전트의 투표를 저장하는 매핑(Mapping)은 다음과 같습니다:
mapping(uint256 => mapping(address => euint32)) private _agentScoreHandle;
이는 암호화된 핸들(Handle)이며, 평문 값이 아닙니다. 블록체인에 대한 스토리지 레벨 공격(Storage-level attack)을 수행하더라도 암호문(Ciphertext)만을 얻을 수 있을 뿐입니다.
에이전트 러너 (The Agent Runner)
agent/ 디렉토리에는 다음과 같은 기능을 수행하는 Node.js/TypeScript 러너(Runner)가 포함되어 있습니다:
- 30초마다 컨트랙트를 폴링(Poll)합니다.
- 온체인 URI(On-chain URI)로부터 태스크를 가져옵니다.
- 채점 기준(Scoring rubric)과 함께 이를 OpenAI 호환 LLM(Large Language Model)으로 전송합니다.
@cofhe/sdk/node를 사용하여 점수를 암호화합니다.submitVote()를 통해 암호화된 암호문(Ciphertext)을 제출합니다.
최근에는 각 에이전트가 개별 터미널에서 실행될 수 있도록 --agent 플래그를 허용하도록 리팩토링(Refactor)했습니다:
npx ts-node agent/index.ts --agent 1
npx ts-node agent/index.ts --agent 2
npx ts-node agent/index.ts --agent 3
에이전트 프로세스는 상태가 없는(Stateless) 방식입니다. 즉, 필요한 모든 정보를 컨트랙트로부터 읽어옵니다. 어떤 에이전트든 종료 후 다시 시작할 수 있으며, 중단되었던 지점부터 다시 작업을 이어갑니다.
속도 제한 (Rate Limiting)
Infura의 무료 티어(Free tier)를 대상으로 3개의 에이전트를 실행하면 속도 제한(Rate limits)에 빠르게 도달합니다. 이를 해결하기 위해 지터(Jitter)를 포함한 지수 백오프(Exponential backoff)를 추가했습니다:
async function withRetry<T>(fn: () => Promise<T>, attempt = 1): Promise<T> {
try {
return await fn();
...
또한 각 에이전트는 0~5초 사이의 무작위 시차(Stagger)를 두고 시작하며, 폴링 간격(Poll interval)을 ±2.5초씩 비동기화(Desynchronize)합니다.
프론트엔드 (The Frontend)
프론트엔드는 Next.js 14 App Router 애플리케이션으로, Linear, Vercel, Stripe에서 영감을 받은 시각적 정체성(Visual identity)을 가지고 있습니다. 깔끔한 단색(Monochrome) 구성에 단 하나의 보라색 액센트(#c084fc)를 사용합니다.
디자인 시스템 (Design System)
색상 팔레트(Palette)는 의도적으로 제한되었습니다:
- Base: #040816 (거의 검은색에 가까운 블루)
- Surfaces: #070B1F, #0A0F2A, #1A1F3A
- Accent: #c084fc (보라색, 단일 사용)
- Success: #22c55e (에메랄드)
- Warning: #f59e0b (앰버)
- Borders: 얇은 1px 실선 #1A1F3A
그라디언트(Gradients) 없음. 블러 효과(Blur effects) 없음. 박스 그림자(Box shadows) 없음. "유리(glass)" 카드는 backdrop-filter 없이 평면 색상(Flat color)과 얇은 테두리(Thin border)로 구현되었습니다 —
.glass {
background: rgba(10, 15, 42, 0.6);
border: 1px solid rgba(30, 35, 60, 0.6);
...
페이지 (Pages)
| 경로 (Route) | 목적 (Purpose) |
|---|---|
/ | 연결되지 않았을 때의 랜딩 페이지 (히어로 섹션, 프로토콜 설명, FHE 비교). 지갑이 연결되면 라운드 목록으로 전환됨. |
| ... |
주요 구성 요소 (Key Components)
- FhePipeline — 애니메이션 타임라인 커넥터와 함께 프로토콜 실행 흐름을 보여주는 수직 단계 다이어그램
- AgentList — 지갑 주소, 투표 상태, 암호문(Ciphertext) 개수, 로컬 복호화(Local decrypt)를 보여주는 참가자 신원 카드
- ConsensusResult — 최종 점수와 프로토콜 완료 바가 포함된 SVG 링 미터(Ring meter)
- RoundCard — 수평 단계 표시기(생성(Created) → 투표(Voting) → 수정(Revision) → 확정(Finalized) → 공개(Revealed))가 포함된 프로토콜 타임라인
- VoteForm — 암호화 후 제출(Encrypt-then-submit) 흐름을 가진 암호화된 투표 슬라이더
- ProtocolStrip — 모든 라운드 상세 페이지 상단에 위치한 단계 상태 바
합의 시각화 (The Consensus Visualization)
랜딩 페이지에는 전체 프로토콜 흐름을 보여주는 애니메이션 SVG가 포함되어 있습니다: 에이전트 노드(Agent nodes)가 맥동하고, 데이터 패킷(Data packets)이 FHE 연산 박스(FHE computation box)로 아래로 이동한 다음, 임계치 복호화(Threshold decryption)를 거쳐, 마지막으로 합의 점수(Consensus score)가 스케일 인(Scale-in) 애니메이션과 함께 나타납니다.
@keyframes data-packet {
0% { opacity: 0; transform: translateY(0); }
25% { opacity: 1; }
...
테스트 (Testing)
전체 프로토콜 사양을 다루는 52개의 테스트 케이스. 다음 명령어로 실행:
npx hardhat test
세부 내역:
| 그룹 (Group) | 테스트 (Tests) | 범위 (What It Covers) |
|---|---|---|
| 라운드 생성 (Create Round) | 4 | 정족수(Quorum) 검증, 중복, 빈 URI, 최대 에이전트 수 |
| ... |
테스트 헬퍼: FHE (Test Helper: FHE)
FHE 컨트랙트를 테스트하려면 로컬 CoFHE 노드가 필요합니다. 테스트 헬퍼가 환경을 설정합니다:
// test/helpers/fhe.ts
import { createCofheConfig, createCofheClient } from "@cofhe/sdk/node";
// ... hardhat 인프로세스(in-process) 환경 설정
배포 (Deployment)
배포 (Deployment)
계약은 Ethereum Sepolia에 다음 주소로 배포되었습니다:
0x0C83824a9800f9ED1e22ec289CB67E065ceA73C2
Hardhat을 통한 배포:
npx hardhat run scripts/deploy.ts --network sepolia
배포 스크립트는 동적 가스 가격 책정(dynamic gas pricing) — 2 × baseFee + maxPriorityFeePerGas — 을 사용하여, 하드코딩된 상한선으로 인해 이전 버전에서 발생했던 "max fee per gas less than block base fee" 오류를 방지합니다.
우리가 배운 것 (What We Learned)
1. FHE는 프로덕션 준비 상태이다 (단, 주의사항이 있다)
Fhenix CoFHE SDK가 작동합니다. 클라이언트 측 암호화(Client-side encryption)는 간단합니다. 임계 복호화 네트워크(threshold decryption network)는 지연 시간(5~15초)을 추가하지만, Sepolia에서는 신뢰성이 높습니다. 가장 큰 마찰 지점은 설정이었습니다: SDK의 environment 옵션은 `
conclave/
├── contracts/
│ └── Conclave.sol # 353 lines, 전체 프로토콜
...
직접 시도해보기
사전 요구 사항
- Sepolia 테스트넷의 자금이 충전된 에이전트 지갑 3개 (각 약 0.005 ETH)
- OpenAI API 키
SEPOLIA_RPC_URL(Infura보다 Alchemy 권장)
빠른 시작
git clone https://github.com/harishkotra/conclave
npm install
...
향후 과제
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기