본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 16. 06:16

난수를 서명하는 것은 연극에 불과합니다. 무엇이 실제로 난수의 신뢰성을 만드는가

요약

단순히 난수에 디지털 서명을 붙이는 것만으로는 진정한 신뢰성을 보장할 수 없음을 지적합니다. 난수의 핵심 역할인 품질(Quality)과 책임성(Accountability)을 구분하고, 생성자가 유리한 결과만 선택할 수 있는 문제를 해결하기 위한 검증 가능한 난수의 필요성을 설명합니다.

핵심 포인트

  • 난수의 두 가지 핵심 역할: 품질(예측 불가능성)과 책임성(사후 검증 가능성)
  • 단순 서명은 데이터의 무결성은 증명하지만, 생성자의 조작(선택적 공개)은 막지 못함
  • 신뢰할 수 있는 시스템을 위해서는 '검증 가능한 난수' 구축이 필수적임

나의 자율 에이전트(autonomous agents) 세 마리가 리더를 선출해야 했습니다. 각 에이전트는 random.random()을 호출했고, 가장 높은 숫자가 나온 쪽이 승리하는 방식이었습니다.

세 에이전트 모두 자신이 승리했다고 보고했습니다.

당연한 결과입니다. 각 에이전트는 자신만의 프로세스에서 각자의 주사위를 던졌고, 그 결과를 발표했습니다. 심판은 없었습니다. 에이전트가 마음에 드는 결과가 나올 때까지 주사위를 계속 던지는 것을 막을 수 있는 것도 없었고, 다른 에이전트들이 그 과정을 확인할 수 있는 방법도 없었습니다. 주사위는 완벽했습니다. 하지만 신뢰는 허상일 뿐이었습니다.

나는 그다음 한 주 동안 "검증 가능한 난수(verifiable randomness)"를 구축하며, 교육적인 시행착오를 거쳐 한 가지 불편한 결론에 도달했습니다. 사람들이 "난수 오라클(randomness oracle)"이라고 부르는 것의 대부분은 연극이며, 그 위에 붙은 서명은 의상에 불과하다는 것입니다. 코드와 함께 그 차이점을 구별하는 방법을 알아보겠습니다.

난수에는 두 가지 역할이 있습니다. 당신은 아마 하나를 무시하고 있을 것입니다.

  1. 품질 (Quality) — 균등하고(uniform), 예측 불가능하며(unpredictable), 상관관계가 없어야(uncorrelated) 합니다.
  2. 책임성 (Accountability) — 사후에 누군가 다른 사람이 해당 숫자가 조작되지 않았음을 증명할 수 있는가?

random.random(), os.urandom, /dev/urandom은 1번 역할은 완벽하게 수행하지만, 2번 역할에 대해서는 말 그대로 아무것도 제공하지 않습니다. 신뢰할 수 있는 단일 프로세스 내에서는 문제가 없습니다. 하지만 숫자가 복권, 리더 선출, 추첨(sortition), 공정한 순서 정하기 등 패배자가 존재하는 상황처럼 두 번째 당사자에게 전달되는 순간, 2번 역할이 곧 핵심 제품이 되며 여러분의 CSPRNG(암호학적으로 안전한 의사 난수 생성기)는 무용지물이 됩니다. 우리는 엔트로피(entropy) 품질에 집착하지만, 정작 엔트로피 품질이 위협이 되지 않는 환경에 그 출력값을 넘겨버리곤 합니다.

"그냥 서명하세요"는 연극입니다

모두가 가장 먼저 떠올리는 방법은 서명입니다. 값을 생성하고 그 위에 Ed25519 서명을 붙여 내보낸 뒤, 공개 키를 게시하면 끝이라고 생각합니다.

import hashlib, base64
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

...

시중에 나와 있는 "난수 비콘(randomness beacons)" 절반의 마케팅 문구를 읽어보면 이 내용이 핵심입니다. 서명되었으므로, 신뢰할 수 있다. 아닙니다.

