당신의 AI 챗봇이 뭐라고 했나요? 양당사자 증명 문제를 이중 비밀 검증으로 해결하기
I’m happy to translate the article for you, but I’ll need the text you’d like translated. Could you please paste the content (excluding the source line you already provided) here? Once I have the article text, I’ll translate it into Korean while preserving all formatting, markdown, and code blocks.
두 당사자 증명 문제
당신의 AI 챗봇이 사용자에게 보험 청구가 승인되었다고 말했는데, 사용자가 나중에 챗봇이 다른 말을 했다고 주장한다면 – 누가 옳을까요?
양쪽 모두 증명할 수 없습니다. 요청은 AI 제공업체(예: OpenAI)로 전송되고, 응답이 돌아오며, 양측은 스크린샷과 신뢰만 남게 됩니다.
이것이 두 당사자 증명 문제이며, AI 상호작용이 더 중요한 영향을 미칠수록 상황은 더욱 악화됩니다.
IOProof 소개
IOProof는 서비스와 모든 AI API 사이에 위치하는 오픈‑소스 프록시로, 모든 상호작용을 캡처하고 암호학적으로 증명합니다.
핵심 혁신은 이중 비밀 검증 모델이며, 이는 양측이 의견이 일치하지 않을 때 유용하게 작동합니다.
IOProof가 하는 일
- 요청과 응답의 원시 바이트를 캡처합니다.
- SHA‑256으로 모든 데이터를 해시합니다.
- 두 개의 독립적인 암호 비밀(소유자 비밀 & 사용자 비밀)을 생성합니다.
- 증명을 블라인드 처리하고 머클 트리로 배치합니다.
- 머클 루트를 Solana에 커밋합니다.
해싱 코어 (고의적으로 간단함)
const crypto = require('crypto');
function sha256(buffer) {
return crypto.createHash('sha256').update(buffer).digest('hex');
}
function buildCombinedHash(requestHash, responseHash, timestamp) {
const payload = `${requestHash}|${responseHash}|${timestamp}`;
return sha256(Buffer.from(payload, 'utf-8'));
}
결합된 해시는 요청, 응답, 그리고 타임스탬프를 함께 묶습니다. 어느 위치에서든 단일 바이트를 변경하면 완전히 다른 해시가 생성됩니다 – 교과서적인 콘텐츠‑주소 지정 무결성.
Source: …
이중‑시크릿 생성
IOProof의 첫 번째 버전은 증명당 하나의 시크릿만 생성했으며, 이는 신뢰 병목을 만들었습니다.
이를 해결하기 위해 증명 생성 시 두 개의 독립적인 시크릿을 생성합니다:
function generateSecret() {
return crypto.randomBytes(32).toString('hex');
}
// In the proxy route:
const secret = generateSecret(); // 소유자 시크릿 (API 호출자를 위한)
const userSecret = generateSecret(); // 사용자 시크릿 (최종‑사용자를 위한)
const blindedHash = blindHash(combinedHash, secret);
두 시크릿 모두 동일한 증명 상세 정보(전체 요청/응답 페이로드, 모든 해시, Merkle 증명, Solana 트랜잭션)를 풀 수 있습니다. 이들은 다른 암호학적 메커니즘을 통해 검증되며, 이는 중요한 차이점입니다.
소유자 시크릿 – 블라인딩을 통해 검증
function blindHash(combinedHash, secret) {
return sha256(Buffer.from(`${combinedHash}|${secret}`, 'utf-8'));
}
검증 시 서버는 제공된 시크릿으로 블라인드 해시를 다시 계산합니다.
SHA‑256(combined_hash | secret) === stored_blinded_hash이면 소유자가 인증됩니다. 저장된 시크릿이 필요하지 않으며, 단순히 수학 연산만 수행합니다.
사용자 시크릿 – 상수 시간 비교를 통해 검증
function safeEqual(a, b) {
if (!a || !b || a.length !== b.length) return false;
return crypto.timingSafeEqual(Buffer.from(a, 'hex'), Buffer.from(b, 'hex'));
}
왜 timingSafeEqual을 사용할까?
단순한 === 비교는 첫 번째 다른 바이트에서 바로 종료되므로, 앞쪽에 몇 글자가 일치했는지에 대한 정보를 누출합니다(전형적인 타이밍 사이드채널). crypto.timingSafeEqual은 항상 동일한 시간만큼 실행되어, 공격자가 시크릿을 바이트 단위로 추측하는 것을 방지합니다.
검증 엔드포인트 로직
let secretValid = false;
let accessType = null;
// Owner: re‑derive the blinded hash
const expectedBlinded = blindHash(proof.combinedHash, secret);
if (expectedBlinded === proof.blindedHash) {
secretValid = true;
accessType = 'owner';
}
// User: constant‑time comparison against stored secret
if (!secretValid && proof.userSecret && safeEqual(secret, proof.userSecret)) {
secretValid = true;
accessType = 'user';
}
// Response includes which party succeeded
res.json({ secretValid, access_type: accessType, proof });
두 당사자는 동일한 증명 데이터를 받지만, 서로를 신뢰하거나 접근을 조정할 필요가 없습니다.
Merkle‑Tree 배치 (온체인 비용 절감)
모든 API 호출마다 Solana 트랜잭션을 작성하면 비용이 많이 들고 속도가 느려집니다.
대신 IOProof는 보류 중인 증명을 Merkle 트리로 배치하고 루트만 커밋합니다:
function buildMerkleTree(leaves) {
if (leaves.length === 0) return { root: null, layers: [] };
if (leaves.length === 1) return { root: leaves[0], layers: [leaves] };
const layers = [leaves.slice()];
let currentLayer = leaves.slice();
while (currentLayer.length > 1) {
const nextLayer = [];
for (let i = 0; i < currentLayer.length; i += 2) {
const left = currentLayer[i];
const right = currentLayer[i + 1] || left;
nextLayer.push(sha256(Buffer.from(left + right, 'utf-8')));
}
layers.push(nextLayer);
currentLayer = nextLayer;
}
return { root: currentLayer[0], layers };
}
무료 호스팅 티어
- 쿼터: 월 100개 증명
- 가입:
설치
npm install ioproof # v0.2.0
- 전체 프록시는 단일 Node.js 프로세스로 실행됩니다.
- 기존 API 호출을 이 프록시로 지정하면, 모든 상호작용이 양측에 의해 독립적으로 검증될 수 있습니다.
- 코드는 MIT 라이선스이며, 읽고, 포크하고, 구멍을 찾아볼 수 있습니다.
IOProof를 사용해야 하는 이유
AI 출력이 고객 지원, 의료 트리아지, 금융 조언, 법률 조사와 같이 실제 세계에 영향을 미치는 경우, 문제는 감사 로그가 필요한가가 아니라 양측이 서로를 신뢰하지 않고도 무슨 일이 있었는지 증명할 수 있어야 하는가입니다.