
소프트웨어로는 구현할 수 없었던 해결책: 온체인 하드웨어 서명기
요약
에이전트의 보안 취약점을 해결하기 위해 소프트웨어 기반의 키 관리를 탈피하고, 하드웨어 월렛(Ledger Nano X)을 활용한 온체인 하드웨어 서명 방식을 도입했습니다. 이를 통해 서버가 침해되더라도 인간이 확인하는 트랜잭션 데이터와 실제 서명되는 바이트 간의 일치성을 암호학적으로 보장합니다.
핵심 포인트
- 소프트웨어 기반 키 관리는 호스트 침해 시 조작 위험이 있음
- 인간의 승인이 실제 서명되는 바이트와 일치하도록 설계
- 결제 키를 서버에서 제거하고 하드웨어 월렛으로 완전히 이전
- 검증자와 서명 호스트를 분리하여 보안 신뢰 모델 구축
지난 포스트에서 저는 낯선 이의 댓글로 인해 제가 무엇을 바꿔야만 했는지 게시했습니다. 그들은 저의 네 가지 장벽이 키(key)와 게이트(gate)는 보호했지만, 정작 인간이 실제로 확인하는 내용은 전혀 보호하지 못한다는 점을 지적했습니다. 저는 해결책의 절반을 배포했습니다. 즉, 인간은 에이전트(agent)가 말한 내용이 아니라, 실제 바이트(bytes)에서 디코딩된 트랜잭션을 보게 됩니다. 그리고 저는 나머지 절반, 즉 서명 호스트(signing host)가 위조할 수 없는 암호학적 약속(cryptographic commitment)은 아직 배포하지 않았다고 명시했습니다. 저는 절반의 해결책으로는 코드상에서 종결될 수 없다고 주장했습니다. 그것은 장치(device) 위에서 종결됩니다.
7월 15일, 해당 장치가 서명했습니다. 무엇이 변했는지, 온체인(on-chain)에서 무엇을 증명했는지, 그리고 — 지난 포스트가 기준을 세웠기에 — 여전히 무엇을 종결시키지 못하고 있는지에 대해 설명하겠습니다.
(만약 첫 번째 포스트를 읽지 않으셨다면, 한 줄 요약은 다음과 같습니다: 네 가지 장벽은 "보안이 침해된 에이전트는 서명할 수 없다"를 사실로 만들었지만, 한 댓글 작성자는 이 장벽들이 인간이 확인하는 내용을 전혀 보호하지 못한다는 것을 보여주었습니다. 그리고 그 수정 사항은 확인(confirmation) 과정을 실제 바이트에 결합하는 것이었습니다. 이것이 그 수정 사항의 누락된 나머지 절반입니다.)
소프트웨어로는 구현할 수 없었던 절반
제가 놓치고 있었던 속성: 인간의 "예"는 서명되는 정확한 바이트를 증명해야 하며, 이는 서명 기계가 속일 수 없는 무언가에 의해 검증되어야 한다.
단일 호스트(single host)에서는 여기에 도달할 수 없습니다. 기계가 확인 화면을 렌더링하고, 그 기계가 서명합니다. 동일한 기계에 의해 계산되고, 보여지고, 재검증되는 암호학적 약속(cryptographic commitment)은 해당 기계에 대해서는 아무런 방어 수단이 되지 못합니다. 만약 기계가 침해되었다면, 그 기계는 세 곳 모두에서 동시에 거짓말을 하기 때문입니다. 저는 지난 포스트의 마지막에 그러한 약속은 "검증자(verifier)가 서명하는 호스트와 독립적일 때만 가치가 있다"라고 썼습니다. 그 문장에는 제가 아직 치르지 않았던 대가가 따랐습니다. 바로 검증자를 호스트 밖으로 옮겨야 한다는 것이었습니다.
실제로 서버에서 옮겨진 것
두 명의 큐레이터 에이전트 (curator agents) 중 하나인 SIGMA는 이전에는 자신의 결제 키 (settlement key)를 서버 내 암호화된 키스토어 (keystore) 형태로 보유하고 있었습니다. 지난번 저는 그 키를 "절대 노출되지 않는다"라고 불렀습니다. 그것은 사실이었지만, 잘못된 기준이었습니다. 키는 "기계 위에" 있었습니다. 키를 보유한 기계는 최악의 경우, 그 키를 사용하도록 강제될 수 있습니다.
그래서 저는 키를 제거했습니다. 교체(rotate)한 것이 아니라 제거했습니다. SIGMA의 결제 키는 더 이상 서버에 존재하지 않습니다. 그것은 하드웨어 월렛 (hardware wallet)인 Ledger Nano X에 존재하며, 그 시드 (seed)는 장치 자체에서 생성되었습니다. 디스크에 복사된 적도, 프롬프트에 입력된 적도, 연결된 기계에 노출된 적도 없습니다. 서버의 역할은 단 하나의 동사로 축소되었습니다. 트랜잭션 (transaction)을 동결하고, 직렬화 (serialize)하며, 지문 (fingerprint)을 출력한 뒤 — 구조적으로 생성할 수 없는 서명 (signature)을 기다리는 것입니다.
"에이전트가 서명할 수 없다"는 것은 과거에는 하나의 아키텍처 (architecture) — 즉, 게이트, 분리, 규율 — 이었습니다. 이제 그 중 일부는 물리적 사실이 되었습니다. 결제를 승인하는 바이트 (bytes)는 서버가 절대 건드릴 수 없는 실리콘 (silicon) 내부에 봉인되어 있습니다.
호스트가 아닌 검증자
이 부분이 다섯 번째 속성을 완성하는 대목입니다.
서버가 동결된 트랜잭션을 장치에 전달할 때, 장치는 서버가 설명하는 트랜잭션 내용을 신뢰하지 않습니다. 보안 요소 (Secure Element)는 호스트에게 아무것도 묻지 않고, 칩 내부에서 서명하려는 바이트로부터 트랜잭션에 대한 자신만의 지문을 계산합니다. 그리고 그 지문을 목적지와 금액 옆에 자신의 화면에 표시합니다.
따라서 이제 인간은 두 개의 독립적인 판독값을 갖게 됩니다. 서버는 실제 콜데이터 (calldata)에서 디코딩된 placeBid(25), 0.001 ETH를 보여줍니다. 장치는 목적지, 값, 그리고 스스로 계산한 지문을 보여줍니다. 만약 해킹된 호스트가 트랜잭션을 동결한 후 내용을 바꿔치기했다면, 장치의 지문은 서버가 표시한 지문과 일치하지 않을 것이며, 인간은 동작을 멈출 것입니다. 검증은 더 이상 서명하는 기계 위에서 이루어지지 않습니다. 이것이 지난 포스트에서 부족하다고 언급했던 독립적인 검증자 (independent verifier)이며, 하드웨어 서명기 (hardware signer)가 단순한 편의 기능이 아닌 다음 단계의 필수 과제였던 이유 전체입니다.
칩 카드를 사용해 본 적이 있다면 이미 이 방식의 절반을 경험해 보셨을 것입니다. 카드는 자체 보안 요소 (secure element) 내부에서 서명하며, 상점의 단말기는 키를 절대 알 수 없습니다. 하지만 카드가 제공하지 못하는 것은 자체 화면입니다. 승인하는 금액은 호스트 (host)인 상점의 단말기에 표시됩니다. 여기서 결제를 실제로 가로챌 수 있는 두 가지 요소, 즉 '어디로 가는지'와 '얼마나 가는지'는 칩에 의해 계산되어 장치 자체의 디스플레이에 나타나야 합니다. 그것이 바로 카드가 호스트에 남겨두는 부분이며, 이 장치는 그렇지 않습니다.
7월 15일, settle의 호스트 측 — 나의 작업 언어인 프랑스어로 작성되었습니다. 서버는 트랜잭션을 동결하고, 이를 디코딩(settleAuction(25), 목적지는 NexusPOC 컨트랙트로 확인됨)한 뒤, Ledger 자체 화면에 표시되어야 할 다이제스트 (digest)인 0x85cfd3a5…를 출력했습니다. 그런 다음 사람에게 승인하기 전에 장치에서 해당 다이제스트를 비교하라고 지시합니다. 호스트는 서명할 수 없습니다. 오직 요청할 수 있을 뿐입니다.
온체인 (On-chain), 그렇지 않으면 일어나지 않은 일이다
제가 "서명했다"라고 말하는 것이 아닙니다. 체인이 그렇게 말합니다.
7월 15일, SIGMA의 하드웨어 금고 (hardware vault)가 벨라스케스의 Les Ménines (tokenId 25)에 입찰했고, 경매는 해당 금고로 정산되었습니다:
- placeBid —
0xdbcacc86…— 장치에서 서명됨:placeBid(25), 0.001 ETH, Base Sepolia. - settleAuction —
0xd76c2b2b…— 토큰 전송, 자금 분할. ownerOf(25)=0x2d45eF16…— 하드웨어 금고. 벨라스케스의 작품은 금고 안에 있습니다.
정산은 wei 단위까지 3등분되어 잔여물 없이 나누어졌습니다: 8.333%는 창작자 로열티(creator royalty)로, 나머지는 큐레이터(curator)와 플랫폼이 나누어 가집니다. 테스트넷(Testnet) — 설계상 실제 가치가 걸려 있지 않습니다. 핵심은 돈이 아니었습니다. 내 말을 믿는 대신 누구나 확인할 수 있는 어딘가에 이 패턴을 두는 것입니다.
장치가 해결하지 못하는 것
지난 포스트에서 규칙을 정했습니다: 누군가 찾아내기 전에 그 간극(gap)을 명시할 것. 세 가지 간극이 있으며, 이 장치는 그중 단 하나만을 완전히 메웁니다.
첫 번째 — 여전히 블라인드 서명(blind-signing) 방식이라는 점입니다. 장치는 저에게 독립적인 _지문(fingerprint)_을 제공하며, 목적지와 금액을 명확하게 보여줍니다. 이는 잘못된 주소로 송금되거나 잘못된 금액이 송금되는 것(자금이 유출되는 유일한 두 가지 방식)을 잡아내기에 충분합니다. 하지만 장치가 보여주지 않는 것은 디코딩된 호출(decoded call), 즉 함수 이름(function name)과 그 인자(argument)입니다. 서버에서는 placeBid(25)가 읽을 수 있는 형태로 나타나지만, 장치 상에서는 불투명한 해시(opaque hash)와 원시 필드(raw fields)로 나타납니다. 따라서 장치를 사용하는 인간은 _돈이 어디로 가는지와 얼마인지_를 검증할 뿐, _어떤 함수가 어떤 인자와 함께 호출되는지_는 검증하지 못합니다. 이는 **판독 가능성(legibility)**의 한계이지, 자금이 새어 나갈 수 있는 구멍은 아닙니다.
이를 해결할 표준은 서명 명확성 기술자(signing-clarity descriptor, ERC-7730)입니다. 이는 장치가 placeBid(25)를 디코딩하여 자체 실리콘(silicon) 상에서 평문으로 출력할 수 있게 합니다. 그것이 나아가야 할 길이며, 저는 그 첫걸음을 뗐습니다 — 저는 기술자를 작성하여 레지스트리(registry)에 제출했습니다 (PR #2632). 하지만 이를 병합(merging)하는 것은 쉬운 절반에 불과합니다. 장치가 실제로 기술자를 받으려면, 파트너 토큰이 필요한 Ledger 엔드포인트(endpoint)를 통해 제공되어야 합니다. 토큰이 없으면 요청은 403 오류를 반환하며, 저는 해당 프로그램에 참여하고 있지 않습니다. 따라서 기술자는 레지스트리에 존재하지만, 제 장치에는 도달하지 못합니다. 양쪽 절반이 일치하게 되면, 장치가 스스로 호출을 디코딩하게 되고 블라인드 서명은 여기서 끝납니다. 현재 한쪽은 완료되었으며, 스크린샷이 보여주지 못하는 디코딩을 암시하게 두느니 차라리 직접 말하겠습니다.
둘 — 2바이트 커버리지, 여전히 미결 상태. 지난번 댓글 작성자가 제기했던 것과 동일한 지점이 한 단계 더 아래에서 발생합니다. 장치는 모든 바이트를 지문(fingerprint)화하지만, 해당 바이트들에 대한 인간이 읽을 수 있는 (human-legible) 설명은 여전히 부분적입니다. 이번 호출 — 하나의 함수, 하나의 uint256, 중첩 호출 없음 — 의 경우, 구조적으로 커버리지가 완벽합니다. 장치 상에서 커버리지를 *증명(prove)*하고, 출처를 밝힐 수 없는 것은 무엇도 표시하지 않도록 거부하는 렌더러(renderer)를 제가 가지고 있는 것은 아닙니다. 이는 "이 트랜잭션은 우연히 완전히 설명되었다"와 "이 장치는 단 하나도 설명하지 못할 수 없다" 사이의 차이입니다. 후자는 구축되지 않았습니다.
셋 — 나는 두 개가 아니라 하나의 볼트(vault)를 옮겼습니다. 다른 에이전트는 여전히 7월 10일 결투에서 사용했던 소프트웨어 키스토어(keystore)를 실행 중입니다. 나는 하나의 결제 경로를 하드웨어로 강화했고, 다른 하나는 원래 위치에 두었습니다. 이 패턴은 한 에이전트에서 증명되었습니다; 시스템 전체에 아직 균일하게 적용된 것은 아닙니다.
여기서 여러분이 얻어가야 할 점
지난번에는 "반증 가능한 모델을 공개하라, 그러면 누군가 그것을 깨뜨릴 것이다"였습니다. 이번에는 더 좁은 범위이며, 실행력(follow-through)에 관한 것입니다.
나는 지난 포스트를 멋진 문구로 남겨둘 수도 있었던 문장으로 끝냈습니다 — 누락된 절반은 내가 잊어버린 코드 한 줄이 아니라, 장치다. 그런 문장들은 값싸게 만들어낼 수 있습니다. 그래서 나는 장치를 만들었고, 그것은 내가 말했던 속성인 정확히 독립적 검증기(independent verifier)를 완성하며, 내가 완성하지 못한 두 가지는 완성하지 않았습니다. 여기서 진정한 진보의 형태는 "해결됨"이 아닙니다. 그것은 다음과 같습니다: 패턴은 일주일 전보다 하나의 속성이 더 진실해졌으며, 나는 여전히 취약한(soft) 두 가지를 지목할 수 있다는 것입니다.
만약 여러분이 되돌릴 수 없는 무언가를 이동시키는 에이전트를 출시하고 있다면, 그것이 바로 훔칠 가치가 있는 루프(loop)입니다. 장벽이 아니라 — 장벽은 틀릴 것입니다. 루프는 이렇습니다: 낯선 이가 깨뜨릴 수 있을 만큼 명확하게 주장을 기술하고, 그들이 깨뜨리게 두며, 공개적으로 교정책을 구축하십시오. 그리고 여러분이 해결책처럼 들리는 다음 문장을 쓸 때, 실제로 그것을 해결하십시오.
공로를 돌려야 할 부분: 다섯 번째 속성인 독립적 검증기(independent verifier)와 바이트 커버리지 정교화(byte-coverage refinement)는 지난번 댓글에서 언급된 ANP2 Network의 것입니다. 그리고 제가 대항하여 구축한 원칙인 — 에이전트가 제안하고, 인간이 승인하며, 하드웨어가 강제한다(agents propose, humans approve, hardware enforces) — 는 Ledger의 것입니다. 저는 그것을 적용했을 뿐, 발명한 것이 아닙니다.
혼자 구축했습니다. Claude Code는 저의 엔지니어링 팀입니다. Claude 채팅 인스턴스는 저의 설계자이자 감사자(auditor)이며, 저에게 반박하도록 지시받았고, 이 프로젝트가 출시되는 동안 실제로 두 번이나 반박했습니다. 두 큐레이터 에이전트(curator agents)는 모두 Anthropic API에서 실행됩니다. 저는 현재 장치를 보유하고 있습니다. 서명은 트랜잭션을 제안하는 머신이 아니라, 제가 제어하는 실리콘(silicon) 내부에서 일어납니다.
- 프로젝트: nexus-art.org
- 코드: github.com/avp9-nexus/nexus-art
- placeBid + settle은 Base Sepolia(테스트넷 — 실제 가치가 걸려 있지 않음)에서 검증 가능합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기