Workspace IndexDev Notes › Liquid's peg-out drain — the mint was poisoned, not the gate

#44PoC

Liquid's peg-out drain — the mint was poisoned, not the gate

The Blockstream Liquid loss was not an authorization bypass — my earlier guess was wrong. An Elements issuance bug minted LBTC that was technically valid, and the peg-out system correctly honored it. The gate worked; the set it counted was contaminated at the source.

Not a build — a correction and a checklist. First correct the guess: this was not an access-control gap (my earlier read), it was an issuance bug in Elements that produced LBTC the peg-out logic then treated as valid. Then audit any system you run that burns/redeems/settles against a supply: does anything downstream trust 'is this a valid unit?' without also trusting 'was this unit minted under the invariant?' List every place those two questions are answered by different code — that gap is where this class lives. Confirm specifics against Blockstream's incident notes and the on-chain OP_RETURN / PGP negotiation before citing them.

Why

The correction is the point. I first read this as an access-control gap — a check that should have refused a withdrawal and didn't. That was wrong, and the way it was wrong is the lesson. Authorization worked. The peg-out logic asked the right question — 'is this a valid LBTC?' — and got a truthful 'yes.' The problem was upstream: an issuance bug in the Elements software minted LBTC that should never have existed, and every downstream check faithfully honored it, because by the rules those checks enforce it was real.

So the failure axis is not 'who was allowed to do what.' It is 'was the thing being counted minted under the invariant.' Those are different questions, usually answered by different code, and a system can pass the first perfectly while the second is already broken. A peg is exactly where this hurts most: the whole product is a promise that on-chain supply equals off-chain reserve, and a mint bug breaks that equality silently — the tokens look valid because they are valid; they are just not backed.

This reframes what an authority audit can and cannot catch. The authority matrix — who may mint, pause, upgrade — is necessary but not sufficient: it proves the right actors hold the right permissions, not that the code those actors run preserves the supply invariant. A correct permission executing buggy issuance produces valid-looking, unbacked units, and no access-control review flags it, because no access control was violated.

How it works

Two questions, two different pieces of code

Question Who answers it Liquid's outcome
Authorization — is this actor allowed to peg out? the peg-out gate worked correctly
Supply integrity — was this LBTC minted 1:1 under the peg invariant? the Elements issuance path broken — produced invalid units that looked valid

The drain rode entirely on the second row. The peg-out was never tricked; it was handed technically-valid tokens and did its job.

Why 'valid token' is a weaker claim than it sounds

Liquid is a federated Bitcoin sidechain: LBTC is meant to be 1:1 with BTC held by the federation — minted on peg-in, burned on peg-out. 'A valid LBTC' means it satisfies the chain's rules. 'A correctly-issued LBTC' means it was minted against a real peg-in. An issuance bug lets those two diverge: units that pass every validity check but were never backed. Downstream nothing tells them apart, so the peg-out honors them like any other.

The negotiation was the unusual part

Instead of vanishing, the attacker negotiated on-chain — an OP_RETURN plus a PGP-signed message with a condition: fix the bug first and patch every node. Blockstream replied, also PGP-signed — bridge nodes patched, safe to return — and the attacker signaled they would return most of the funds. As of this writing the return is not complete. The shape is worth noting: the chain's own data field carried the diplomacy, and PGP signatures stood in for identity because on a bridge there is no other trusted channel between the two sides.

The negotiation points at a second, bigger problem — proving who sent a message

The on-chain diplomacy is a crypto story, but its implication is general: how do you prove a message really came from a given organization? The OP_RETURN carried the words; the PGP signatures carried the identity, because on a bridge there is no other trusted channel. This catalogue's 2026-08-01 note on a hijacked executive account used to promote an RWA token was the same axis seen from the attacker's side — here it returns as the defender's need. The observation recorded then is confirmed in practice now: there is real demand for verified-identity official-announcement channels — signed notices, on-chain attestations — and as AI-generated content grows, that demand grows with it. A PGP-signed OP_RETURN is a crude version of exactly that channel.

