Post-quantum migration is a coordination problem, not a cryptography problem TODO
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))
docs/code/algorithms/algorithms-95.py
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/.