서명은 책임 소재(accountability)를 증명하는 것이지, 예측 불가능성(unpredictability)을 보장하는 것이 아니며, 결코 공정성(fairness)을 의미하지도 않습니다. 서명은 누가 바이트(bytes)를 생성했는지, 그리고 전송 과정에서 아무도 이를 수정하지 않았음을 증명합니다. 하지만 생성자가 사적으로 수천 개의 후보를 생성한 뒤 자신에게 이득이 되는 단 하나만을 공개했는지에 대해서는 아무것도 말해주지 않습니다. 만약 서명자가 결과로부터 이득을 얻는 구조라면, 서명된 비콘(signed beacon)은 서명자가 정직한 만큼만 정직합니다. 그리고 당신은 그것을 암호학(cryptography)으로 감싸서 마치 엄격한(rigorous) 것처럼 보이게 만들 뿐입니다. 이는 암호 기술을 사용하지 않는 것보다 더 나쁩니다. 이제는 설득력까지 갖추게 되었기 때문입니다.

서명된 비콘은 적대적이지 않은 80%의 상황 — 몬테카를로 시드(Monte-Carlo seeds), 지터(jitter), 샘플링(sampling), 아무도 이의를 제기하지 않는 동점자 처리(tie-breaks) — 에는 괜찮으며, 디버깅을 위한 영수증 용도로는 훌륭합니다. 다만 이것이 공정성 문제를 해결한다는 척하는 것만 그만두십시오. 해결하지 못합니다.

실제로 부정행위를 막는 것: 커밋-리빌 (commit-reveal)

진정한 적대자는 당신의 바이트를 추측하는 외부인이 아닙니다. 바로 값을 조작(grinding)하려는 _제공자(provider)_입니다. 해결책은 블록체인이 등장하기 수십 년 전부터 존재했습니다. 상대방의 입력을 확인하기 _전(before)_에 비밀을 커밋(commit)하고, 그 후에 리빌(reveal)하는 것입니다.

# 1단계 — 클라이언트가 무엇인가를 보내기 전, 커밋(commit)
preimage   = f"{secret_state}:{server_nonce}:{round}"
commitment = sha256(preimage).hexdigest()      # 이것을 게시하고 서명함
...

어느 쪽도 값을 조작(grind)할 수 없습니다. 서버는 클라이언트의 시드(seed)가 존재하기 전에 서명된 커밋먼트(commitment)에 자신의 프리이미지(preimage)를 고정했습니다. 클라이언트는 프리이미지를 모르는 상태에서 자신의 시드를 선택했습니다. 결과는 양쪽 데이터가 모두 내려온 순간 고정됩니다. 이것은 약 15줄 정도의 코드이며, 이 글 전체에서 가장 영향력이 큰 핵심 요소입니다. 만약 당신의 "오라클(oracle)"이 클라이언트 입력을 받으면서 이 방식을 사용하지 않고 있다면, 당신은 불필요한 절차만 추가된 "나를 믿으라(trust-me)" 서비스를 운영하고 있는 것입니다.

제공자를 전혀 신뢰할 수 없는 상황 — 공개 복권, 검증인 선택(validator selection), 변호사가 검토할 만한 모든 것 — 이 필요하다면 더 높은 단계로 올라가야 합니다. 그것이 바로 VDF와 임계값/ECVRF(threshold/ECVRF)입니다.

VDF: 증명 가능한 시간을 판매하기

검증 가능한 지연 함수(Verifiable Delay Function, VDF)는 정해진 양의 순차적(sequential) 작업 — 병렬 처리(parallelism)로는 도움을 줄 수 없는 — 을 강제하고, 아주 작은 증명(proof)을 내뱉습니다. 아무도 인수분해하지 못한 RSA 그룹 상에서의 Wesolowski 방식은 모욕적일 정도로 압축적입니다:

# eval: y = g^(2^T) mod N   — T sequential squarings = the enforced delay
y = g
for _ in range(T):
...

