~/blockchain-dev.md
BLOCKCHAIN_DEV

브리지는 왜 RPC를 신뢰 경계로 봐야 할까: KelpDAO 사고로 보는 DVN 쿼럼

10분 읽기·2026.07.24·출처 6·00
오늘의 질문

브리지 컨트랙트가 유효한 서명을 검증했는데도 왜 가짜 cross-chain message를 실행할 수 있었을까?

blockchain-dev.md
DVN RPC Quorum

브리지는 왜 RPC를 신뢰 경계로 봐야 할까: KelpDAO 사고로 보는 DVN 쿼럼

1. 왜 지금 봐야 하나

브리지 사고를 볼 때 우리는 보통 “스마트컨트랙트 버그였나?”, “키가 털렸나?”, “서명 검증이 잘못됐나?”부터 묻는다. 그런데 2026년 4월 KelpDAO rsETH bridge 사고는 조금 더 불편한 질문을 던진다.

LayerZero의 공식 incident report에 따르면 2026년 4월 18일 KelpDAO의 rsETH bridge는 LayerZero cross-chain messaging 위에서 공격을 받았고, 116,500 rsETH, 당시 약 2억 9,200만 달러 규모가 빠져나갔다. 보고서는 이 공격을 DPRK 계열 TraderTraitor/UNC4899로 귀속했다. 중요한 점은 LayerZero가 “protocol layer는 configured policy를 그대로 집행했고, DVN signing key도 유출되지 않았다”고 설명한다는 것이다.

그런데도 destination contract는 forged cross-chain message를 받아들였다. 왜 그랬을까? LayerZero 보고서의 핵심 설명은 이렇다. 공격자는 LayerZero Labs 개발자를 사회공학으로 침해해 session key를 얻고, LayerZero의 RPC cloud 환경으로 pivot했다. 이후 내부 RPC node의 실행 중인 op-geth process를 메모리에서 patch해, 모니터링 도구에는 정상 응답을 주면서 DVN signing service에는 조작된 chain state를 보여줬다. 동시에 외부 RPC provider에 DoS를 걸어 DVN이 compromised internal RPC에만 의존하게 만들었다.

이 글의 핵심은 가격이나 사후 책임 공방이 아니다. 개발자 관점에서 배워야 할 원리는 하나다.

브리지에서 “유효한 서명”은 “서명자가 정확한 체인 상태를 봤다”는 뜻이 아니다.

즉 cross-chain system의 trust boundary는 on-chain contract, signer key, multisig에서 끝나지 않는다. verifier가 chain state를 읽는 RPC, failover 정책, verifier quorum, application-level config, invariant monitoring까지 하나의 보안 모델로 봐야 한다.

2. 핵심 개념

LayerZero V2 문서는 각 application이 source/destination pathway마다 Security Stack을 구성하고, 여러 DVN(Decentralized Verifier Network)이 message의 payloadHash를 독립적으로 검증한다고 설명한다. configured threshold가 충족되면 message nonce가 destination Endpoint에 commit되고 실행 가능해진다.

여기서 헷갈리기 쉬운 지점이 있다.

1. DVN은 oracle처럼 체인 상태를 읽는다.
DVN은 source chain에서 어떤 message가 발생했는지 확인해야 한다. 이를 위해 node/RPC infrastructure를 사용한다. 이 RPC가 거짓 상태를 보여주면 DVN은 정직하게 잘못된 사실에 서명할 수 있다. 이 경우 signing key는 안전하고 signature도 valid하지만, signature의 입력이 오염된다.

2. 1-of-1 verifier는 “하나의 관측자”를 “사실”로 승격한다.
LayerZero incident report는 affected OApp이 single-verifier configuration이었고, second independent DVN이 요구되지 않았기 때문에 destination contract가 single valid attestation을 받아들였다고 설명한다. 즉 contract 입장에서는 규칙 위반이 아니다. configured policy가 “한 명이 봤다고 하면 실행”이었고, 그 한 명이 오염된 RPC를 보고 서명했다.

3. required DVN과 optional DVN은 liveness와 safety를 동시에 건드린다.
LayerZero 문서의 X-of-Y-of-N 모델에서 required DVN은 반드시 attest해야 하고, optional DVN은 threshold만 맞으면 된다. required 수를 늘리면 단일 verifier compromise에는 강해지지만 한 verifier 장애가 message delivery를 막을 수 있다. optional pool을 잘 쓰면 liveness를 보완할 수 있지만, receiver가 실제로 요구하는 threshold와 sender 쪽 설정이 어긋나면 channel이 멈추거나 의도와 다른 posture가 된다.

