Pools 18
Miners n.v.t.
Workers n.v.t.

< Alle artikelen

De Ravencoin KAWPOW-bug: een blokheader die zijn eigen proof-of-work-controle koos

Een Ravencoin-blok gemined op hoogte 4.487.776 schreef een lagere hoogte in zijn eigen header. Omdat dat getal onder het laatste checkpoint op 2.383.550 lag, vertrouwde de validatie de mix_hash die al in de header stond, in plaats van de volledige geheugenintensieve controle uit te voeren.

Ravencoin heeft de tweede week van augustus besteed aan het terugdraaien van zijn chain met ongeveer drie dagen. De oorzaak was geen mining-kartel, geen wallet-exploit en geen kapotte hashfunctie. Het was één veld in de blokheader waarop de node vertrouwde om te bepalen welke van twee proof-of-work-controles hij moest uitvoeren. Elk stukje code dat hieronder wordt aangehaald, is gelezen uit de eigen consensusbroncode van Ravencoin en niet uit een aankondiging, en waar de aankondigingen iets zeggen wat de broncode niet laat zien, zegt dit artikel dat in plaats van te gissen.

Wat een KAWPOW-header bevat

KAWPOW is de proof of work van Ravencoin, een ProgPoW-variant waarop de chain in mei 2020 overstapte om de coin op gewone GPU's te houden. Die overstap veranderde de blokheader. Vóór het activeringsmoment eindigt de header met de gewone 32-bit nonce die elke van Bitcoin afgeleide chain gebruikt. Daarna schrijft de serializer in die plek drie velden, in deze volgorde: nHeight, dan nNonce64, dan mix_hash.

Twee daarvan zijn onopvallend. nNonce64 is gewoon een bredere nonce. mix_hash is het tussenresultaat van het geheugenintensieve deel van het algoritme, meegenomen in de header zodat een verifier een waarde heeft om tegen te controleren in plaats van een berekening die hij vanaf nul moet herhalen. De derde is de interessante. nHeight is het blok dat in zijn eigen header vermeldt op welke hoogte het denkt te staan.

De echte positie van een blok in de chain is geen kwestie van mening. Ze ligt vast door hashPrevBlock: op welk blok u ook bouwt, dat bepaalt waar u terechtkomt, en daarover kunt u niet liegen en toch aan de tip vastzitten. nHeight is een tweede, beschrijfbare kopie van datzelfde feit, en een kopie van een feit is nooit meer waard dan de controle die haar met het origineel vergelijkt.

Twee hashfuncties, waarvan er één niets verifieert

De hash.cpp van Ravencoin definieert twee KAWPOW-ingangen, en hun signaturen vertellen het grootste deel van het verhaal al voordat u één regel van de inhoud leest. De eerste is uint256 KAWPOWHash(const CBlockHeader& blockHeader, uint256& mix_hash). De tweede is uint256 KAWPOWHash_OnlyMix(const CBlockHeader& blockHeader).

De eerste neemt mix_hash als uitvoerparameter, omdat ze er een produceert. Ze kiest de dataset met ethash::get_epoch_number(blockHeader.nHeight), bouwt die epoch-context als ze die nog niet heeft, en voert dan progpow::hash erover uit. De mix die ze teruggeeft, is een bijproduct van het gedane werk. Dit is het geheugenintensieve pad, en die geheugenintensiteit is de hele reden dat KAWPOW bestaat in plaats van een gewone hash.

De tweede neemt zo'n parameter niet, omdat ze niets produceert. Ze gebruikt de mix die al in de header staat, via progpow::hash_no_verify(blockHeader.nHeight, header_hash, to_hash256(blockHeader.mix_hash.GetHex()), blockHeader.nNonce64). De upstream-bibliotheek noemde die functie hash_no_verify en de naam klopt precies. Ze voert de laatste mengstap uit over een waarde die ze kreeg en geeft het antwoord terug. Ze bouwt nooit de dataset, dus ze stelt nooit vast dat de waarde die ze kreeg alleen uit het echte werk kan zijn gekomen.

Geen van beide functies is een vergissing. De goedkope bestaat met een goede reden: een node die miljoenen oude blokken synchroniseert die een hardgecodeerd checkpoint al heeft vastgelegd, hoeft niet per epoch een dataset van meerdere gigabytes opnieuw op te bouwen om geschiedenis te bevestigen die niemand betwist. De enige vraag die zo'n ontwerp moet beantwoorden, is welke blokken die route mogen nemen.

De poort, en waarop die vertrouwde