비콘 (beacon)을 VDF (Verifiable Delay Function)로 감싸면 그라인딩 (grinding)은 더 이상 경제적이지 않게 됩니다. 다른 결과를 시도한다는 것은 시도당 _강제된 실제 시간 (enforced wall-clock)_을 다시 실행해야 함을 의미합니다. 지연 시간 (latency)을 대가로 지불해야 하므로, 이는 지터 (jitter) 방지용이 아닌 높은 이해관계 (high-stakes)가 걸린 상황을 위한 것입니다.

제가 시작할 때 누군가 제 몸에 문신으로 새겨주었으면 했던 계층 구조는 다음과 같습니다:

  • 서명된 비콘 (Signed beacon) → 무결성 (integrity) + 책임성 (accountability). 저렴함. 비적대적 (non-adversarial) 상황에서만 유효.
  • 커밋-리빌 (Commit-reveal) → 편향 저항성 (bias resistance). 약 15줄의 코드. 두 당사자가 서로를 신경 쓰는 순간 사용하는 기본 (default) 방식.
  • VDF / 임계값 (threshold) / ECVRF → 신뢰가 필요 없는 (trustless). 실제 비용 발생. 돈이 직접적으로 연관되었을 때만 사용.

당신의 실제 위협 모델 (threat model)을 견뎌낼 수 있는 가장 낮은 단계의 계층을 선택하세요. drand를 주사위 굴리기에 맹목적으로 적용하는 것은 엄밀함 (rigor)이 아니라, 당신의 주사위에 대한 불안함일 뿐입니다.

내가 바보 같았던 두 가지 순간

아무것도 조종하지 못한 조종. 나의 엔트로피 (entropy)는 매개변수로 "조종"할 수 있는 32개의 결합된 진동자 (coupled oscillators)로 구성된 카오스 시스템에서 왔으며, 이는 중간점 RK2 (midpoint RK2) 방식으로 적분되었습니다. 2주 동안 조종은 아무런 효과가 없었습니다. 중간점 방식은 단계(step)를 위해 두 번째 평가값만을 사용하기 때문입니다:

k1 = f(y)
k2 = f(y + dt/2 * k1)
y_next = y + dt * k2        # k2만이 출력에 도달함

나는 조종 항 (steering term)을 조용히 누락시키는 생성자로 중간점 상태를 구축했습니다. 그래서 k2는 기본값으로 실행되었고, 나의 입력은 매 단계마다 증발해 버렸습니다. 한 줄로 해결되었습니다. 이 교훈은 잔혹하고 일반적입니다: RK 방법론에서는 중간 단계로 전달하는 것을 잊어버린 모든 것은 "평균화"되는 것이 아니라 삭제됩니다. 당신의 입력이 출력을 변화시킨다는 것을 단언하는 테스트를 작성하세요. 저는 이제 테스트가 있지만, 그때는 없었습니다.

충돌 공포 (The collision scare). 1 MB의 출력에 대한 적대적 테스트 스위트 (adversarial test suite): 압축 (compression), 자기상관 (autocorrelation), 스펙트럼 (spectral), 생일 충돌 (birthday collisions). 다섯 가지 테스트는 "os.urandom과 구별할 수 없음"이라고 말했습니다. 하지만 하나가 비명을 질렀습니다: 약 8개가 예상되었던 32비트 워드 충돌이 0개였습니다 (p ≈ 0.0007). 그것은 숨겨진 구조를 가진 생성기의 지문입니다. 심장이 바닥으로 떨어지는 기분이었습니다.

코드 한 줄을 건드리기 전에, 나는 다섯 개의 새로운 샘플을 생성했습니다: [7, 5, 6, 8, 10], 평균 7.2. os.urandom을 사용한 동일한 테스트: [11, 7, 5, 10, 7], 평균 8.0. 그 "버그"는 운 나쁜 1메가바이트의 결과였습니다. 0.07%의 사건이 0.07%의 사건이 그래야 하는 만큼 발생했을 뿐입니다. 나는 아무것도 고치지 않기 위해 멀쩡한 생성기를 거의 새로 작성할 뻔했습니다. 하나의 공포스러운 p-value(유의 확률)에 대한 올바른 대응은 refactor(리팩터링)가 아니라 resample(재샘플링)입니다.