4. RPC quorum은 DVN quorum과 다른 층이다.
DVN을 2개 이상 요구해도 두 DVN이 같은 RPC vendor, 같은 cloud account, 같은 client binary, 같은 운영팀의 failover 정책에 의존한다면 독립성이 생각보다 낮다. 반대로 DVN이 하나뿐이면 내부에서 multi-source RPC quorum을 만들어도 최종 attestation 주체가 하나라는 한계가 남는다.

3. 최신 이슈와 연결

공식 report의 attack chain을 개발자 언어로 다시 쓰면 다음과 같다.

  1. Identity compromise: 2026년 3월 6일 LayerZero Labs 개발자가 malicious GitHub repo를 clone했고, FLATROOF/ROOFDECK malware가 설치되어 session key가 탈취됐다.
  2. Infrastructure pivot: 공격자는 GCP/GitHub 환경을 reconnaissance하고 RPC infrastructure에 접근했다.
  3. RPC poisoning: 두 internal RPC node의 running process가 patch되어 DVN에는 forged response를, monitoring tool에는 정상 response를 반환했다.
  4. Failover manipulation: 2026년 4월 18일 외부 RPC provider가 DoS를 받아 DVN signing service가 compromised internal node에만 의존하게 됐다.
  5. Valid attestation over false state: DVN은 조작된 source-chain state를 보고 forged cross-chain message에 유효한 attestation을 만들었다.
  6. Destination execution: affected OApp의 configuration은 1-of-1 DVN이었으므로 destination contract는 해당 attestation만으로 rsETH를 unlock했다.

LayerZero는 이후 운영 태도를 바꿨다고 공지했다. LayerZero Labs DVN은 자신이 sole required attestor인 channel에 더 이상 sign하지 않고, security-related data를 읽을 때 multiple independent RPC source quorum을 요구하며, cloud environment를 rebuild하고, client diversity를 추진한다고 밝혔다. 5월 9일 update에서도 “고가치 transaction에서 우리 DVN이 1/1로 동작하도록 허용한 것은 실수”였다고 인정했다.

LayerZero docs도 현재 production guidance에서 single-DVN configuration은 verifier 하나의 compromise가 해당 pathway의 unrestricted forged message로 이어질 수 있다고 경고하고, production deployment는 independent operator의 multiple required DVN을 쓰라고 안내한다. 또한 defaults에 의존하지 말고 pathway마다 DVN configuration을 명시적으로 pin하라고 강조한다.

Chainalysis 분석은 이 사고를 “non-existent burn에 대해 rsETH가 release된 사건”으로 해석한다. 즉 calldata와 signature만 보면 정상처럼 보였지만, source chain에 대응되는 burn/lock state transition이 없었다. 이것은 단일 transaction 검증 문제가 아니라 cross-chain invariant 문제다.

4. 개발자 관점 해석

이 사건을 “브리지는 위험하다”로만 외우면 학습 가치가 낮다. 더 유용한 해석은 브리지 보안을 네 개의 계약으로 나누는 것이다.

계약 A: Message authenticity

“이 message가 특정 source pathway에서 온 것이 맞는가?”에 대한 계약이다. 보통 endpoint, message library, nonce, payload hash, signature verification으로 표현된다. 이 층은 on-chain code audit으로 비교적 잘 점검된다.

KelpDAO 사고에서 무서운 점은 이 층이 겉으로는 통과했다는 것이다. signature는 valid했고, protocol은 configured policy를 집행했다.

계약 B: Observation correctness

“Verifier가 본 source-chain state가 사실인가?”에 대한 계약이다. RPC, archive node, block confirmation, reorg handling, client diversity, provider diversity가 여기에 들어간다.

이번 사고에서 깨진 것은 이 층이다. RPC node가 거짓 state를 보여줬고, monitoring query에는 정상 응답을 주는 식으로 관측 계층이 속았다.

계약 C: Quorum independence

“여러 attestor가 정말 독립적인가?”에 대한 계약이다. 2-of-2라고 써 있어도 둘이 같은 cloud project, 같은 RPC provider, 같은 operational credential, 같은 source client를 쓰면 독립성은 낮다. 반대로 하나의 vendor 안에서도 client diversity와 RPC quorum을 만들 수 있지만, 그것이 application-level multi-DVN을 완전히 대체하지는 못한다.

계약 D: Supply invariant

“한 chain에서 unlock/mint가 일어났다면 다른 chain에는 대응되는 lock/burn이 존재하는가?”에 대한 계약이다. 브리지와 omnichain token은 이 invariant를 제품의 생명줄로 삼는다. 이 invariant를 실시간으로 감시하지 않으면, valid-looking transaction이 전체 supply accounting을 망가뜨릴 수 있다.

KelpDAO 사고의 실전 교훈은 “signature가 맞는가?”보다 “그 signature가 어떤 관측 pipeline에서 만들어졌는가?”를 물어야 한다는 점이다.