Die vraag wordt beantwoord door CheckBlockHeader in validation.cpp, en het commentaar direct boven het antwoord noemt de bedoeling duidelijk: "If we are checking a KAWPOW block below a know checkpoint height. We can validate the proof of work using the mix_hash". De bedoeling is juist. De uitvoering toetst op block.nHeight <= (uint32_t)pcheckpoint->nHeight, en block.nHeight is het getal dat het blok over zichzelf schreef.

De regel eronder roept de algemene CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params&) aan. Kijk wat die signatuur niet bevat. Er is geen mix, geen nonce en geen header. Ze krijgt een kant-en-klare hash en een doel en vergelijkt die twee, en ze kan op geen enkele manier weten welke van de twee functies het getal heeft gemaakt dat ze kreeg. Voor een KAWPOW-blok geeft CBlockHeader::GetHash() KAWPOWHash_OnlyMix(*this) terug, dus het getal dat ze krijgt, is het goedkope.

Achter elkaar gezet werd een blok dat een nHeight opgaf die laag genoeg was, gevalideerd door te vragen of de waarde die uit zijn eigen mix hash was afgeleid onder het doel viel. Nergens op dat pad vroeg iets waar de mix vandaan kwam, en nergens op dat pad vergeleek iets de opgegeven hoogte met de positie die het blok werkelijk innam.

Hoe laag laag genoeg is, blijkt allesbehalve subtiel. Het hoogste checkpoint in de uitgebrachte mainnet-chainparameters is 2.383.550. Het eerste vervalste blok stond op 4.487.776. Zijn header moest een positie claimen die ongeveer 2,1 miljoen blokken achter de werkelijke lag, en de tak die het daarmee koos, keek nooit naar het verschil.

Wat de shortcut wel en niet opleverde

Hier loont het om precies te zijn, want de meeste berichtgeving was er slordig over, en de slordige versie laat de bug zowel eenvoudiger als vreemder klinken dan hij is.

De shortcut verlaagde de moeilijkheid niet. nBits wordt gevalideerd tegen wat het echte vorige blok vereist, en het echte vorige blok wordt gekozen door hashPrevBlock, dat een aanvaller niet kan vervalsen zonder van de tip af te vallen. Een vervalst blok moest precies hetzelfde doel halen als een eerlijk blok dat in dezelfde minuut werd gemined.

Wat veranderde, is de prijs van één poging. Een eerlijke miner betaalt voor elke poging met een volledige doorgang over een dataset van gigabytes, wat het hele punt is van een geheugenintensief algoritme en de reden dat KAWPOW ASIC's al zes jaar van Ravencoin weghoudt. De shortcut kost een hash van de header plus één laatste mengstap over een waarde die de aanvaller vrij koos. Dezelfde loterij, dezelfde kans per lot, loten die ordes van grootte goedkoper zijn. 2Miners noemde het resultaat blokken met bijna nul werk, en gemeten aan wat KAWPOW zou moeten kosten, is dat een eerlijke omschrijving.

Wat er op het netwerk gebeurde

Het eerste bekende ongeldige blok heeft hoogte 4.487.776, met tijdstempel 2026-08-07 15:44:01 UTC. Het misbruik ging door tot de noodrelease op 10 augustus. In één onderzocht stuk van 2.089 blokken telde 2Miners er 96 die getroffen waren, dus de vervalste blokken liggen verspreid door de tak in plaats van samen in één reeks, en daarom moest de oplossing een reorganisatie zijn in plaats van een chirurgische verwijdering.

Voor node-beheerders was het zichtbare symptoom erger dan een ongeldig blok dat stilletjes werd geaccepteerd. Nodes crashten op deze blokken en stopten na een herstart met synchroniseren. Tron Black heeft dat mechanisme sindsdien beschreven, en het is een keurig staaltje nevenschade. De blokindex slaat de hoogte op waarop een blok werkelijk staat, niet de hoogte die zijn header opgaf. Bij een herstart bouwt de node elke header opnieuw op uit de index en berekent hij de hash opnieuw, dus voor een vervalst blok hasht hij de echte hoogte waar de header een valse had. De hash klopt niet meer, de proof-of-work-controle faalt en LoadBlockIndexGuts breekt af. Het blok werd één keer geaccepteerd en werd daarna onlaadbaar, en daarom duurde het tweede symptoom langer dan het eerste.

Verschillende exchanges schortten stortingen en opnames van RVN op zolang de tak niet was opgelost. Dat is de gewone en juiste reactie op een chain waarvan de recente geschiedenis op het punt staat te worden herschreven, en het is iets om te verwachten, niet om te lezen als een oordeel over de coin.

De patch, en waar die vandaan kwam