암호학 튜토리얼이 말해주지 않는 부분

나는 Ed25519, 하이브리드 양자 내성 서명 (hybrid post-quantum signatures), VDF 수학, NIST 테스트 세트 등에 시간을 쏟았습니다. 그 중 어려운 것은 없었습니다. 해싱 (Hashing)과 서명 (signing)은 이미 해결된 문제이며, 라이브러리들은 훌륭하고, 수학은 검증되거나 되지 않거나 둘 중 하나입니다.

진짜 어려운 질문은 이것입니다 — "누가 실제로 이것에 비용을 지불하며, 왜 그것을 신뢰하는가?"

왜냐하면 "혼돈스러운 시뮬레이션을 REST API로 감싸고 그것을 오라클 (oracle)이라고 불렀다"는 것은, 화려한 시각 자료를 걷어내고 나면, 다음 두 가지 질문에 구체적으로 답할 수 있지 않는 한 아무도 요구하지 않은 숫자를 파는 자판기에 불과하기 때문입니다:

  1. 수요 (Demand). 그것 없이는 무엇이 망가지는가? 난수 (Randomness)의 경우: 리더 선출 (leader election), 복권 (lotteries), 추첨 (sortition), 커밋-리빌 코인 플립 (commit-reveal coin-flips), 감사 추적 (audit trails) — 이것들은 실재합니다. "32차원 혼돈 영역을 조종한다"고요? 그 누구의 워크플로우도 그것을 필요로 하지 않으며, 나는 구매자가 없는 그 프레이밍을 폐기해야 했습니다. 이제 그것은 튜토리얼로 제공됩니다 https://github.com/alexar76/platon

  2. 신뢰 계층 (Trust tier). 사용 사례가 세 가지 수준 중 어느 수준을 요구하며, 당신은 그것을 제공했는가 — 아니면 그 옷을 입고 서명만 달아놓은 더 약한 수준의 것을 제공했는가?

대부분의 "오라클" 프로젝트들은 둘 다 답하지 못한 채 랜딩 페이지 뒤에 숨어 있습니다. 암호학은 어떤 대상을 검증 가능하게 (verifiable) 만듭니다. 그것이 대상을 원하게 (wanted) 만들지는 않습니다. 그리고 랜딩 페이지는 위협 모델 (threat model)이 아닙니다. 이것들이 실제로 중요한 두 가지 문제이며, 모두가 자랑하는 암호학 — 그 부분 — 은 오히려 쉬운 문제입니다.

따라서: 이해관계자가 한 명 이상인 어떤 작업에서든 random()을 사용하려 할 때, 잠시 멈추고 책임 소재(accountability)에 대한 질문을 던지십시오. 그다음, 당신의 위협 모델 (threat model)을 통과하는 가장 저렴한 계층 (tier)을 배포하십시오. 그리고 — 모든 튜토리얼이 건너뛰는 단계인데 — 당신이 증명하려고 그토록 자랑스러워하는 그 숫자를 실제로 사람이 필요로 하는지 확인하십시오.

이제 나의 세 에이전트 (agents)는 공유된 심판 (referee)을 통해 커밋-리빌 (commit-reveal) 동전 던지기를 수행할 수 있습니다. 정확히 한 명만이 승리합니다. 그들은 여전히 짜증이 나 있습니다. 다만 더 이상 그것에 대해 논쟁할 수는 없게 되었습니다.

실제 운영 환경에서 검증 가능한 난수 (verifiable randomness)를 배포해 보셨나요 — VRF, drand, 커밋-리빌 (commit-reveal), 혹은 자체 제작 방식인가요? 당신이 어떤 계층에 도달했는지, 그리고 어떤 부분에서 어려움을 겪었는지 알려주세요. 댓글에서 함께 논쟁해 봅시다.

전체 코드: github.com/alexar76/oracles

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0