Workspace IndexAlgorithms › Day 95

Post-quantum migration is a coordination problem, not a cryptography problem TODO

Algorithms · Day 95 / 100 · F. Cryptography & ZK (Day 82-96)

Concept

Quantum computers break discrete log and integer factorization via Shor's algorithm, which threatens today's elliptic-curve signatures, while Grover's algorithm lowers the search difficulty of hashes but is fairly easily countered by lengthening the output. The replacement algorithms themselves — lattice-based, hash-based, and so on — have already gone through standardization, so what remains hard isn't the math, it's deployment and coordination. Changing a blockchain's signature scheme is a consensus-rule change that needs a hard fork, requiring wallets, hardware signers, bridges, indexers, and audited contracts to move in lockstep, and larger keys and signatures also mean more block space and verification cost. Accounts whose public key is already exposed on-chain, plus the harvest-now-decrypt-later threat — where data collected today is decrypted later — mean the strategy of "migrate after the risk becomes real" doesn't hold, which raises the coordination pressure further. So the practical transition usually goes through a hybrid phase that requires both the old and the new scheme at once, moving gradually from there.

The failure point in a cryptographic transition is never the algorithm choice — it's a design with no migration path — and that has a direct bearing on how you design key management and upgradability in the system you're building right now. This is especially lethal in on-chain systems where keys are permanently pinned.

Code & Formula

# 포스트퀀텀 전환은 조정(coordination) 문제 — 하나의 스킴을 한번에 스왑하는 대신
# 클래식 서명 + 해시 기반(PQ 내성) 서명을 함께 요구하는 "하이브리드 검증" 예시.
# 해시 기반 Lamport 서명은 실제로 양자 내성이 있다고 여겨지는 원시연산이다(1회용).

import hashlib, hmac, os

def H(b: bytes) -> bytes:
    return hashlib.sha256(b).digest()

def msg_to_bits(msg: bytes, bits: int) -> list:
    n = int.from_bytes(hashlib.sha256(msg).digest(), "big")
    return [(n >> i) & 1 for i in range(bits)]

# --- Lamport 서명: 해시만으로 구성된 PQ 내성 1회용 서명 ---
def lamport_keygen(bits=16):
    sk = [(os.urandom(16), os.urandom(16)) for _ in range(bits)]
    pk = [(H(a), H(b)) for a, b in sk]
    return sk, pk

def lamport_sign(msg, sk):
    bits = msg_to_bits(msg, len(sk))
    return [sk[i][b] for i, b in enumerate(bits)]

def lamport_verify(msg, sig, pk):
    bits = msg_to_bits(msg, len(pk))
    return all(H(sig[i]) == pk[i][b] for i, b in enumerate(bits))

# --- "클래식" 서명: 지금 널리 쓰이는 스킴(ECDSA 등)의 자리 표시자 (양자에 취약하다고 가정) ---
classical_key = os.urandom(32)
def classical_sign(msg):
    return hmac.new(classical_key, msg, hashlib.sha256).digest()
def classical_verify(msg, sig):
    return hmac.compare_digest(classical_sign(msg), sig)

# --- 하이브리드 검증: 둘 다 통과해야 유효 — 한쪽이 깨져도 즉시 전면 위험에 빠지지 않는다 ---
def hybrid_verify(msg, classical_sig, pq_sig, pq_pk):
    return classical_verify(msg, classical_sig) and lamport_verify(msg, pq_sig, pq_pk)

msg = b"withdraw 100 to addr X"
sk, pk = lamport_keygen()
c_sig = classical_sign(msg)
pq_sig = lamport_sign(msg, sk)

print("정상 트랜잭션 하이브리드 검증:", hybrid_verify(msg, c_sig, pq_sig, pk))

forged_classical = os.urandom(32)  # 양자 컴퓨터가 클래식 서명을 위조했다고 가정
print("클래식 서명만 위조돼도 하이브리드는 거부:", not hybrid_verify(msg, forged_classical, pq_sig, pk))

Exercise

In a system you work on, list every point where the signature algorithm is locked to exactly one choice, and write down what would need to change at each point to allow both algorithms to be accepted at once.

Practical Connection

If Verex's contracts hard-code how signatures are verified, there's no migration path when the scheme needs to change later, so keeping the verifier as a swappable module is what real preparedness looks like.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

포스트퀀텀 전환은 암호가 아니라 조정(coordination) 문제 TODO

Algorithms · Day 95 / 100 · F. 암호학·ZK (Day 82–96)

개념