De eerste fix kwam op 10 augustus uit als versie 4.6.1.1-hf1, en hij kwam van 2Miners, een mining pool, en niet van het Ravencoin-project. Hij stopte het bloeden voor iedereen die hem installeerde. Een dag later werd hij vervangen.

Versie 4.8.0, op 11 augustus gepubliceerd via de Ravencoin-repository, is de release om te draaien. Neem geen genoegen met iets eerders. Ze weigert vanaf 4.487.776 blokken waarvan de opgegeven headerhoogte niet overeenkomt met hun echte positie, legt 4.487.775 vast als checkpoint en laatste schone blok, bouwt een beschadigde chainstatus automatisch opnieuw op in plaats van af te breken, en bevat een fix voor een overflow bij assetoverdrachten die de noodhotfix niet had, samen met ander upstream-werk.

Alleen de eerste daarvan is de reparatie. Weiger een blok waarvan de opgegeven hoogte niet overeenkomt met waar het werkelijk staat, en de poort is dicht. Het checkpoint en de automatische heropbouw zijn herstel, en vooral een checkpoint is een bot instrument: het is een hardgecodeerde bewering dat één bepaald blok het juiste is, iets waar een chain zo weinig mogelijk van wil hebben. Het is er omdat de eerlijke tak sneller vast moest komen te liggen dan hashrate alleen dat zou doen.

Het herstelpunt zelf was een afweging en geen vanzelfsprekendheid. Tron Black heeft geschreven dat hij de pools vroeg een recentere snede te overwegen, om de reorganisatie minder diep te maken en de vervalste blokken als uitzondering te behandelen. Zij weigerden en kozen de schoonst mogelijke lijn. In een proof-of-work-systeem beslecht die discussie zichzelf: wanneer de meerderheid van de eerlijke hashrate een geschiedenis weigert, verliest die geschiedenis, en de twee grootste pools hadden samen de meerderheid.

Wat een reorg van drie dagen echt betekent

Omdat kwetsbare nodes de vervalste blokken accepteerden, groeide er op de tak die ze bevatte echte chain bovenop. Die weggooien betekent een reorganisatie, en bij het blokdoel van één minuut van Ravencoin is het stuk van 4.487.776 tot de patch ongeveer drie dagen aan blokken.

Als u RVN bezit, is de praktische betekenis dat transacties die op de weggegooide tak waren bevestigd, niet langer bevestigd zijn. De meeste keren terug naar de mempool en worden opnieuw gemined op de overlevende chain, maar dat is niet gegarandeerd, en één soort kan helemaal niet terugkomen. Een transactie die de coinbase van een weggegooid blok uitgaf, geeft coins uit die op de overlevende chain niet meer bestaan, en hetzelfde geldt voor alles wat daarop is gebouwd. Die zijn gewoon ongeldig in plaats van in behandeling. Daarom waren de opschortingen door exchanges de juiste beslissing en geen overreactie, en daarom verdiende activiteit na 4.487.775 voorzichtigheid tot er één tip vaststond.

Eén detail is goed om te weten als u in die week een node zag weigeren verder te gaan. Ravencoin-nodes hebben een regel tegen lange reorgs, die bestaat om te voorkomen dat een onverwachte meerderheidsaanval diepe geschiedenis herschrijft. De meeste oude nodes die onafgebroken draaiden, zakten uiteindelijk onder het aantal peers waarvan die regel afhangt en accepteerden daarna de langere chain, zoals bedoeld. Een paar deden dat niet, omdat ze nooit genoeg peers verloren en omdat er volgens de regels zoals geschreven niets was geschonden. Dat is een bescherming die precies werkt zoals gespecificeerd, op een probleem waarvoor ze nooit is ontworpen. De splitsing kwam door een fout in de validatie, niet door een aanval op de meerderheid, en de regel kan die twee niet uit elkaar houden.

Wat te doen als u RVN mint

Als u uw eigen Ravencoin-node draait, draai dan 4.8.0, en uitdrukkelijk niet een van de eerdere releases die maar een deel hiervan oplosten. Stop de node, vervang het binaire bestand, start hem opnieuw. Er is geen reindex nodig en geen handmatige stap: de software bouwt zelf opnieuw op wat ze nodig heeft. Een node die van de noodhotfix komt, start snel opnieuw. Een node die van software zonder patch komt, moet opnieuw afspelen en opbouwen, wat op gewone hardware enkele uren duurt, en bad-blk-height-fouten in die periode zijn de patch die zijn werk doet, geen fout erin.