Where this lands in Jayverse

  • The token bridge (jayverse-token-bridge) is a 1:1 lock/mint vault — its entire safety claim is the supply invariant this incident broke. An invariant test ('minted on dest == locked on source, always') is the direct defense, and it belongs with the mint path, not the withdraw gate.
  • The Authority Auditor (#8) proves permissions, not issuance correctness — a concrete case of why the authority matrix is necessary but not sufficient, and it should say so.
  • Security-hole research (#10 Dark Horse) — reproduce this class on a local fork: a mint bug that yields valid-but-unbacked units and a redeem path that honors them. The invariant that catches it is conservation, not access control.

The one-line takeaway

Checking the actor is not the same as checking the object. When the object was minted wrong, a correct check faithfully approves a poisoned unit — the gate was fine; the count was contaminated.

Review clarification

From incident to invariant, made operational

The section above is the failure (supply integrity, not authorization). This is the defense as a running layer — because 'add an invariant test' is easy to say and easy to build wrong.

A good invariant has three properties, and the third is the one people skip. It must be (1) independently computable — derivable without trusting the system's own bookkeeping, or it just re-reads the same lie the peg-out believed; (2) violation-is-meaningless — if reserves = issuance is false, every downstream number is void, so there is no 'degraded mode' to keep limping in; and (3) wired to an automatic halt — an alert is a request for a human to notice, and the whole failure mode is that the human did not. Reserves = issuance had (1) and (2) for free and was checked by nothing — (3) missing.

Put the checker where the thing it watches cannot reach it. If the invariant monitor shares infrastructure and trust boundary with the mint/settle path, the compromise that breaks one breaks both — the watcher dies with the watched. Tune it to a narrow halt scope, not a loose threshold: a monitor that fires often gets muted, and a muted monitor is worse than none because it was believed.

Simulate before, check after — one discipline. The invariant asks did the equation just break; pre-execution simulation asks would this transaction break it, replaying the tx against current state and reading the state diff before signing. Tooling like Tenderly (or a Foundry fork in CI) makes that a check on the mint transaction itself — a single alert on 'total issuance changed unexpectedly' is the shape that catches this before the peg-out ever sees the token.

The scoping exercise for our own systems

List every equation that must always hold, is computable independently, and name its automatic stop:

Invariant (must always hold) Independently computable from Halts on violation
minted on dest = locked on source source vault + dest supply new mints / withdrawals
settlement sum = per-market sum market ledgers, summed settlement writes
escrow balance >= open claims escrow account + claim registry new claims

Cross-refs: foundry-invariant-reachability (the same idea at test time — an invariant test only catches what its actions reach), simulate-before-sign (the pre-execution half), who-holds-the-mint (the mint is exactly the privileged write an invariant should bound), receipt-is-not-settlement (a success flag is not proof the state is sound).

← All Dev Notes · Workspace Index · Top ↑

Liquid 페그아웃 유출 — 뚫린 건 문이 아니라 발행이었다

블록스트림 Liquid 손실은 인가 우회가 아니었습니다 — 제 이전 추측은 틀렸습니다. Elements 발행 버그가 기술적으로 유효한 LBTC를 만들어냈고, 페그아웃 시스템은 그것을 정상적으로 인정했습니다. 문(인가)은 제대로 작동했고, 세던 대상이 발행 단계에서 오염돼 있었습니다.

구현이 아니라 정정과 체크리스트입니다. 먼저 추측을 정정 — 이건 접근제어 결함(제 이전 판단)이 아니라, 페그아웃 로직이 유효하다고 인정한 LBTC를 만들어낸 Elements 발행 버그였습니다. 그다음, 공급량을 근거로 소각/상환/정산하는 시스템을 감사하세요: 하류 어딘가가 '이게 유효한 단위인가?'만 믿고 '이 단위가 불변식에 따라 발행됐는가?'는 안 믿는 곳이 있는가? 두 질문을 서로 다른 코드가 답하는 지점을 전부 적으세요 — 그 틈이 이 부류가 사는 곳입니다. 구체 수치는 블록스트림 사고 공지와 온체인 OP_RETURN / PGP 협상 기록으로 확인하세요.

정정이 곧 핵심입니다. 저는 처음에 이걸 접근제어 결함으로 봤습니다 — 출금을 막았어야 할 검사가 안 막은 것. 그건 틀렸고, 틀린 방식이 바로 교훈입니다. 인가는 작동했습니다. 페그아웃 로직은 옳은 질문('이게 유효한 LBTC인가?')을 던졌고 정직한 '예'를 받았습니다. 문제는 상류에 있었습니다 — Elements 소프트웨어의 발행 버그가 존재해선 안 될 LBTC를 만들었고, 하류의 모든 검사는 그것을 충실히 인정했습니다. 그 검사들이 강제하는 규칙상 그건 진짜였으니까요.

그래서 실패의 축은 '누가 무엇을 하도록 허용됐는가'가 아닙니다. '세고 있는 대상이 불변식에 따라 발행됐는가'입니다. 이 둘은 서로 다른 질문이고 대개 서로 다른 코드가 답하며, 시스템은 첫 번째를 완벽히 통과하면서 두 번째가 이미 깨져 있을 수 있습니다. 페그는 이게 가장 아픈 자리입니다 — 상품 전체가 '온체인 공급량 = 오프체인 준비금'이라는 약속인데, 발행 버그는 그 등식을 조용히 깹니다. 토큰은 유효하기 때문에 유효해 보이지만, 다만 뒷받침이 없습니다.

이건 권한 감사가 무엇을 잡고 무엇을 못 잡는지를 다시 정의합니다. 권한 행렬(누가 발행/정지/업그레이드할 수 있는가)은 필요조건이지 충분조건이 아닙니다 — 올바른 주체가 올바른 권한을 쥐었음은 증명하지만, 그 주체가 돌리는 코드가 공급 불변식을 보존하는지는 증명하지 않습니다. 올바른 권한이 버그 있는 발행을 실행하면 유효해 보이지만 뒷받침 없는 단위가 나오고, 어떤 접근제어 검토도 이를 잡지 못합니다 — 접근제어는 위반된 적이 없기 때문입니다.

동작 방식

두 개의 질문, 서로 다른 두 코드

질문 누가 답하나 Liquid의 결과
인가 — 이 주체가 페그아웃해도 되는가? 페그아웃 게이트 정상 작동
공급 무결성 — 이 LBTC가 페그 불변식에 따라 1:1로 발행됐는가? Elements 발행 경로 깨짐 — 유효해 보이는 무효 단위 생성

유출은 전적으로 두 번째 행을 타고 일어났습니다. 페그아웃은 속은 적이 없습니다 — 기술적으로 유효한 토큰을 건네받아 제 일을 했을 뿐입니다.

'유효한 토큰'이 들리는 것보다 약한 주장인 이유

Liquid은 연합형 비트코인 사이드체인입니다: LBTC는 연합이 보유한 BTC와 1:1이어야 하고, 페그인 때 발행되고 페그아웃 때 소각됩니다. '유효한 LBTC'는 체인의 규칙을 만족한다는 뜻입니다. '올바르게 발행된 LBTC'는 실제 페그인에 대응해 발행됐다는 뜻입니다. 발행 버그는 이 둘을 갈라놓습니다 — 모든 유효성 검사를 통과하지만 뒷받침이 없는 단위. 하류에서는 이를 구분할 방법이 없어, 페그아웃은 다른 것과 똑같이 인정합니다.

협상이 특이한 부분이었다

공격자는 사라지는 대신 온체인에서 협상했습니다 — OP_RETURN과 PGP 서명 메시지로 조건을 걸었습니다: 버그부터 고치고 모든 노드를 패치하라. 블록스트림도 PGP 서명으로 답했고 — 브릿지 노드 패치 완료, 반환해도 안전하다 — 공격자는 대부분을 반환하겠다고 신호했습니다. 이 글을 쓰는 시점에 반환은 완료되지 않았습니다. 모양이 주목할 만합니다: 체인 자체의 데이터 필드가 협상을 실어 날랐고, 브릿지에는 양측 사이에 다른 신뢰 채널이 없으므로 PGP 서명이 신원을 대신했습니다.

이 협상은 더 큰 두 번째 문제를 가리킨다 — 누가 이 메시지를 보냈는가

온체인 협상은 크립토 이야기지만 함의는 넓습니다: 어떤 메시지가 정말 그 조직에서 나왔음을 어떻게 증명하는가. OP_RETURN은 말을 실었고 PGP 서명은 신원을 실었습니다 — 브릿지에는 다른 신뢰 채널이 없기 때문입니다. 이 목록이 2026-08-01에 기록한 '탈취된 임원 계정으로 RWA 토큰을 홍보한' 사건은 공격자 쪽에서 본 같은 축이었고, 여기서는 방어자의 필요로 돌아옵니다. 그때 적은 관찰이 지금 실물로 확인됩니다: 검증된 신원의 공식 발표 채널(서명된 공지·온체인 attestation)에 대한 실수요가 있고, AI 생성 콘텐츠가 늘수록 그 수요는 커집니다. PGP 서명된 OP_RETURN은 바로 그 채널의 조악한 버전입니다.

Jayverse에서의 자리

  • 토큰 브릿지(jayverse-token-bridge)는 1:1 lock/mint 금고입니다 — 안전 주장 전체가 이번에 깨진 그 공급 불변식입니다. 불변식 테스트('목적지 발행량 == 출발지 잠금량, 항상')가 직접적 방어이며, 인출 게이트가 아니라 발행 경로에 붙어야 합니다.
  • 권한 감사기(#8)는 권한을 증명하지 발행의 정확성을 증명하지 않습니다 — 권한 행렬이 필요조건이지 충분조건이 아닌 이유의 구체 사례이며, 문서가 이를 말해야 합니다.
  • 보안 취약점 연구(#10 Dark Horse) — 이 부류를 로컬 포크에서 재현: 유효하지만 뒷받침 없는 단위를 만드는 발행 버그와 그걸 인정하는 상환 경로. 이를 잡는 불변식은 접근제어가 아니라 보존(conservation)입니다.

한 줄 요약

주체를 검사하는 것과 대상을 검사하는 것은 다릅니다. 대상이 잘못 발행됐을 때, 올바른 검사는 오염된 단위를 충실히 승인합니다 — 문은 멀쩡했고, 세는 대상이 오염돼 있었습니다.

검토 후 보완

사건에서 불변식으로 — 운영 가능한 형태로

위 섹션은 실패(인가가 아니라 공급 무결성)입니다. 여기는 방어를 돌아가는 계층으로 본 것입니다 — '불변식 테스트를 추가하라'는 말은 쉽고 잘못 만들기도 쉽기 때문입니다.

좋은 불변식은 세 성질을 갖고, 사람들이 빼먹는 건 세 번째입니다. (1) 독립적으로 계산 가능 — 시스템 자체 장부를 믿지 않고 도출되어야 함, 아니면 페그아웃이 믿은 그 거짓말을 다시 읽을 뿐; (2) 위반이 곧 무의미준비금 = 발행량이 거짓이면 하위 숫자 전부가 무효라, 절뚝이며 굴러갈 '저하 모드'가 없음; (3) 자동 정지에 연결 — 경보는 사람이 알아채 달라는 요청인데, 실패의 요점은 사람이 못 알아챘다는 것. 준비금 = 발행량은 (1)(2)를 공짜로 갖고도 아무것도 확인하지 않았습니다 — (3)이 빠진 것.

감시자는 감시 대상이 닿을 수 없는 곳에 두세요. 불변식 감시자가 발행/정산 경로와 인프라·신뢰 경계를 공유하면, 하나를 뚫는 침해가 둘 다 뚫습니다 — 감시자가 대상과 함께 죽습니다. 느슨한 임계값이 아니라 좁은 정지 범위로 조율하세요: 자주 울리는 감시자는 꺼지고, 꺼진 감시자는 믿어졌기에 없느니만 못합니다.

앞에서 시뮬레이션, 뒤에서 검사 — 하나의 규율. 불변식은 등식이 방금 깨졌나를, 실행 전 시뮬레이션은 이 트랜잭션이 그걸 깰까를 묻습니다 — 현재 상태에 트랜잭션을 재생해 서명 전에 상태 diff를 읽는 것. Tenderly(또는 CI의 Foundry 포크) 같은 도구는 그걸 발행 트랜잭션 자체에 대한 검사로 만듭니다 — '총발행량이 예기치 않게 변함' 경보 하나가, 페그아웃이 그 토큰을 보기도 전에 잡습니다.

우리 시스템을 위한 범위 잡기

항상 성립해야 하고 독립적으로 계산 가능한 모든 등식을 나열하고, 각각의 자동 정지를 적으세요:

불변식(항상 성립) 독립 계산 근거 위반 시 멈추는 것
목적지 발행 = 출발지 잠금 출발지 금고 + 목적지 공급량 신규 발행 / 인출
정산합계 = 마켓별합계 마켓 장부 합산 정산 쓰기
에스크로 잔고 >= 미결제 청구액 에스크로 계정 + 청구 레지스트리 신규 청구

교차: foundry-invariant-reachability(테스트 시점의 같은 생각 — 불변식 테스트는 자기 액션이 닿는 것만 잡는다), simulate-before-sign(실행 전 절반), who-holds-the-mint(발행은 불변식이 묶어야 할 바로 그 특권 쓰기), receipt-is-not-settlement(성공 플래그는 상태가 건전하다는 증명이 아니다).

← 전체 개발 노트 · 워크스페이스 인덱스 · 맨 위 ↑