---
title: "Nunchuk zit 3.940 bytes van reproduceerbaar af · Bitsaga Inzichten"
description: "WalletScrutiny meldde Nunchuk Desktop als niet-reproduceerbaar. Wij controleerden elk cijfer in dat rapport en bouwden vervolgens de versie na die tien minuten voor onze start was uitgebracht. Van de 1.587 bestanden verschillen er 3, en elke afwijkende byte is een kloksstand."
url: "https://bitsaga.be/nl/insights/nunchuk-reproducible-build"
language: "nl"
---

> WalletScrutiny meldde Nunchuk Desktop als niet-reproduceerbaar. Wij controleerden elk cijfer in dat rapport en bouwden vervolgens de versie na die tien minuten voor onze start was uitgebracht. Van de 1.587 bestanden verschillen er 3, en elke afwijkende byte is een kloksstand.
[Inzichten](https://bitsaga.be/nl/insights) · Beveiliging

# Nunchuk zit 3.940 bytes van reproduceerbaar af

Door [Rob Segers](https://bitsaga.be/nl/about) · 20 augustus 2026

> **Kort samengevat.** Een wallet-auditor bestempelde de desktopwallet van Nunchuk als niet-reproduceerbaar, wat betekent dat niemand buiten het bedrijf kon aantonen dat de download overeenkwam met de gepubliceerde broncode, en wij bevestigden elk cijfer in dat rapport zelf. Nunchuk loste het daarna binnen twee dagen op, en onze eigen herbouw van de nieuwe versie komt in 1.584 van de 1.587 bestanden overeen met die van hen, waarbij de enige verschillen 1.302 ingebedde klokstanden zijn en geen enkele regel code.

## De korte versie

Als je een bitcoinwallet downloadt, download je een programma dat iemand anders heeft gecompileerd. Dat de broncode op GitHub staat, bewijst niet dat die twee iets met elkaar te maken hebben. Iemand kan schone code publiceren en jou iets anders opsturen, en jij hebt geen enkele manier om dat te merken.

De oplossing daarvoor heet een **reproduceerbare build**. Als jij de gepubliceerde broncode zelf kunt compileren en er een bestand uit komt dat identiek is aan dat op de downloadpagina, dan is aantoonbaar dat de download uit die broncode komt. Lukt dat niet, dan vertrouw je het bedrijf en niet de code.

[WalletScrutiny](https://walletscrutiny.com) is een project dat precies dit probeert, wallet voor wallet, en het resultaat publiceert. Op 18 augustus publiceerden zij een rood oordeel voor Nunchuk Desktop 2.6.4: **niet reproduceerbaar**. Dat was het negende oordeel van dit soort over dezelfde wallet, in een draad die al sinds april 2025 liep.

Wij deden drie dingen. We controleerden of het rapport klopte. Dat deed het, tot in elk detail. Vervolgens ontdekten we dat Nunchuk het onderliggende probleem stilletjes had opgelost op 19 en 20 augustus, uren na het laatste rapport. En daarna compileerden we versie 2.6.6 zelf, op onze eigen machine, en vergeleken die met de officiële download.

3 van 1.587

bestanden verschillen tussen onze eigen build en de officiële download van Nunchuk

Van die drie zijn er twee hetzelfde bestand dat twee keer is meegeleverd. En de verschillen erin zijn geen code. Het zijn 1.302 vastgelegde tijdstempels: het moment waarop elk bestand toevallig op de bouwmachine werd gezet. Onze kopie en die van hen zijn 58 minuten na elkaar gemaakt, en dat is het hele meningsverschil.

**Wat dit voor jou betekent.** Nunchuk Desktop op Linux is nog niet perfect reproduceerbaar, maar het scheelt heel weinig, en het resterende gat is boekhouding en niet iets waarin kwaadaardige code zich kan verstoppen. De versie ervoor, 2.6.5, werd één dag eerder gecontroleerd en verschilde op 157 punten. Dat is een echte verbetering in zeer korte tijd.

Drie waarschuwingen als je deze wallet gebruikt. Versie 2.6.6 staat gemarkeerd als pre-release en wordt, anders dan elke versie ervoor, **geleverd zonder handtekeningbestand**, dus er is op dit moment niets om de download tegen te controleren. Het is alleen Linux, dus gebruikers van macOS en Windows hebben hier nog niets aan. En 2.6.5, de huidige gewone release, staat nog steeds bij de versies die als niet-reproduceerbaar zijn gemarkeerd.

Alles hieronder is het rekenwerk: de exacte commando's, de exacte cijfers, en vier kleine correcties die wij getest hebben en die het gat volledig zouden dichten. Sla het over, tenzij je ons wilt controleren.

---

## De technische details

### 1. Klopt het rapport zelf?

Voordat we Nunchuk controleerden, controleerden we degene die Nunchuk controleert. Elk cijfer in [verificatie `bcf39301…`](https://walletscrutiny.com/verifier/?pubkey=1f9e547c2f31942623b8ad1d07713282e8640fd8cf474e9f79f18ace8af216ed#verificationId=bcf393011779767d6a76af7220c0fd6532861d95a2506dd50a38bc2a6861f265) is hier vanaf nul nagerekend.

| Bewering in het rapport | Ons resultaat |
|---|---|
| `nunchuk-linux-v2.6.4.zip` is `5d7b4c36…ae26` | komt overeen |
| Die hash komt overeen met de release `SHA256SUMS` | komt overeen |
| `SHA256SUMS.asc` is een geldige handtekening van `8C8ECD3F…ED2EF525` | geldige handtekening, sleutel is *Ta Tat Tai (Nunchuk binary release signing key)* |
| AppImage erin, 181.107.192 bytes, `87ebfd10…b195` | byte-exacte overeenkomst |

Het rapport klopt. We vonden één punt dat het te zwak formuleert. `SHA256SUMS.asc` is geen losstaande handtekening over `SHA256SUMS`; het is een clearsigned kopie die de sommen zelf meedraagt. Het commando waar de meeste mensen naar grijpen, mislukt:

```
$ gpg --verify SHA256SUMS.asc SHA256SUMS
gpg: not a detached signature
```

Je moet de ondertekende inhoud eruit halen en die vergelijken met het gewone bestand. Dat deden wij, ze zijn identiek, dus de keten houdt stand. Maar wie zijn intuïtie volgt, loopt tegen een foutmelding aan en kan concluderen dat de handtekening kapot is terwijl er niets mis mee is. Een losstaande `.sig` ernaast zou niets kosten.

### 2. Is een Nunchuk-desktopbuild ooit reproduceerbaar geweest?

Niet volgens de administratie. De [Nunchuk-desktoppagina](https://walletscrutiny.com/desktop/nunchuk/) toont negen verificaties, allemaal van dezelfde verificateur, en bij elke staat **Niet reproduceerbaar**: 1.9.47, 1.9.48, 1.9.49, 2.5.0, 2.6.0, 2.6.2, 2.6.3, 2.6.4 en 2.6.5. Bij geen enkele staat Reproduceerbaar bij controle. Nog één 1.9.48-bestand staat vermeld zonder enige verificatie, en vijf oudere regels zitten achter een knop "Show 5 more" die wij niet hebben uitgeklapt, dus daarover kunnen wij niets zeggen.

De geschreven neerslag loopt ernaast in [nunchuk-desktop issue #86](https://github.com/nunchuk-io/nunchuk-desktop/issues/86), geopend op 10 april 2025 door dezelfde tester (`xrviv` op GitHub, `dannybuntu` op WalletScrutiny) en inmiddels vijftien reacties lang. Het openingsrapport gaat niet eens over hashes: de build kon niet starten, omdat `contrib/libnunchuk` verwees naar een privé GitLab-repository die buitenstaanders helemaal niet konden klonen.

Vanaf 2.6.2 werden de rapporten forensisch. De herbouw verschilde van de release in een vast patroon van 85 posten: 6 bestanden anders, 3 bibliotheken alleen in de release, 76 bibliotheken alleen in de herbouw. Diezelfde 85 kwamen terug bij 2.6.3 en opnieuw bij 2.6.4. De reactie van 19 augustus stelde daarna de vraag waar het om draaide, nadat twee eerdere vragen onbeantwoord waren gebleven:

> **Wat is er tussen v2.6.4 en v2.6.5 veranderd aan de Linux-releaseverpakking?** Er is niets in `reproducible-builds/` veranderd [...] en toch is het uitgebrachte bestand ingrijpend anders. Iets buiten die map maakt de release, en wat dat ook is, het is tussen die twee tags verschoven.

### 3. Wat er op 19 en 20 augustus gebeurde

Versie 2.6.6 werd op 20 augustus 2026 om 05:54 UTC gepubliceerd. Ons eerste commando in deze controle liep om 06:04 UTC. De release was tien minuten oud, en daarom documenteren wij uiteindelijk een lopende oplossing in plaats van een blijvende fout.

| Wanneer (UTC) | Wat |
|---|---|
| 18 aug 08:22 | rapport over 2.6.4, drie openstaande vragen |
| 19 aug 10:30 | rapport over 2.6.5, "welke workflow maakt de release-ZIP eigenlijk?" |
| 19 aug | vier commits die `reproducible-builds/`herzien, en `build-linux.yml` wordt **verwijderd** |
| 20 aug 05:09 | tag `2.6.6`, commit `025aba7`, bericht "Reproducibile build" |
| 20 aug 05:54 | 2.6.6 gepubliceerd |

**Welke workflow maakte de release-ZIP?** Bij tag 2.6.4 publiceerde geen van beide publieke workflows iets naar een release. `build-linux.yml` liep alleen op handmatige trigger; die bouwde wel een ZIP, maar uploadde alleen de AppImage als CI-artefact, en die ZIP zit om een map heen terwijl de uitgebrachte ZIP de AppImage in de hoofdmap heeft. `reproducible_linux.yml` liep op tags maar uploadde eveneens alleen de AppImage. Geen enkele publieke workflow zette dus `nunchuk-linux-v2.6.4.zip` op de releasepagina. Iets buiten de repository deed dat. Wij kunnen niet zien wat, en dat is precies het punt: de herbouw werd vergeleken met een bestand waarvan het recept niet gepubliceerd was.

**Kan de release de reproduceerbare build publiceren?** Sinds tag 2.6.6 wel. `reproducible_linux.yml` is weg. De nieuwe `build-linux.yml` start bij het pushen van een tag, bouwt binnen de gedocumenteerde Docker-image via `build_linux.sh`, en eindigt met `gh release create` dat precies die ZIP uploadt. Het uitgebrachte bestand is nu het CI-bestand.

**En de OAuth-waarden uit de compilatie?** Bij 2.6.4 compileerde `CMakeLists.txt` de waarden `OAUTH_CLIENT_ID`, `OAUTH_CLIENT_SECRET` en `OAUTH_REDIRECT_URI` in het binaire bestand met `add_definitions`, gevoed vanuit repository-secrets. Of de releasebuilds daadwerkelijk niet-lege waarden hadden, kunnen wij van buitenaf niet zien, en dat was ook het punt van de tester: als ze gezet waren, kon geen enkele buitenstaander het binaire bestand ooit evenaren. Bij 2.6.6 is het mechanisme verdwenen. De drie definities ontbreken in `CMakeLists.txt`, ontbreken in de workflow, en de teksten ontbreken in het uitgeleverde binaire bestand, dat nu dynamisch linkt naar `libQt5NetworkAuth.so.5` . De vraag is niet zozeer beantwoord als wel overbodig geworden, en dat is de betere uitkomst.

Je kunt de verandering in de uitgeleverde bestanden zien zonder ook maar één regel code te lezen:

| | 2.6.4 uitgebracht | 2.6.6 uitgebracht |
|---|---|---|
| meegeleverde bibliotheken | 62 | 136 |
| `bin/nunchuk-qt` | 167.157.224 bytes | 117.127.872 bytes |

De officiële 2.6.6 levert 77 bibliotheken mee die 2.6.4 niet had, en heeft alle drie de bibliotheken laten vallen die alleen de oude officiële build meedroeg (`libcrypto.so.3`, `libssl.so.3`, `libpq.so.5`). Die drie zijn precies de bibliotheken die de tester meldde als alleen aanwezig in de release. De officiële download heeft de vorm aangenomen die een herbouw vanuit broncode oplevert.

### 4. Een detail waar niemand naar vroeg: hwi

`bin/hwi` was een van de zes bestanden die altijd verschilden. Het wordt niet uit broncode gecompileerd; het wordt tijdens het inpakken gedownload, dus keken we waarvandaan.

Bij tag 2.6.4 haalde `package_linux.sh` HWI **3.1.0** op bij `giahuy98/HWI`, een persoonlijke fork, met de officiële regel `bitcoin-core/HWI` er direct boven uitgecommentarieerd, en zonder checksum.

De `hwi` die daadwerkelijk in de release 2.6.4 zat, heeft hash `e8669f2f…d05b4`. Wij downloadden HWI **3.2.0-displayaddress** bij `nogibi/HWI`, een andere fork op een andere versie, en die komt exact overeen. De uitgebrachte wallet leverde dus een hardwarewallet-koppelbestand mee dat het eigen publieke buildscript niet had kunnen maken.

Bij 2.6.6 pint het script de nogibi-release vast **en controleert het de SHA-256 voordat het bestand wordt geïnstalleerd**, en dat is het bestand dat wordt meegeleverd. Dat specifieke gat is gedicht, al blijft de afhankelijkheid een fork in plaats van de bron.

### 5. Onze eigen herbouw van 2.6.6

Wij volgden `reproducible-builds/README.md` precies: klonen, `git checkout 2.6.6`, submodules, de Docker-image bouwen, de build draaien. De builder-image kostte ongeveer 18 minuten, de applicatiebuild 16 minuten en 48 seconden op 12 cores.

Onze AppImage kwam uit op **184.678.904 bytes**. De officiële is **184.678.904 bytes**. Even groot tot op de byte, andere SHA-256. Dus pakten we beide SquashFS-bomen uit en vergeleken we elk bestand.

| Bestand | Afwijkende bytes | Van |
|---|---|---|
| `bin/nunchuk-qt` | 3,894 | 117,127,872 |
| `lib/libcrypto.so` | 23 | 3,417,040 |
| `lib/libcrypto.so.1.1` | 23 | identieke kopie van het bovenstaande |
| **de andere 1.584 bestanden** | **0** | |

3.940 bytes van de 184.678.904, oftewel 0,0021 procent. De bestandslijsten komen exact overeen: dezelfde 1.587 bestanden, dezelfde paden, aan geen van beide kanten iets extra's.

#### libcrypto.so: 23 bytes

Negentien daarvan zitten in de 20-byte GNU build-ID, die een hash is over de rest van het bestand en dus verandert zodra er iets anders verandert. De andere vier staan hier:

```
ours:     built on: Thu Aug 20 06:20:27 2026 UTC
official: built on: Thu Aug 20 03:47:21 2026 UTC
```

OpenSSL stempelt zijn eigen compilatietijd in de bibliotheek. Vier cijfers van een klok, en verder niets in drieënhalve megabyte.

#### nunchuk-qt: 3.894 bytes

Twintig zijn de build-ID. De andere 3.874 zitten allemaal in `.rodata`, en ze laten zich lezen als 64-bits tijdstempels in milliseconden. Het zijn er **1.300**, en elk daarvan verschilt van de officiële build met precies dezelfde **3.477 seconden**.

```
off=79,719,888   ours=2026-08-20 06:07:52.934   official=2026-08-20 05:09:55.086
off=79,719,954   ours=2026-08-20 06:07:52.934   official=2026-08-20 05:09:55.086
off=79,719,976   ours=2026-08-20 06:07:52.708   official=2026-08-20 05:09:54.970
delta(seconds) -> count: {3477: 1300}
```

Dat is de resource-compiler van Qt. Wanneer `rcc` een QML-bestand of een pictogram in het binaire bestand opneemt, legt het de laatste wijzigingsdatum van dat bestand ernaast vast. `git clone` zet die tijden op het moment waarop jij hebt gekloond. Zo dragen 1.300 onderdelen het moment waarop de repository is uitgechecked, en verschillen de twee builds precies de 58 minuten die tussen hun checkouts zaten.

Geen enkele byte gecompileerde code verschilt, en de ELF-sectie-indeling is identiek. De twee binaire bestanden zijn hetzelfde programma dat het oneens is over hoe laat het was.

### 6. Vier correcties, elk getest

We hebben elk van deze correcties binnen de eigen builder-image van Nunchuk uitgevoerd in plaats van erover te redeneren.

**1. Zet `SOURCE_DATE_EPOCH` in `build_linux.sh`.** De `rcc` van Qt 5.15.2 respecteert de variabele. Dezelfde resource, twee verschillende bestandstijden:

```
without SOURCE_DATE_EPOCH:  786b4df4…   2b229e45…   different
with SOURCE_DATE_EPOCH:     69277d4f…   69277d4f…   identical
```

Dat verwijdert alle 1.300 tijdstempels. Nunchuk doet dit al op Windows, waar `windows.sh` de regel `export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)`bevat. Het Linux-pad heeft die regel nooit gekregen.

**2. Zet de variabele ook in `Dockerfile.linux` ,** voordat OpenSSL wordt gecompileerd. OpenSSL 1.1.1g leest hem rechtstreeks, in `util/mkbuildinf.pl` regel 15:

```
my $date = gmtime($ENV{'SOURCE_DATE_EPOCH'} || time()) . " UTC";
```

Dat verwijdert de regel `built on:` .

**3. Geef twee opties door aan `mksquashfs`.** `appimagetool` negeert `SOURCE_DATE_EPOCH` en stempelt hoe dan ook een verse bouwtijd in de SquashFS, zelfs wanneer de invoerbestanden identieke bestandstijden hebben. Twee runs met drie seconden ertussen:

```
appimagetool ...                              2bdd2d6f…   f12f50e1…   different
appimagetool --mksquashfs-opt -mkfs-time \
             --mksquashfs-opt 0 \
             --mksquashfs-opt -all-time \
             --mksquashfs-opt 0 ...           c453166f…   c453166f…   identical
```

**4. Zet de bestandstijd van de AppImage zelf vast voordat je hem inpakt.** De ZIP legt die vast, dus zonder dit verandert het archief elke keer, ook als de inhoud gelijk blijft:

```
7z with a fresh mtime:   3a70b78d…   61b6c72e…   different
7z with a pinned mtime:  3ca298f8…   3ca298f8…   identical
```

Punt 3 weegt het zwaarst voor de documentatie. `README.md` zegt op dit moment dat je jouw ZIP moet `diff` tegen de officiële, en **die test kan vandaag niet slagen** wat iemand ook doet, want hij meet de klok en niet de build.

### 7. Wat er nog niet klopt

- **2.6.6 is een pre-release en is niet ondertekend.** Elke release van 2.5.0 tot en met 2.6.5 leverde `SHA256SUMS` en `SHA256SUMS.asc`mee. 2.6.6 levert geen van beide. De geautomatiseerde pijplijn won reproduceerbaarheid en verloor onderweg de GPG-handtekening. Allebei zijn ze het waard.

- **Alleen Linux.** macOS en Windows staan in de README als "standby", en 2.6.6 levert geen van beide.

- **2.6.5 is nog steeds de huidige volledige release,** en het is een van de versies die als niet-reproduceerbaar zijn gemarkeerd.

- **De builder-image is niet vastgepind.** `apt install` zonder versies, en `appimagetool` dat wordt opgehaald uit de `continuous` release, zorgen ervoor dat de container meebeweegt met de buitenwereld. Qt, qtkeychain, olm, CQtDeployer en OpenSSL zijn allemaal netjes vastgepind. Die twee niet.

- **De overgebleven afhankelijkheid is een fork.** `hwi` komt nu met een checksum, maar van `nogibi/HWI` in plaats van `bitcoin-core/HWI`.

## Met dank

dannybuntu van WalletScrutiny diende zestien maanden lang geduldige, precieze en reproduceerbare rapporten in, meestal in stilte, en het laatste bracht een walletbedrijf binnen twee dagen in beweging. Nunchuk heeft het, toen ze eenmaal reageerden, niet toegedekt: ze haalden de workflow weg die het niet-verifieerbare bestand maakte, lieten de tagbuild de releasebuild worden, haalden het mechanisme voor compilatiegeheimen eruit, en zetten een checksum op de ene afhankelijkheid die ze blind binnenhaalden. Dat is de juiste reactie en die is zeldzamer dan zou moeten.

De vier correcties hierboven zijn openbaar en na te rekenen, dus iedereen die ze wil, Nunchuk incluis, kan ze hier zo overnemen.

---

Dit is een onafhankelijke controle. Wij zijn niet verbonden aan Nunchuk of WalletScrutiny en wij verkopen geen Nunchuk-producten. Alles hierboven is op 20 augustus 2026 op onze eigen machine uitgevoerd tegen de publieke releases en de publieke repository, en elke hash hier is na te rekenen met de commando's in Nunchuks eigen `reproducible-builds/README.md`. Als wij iets fout hebben, horen wij dat liever wel dan niet.