Als u via een pool mint, hoeft u aan uw kant niets te veranderen. Geen nieuw adres, geen nieuwe poort, geen miner-update. KAWPOW zelf was hier nooit het probleem: het algoritme deed precies waarvoor het is ontworpen, en het defect zat in de code die besliste wanneer het werd uitgevoerd. De hashrate die u op de oude tak richtte, was echt werk. Ze kwam gewoon terecht op blokken die werden weggegooid, samen met de vervalste blokken die daartussen verspreid lagen.

Er is een manier in één regel om te bevestigen dat u op de juiste chain bent beland, en het loont om dat te doen in plaats van het aan te nemen. Controleer de hash van blok 4.494.143. Op de canonieke chain eindigt die op 4c67d6a4, en de onafhankelijke Ravencoin-explorers toonden allemaal diezelfde chain toen het project het netwerk hersteld verklaarde.

De vorm van de bug

Haal de details weg en wat overblijft, is een vorm die steeds terugkomt, in chains en ver daarbuiten. Een waarde die van buiten kwam, werd gebruikt om te bepalen hoe zorgvuldig diezelfde waarde zou worden onderzocht. De header vermeldde zijn eigen hoogte, en de node gebruikte die vermelding om te kiezen welke proof-of-work-controle de header daarna moest doorstaan.

De juiste versie is niet ingewikkeld, en de patch is in wezen één regel daarvan: vergelijk de opgegeven hoogte met de positie die het blok werkelijk inneemt voordat ze ook maar iets mag beslissen. Waarom die ontbrak, is de interessantere helft. De shortcut werd geschreven voor blokken die al vertrouwd zijn, en binnen die aanname leest de poort als een feitelijke vaststelling en niet als een vraag die iemand oneerlijk zou kunnen beantwoorden. Zo leest ze niet meer op het moment dat een niet-vertrouwd blok het veld mag invullen, en niets in de code markeerde wanneer dat veranderde.

Twee dingen over hoe het werd gevonden, zijn het vastleggen waard. Ravencoin heeft professionele beveiligingsaudits van zijn code gehad, en die vonden dit niet, en ook niet de asset-overflow die ermee samen werd opgelost. Tron Black heeft geschreven dat hij sterk vermoedt dat beide aan het licht kwamen door een beveiligingsreview met een frontier-AI-model. Wat de tooling ook was, het past bij de vorm: de fout is onzichtbaar zolang u de shortcut op zijn eigen voorwaarden leest, en verschijnt pas wanneer u vraagt wie het veld mag invullen waarop de shortcut vertrouwt.

Het laatste wat het opmerken waard is, is wie in actie kwam, en in welke volgorde. Een mining pool las het validatiepad, begreep het en bracht binnen drie dagen een binair noodbestand uit. De twee grootste pools kwamen daarna overeen een chain te minen die het hele aanvalsvenster weggooide in plaats van de handige snede te nemen, en toen hun werd gevraagd de minder diepe te nemen, weigerden ze. Het project volgde een dag later met de release die de hotfix vervangt en meer oplost dan die deed. Wie van hen de eerste was, doet er minder toe dan dat beide gebeurden, en dat wat het uiteindelijk beslechtte geen van beide aankondigingen was, maar het opgebouwde gewicht van echt werk aan één kant van de splitsing. Zo ziet decentralisatie eruit op een dag dat ze gewicht moet dragen in plaats van in een whitepaper te staan.

Waar deze cijfers vandaan komen

Zodat iedereen het kan controleren in plaats van ons op ons woord te geloven. De headerindeling, de twee hashfuncties en hun signaturen, de epochkeuze en de validatietak met zijn commentaar zijn gelezen uit de eigen broncode van Ravencoin, in src/primitives/block.h, src/primitives/block.cpp, src/hash.cpp, src/hash.h, src/pow.h en src/validation.cpp. De lijst met checkpoints, met 2.383.550 als hoogste op mainnet, is gelezen uit src/chainparams.cpp. De eerste ongeldige hoogte en de tijdstempel ervan komen uit het eigen netwerkbericht van het Ravencoin-project. Het crashpad van de blokindex, de transacties die van de coinbase afhangen en niet kunnen terugkomen, het gedrag van de regel tegen lange reorgs, het geweigerde minder diepe herstelpunt, de opmerkingen over de audit en de AI-review, wat 4.8.0 bevat en de controle van 4.494.143 komen uit het verslag van Tron Black voor het Ravencoin-project. De versie van de noodhotfix en het aantal van 96 getroffen blokken binnen 2.089 komen uit de release van 2Miners en de berichtgeving eromheen. Wat de bronnen niet vermelden, is uit dit artikel weggelaten in plaats van ingevuld.