5. 내 프로젝트에 적용할 체크포인트

크로스체인 메시지, OFT, bridge adapter, L2 withdrawal, oracle-like attestation을 다룬다면 아래를 점검하자.

  • 1-of-1 verifier 금지: production pathway에 단일 DVN, 단일 oracle, 단일 relayer attestation만으로 asset unlock/mint가 가능한 경로가 있는지 확인한다.
  • DVN/operator 독립성 문서화: “2개를 쓴다”에서 끝내지 말고 운영 주체, cloud, RPC provider, client implementation, key custody, incident response 책임이 분리되는지 적는다.
  • RPC quorum 별도 설계: verifier가 source-chain state를 읽을 때 internal/external provider, region, client diversity를 요구하는지 확인한다. failover가 공격자에게 “poisoned source만 남기는” 경로가 되지 않게 한다.
  • defaults를 config로 착각하지 않기: LayerZero docs는 defaults가 placeholder이며 변할 수 있다고 경고한다. send/receive 양쪽 pathway에 requiredDVNs, optionalDVNs, optionalDVNThreshold, confirmations를 명시적으로 pin한다.
  • send/receive symmetry 검사: source send config와 destination receive config가 의도대로 맞는지 CI 또는 배포 스크립트에서 getConfig/raw app config를 비교한다.
  • invariant monitoring: destination unlock/mint 이벤트가 발생하면 source lock/burn 이벤트와 supply delta가 맞는지 독립 indexer로 확인한다. “alert만”이 아니라 pause authority와 runbook까지 연결한다.
  • pause 권한의 속도와 남용 균형: emergency pause는 중앙화 리스크지만 bridge invariant break에서는 몇 분이 중요하다. multi-sig, timelock bypass 조건, post-mortem 절차를 미리 정한다.

6. 오늘 10분 액션

오늘 할 일은 코드를 많이 쓰는 것이 아니라 내 시스템의 “관측 신뢰 지도”를 그리는 것이다.

  1. 내가 쓰는 bridge/cross-chain SDK에서 message가 실행되기까지 필요한 attestor 목록을 적는다.
  2. 각 attestor가 source-chain state를 어디서 읽는지 적는다. 모르면 “unknown RPC dependency”로 표시한다.
  3. production config가 default인지 explicit pin인지 확인한다.
  4. unlock/mint 이벤트 하나를 골라 대응되는 lock/burn 이벤트를 query로 찾는 작은 invariant check를 작성한다.
  5. 그 invariant가 깨졌을 때 누가 몇 분 안에 pause할 수 있는지 runbook 첫 줄을 쓴다.

예시 pseudo-check는 이렇게 생겼다.

zsh — 생존확인.sh
for each destination Unlock(asset, amount, sourceChain, sourceTxHash):
  sourceEvent = find BurnOrLock(sourceChain, sourceTxHash, asset, amount)
  if sourceEvent is missing after confirmation_window:
    alert("bridge invariant broken")
    if asset_risk > threshold:
      trigger_pause_runbook(pathway)

이 작은 체크 하나가 첫 번째 손실을 막지는 못할 수 있다. 하지만 두 번째 forged packet, 후속 swap, lending collateralization을 막는 시간은 벌 수 있다.

7. 더 볼 자료

  • LayerZero의 Security Stack (DVNs) 문서에서 required/optional DVN threshold가 어떻게 message execution 조건이 되는지 확인한다.
  • LayerZero의 Production DVN Configuration 문서에서 2-of-2, 2-of-3, 3-of-N tradeoff를 비교한다.
  • LayerZero incident report PDF의 layer-by-layer breakdown을 읽고 내 프로젝트의 protocol layer, signer key, off-chain RPC layer를 같은 방식으로 나눠본다.
  • Chainalysis의 KelpDAO exploit 분석에서 “non-existent burn against release”라는 invariant 관점을 확인한다.

중복 회피 메모

로컬 content/generated와 Supabase의 최근 blockchain_dev 글을 확인했다. 최근 글은 EIP-4444/EIP-7642 history expiry, EIP-7917 proposer lookahead, EIP-7939 CLZ opcode, EIP-7907 code size gas, Solana Alpenglow finality, Taiko bridge message-origin verification, OP Stack fault proof soundness, ERC-4626 NAV manipulation, P-256/passkey precompile, MODEXP repricing, PeerDAS/blob fee reserve를 다뤘다. 이번 글은 Taiko처럼 on-chain bridge proof/source event 검증을 반복하지 않고, KelpDAO/LayerZero 사고를 통해 off-chain RPC observation, DVN quorum independence, default configuration, bridge supply invariant monitoring을 신뢰 경계로 보는 관점에 집중해 중복을 피했다.

오늘 10분 액션+5%

댓글 0

최신순 ▾
한 줄 남기기

혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)