양자 컴퓨터는 Shor 알고리즘으로 이산로그와 소인수분해를 깨므로 현행 타원곡선 서명이 위협받고, Grover 알고리즘은 해시의 탐색 난이도를 낮추지만 출력 길이를 늘리는 것으로 비교적 쉽게 대응된다. 대체 알고리즘 자체는 격자·해시 기반 등으로 이미 표준화 과정을 거쳐 왔기 때문에 남은 어려움은 수학이 아니라 배포와 조정에 있다. 블록체인에서 서명 스킴을 바꾸는 일은 합의 규칙 변경이라 하드포크가 필요하고, 지갑·하드웨어 서명기·브리지·인덱서·감사받은 컨트랙트가 동시에 움직여야 하며, 키와 서명이 커지면 블록 공간과 검증 비용도 함께 늘어난다. 이미 공개키가 체인에 노출된 계정이나, 지금 수집해 두었다가 나중에 해독하는 harvest-now-decrypt-later 위협 때문에 '위험이 현실화된 뒤에 옮긴다'는 전략이 성립하지 않는다는 점도 조정 압박을 키운다. 그래서 실무적 전환은 대개 기존 스킴과 새 스킴을 동시에 요구하는 하이브리드 단계를 거쳐 점진적으로 진행된다.

암호 전환의 실패 지점은 알고리즘 선택이 아니라 마이그레이션 경로가 없는 설계에 있고, 이는 지금 짜는 시스템의 키 관리와 업그레이드 가능성 설계에 바로 영향을 준다. 특히 키가 영구적으로 박제되는 온체인 시스템에서 치명적이다.

코드 · 수식

# 포스트퀀텀 전환은 조정(coordination) 문제 — 하나의 스킴을 한번에 스왑하는 대신
# 클래식 서명 + 해시 기반(PQ 내성) 서명을 함께 요구하는 "하이브리드 검증" 예시.
# 해시 기반 Lamport 서명은 실제로 양자 내성이 있다고 여겨지는 원시연산이다(1회용).

import hashlib, hmac, os

def H(b: bytes) -> bytes:
    return hashlib.sha256(b).digest()

def msg_to_bits(msg: bytes, bits: int) -> list:
    n = int.from_bytes(hashlib.sha256(msg).digest(), "big")
    return [(n >> i) & 1 for i in range(bits)]

# --- Lamport 서명: 해시만으로 구성된 PQ 내성 1회용 서명 ---
def lamport_keygen(bits=16):
    sk = [(os.urandom(16), os.urandom(16)) for _ in range(bits)]
    pk = [(H(a), H(b)) for a, b in sk]
    return sk, pk

def lamport_sign(msg, sk):
    bits = msg_to_bits(msg, len(sk))
    return [sk[i][b] for i, b in enumerate(bits)]

def lamport_verify(msg, sig, pk):
    bits = msg_to_bits(msg, len(pk))
    return all(H(sig[i]) == pk[i][b] for i, b in enumerate(bits))

# --- "클래식" 서명: 지금 널리 쓰이는 스킴(ECDSA 등)의 자리 표시자 (양자에 취약하다고 가정) ---
classical_key = os.urandom(32)
def classical_sign(msg):
    return hmac.new(classical_key, msg, hashlib.sha256).digest()
def classical_verify(msg, sig):
    return hmac.compare_digest(classical_sign(msg), sig)

# --- 하이브리드 검증: 둘 다 통과해야 유효 — 한쪽이 깨져도 즉시 전면 위험에 빠지지 않는다 ---
def hybrid_verify(msg, classical_sig, pq_sig, pq_pk):
    return classical_verify(msg, classical_sig) and lamport_verify(msg, pq_sig, pq_pk)

msg = b"withdraw 100 to addr X"
sk, pk = lamport_keygen()
c_sig = classical_sign(msg)
pq_sig = lamport_sign(msg, sk)

print("정상 트랜잭션 하이브리드 검증:", hybrid_verify(msg, c_sig, pq_sig, pk))

forged_classical = os.urandom(32)  # 양자 컴퓨터가 클래식 서명을 위조했다고 가정
print("클래식 서명만 위조돼도 하이브리드는 거부:", not hybrid_verify(msg, forged_classical, pq_sig, pk))

연습

자신이 다루는 시스템에서 서명 알고리즘이 한 번에 하나로 고정된 지점을 모두 찾아 목록화하고, 각 지점에 '두 알고리즘을 동시에 허용하는' 경로를 넣으려면 무엇을 바꿔야 하는지 적어 보라.

실무 · Verex 연결

Verex의 컨트랙트가 서명 검증 방식을 하드코딩해 두면 나중에 스킴을 바꿀 때 마이그레이션 경로가 없어지므로, 검증기를 교체 가능한 모듈로 두는 설계가 실질적인 대비가 된다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 94. 프라이버시 프리미티브96. [복습] 검증 가능한 시스템 설계 체크리스트 →