{"id":169,"date":"2026-09-13T09:23:05","date_gmt":"2026-09-13T09:23:05","guid":{"rendered":"https:\/\/getbitcoin.guide\/articles\/?p=169"},"modified":"2026-10-17T09:32:37","modified_gmt":"2026-10-17T09:32:37","slug":"crypto-wallet-open-source","status":"publish","type":"post","link":"https:\/\/getbitcoin.guide\/articles\/crypto-wallet-open-source\/","title":{"rendered":"Crypto Wallet Open Source: What Reading the Code Proves"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">When a crypto wallet says it\u2019s \u201copen source,\u201d it basically means anyone can look at the code. But that doesn\u2019t automatically mean someone has checked it, or that the app on your phone was actually built from that exact code. Reproducible builds give you a way to check that the two match. The catch is that doing this verification yourself can be quite technical and time-consuming, much more work than the simple \u201copen source\u201d label might suggest.<\/p>\n\n\n\n<div class=\"msq-audio\" data-article=\"crypto-wallet-open-source\" data-minutes=\"13\">\n<style>.msq-audio{margin:1.25em 0 1.75em;padding:16px 18px;background:#EEF3F8;border:1px solid #CBD8E6;border-radius:12px}\n.msq-audio-lead{margin:0 0 8px;font-size:.95em}.msq-audio audio{width:100%;margin:0 0 12px;display:block}\n.msq-toc-title{margin:0 0 6px;font-weight:700}.msq-toc ol{margin:0;padding:0;list-style:none}\n.msq-toc li{display:flex;gap:10px;align-items:center;padding:6px 6px;border-bottom:1px solid #CBD8E6;border-radius:8px}\n.msq-toc li:last-child{border-bottom:0}.msq-toc li.is-active{background:#fff}\n.msq-toc-play{flex:0 0 auto;width:30px;height:30px;border-radius:50%;border:0;background:#1F4E79;color:#fff;font:inherit;font-size:.72em;line-height:30px;padding:0;cursor:pointer}\n.msq-toc-play.is-playing{background:#B4531A}\n.msq-toc-time{flex:0 0 auto;min-width:3.1em;font-variant-numeric:tabular-nums;color:#4A5560;font-size:.9em}\n.msq-toc a{color:#1B1F24;text-decoration:none;line-height:1.35}.msq-toc li.is-active a{font-weight:700;color:#1F4E79}\n.msq-audio-note{margin:10px 0 0;font-size:.82em;color:#4A5560}\nh2.wp-block-heading[id]{scroll-margin-top:24px}\n.msq-hplay{display:inline-block;vertical-align:middle;width:26px;height:26px;margin:0 0 0 10px;border-radius:50%;border:0;background:#1F4E79;cursor:pointer;padding:0;position:relative;top:-2px}\n.msq-hplay::before{content:'\u25b6\ufe0e';display:block;color:#fff;font-size:11px;line-height:26px;text-align:center}\n.msq-hplay.is-playing{background:#B4531A}.msq-hplay.is-playing::before{content:'\u2016';font-size:13px;font-weight:700}\n.msq-bar{position:fixed;left:0;right:0;bottom:0;z-index:9000;display:flex;align-items:center;gap:10px;padding:8px 12px;padding-bottom:calc(8px + env(safe-area-inset-bottom));background:#FFFFFF;border-top:1px solid #CBD8E6;box-shadow:0 -4px 16px rgba(0,0,0,.08);font-size:.92em;color:#1B1F24;transition:transform .2s ease}\n.msq-bar.is-hidden{transform:translateY(130%)}\n.msq-bar-play{display:inline-flex;align-items:center;gap:8px;border:0;border-radius:999px;background:#1F4E79;color:#fff;font:inherit;font-weight:700;padding:8px 14px 8px 12px;cursor:pointer;white-space:nowrap}\n.msq-bar-play.is-playing{background:#B4531A}\n.msq-bar-icon::before{content:'\u25b6\ufe0e';font-size:12px}.msq-bar-play.is-playing .msq-bar-icon::before{content:'\u2016';font-size:14px;font-weight:700}\n.msq-bar-chapter{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.msq-bar-time{flex:0 0 auto;font-variant-numeric:tabular-nums;color:#4A5560;font-size:.9em}\n.msq-bar-rate,.msq-bar-close{flex:0 0 auto;border:1px solid #CBD8E6;background:#fff;color:#1B1F24;font:inherit;font-size:.85em;line-height:1;border-radius:8px;padding:7px 9px;cursor:pointer}\n.msq-bar-close::before{content:'\u2715'}\n.msq-bar.msq-bar-pill{left:auto;right:14px;bottom:14px;padding:6px;padding-bottom:6px;border:1px solid #CBD8E6;border-radius:999px;box-shadow:0 6px 20px rgba(0,0,0,.15)}\n.msq-bar.msq-bar-pill .msq-bar-chapter,.msq-bar.msq-bar-pill .msq-bar-time,.msq-bar.msq-bar-pill .msq-bar-rate{display:none}\nbody.msq-has-bar{padding-bottom:76px}\n@media (max-width:600px){.msq-bar-time{display:none}.msq-bar{gap:8px;padding-left:10px;padding-right:10px}}\n.msq-s.msq-now{background:#EEF3F8;box-shadow:0 2px 0 #1F4E79;border-radius:2px}\ntr.msq-now td,tr.msq-now th{background:#EEF3F8;box-shadow:inset 0 -2px 0 #1F4E79}\n@media (hover:hover){.msq-s,tr[data-i]{cursor:pointer}.msq-s:hover{background:#EEF3F8}}<\/style>\n<script type=\"application\/ld+json\">{\"@context\":\"https:\/\/schema.org\",\"@type\":\"AudioObject\",\"@id\":\"https:\/\/getbitcoin.guide\/articles\/crypto-wallet-open-source\/#audio\",\"name\":\"Crypto Wallet Open Source: What Reading the Code Proves, read aloud\",\"description\":\"Synthetic voice reading of the article, recorded 10 October 2026.\",\"contentUrl\":\"https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/10\/crypto-wallet-open-source-audio-202610102038.mp3\",\"encodingFormat\":\"audio\/mpeg\",\"duration\":\"PT12M44S\",\"uploadDate\":\"2026-10-10\",\"inLanguage\":\"en-GB\",\"isAccessibleForFree\":true,\"mainEntityOfPage\":\"https:\/\/getbitcoin.guide\/articles\/crypto-wallet-open-source\/\",\"hasPart\":[{\"@type\":\"Clip\",\"name\":\"Crypto Wallet Open Source: What Reading the Code Proves\",\"startOffset\":0,\"endOffset\":62},{\"@type\":\"Clip\",\"name\":\"What does open source actually prove about a crypto wallet?\",\"startOffset\":62,\"endOffset\":107,\"url\":\"https:\/\/getbitcoin.guide\/articles\/crypto-wallet-open-source\/#what-does-open-source-actually-prove-about-a-crypto-wallet\"},{\"@type\":\"Clip\",\"name\":\"Why is code you can read not the same as code someone has checked?\",\"startOffset\":107,\"endOffset\":208,\"url\":\"https:\/\/getbitcoin.guide\/articles\/crypto-wallet-open-source\/#why-is-code-you-can-read-not-the-same-as-code-someone-has-ch\"},{\"@type\":\"Clip\",\"name\":\"How do you know the app you installed was built from that code?\",\"startOffset\":208,\"endOffset\":293,\"url\":\"https:\/\/getbitcoin.guide\/articles\/crypto-wallet-open-source\/#how-do-you-know-the-app-you-installed-was-built-from-that-co\"},{\"@type\":\"Clip\",\"name\":\"What can you actually verify for each wallet today?\",\"startOffset\":293,\"endOffset\":465,\"url\":\"https:\/\/getbitcoin.guide\/articles\/crypto-wallet-open-source\/#what-can-you-actually-verify-for-each-wallet-today\"},{\"@type\":\"Clip\",\"name\":\"What do people who tried to reproduce wallet firmware report?\",\"startOffset\":465,\"endOffset\":616,\"url\":\"https:\/\/getbitcoin.guide\/articles\/crypto-wallet-open-source\/#what-do-people-who-tried-to-reproduce-wallet-firmware-report\"},{\"@type\":\"Clip\",\"name\":\"Is closed firmware automatically less safe?\",\"startOffset\":616,\"endOffset\":693,\"url\":\"https:\/\/getbitcoin.guide\/articles\/crypto-wallet-open-source\/#is-closed-firmware-automatically-less-safe\"},{\"@type\":\"Clip\",\"name\":\"What should a non developer check instead?\",\"startOffset\":693,\"endOffset\":764,\"url\":\"https:\/\/getbitcoin.guide\/articles\/crypto-wallet-open-source\/#what-should-a-non-developer-check-instead\"}],\"contentSize\":\"5.3 MB\"}<\/script>\n<p class=\"msq-audio-lead\"><strong>Listen to this article<\/strong>, about 13 minutes. A synthetic voice reads the article and announces each section title as it begins; tables are read row by row and the questions at the end are left out.<\/p>\n<audio class=\"msq-audio-player\" controls preload=\"metadata\" src=\"https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/10\/crypto-wallet-open-source-audio-202610102038.mp3\"><\/audio>\n<nav class=\"msq-toc\" aria-label=\"In this article\"><p class=\"msq-toc-title\">In this article<\/p>\n<ol>\n<li data-start=\"0.0\"><button type=\"button\" class=\"msq-toc-play\" data-start=\"0.0\" aria-label=\"Play from: Crypto Wallet Open Source: What Reading the Code Proves\">&#x25B6;&#xFE0E;<\/button><span class=\"msq-toc-time\">0:00<\/span><a href=\"#top\">Crypto Wallet Open Source: What Reading the Code Proves<\/a><\/li>\n<li data-start=\"61.86\"><button type=\"button\" class=\"msq-toc-play\" data-start=\"61.86\" aria-label=\"Play from: What does open source actually prove about a crypto wallet?\">&#x25B6;&#xFE0E;<\/button><span class=\"msq-toc-time\">1:02<\/span><a href=\"#what-does-open-source-actually-prove-about-a-crypto-wallet\">What does open source actually prove about a crypto wallet?<\/a><\/li>\n<li data-start=\"107.0\"><button type=\"button\" class=\"msq-toc-play\" data-start=\"107.0\" aria-label=\"Play from: Why is code you can read not the same as code someone has checked?\">&#x25B6;&#xFE0E;<\/button><span class=\"msq-toc-time\">1:47<\/span><a href=\"#why-is-code-you-can-read-not-the-same-as-code-someone-has-ch\">Why is code you can read not the same as code someone has checked?<\/a><\/li>\n<li data-start=\"207.74\"><button type=\"button\" class=\"msq-toc-play\" data-start=\"207.74\" aria-label=\"Play from: How do you know the app you installed was built from that code?\">&#x25B6;&#xFE0E;<\/button><span class=\"msq-toc-time\">3:28<\/span><a href=\"#how-do-you-know-the-app-you-installed-was-built-from-that-co\">How do you know the app you installed was built from that code?<\/a><\/li>\n<li data-start=\"292.8\"><button type=\"button\" class=\"msq-toc-play\" data-start=\"292.8\" aria-label=\"Play from: What can you actually verify for each wallet today?\">&#x25B6;&#xFE0E;<\/button><span class=\"msq-toc-time\">4:53<\/span><a href=\"#what-can-you-actually-verify-for-each-wallet-today\">What can you actually verify for each wallet today?<\/a><\/li>\n<li data-start=\"464.58\"><button type=\"button\" class=\"msq-toc-play\" data-start=\"464.58\" aria-label=\"Play from: What do people who tried to reproduce wallet firmware report?\">&#x25B6;&#xFE0E;<\/button><span class=\"msq-toc-time\">7:45<\/span><a href=\"#what-do-people-who-tried-to-reproduce-wallet-firmware-report\">What do people who tried to reproduce wallet firmware report?<\/a><\/li>\n<li data-start=\"616.44\"><button type=\"button\" class=\"msq-toc-play\" data-start=\"616.44\" aria-label=\"Play from: Is closed firmware automatically less safe?\">&#x25B6;&#xFE0E;<\/button><span class=\"msq-toc-time\">10:16<\/span><a href=\"#is-closed-firmware-automatically-less-safe\">Is closed firmware automatically less safe?<\/a><\/li>\n<li data-start=\"692.58\"><button type=\"button\" class=\"msq-toc-play\" data-start=\"692.58\" aria-label=\"Play from: What should a non developer check instead?\">&#x25B6;&#xFE0E;<\/button><span class=\"msq-toc-time\">11:33<\/span><a href=\"#what-should-a-non-developer-check-instead\">What should a non developer check instead?<\/a><\/li>\n<\/ol><\/nav>\n<p class=\"msq-audio-note\">Recorded 10 October 2026 from the article as published that day. Press a chapter to jump there; the highlighted line is what is playing. A play button sits beside every section heading, and once you scroll past this player a bar at the bottom of the screen keeps the controls in reach. Tap or click any sentence to hear the reading start from there.<\/p>\n<script>var MSQ_SENT = [[0.0,\"h\",\"cryptowalletopensourcewh\",46],[3.88,\"p\",\"whenacryptowalletsaysits\",72],[9.24,\"p\",\"butthatdoesntautomatical\",105],[15.32,\"p\",\"reproduciblebuildsgiveyo\",51],[19.32,\"p\",\"thecatchisthatdoingthisv\",130],[27.04,\"p\",\"onthispageadetailedcompa\",130],[37.04,\"p\",\"checkedon13september2026\",82],[44.0,\"p\",\"mostbestcryptowalletlist\",116],[52.72,\"p\",\"butbeingopensourceismore\",51],[56.2,\"p\",\"thispagelooksatwhatthatl\",72],[61.86,\"h\",\"whatdoesopensourceactual\",49],[65.5,\"p\",\"itprovesthesourceispubli\",74],[70.5,\"p\",\"thatisagenuinegood\",18],[72.14,\"p\",\"awalletthathidesitscodea\",109],[80.34,\"p\",\"thetroubleisthedistanceb\",116],[88.74,\"p\",\"onewrinklecoldcardlicens\",125],[97.82,\"p\",\"forsecuritythatbarelymat\",28],[100.58,\"p\",\"whatprotectsyouisbeingab\",73],[107.0,\"h\",\"whyiscodeyoucanreadnotth\",52],[110.84,\"p\",\"twopublicrecordsmakethep\",28],[112.84,\"p\",\"libbitcoinexploreracomma\",152],[126.96,\"p\",\"themilksaddisclosuredesc\",84],[136.72,\"p\",\"thecodewasreadablethewho\",30],[138.92,\"p\",\"thesecondrecordisclosert\",52],[143.04,\"p\",\"coldcardsfirmwareispubli\",89],[148.8,\"p\",\"theseedentropydefectdisc\",217],[171.96,\"p\",\"thatpagestatesthatupdati\",86],[177.84,\"p\",\"ifyouownoneofthesedevice\",83],[183.96,\"p\",\"areproduciblebuildwouldh\",76],[189.36,\"p\",\"thatsbecausereproducibil\",85],[195.8,\"p\",\"itdoesnottellyouwhethert\",65],[200.04,\"p\",\"ourcoldwalletexplainerex\",108],[207.74,\"h\",\"howdoyouknowtheappyouins\",50],[210.98,\"p\",\"thisisthegapnorankingpag\",43],[214.34,\"p\",\"acompanycanpublishcleanc\",100],[221.74,\"p\",\"ledgersownincidentreport\",197],[237.82,\"p\",\"ledgerputstheactivedrain\",48],[240.82,\"p\",\"areproduciblebuildisthed\",55],[244.42,\"p\",\"independentpeoplecompile\",125],[251.78,\"p\",\"whenseveralunconnectedbu\",72],[257.34,\"p\",\"bitcoincoreisthereferenc\",29],[259.5,\"p\",\"itsreleasesarebuiltwithg\",164],[270.82,\"p\",\"evenapasshaslimits\",18],[272.9,\"p\",\"walletscrutinysmethodolo\",115],[282.54,\"p\",\"italsosaysnoiphonewallet\",137],[292.8,\"h\",\"whatcanyouactuallyverify\",42],[296.04,\"p\",\"compiledfromeachvendorso\",74],[302.04,\"p\",\"itrecordswhatanoutsiderc\",31],[304.52,\"p\",\"itisnotarankingandnotare\",48],[308.32,\"t\",\"trezordevices\",166],[323.8,\"t\",\"bitbox02\",165],[339.08,\"t\",\"coldcard\",157],[357.72,\"t\",\"blockstreamjade\",173],[375.16,\"t\",\"onekey\",161],[393.0,\"t\",\"ledgerdevices\",178],[407.88,\"t\",\"bitcoincore\",158],[424.28,\"t\",\"sparrow\",168],[439.88,\"p\",\"thevendorsthatdocumentre\",149],[450.0,\"p\",\"thebitbox02bootloaderiso\",142],[459.28,\"p\",\"abuildguidethoughisonlya\",68],[464.58,\"h\",\"whatdopeoplewhotriedtore\",51],[468.34,\"p\",\"wereadfivepublicgithubis\",149],[478.9,\"p\",\"thisiswhatcontributorsre\",106],[486.46,\"p\",\"inseptember2023auserrepo\",210],[503.46,\"p\",\"amaintainerrepliedthenex\",105],[511.42,\"p\",\"innovember2023awalletscr\",174],[524.78,\"p\",\"theissuewasclosedinmarch\",28],[527.58,\"p\",\"thelongestrecordisanissu\",119],[536.14,\"p\",\"over45commentsandsixteen\",128],[545.34,\"p\",\"theeffortmovedtoafollowu\",177],[561.22,\"p\",\"itwasstillopenon13septem\",31],[564.9,\"p\",\"thatrecordsanunfinishedv\",80],[570.86,\"p\",\"attheotherendoftherangei\",188],[584.06,\"p\",\"thatthreadisnearlyfiveye\",63],[588.58,\"p\",\"andthebitbox02releasesfo\",113],[599.42,\"p\",\"wheretheaccountsagreewit\",109],[607.02,\"p\",\"wheretheydifferfromwhatt\",137],[616.44,\"h\",\"isclosedfirmwareautomati\",37],[619.24,\"p\",\"noandherewepartcompanywi\",57],[624.04,\"p\",\"ledgersaysinitsexplanati\",137],[633.2,\"p\",\"itschieftechnologyoffice\",202],[648.4,\"p\",\"ledgerdoesnotpublishthat\",48],[652.24,\"p\",\"heishalfright\",13],[653.32,\"p\",\"opencodedidnotsavecoldca\",52],[657.4,\"p\",\"butinspectionistheonlywa\",135],[666.88,\"p\",\"aclosedlayerremovesbothl\",69],[671.88,\"p\",\"thengravezeroisthesharpe\",159],[684.16,\"p\",\"ourtrezorandledgercompar\",102],[692.58,\"h\",\"whatshouldanondeveloperc\",35],[694.86,\"p\",\"youdonotneedtocompilefir\",56],[698.94,\"p\",\"thischecklistisderivedfr\",59],[702.74,\"p\",\"downloadwalletsoftwareon\",94],[709.38,\"p\",\"sparrowbuildsthatcheckin\",44],[712.62,\"p\",\"updatefirmwarethroughthe\",127],[720.7,\"p\",\"lookforapublicreproducib\",115],[729.1,\"p\",\"checkthevendorssecuritys\",80],[734.3,\"p\",\"apatcheddevicedoesnotfix\",47],[738.02,\"p\",\"nevertypeyourrecoveryphr\",75],[743.34,\"p\",\"nolegitimateverification\",35],[745.82,\"p\",\"treatopensourceinanappst\",77],[750.94,\"p\",\"foreverythingelseworthwe\",202]];\n(function(){\n  function init(){\n  var root = document.querySelector('.msq-audio'); if (!root) { return; }\n  var audio = root.querySelector('audio'), items = Array.prototype.slice.call(root.querySelectorAll('.msq-toc li'));\n  var starts = items.map(function(li){ return parseFloat(li.getAttribute('data-start')) || 0; });\n  var names = items.map(function(li){ return li.querySelector('a').textContent; });\n  var anchors = items.map(function(li){ var h = li.querySelector('a').getAttribute('href') || ''; return (h.charAt(0) === '#' && h !== '#top') ? document.getElementById(h.slice(1)) : null; });\n  var article = root.getAttribute('data-article') || location.pathname, minutes = root.getAttribute('data-minutes') || '';\n  var marks = {}, started = false, pausedAt = -1, closed = false, ended = false;\n  var RATES = [1, 1.25, 1.5], rate = 1;\n  try { var saved = parseFloat(localStorage.getItem('msq-audio-rate')); if (RATES.indexOf(saved) >= 0) { rate = saved; } } catch (e) {}\n  audio.playbackRate = rate;\n  function track(name, extra){ try { if (typeof gtag === 'function') { var p = { article: article }; for (var k in extra) { p[k] = extra[k]; } gtag('event', name, p); } } catch (e) {} }\n  function current(){ var t = audio.currentTime, idx = 0; for (var i = 0; starts.length > i; i += 1) { if (t + 0.25 >= starts[i]) { idx = i; } } return idx; }\n  function onScreen(){\n    var line = window.innerHeight \/ 3, idx = 0;\n    for (var i = 0; anchors.length > i; i += 1) { var el = anchors[i]; if (el && line >= el.getBoundingClientRect().top) { idx = i; } }\n    return idx;\n  }\n  function fmt(s){ s = Math.max(0, Math.round(s || 0)); var m = Math.floor(s \/ 60), r = s % 60; return m + ':' + (10 > r ? '0' : '') + r; }\n  function play(from, source){\n    if (from !== null) { audio.currentTime = from; }\n    var p = audio.play(); if (p && p.catch) { p.catch(function(){}); }\n    track(from === null ? 'audio_resume' : 'audio_chapter', { chapter: names[current()], start: Math.round(audio.currentTime), source: source });\n  }\n  var hbtns = anchors.map(function(el, i){\n    if (!el) { return null; }\n    var b = document.createElement('button'); b.type = 'button'; b.className = 'msq-hplay'; b.setAttribute('aria-label', 'Play this section: ' + names[i]);\n    b.addEventListener('click', function(){\n      if (!audio.paused && current() === i) { audio.pause(); track('audio_pause', { source: 'heading' }); return; }\n      play(starts[i], 'heading');\n    });\n    el.appendChild(b); return b;\n  });\n  var bar = document.createElement('div'); bar.className = 'msq-bar msq-bar-pill is-hidden'; bar.setAttribute('role', 'region'); bar.setAttribute('aria-label', 'Audio player');\n  var playBtn = document.createElement('button'); playBtn.type = 'button'; playBtn.className = 'msq-bar-play';\n  var icon = document.createElement('span'); icon.className = 'msq-bar-icon'; icon.setAttribute('aria-hidden', 'true');\n  var label = document.createElement('span'); label.className = 'msq-bar-label';\n  playBtn.appendChild(icon); playBtn.appendChild(label);\n  var chap = document.createElement('span'); chap.className = 'msq-bar-chapter';\n  var clock = document.createElement('span'); clock.className = 'msq-bar-time';\n  var rateBtn = document.createElement('button'); rateBtn.type = 'button'; rateBtn.className = 'msq-bar-rate'; rateBtn.setAttribute('aria-label', 'Playback speed');\n  var closeBtn = document.createElement('button'); closeBtn.type = 'button'; closeBtn.className = 'msq-bar-close'; closeBtn.setAttribute('aria-label', 'Close the audio bar');\n  bar.appendChild(playBtn); bar.appendChild(chap); bar.appendChild(clock); bar.appendChild(rateBtn); bar.appendChild(closeBtn);\n  document.body.appendChild(bar);\n  function showRate(){ rateBtn.textContent = (rate === 1 ? '1' : String(rate)) + 'x'; }\n  showRate();\n  rateBtn.addEventListener('click', function(){\n    rate = RATES[(RATES.indexOf(rate) + 1) % RATES.length]; audio.playbackRate = rate; showRate();\n    try { localStorage.setItem('msq-audio-rate', String(rate)); } catch (e) {}\n    track('audio_rate', { rate: rate });\n  });\n  closeBtn.addEventListener('click', function(){\n    closed = true; if (!audio.paused) { audio.pause(); } paint(); track('audio_bar_close', {});\n  });\n  function mode(){\n    if (!audio.paused) { return 'pause'; }\n    if (ended) { return 'again'; }\n    if (!started) { return 'listen'; }\n    return (pausedAt === onScreen()) ? 'resume' : 'section';\n  }\n  playBtn.addEventListener('click', function(){\n    var m = mode();\n    if (m === 'pause') { audio.pause(); track('audio_pause', { source: 'bar' }); return; }\n    if (m === 'again') { ended = false; play(0, 'bar'); return; }\n    if (m === 'resume') { play(null, 'bar'); return; }\n    play(starts[onScreen()], 'bar');\n  });\n  function paint(){\n    var idx = current(), playing = !audio.paused, m = mode();\n    items.forEach(function(li, i){\n      var on = (i === idx) && playing, b = li.querySelector('.msq-toc-play'), a = li.querySelector('a');\n      li.classList.toggle('is-active', on); b.classList.toggle('is-playing', on);\n      b.textContent = on ? '\u2016' : '\u25b6\ufe0e';\n      b.setAttribute('aria-label', (on ? 'Pause: ' : 'Play from: ') + a.textContent);\n    });\n    hbtns.forEach(function(b, i){ if (b) { var on = (i === idx) && playing; b.classList.toggle('is-playing', on); b.setAttribute('aria-label', (on ? 'Pause this section: ' : 'Play this section: ') + names[i]); } });\n    playBtn.classList.toggle('is-playing', playing);\n    label.textContent = m === 'pause' ? 'Pause' : m === 'again' ? 'Play again' : m === 'resume' ? 'Resume' : m === 'section' ? 'Play this section' : 'Listen, ' + minutes + ' min';\n    playBtn.setAttribute('aria-label', label.textContent);\n    chap.textContent = started ? names[(playing || m === 'resume') ? idx : onScreen()] : '';\n    clock.textContent = started ? fmt(audio.currentTime) + ' \/ ' + fmt(audio.duration) : '';\n    bar.classList.toggle('msq-bar-pill', !started);\n    var show = !closed && (started || 0 > root.getBoundingClientRect().bottom);\n    bar.classList.toggle('is-hidden', !show);\n    document.body.classList.toggle('msq-has-bar', show);\n  }\n  root.addEventListener('click', function(e){\n    var b = e.target.closest ? e.target.closest('.msq-toc-play') : null; if (!b) { return; }\n    var li = b.parentNode, s = parseFloat(b.getAttribute('data-start')) || 0;\n    if (!audio.paused && current() === items.indexOf(li)) { audio.pause(); track('audio_pause', { source: 'index' }); return; }\n    play(s, 'index');\n  });\n  audio.addEventListener('play', function(){ ended = false; closed = false; if (!started) { started = true; track('audio_play', { minutes: Math.round((audio.duration || 0) \/ 60) }); } paint(); });\n  audio.addEventListener('pause', function(){ pausedAt = current(); paint(); });\n  audio.addEventListener('timeupdate', function(){\n    paint();\n    if (audio.duration > 0) {\n      var pct = Math.floor(audio.currentTime \/ audio.duration * 100);\n      [25, 50, 75].forEach(function(m){ if (pct >= m && !marks[m]) { marks[m] = true; track('audio_progress', { percent: m }); } });\n    }\n  });\n  audio.addEventListener('ended', function(){ ended = true; track('audio_complete', {}); paint(); });\n  var ticking = false;\n  function onScroll(){ if (ticking) { return; } ticking = true; setTimeout(function(){ ticking = false; paint(); }, 80); }\n  window.addEventListener('scroll', onScroll, { passive: true }); window.addEventListener('resize', onScroll);\n  paint();\n  }\n  if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }\n})();\n\n(function(){\n  function start(){\n    var root = document.querySelector('.msq-audio'), audio = root ? root.querySelector('audio') : null;\n    var map = (typeof MSQ_SENT !== 'undefined') ? MSQ_SENT : [];\n    if (!root || !audio || !map.length) { return; }\n    var scope = root.closest('main') || root.closest('article') || root.closest('.entry-content') || document.body;\n    var SKIP = '.msq-audio,.msq,.msq-tool,.msq-end,.msq-bar,script,style,noscript,figcaption,#rank-math-faq,.rank-math-block,nav,header,footer';\n    function norm(s){ return s.toLowerCase().replace(\/[^a-z0-9]+\/g, ''); }\n    var nodes = [], stream = '', walker = document.createTreeWalker(scope, NodeFilter.SHOW_TEXT, null), n;\n    while ((n = walker.nextNode())) {\n      var el = n.parentElement; if (!el || el.closest(SKIP)) { continue; }\n      var raw = n.nodeValue, keep = [], norms = '';\n      for (var i = 0; raw.length > i; i += 1) { var c = raw.charAt(i).toLowerCase(); if (\/[a-z0-9]\/.test(c)) { keep.push(i); norms += c; } }\n      if (!norms) { continue; }\n      nodes.push({ node: n, a: stream.length, b: stream.length + norms.length, keep: keep });\n      stream += norms;\n    }\n    var segs = [], cursor = 0, lastRow = null;\n    map.forEach(function(m, idx){\n      var kind = m[1], f = m[2], len = m[3];\n      if (kind === 't') {\n        var rows = scope.querySelectorAll('tr'), after = !lastRow;\n        for (var r = 0; rows.length > r; r += 1) {\n          var tr = rows[r];\n          if (!after) { if (tr === lastRow) { after = true; } continue; }\n          var cell = tr.querySelector('td,th');\n          if (cell && norm(cell.textContent).indexOf(f) === 0) { tr.setAttribute('data-i', idx); lastRow = tr; break; }\n        }\n        return;\n      }\n      var p = stream.indexOf(f, cursor); if (0 > p) { return; }\n      segs.push({ i: idx, a: p, b: Math.min(p + len, stream.length) }); cursor = p + len;\n    });\n    var si = 0;\n    nodes.forEach(function(nd){\n      var frag = document.createDocumentFragment(), raw = nd.node.nodeValue, rawPos = 0, any = false;\n      for (var k = si; segs.length > k; k += 1) {\n        var sg = segs[k]; if (sg.a >= nd.b) { break; } if (nd.a >= sg.b) { continue; }\n        var s = Math.max(sg.a, nd.a), e = Math.min(sg.b, nd.b);\n        var rs = nd.keep[s - nd.a], re2 = nd.keep[e - nd.a - 1] + 1;\n        if (rs > rawPos) { frag.appendChild(document.createTextNode(raw.slice(rawPos, rs))); }\n        var span = document.createElement('span'); span.className = 'msq-s'; span.setAttribute('data-i', sg.i); span.textContent = raw.slice(rs, re2);\n        frag.appendChild(span); rawPos = re2; any = true;\n        if (sg.b > nd.b) { break; }\n      }\n      while (segs.length > si && nd.b >= segs[si].b) { si += 1; }\n      if (!any) { return; }\n      if (raw.length > rawPos) { frag.appendChild(document.createTextNode(raw.slice(rawPos))); }\n      nd.node.parentNode.replaceChild(frag, nd.node);\n    });\n    var starts = map.map(function(m){ return m[0]; }), curIdx = -1, lastUser = 0, timer = null, autoUntil = 0, dragging = false;\n    function idxAt(t){ var lo = 0, hi = starts.length; while (hi > lo + 1) { var mid = (lo + hi) >> 1; if (t + 0.15 >= starts[mid]) { lo = mid; } else { hi = mid; } } return (t + 0.15 >= starts[0]) ? lo : -1; }\n    function setNow(i){\n      if (i === curIdx) { return; }\n      Array.prototype.forEach.call(scope.querySelectorAll('.msq-now'), function(el){ el.classList.remove('msq-now'); });\n      curIdx = i; if (0 > i) { return; }\n      var els = scope.querySelectorAll('[data-i=\"' + i + '\"]'); if (!els.length) { return; }\n      Array.prototype.forEach.call(els, function(el){ el.classList.add('msq-now'); });\n      var r = els[0].getBoundingClientRect(), now = Date.now();\n      if (!dragging && now > lastUser + 2500 && (80 > r.top || r.bottom > window.innerHeight - 140)) {\n        var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n        autoUntil = now + (reduce ? 400 : 1600);\n        els[0].scrollIntoView({ block: 'center', behavior: reduce ? 'auto' : 'smooth' });\n      }\n    }\n    function tick(){ if (!audio.paused) { setNow(idxAt(audio.currentTime)); } }\n    function userScrolled(){ lastUser = Date.now(); }\n    ['wheel', 'touchmove', 'keydown'].forEach(function(ev){ window.addEventListener(ev, userScrolled, { passive: true }); });\n    window.addEventListener('scroll', function(){ if (Date.now() > autoUntil) { userScrolled(); } }, { passive: true });\n    window.addEventListener('mousedown', function(e){ if (e.clientX >= document.documentElement.clientWidth) { dragging = true; userScrolled(); } }, { passive: true });\n    window.addEventListener('mouseup', function(){ if (dragging) { dragging = false; userScrolled(); } }, { passive: true });\n    audio.addEventListener('play', function(){ if (timer) { clearInterval(timer); } timer = setInterval(tick, 150); tick(); });\n    audio.addEventListener('pause', function(){ if (timer) { clearInterval(timer); timer = null; } });\n    scope.addEventListener('click', function(e){\n      if (e.target.closest('a, button, input, select, textarea, label, summary, .msq, .msq-tool, .msq-end, .msq-audio, .msq-bar')) { return; }\n      var sel = window.getSelection ? String(window.getSelection()) : '';\n      if (sel && sel.trim()) { return; }\n      var el = e.target.closest('[data-i]'); if (!el) { return; }\n      var i = parseInt(el.getAttribute('data-i'), 10); if (isNaN(i)) { return; }\n      if (!audio.paused && i === curIdx) { return; }\n      lastUser = Date.now();\n      audio.currentTime = starts[i];\n      var p = audio.play(); if (p && p.catch) { p.catch(function(){}); }\n      try { if (typeof gtag === 'function') { gtag('event', 'audio_chapter', { article: location.pathname, source: 'sentence', start: Math.round(starts[i]) }); } } catch (err) {}\n    });\n    audio.addEventListener('seeked', tick);\n    audio.addEventListener('ended', function(){ setNow(-1); });\n  }\n  if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', start); } else { start(); }\n})();<\/script>\n<\/div>\n\n\n\n<p class=\"msq-hook wp-block-paragraph\">On this page: a detailed comparison table of wallet reproducibility, a checklist for non developers, and a timeline of community build verification efforts.<\/p>\n\n\n\n<div class=\"msq\" id=\"ms-quiz\" data-slug=\"crypto-wallet-open-source\" data-category=\"Self custody\" data-list=\"1\">\n<style>.msq,.msq-hook,.msq-box,.msq-end,.msq-tool{--msq-accent:#1F4E79;--msq-accent-text:#FFFFFF;--msq-line:#CBD8E6;--msq-warn:#B4531A;--msq-muted:#4A5560;--msq-tint:#EEF3F8;--msq-base:#FFFFFF}\n.msq{margin:1.25em 0 1.75em}\n.msq-hook{border-left:3px solid var(--msq-accent);padding:6px 14px;color:var(--msq-muted);font-size:.97em}\n.msq-box{background:var(--msq-tint);border:1px solid var(--msq-line);border-radius:12px;padding:0}\n.msq-box>summary{cursor:pointer;list-style:none;padding:14px 18px;font-weight:700;color:var(--msq-accent)}\n.msq-box>summary::-webkit-details-marker{display:none}\n.msq-box>summary::before{content:\"+\";display:inline-block;width:1.1em;font-weight:800}\n.msq-box[open]>summary::before{content:\"\\00d7\"}\n.msq-body{padding:2px 18px 18px}\n.msq-intro{margin:0 0 12px;font-size:.95em;color:var(--msq-muted)}\n.msq-list{margin:0;padding:0;list-style:none}\n.msq-q{margin:0 0 18px;padding:0}\n.msq-question{font-weight:700;margin:0 0 8px}\n.msq-options{display:flex;flex-direction:column;gap:8px}\n.msq-opt{text-align:left;background:var(--msq-base);border:1px solid var(--msq-line);border-radius:8px;padding:10px 12px;font:inherit;font-size:.95em;color:inherit;cursor:pointer;line-height:1.35}\n.msq-opt:hover{border-color:var(--msq-accent)}\n.msq-opt[disabled]{cursor:default}\n.msq-opt.is-right{border-color:var(--msq-accent);background:var(--msq-line);font-weight:700}\n.msq-opt.is-wrong{border-color:var(--msq-warn);color:var(--msq-warn)}\n.msq-explain{margin:8px 0 0;font-size:.92em;padding:10px 12px;border-left:3px solid var(--msq-accent);background:var(--msq-base);border-radius:0 8px 8px 0}\n.msq-explain[hidden]{display:none}\n.msq-verdict{font-weight:700}\n.msq-result{margin-top:14px;padding:14px 16px;background:var(--msq-base);border:1px solid var(--msq-line);border-radius:10px}\n.msq-result[hidden]{display:none}\n.msq-result h3{margin:0 0 6px;font-size:1.1em}\n.msq-result p{margin:0 0 8px}\n.msq-jump{margin:0 0 8px;padding-left:1.2em}\n.msq-form{margin-top:10px;display:flex;flex-wrap:wrap;gap:8px;align-items:center}\n.msq-form input[type=email]{flex:1 1 220px;padding:10px 12px;border:1px solid var(--msq-muted);border-radius:8px;font:inherit;background:#fff}\n.msq-btn{background:var(--msq-accent);color:var(--msq-accent-text);border:0;border-radius:8px;padding:11px 18px;font:inherit;font-weight:700;cursor:pointer}\n.msq-btn:hover{filter:brightness(.92)}\n.msq-btn[disabled]{opacity:.7;cursor:default}\n.msq-no{font-size:.9em;color:var(--msq-muted);margin-left:4px}\n.msq-privacy{margin:6px 0 0;font-size:.82em;color:var(--msq-muted);flex-basis:100%}\n.msq-hp{position:absolute;left:-5000px;width:1px;height:1px;overflow:hidden}\n.msq-msg{margin:8px 0 0;font-size:.92em}\n.msq-end{margin:1.5em 0;padding:18px;border:1px solid var(--msq-accent);border-radius:12px;text-align:center;background:var(--msq-tint)}\n.msq-end p{margin:0 0 12px}\n.msq-end .msq-body{text-align:left;padding:16px 0 0}\n.msq-end .msq-start-test[hidden]{display:none}\n.msq-tool{margin:1.5em 0;padding:18px 20px;background:var(--msq-tint);border:1px solid var(--msq-line);border-radius:12px}\n.msq-tool h3{margin:0 0 6px;font-size:1.15em}\n.msq-tool p{margin:0 0 10px}\n.msq-tool .msq-note{font-size:.85em;color:var(--msq-muted);margin:10px 0 0}\n.msq-check label{display:flex;gap:10px;align-items:flex-start;padding:9px 0;border-bottom:1px solid var(--msq-line);cursor:pointer;line-height:1.4}\n.msq-check label:last-of-type{border-bottom:0}\n.msq-check input{margin-top:5px;flex:0 0 auto;width:18px;height:18px;accent-color:var(--msq-accent)}\n.msq-check-count{font-weight:700;margin:10px 0 0}\n.msq-verdict-box{margin:12px 0 0;padding:10px 14px;border-radius:8px;background:var(--msq-base);border-left:3px solid var(--msq-warn)}\n.msq-verdict-box[hidden]{display:none}\n.msq-calc{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px}\n.msq-calc label{display:flex;flex-direction:column;gap:4px;font-size:.92em}\n.msq-calc input,.msq-calc select{padding:8px 10px;border:1px solid var(--msq-muted);border-radius:8px;font:inherit;background:#fff}\n.msq-calc-out{grid-column:1 \/ -1;margin-top:6px;padding:12px 14px;background:var(--msq-base);border-radius:8px;border:1px solid var(--msq-line)}\n.msq-calc-out strong{font-size:1.15em}\n.msq-choose label{display:block;padding:9px 12px;margin:0 0 8px;background:var(--msq-base);border:1px solid var(--msq-line);border-radius:8px;cursor:pointer}\n.msq-choose input{margin-right:8px;accent-color:var(--msq-accent)}\n.msq-choice-result{margin-top:10px;padding:12px 14px;background:var(--msq-base);border-left:3px solid var(--msq-accent);border-radius:0 8px 8px 0}\n.msq-choice-result[hidden]{display:none}\n@media (max-width:600px){.msq-calc{grid-template-columns:1fr}}\nh2.wp-block-heading[id]{scroll-margin-top:24px}<\/style>\n<noscript><style>.msq-explain{display:block !important}<\/style><\/noscript>\n<details class=\"msq-box\">\n<summary>Think you already know this? Five questions, one minute.<\/summary>\n<div class=\"msq-body\">\n<p class=\"msq-intro\">Every answer is on this page. Answer from what you know now; the page fills in the rest.<\/p>\n<ol class=\"msq-list\">\n<li class=\"msq-q\" data-q=\"1\" data-answer=\"b\" data-title=\"What does open source actually prove about a crypto wallet?\" data-section=\"#what-does-open-source-actually-prove-about-a-crypto-wallet\">\n<p class=\"msq-question\">1. What is the main limitation of a wallet simply being open source?<\/p>\n<div class=\"msq-options\" role=\"group\" aria-label=\"Question 1 options\"><button type=\"button\" class=\"msq-opt\" data-opt=\"a\">It guarantees that the code has been audited by security firms.<\/button><button type=\"button\" class=\"msq-opt\" data-opt=\"b\">It proves the code is published but does not mean someone has checked it.<\/button><button type=\"button\" class=\"msq-opt\" data-opt=\"c\">It ensures the app on your phone matches the published code.<\/button><\/div>\n<p class=\"msq-explain\" hidden><span class=\"msq-verdict\"><\/span>Publishing code allows anyone to inspect it, but it does not mean someone has checked it. For example, some firmware is published under a <a href=\"https:\/\/github.com\/Coldcard\/firmware\/blob\/master\/COPYING-CC\" target=\"_blank\" rel=\"noopener\">Commons Clause<\/a> licence which allows inspection but restricts commercial redistribution. <a class=\"msq-section\" href=\"#what-does-open-source-actually-prove-about-a-crypto-wallet\">Read the section<\/a><\/p>\n<\/li>\n<li class=\"msq-q\" data-q=\"2\" data-answer=\"a\" data-title=\"Why is code you can read not the same as code someone has checked?\" data-section=\"#why-is-code-you-can-read-not-the-same-as-code-someone-has-ch\">\n<p class=\"msq-question\">2. What did the Milk Sad disclosure reveal about open source wallet code?<\/p>\n<div class=\"msq-options\" role=\"group\" aria-label=\"Question 2 options\"><button type=\"button\" class=\"msq-opt\" data-opt=\"a\">A vulnerability in Libbitcoin Explorer existed despite the code being publicly readable.<\/button><button type=\"button\" class=\"msq-opt\" data-opt=\"b\">A closed source library was secretly used inside an open source wallet.<\/button><button type=\"button\" class=\"msq-opt\" data-opt=\"c\">The code was automatically corrected by independent community developers.<\/button><\/div>\n<p class=\"msq-explain\" hidden><span class=\"msq-verdict\"><\/span>The <a href=\"https:\/\/milksad.info\/disclosure.html\" target=\"_blank\" rel=\"noopener\">Milk Sad disclosure<\/a> detailed how a vulnerability, tracked as <a href=\"https:\/\/github.com\/advisories\/GHSA-prgj-h7jq-7p9h\" target=\"_blank\" rel=\"noopener\">CVE-2023-39910<\/a>, existed in Libbitcoin Explorer even though the code was completely public. <a class=\"msq-section\" href=\"#why-is-code-you-can-read-not-the-same-as-code-someone-has-ch\">Read the section<\/a><\/p>\n<\/li>\n<li class=\"msq-q\" data-q=\"3\" data-answer=\"b\" data-title=\"How do you know the app you installed was built from that code?\" data-section=\"#how-do-you-know-the-app-you-installed-was-built-from-that-co\">\n<p class=\"msq-question\">3. How can users verify that their Bitcoin Core download matches the official source code?<\/p>\n<div class=\"msq-options\" role=\"group\" aria-label=\"Question 3 options\"><button type=\"button\" class=\"msq-opt\" data-opt=\"a\">By requiring users to compile the entire program from scratch on every update.<\/button><button type=\"button\" class=\"msq-opt\" data-opt=\"b\">By comparing the download against signed assertions from multiple builders.<\/button><button type=\"button\" class=\"msq-opt\" data-opt=\"c\">By routing all downloads through a single centralized app store.<\/button><\/div>\n<p class=\"msq-explain\" hidden><span class=\"msq-verdict\"><\/span>Users can verify releases against signed hashes in the <a href=\"https:\/\/github.com\/bitcoin-core\/guix.sigs\" target=\"_blank\" rel=\"noopener\">guix.sigs repository<\/a> as explained on the <a href=\"https:\/\/bitcoincore.org\/en\/download\/\" target=\"_blank\" rel=\"noopener\">Bitcoin Core download page<\/a>. <a class=\"msq-section\" href=\"#how-do-you-know-the-app-you-installed-was-built-from-that-co\">Read the section<\/a><\/p>\n<\/li>\n<li class=\"msq-q\" data-q=\"4\" data-answer=\"b\" data-title=\"What can you actually verify for each wallet today?\" data-section=\"#what-can-you-actually-verify-for-each-wallet-today\">\n<p class=\"msq-question\">4. Which of the following is true regarding reproducible builds on iOS devices?<\/p>\n<div class=\"msq-options\" role=\"group\" aria-label=\"Question 4 options\"><button type=\"button\" class=\"msq-opt\" data-opt=\"a\">Most iPhone wallet apps are fully reproducible due to App Store policies.<\/button><button type=\"button\" class=\"msq-opt\" data-opt=\"b\">No iPhone wallet app has achieved a reproducible build according to WalletScrutiny.<\/button><button type=\"button\" class=\"msq-opt\" data-opt=\"c\">Only wallets with closed source layers can be reproduced on iOS.<\/button><\/div>\n<p class=\"msq-explain\" hidden><span class=\"msq-verdict\"><\/span>According to <a href=\"https:\/\/walletscrutiny.com\/methodology\/\" target=\"_blank\" rel=\"noopener\">WalletScrutiny&#8217;s methodology<\/a>, no iPhone wallet app has achieved a reproducible build. <a class=\"msq-section\" href=\"#what-can-you-actually-verify-for-each-wallet-today\">Read the section<\/a><\/p>\n<\/li>\n<li class=\"msq-q\" data-q=\"5\" data-answer=\"a\" data-title=\"Is closed firmware automatically less safe?\" data-section=\"#is-closed-firmware-automatically-less-safe\">\n<p class=\"msq-question\">5. What is Ledger&#8217;s justification for not making its secure element firmware fully open source?<\/p>\n<div class=\"msq-options\" role=\"group\" aria-label=\"Question 5 options\"><button type=\"button\" class=\"msq-opt\" data-opt=\"a\">The chip manufacturer requires a non disclosure agreement for that layer.<\/button><button type=\"button\" class=\"msq-opt\" data-opt=\"b\">Open source code is inherently more vulnerable to immediate hacking attempts.<\/button><button type=\"button\" class=\"msq-opt\" data-opt=\"c\">They believe security audits are only effective when kept entirely private.<\/button><\/div>\n<p class=\"msq-explain\" hidden><span class=\"msq-verdict\"><\/span>Ledger explains in <a href=\"https:\/\/www.ledger.com\/blog-ledger-is-95-opensource-why-not-100\" target=\"_blank\" rel=\"noopener\">its explanation of why it is not fully open source<\/a> that the code interacting with its secure element chip is under a non disclosure agreement. <a class=\"msq-section\" href=\"#is-closed-firmware-automatically-less-safe\">Read the section<\/a><\/p>\n<\/li>\n<\/ol>\n<div class=\"msq-result\" hidden><\/div>\n<\/div>\n<\/details>\n<script>(function(){\n  if (window.__msqInit) { return; }\n  window.__msqInit = true;\n  var root = document.getElementById('ms-quiz');\n  if (!root) { return; }\n  var meta = { slug: root.getAttribute('data-slug'), category: root.getAttribute('data-category'),\n               list: root.getAttribute('data-list') || '1' };\n  function qs(el, s){ return el.querySelector(s); }\n  function qsa(el, s){ return Array.prototype.slice.call(el.querySelectorAll(s)); }\n  function track(name, params){\n    try { if (typeof gtag === 'function') { var p = params || {}; p.article = meta.slug; gtag('event', name, p); } } catch (e) {}\n  }\n  function jumpList(missed){\n    if (!missed.length) { return ''; }\n    return '<ul class=\"msq-jump\">' + missed.map(function(q){\n      return '<li><a href=\"' + q.getAttribute('data-section') + '\">' + q.getAttribute('data-title') + '<\/a><\/li>';\n    }).join('') + '<\/ul>';\n  }\n  function emailForm(){\n    return '<div class=\"msq-sub\"><p><strong>Get the next one.<\/strong> We publish new articles on ' + meta.category\n      + ' most weeks and recheck this page on Sundays. Email me when a new article goes up or this page changes.<\/p>'\n      + '<form class=\"msq-form\" method=\"post\" action=\"\/mail\/form.php\" novalidate>'\n      + '<input type=\"hidden\" name=\"list\" value=\"' + meta.list + '\">'\n      + '<input type=\"hidden\" name=\"name\" value=\"\">'\n      + '<label class=\"msq-hp\" aria-hidden=\"true\">Leave this empty<input type=\"text\" name=\"website\" tabindex=\"-1\" autocomplete=\"off\"><\/label>'\n      + '<input type=\"email\" name=\"email\" placeholder=\"you@example.com\" required aria-label=\"Email address\" autocomplete=\"email\">'\n      + '<button type=\"submit\" class=\"msq-btn\">Email me the updates<\/button>'\n      + '<a href=\"#\" class=\"msq-no\">No thanks<\/a>'\n      + '<p class=\"msq-privacy\">We store your email and this page\\'s topic, nothing else. Unsubscribe any time.<\/p>'\n      + '<\/form><p class=\"msq-msg\" aria-live=\"polite\"><\/p><\/div>';\n  }\n  \/* The autoresponder always answers 200 with its own page. A \".msg.err\" box means the\n     address was rejected, \"You're subscribed\" means accepted, and a redirect only happens\n     after a successful subscribe. Same reading as the landing pages on the other sites. *\/\n  function sendForm(form){\n    return fetch(form.getAttribute('action'), { method: 'POST', body: new FormData(form), credentials: 'same-origin', redirect: 'manual' })\n      .then(function(res){\n        if (res.type === 'opaqueredirect') { return { status: 'ok' }; }\n        if (res.status === 404) { return { status: 'notlive' }; }\n        if (!res.ok) { throw new Error('HTTP ' + res.status); }\n        return res.text().then(function(t){\n          var doc = new DOMParser().parseFromString(t, 'text\/html');\n          var err = doc.querySelector('.msg.err');\n          if (err) {\n            var m = err.textContent.trim();\n            if (\/something went wrong\/i.test(m)) { throw new Error(m); }\n            return { status: 'rejected', message: m };\n          }\n          if (\/you.re subscribed\/i.test(doc.body.textContent)) { return { status: 'ok' }; }\n          return { status: 'unknown' };\n        });\n      });\n  }\n  function wireForm(sub){\n    var form = qs(sub, '.msq-form'), msg = qs(sub, '.msq-msg');\n    qs(form, '.msq-no').addEventListener('click', function(e){\n      e.preventDefault();\n      sub.innerHTML = '<p class=\"msq-msg\">No problem. The Sunday refresh keeps this page current either way.<\/p>';\n      track('quiz_email', { result: 'declined' });\n    });\n    form.addEventListener('submit', function(e){\n      e.preventDefault();\n      var input = qs(form, 'input[name=email]'), btn = qs(form, '.msq-btn');\n      var email = input.value.trim(); input.value = email;\n      if (!\/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\/.test(email)) { msg.textContent = 'That email does not look right.'; return; }\n      btn.disabled = true; btn.textContent = 'Sending';\n      function reset(){ btn.disabled = false; btn.textContent = 'Email me the updates'; }\n      sendForm(form).then(function(r){\n        if (r.status === 'ok' || r.status === 'unknown') {\n          sub.innerHTML = '<p class=\"msq-msg\"><strong>Done.<\/strong> We will email you when the next article on ' + meta.category\n            + ' goes up and when this page changes. If a confirmation email arrives, click it.<\/p>';\n          track('quiz_email', { result: r.status });\n          if (r.status === 'ok') { track('generate_lead', { method: 'quiz' }); }\n        } else if (r.status === 'notlive') {\n          msg.textContent = 'Sign up is not switched on yet for this site. Nothing was stored.'; reset();\n          track('quiz_email', { result: 'notlive' });\n        } else {\n          msg.textContent = r.message || 'Please check the email address and try again.'; reset();\n          track('quiz_email', { result: 'rejected' });\n        }\n      }).catch(function(){\n        msg.textContent = 'Could not reach the sign up service just now. Nothing was stored.'; reset();\n        track('quiz_email', { result: 'error' });\n      });\n    });\n  }\n  function finish(container, mode, score, total, missed){\n    var result = qs(container, '.msq-result');\n    track('quiz_complete', { mode: mode, score: score, total: total });\n    var h;\n    if (mode === 'pre') {\n      h = '<h3>You knew ' + score + ' of ' + total + ' before reading.<\/h3>';\n      h += (score === total) ? '<p>All of them. Read on anyway; the sections below carry the detail behind each answer.<\/p>'\n                             : '<p>The rest is below. Jump straight to what you missed:<\/p>' + jumpList(missed);\n      try { sessionStorage.setItem('msq-pre-' + meta.slug, String(score)); } catch (e) {}\n    } else {\n      var pre = null;\n      try { pre = sessionStorage.getItem('msq-pre-' + meta.slug); } catch (e) {}\n      h = '<h3>You got ' + score + ' of ' + total + '.<\/h3>';\n      if (pre !== null) { h += '<p>Before reading you had ' + pre + ' of ' + total + '.<\/p>'; }\n      if (missed.length) { h += '<p>Worth a second look:<\/p>' + jumpList(missed); }\n      h += emailForm();\n    }\n    result.innerHTML = h; result.hidden = false;\n    if (mode !== 'pre') { wireForm(qs(result, '.msq-sub')); }\n    result.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n  }\n  function setup(container, mode){\n    var qEls = qsa(container, '.msq-q'), answered = 0, score = 0, missed = [];\n    qEls.forEach(function(q){\n      var answer = q.getAttribute('data-answer'), opts = qsa(q, '.msq-opt'), explain = qs(q, '.msq-explain');\n      q.removeAttribute('data-done'); explain.hidden = true; qs(explain, '.msq-verdict').textContent = '';\n      opts.forEach(function(b){ b.disabled = false; b.classList.remove('is-right', 'is-wrong'); });\n      opts.forEach(function(b){\n        b.addEventListener('click', function(){\n          if (q.getAttribute('data-done')) { return; }\n          q.setAttribute('data-done', '1');\n          var ok = b.getAttribute('data-opt') === answer;\n          opts.forEach(function(o){ o.disabled = true; if (o.getAttribute('data-opt') === answer) { o.classList.add('is-right'); } });\n          if (ok) { score += 1; } else { b.classList.add('is-wrong'); missed.push(q); }\n          qs(explain, '.msq-verdict').textContent = ok ? 'Right. ' : 'Not quite. ';\n          explain.hidden = false;\n          answered += 1;\n          track('quiz_answer', { mode: mode, question: q.getAttribute('data-q'), correct: ok ? 1 : 0 });\n          if (answered === qEls.length) { finish(container, mode, score, qEls.length, missed); }\n        });\n      });\n    });\n  }\n  var box = qs(root, '.msq-box');\n  setup(root, 'pre');\n  if (box) { var skipFirst = box.hasAttribute('data-default-open'); box.addEventListener('toggle', function(){ if (!box.open) { return; } if (skipFirst) { skipFirst = false; return; } track('quiz_open', { mode: 'pre' }); }); }\n  \/* The end block sits far below this script, so it does not exist yet while this runs\n     during parsing. A delegated listener finds it at click time. *\/\n  document.addEventListener('click', function(e){\n    var startBtn = (e.target && e.target.closest) ? e.target.closest('.msq-start-test') : null;\n    if (!startBtn) { return; }\n    var end = startBtn.closest('.msq-end');\n    if (!end) { return; }\n    var mount = qs(end, '.msq-mount');\n    var clone = qs(root, '.msq-body').cloneNode(true);\n    var intro = qs(clone, '.msq-intro');\n    if (intro) { intro.textContent = 'The same five questions, now that you have read the page.'; }\n    var res = qs(clone, '.msq-result'); if (res) { res.hidden = true; res.innerHTML = ''; }\n    mount.innerHTML = ''; mount.appendChild(clone);\n    setup(mount, 'test');\n    startBtn.hidden = true;\n    track('quiz_open', { mode: 'test' });\n    mount.scrollIntoView({ behavior: 'smooth', block: 'start' });\n    var first = qs(mount, '.msq-opt'); if (first) { first.focus({ preventScroll: true }); }\n  });\n  \/* Shared tool behaviour: checklists remember their ticks on this device, and a chooser\n     shows the result for the option picked. Calculators carry their own small script. *\/\n  function initTools(){\n    qsa(document, '.msq-check').forEach(function(box){\n      var key = 'msq-check-' + meta.slug + '-' + (box.getAttribute('data-id') || 'x');\n      var boxes = qsa(box, 'input[type=checkbox]'), count = qs(box, '.msq-check-count');\n      var yes = qs(box, '.msq-verdict-yes'), no = qs(box, '.msq-verdict-no');\n      var saved = {};\n      try { saved = JSON.parse(localStorage.getItem(key) || '{}'); } catch (e) {}\n      boxes.forEach(function(cb, i){ if (saved[i]) { cb.checked = true; } });\n      function refresh(){\n        var n = boxes.filter(function(cb){ return cb.checked; }).length, state = {};\n        boxes.forEach(function(cb, i){ if (cb.checked) { state[i] = 1; } });\n        try { localStorage.setItem(key, JSON.stringify(state)); } catch (e) {}\n        if (count) { count.textContent = n + ' of ' + boxes.length + (box.getAttribute('data-verb') || ' done'); }\n        if (yes && no) { yes.hidden = n === 0; no.hidden = n !== 0; }\n      }\n      boxes.forEach(function(cb){ cb.addEventListener('change', function(){ refresh(); track('tool_check', { tool: box.getAttribute('data-id') }); }); });\n      refresh();\n    });\n    qsa(document, '.msq-choose').forEach(function(box){\n      var results = qsa(box, '.msq-choice-result');\n      qsa(box, 'input[type=radio]').forEach(function(r){\n        r.addEventListener('change', function(){\n          results.forEach(function(el){ el.hidden = el.getAttribute('data-for') !== r.value; });\n          track('tool_choose', { tool: box.getAttribute('data-id'), choice: r.value });\n        });\n      });\n    });\n  }\n  \/\/ read progress: how far down the article the reader has scrolled, fired once per quarter\n  (function(){\n    var art = root.closest('article') || root.parentNode, fired = {};\n    function check(){\n      var top = art.getBoundingClientRect().top + window.pageYOffset, h = art.offsetHeight || 1;\n      var seen = (window.pageYOffset + window.innerHeight - top) \/ h * 100;\n      [25, 50, 75, 100].forEach(function(m){ if (seen >= m && !fired[m]) { fired[m] = true; track('read_progress', { percent: m }); } });\n    }\n    window.addEventListener('scroll', check, { passive: true }); window.addEventListener('load', check);\n  })();\n  if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initTools); } else { initTools(); }\n})();<\/script>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Checked on 13 September 2026 using the wallet makers\u2019 own documentation and public GitHub records. Most \u201cbest crypto wallet\u201d lists treat \u201copen source\u201d like just another feature, something to tick off alongside price, design and colour choices. But being open source is more than a tick in a comparison table. This page looks at what that label actually means and how much trust it really gives you.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-does-open-source-actually-prove-about-a-crypto-wallet\">What does open source actually prove about a crypto wallet?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">It proves the source is published, so anyone with the skill and the patience can inspect it. That is a genuine good. A wallet that hides its code asks you to trust the company&#8217;s engineering blind, and one that publishes it at least lets outsiders look. The trouble is the distance between &#8220;can look&#8221; and &#8220;did look&#8221;, and a second, quieter distance between the code on GitHub and the file on your device.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One wrinkle: Coldcard licenses its firmware under MIT with the <a href=\"https:\/\/github.com\/Coldcard\/firmware\/blob\/master\/COPYING-CC\" target=\"_blank\" rel=\"noopener\">Commons Clause<\/a>, which withholds the right to sell it, so purists call it source available. For security that barely matters. What protects you is being able to read and rebuild the code, and that licence allows both.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"why-is-code-you-can-read-not-the-same-as-code-someone-has-ch\">Why is code you can read not the same as code someone has checked?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Two public records make the point. Libbitcoin Explorer, a command line bitcoin tool with its code in the open, generated seeds in versions 3.0.0 through 3.6.0 from a random number generator seeded with 32 bits of system time. The <a href=\"https:\/\/milksad.info\/disclosure.html\" target=\"_blank\" rel=\"noopener\">Milk Sad disclosure<\/a> describes funds taken from affected wallets in 2023, logged as <a href=\"https:\/\/github.com\/advisories\/GHSA-prgj-h7jq-7p9h\" target=\"_blank\" rel=\"noopener\">CVE-2023-39910<\/a>. The code was readable the whole time.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1376\" height=\"768\" src=\"https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/09\/close-up-metal-card-crypto-wallet-open-source.jpg\" alt=\"Hands examining a blank metal backup card on a wooden desk during a crypto wallet open source review\" class=\"wp-image-167\" srcset=\"https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/09\/close-up-metal-card-crypto-wallet-open-source.jpg 1376w, https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/09\/close-up-metal-card-crypto-wallet-open-source-300x167.jpg 300w, https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/09\/close-up-metal-card-crypto-wallet-open-source-1024x572.jpg 1024w, https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/09\/close-up-metal-card-crypto-wallet-open-source-768x429.jpg 768w\" sizes=\"auto, (max-width: 1376px) 100vw, 1376px\" \/><figcaption class=\"wp-element-caption\">Published code covers the software. It says nothing about how a seed was made.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The second record is closer to home for hardware wallet owners. Coldcard&#8217;s firmware is published, and its repository carries <a href=\"https:\/\/github.com\/Coldcard\/firmware\/blob\/master\/docs\/notes-on-repro.md\" target=\"_blank\" rel=\"noopener\">instructions for rebuilding it byte for byte<\/a>. The seed entropy defect disclosed in July 2026 still affected, according to <a href=\"https:\/\/coldcard.com\/security\/status\" target=\"_blank\" rel=\"noopener\">Coinkite&#8217;s Coldcard security status page<\/a> as last updated on 6 September 2026, Mk4 and Mk5 standard firmware before 5.6.0, Q standard firmware before 1.5.0Q, and Mk2 and Mk3 firmware before 4.2.0. That page states that updating firmware does not repair a seed already generated on affected firmware. If you own one of these devices, follow that page directly rather than any summary, including this one.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A reproducible build would have recreated the firmware exactly as it was, including the bug. That\u2019s because reproducibility only shows that the app or firmware matches the published source code. It does not tell you whether that source code itself is safe or free of mistakes. Our <a href=\"https:\/\/getbitcoin.guide\/articles\/what-is-a-cold-wallet\/\">cold-wallet explainer<\/a> explains what happened and how even people who followed the recommended steps could still be affected.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"how-do-you-know-the-app-you-installed-was-built-from-that-co\">How do you know the app you installed was built from that code?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is the gap no ranking page for this search names. A company can publish clean code and ship a different file, by design, by accident, or because someone got into its pipeline. Ledger&#8217;s <a href=\"https:\/\/www.ledger.com\/blog\/security-incident-report\" target=\"_blank\" rel=\"noopener\">own incident report<\/a> for 14 December 2023 describes the distribution version: a former employee whose npm access had not been revoked was phished, and malicious versions 1.1.5 to 1.1.7 of the Ledger Connect Kit library were published. Ledger puts the active draining window at under two hours.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A reproducible build is the defence against the build half of that. Independent people compile the published source with a pinned toolchain and confirm they get the same bytes as the official release, signature aside. When several unconnected builders match, a quietly altered binary is very hard to hide. KeepKey, for one, prints the pinned build command and the expected hash in each recent release note, which <a href=\"https:\/\/getbitcoin.guide\/articles\/keepkey-wallet-review\/\">our KeepKey wallet review<\/a> reads release by release.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Bitcoin Core is the reference case. Its releases are built with Guix, several named builders publish signed hashes in the <a href=\"https:\/\/github.com\/bitcoin-core\/guix.sigs\" target=\"_blank\" rel=\"noopener\">guix.sigs repository<\/a>, and the <a href=\"https:\/\/bitcoincore.org\/en\/download\/\" target=\"_blank\" rel=\"noopener\">Bitcoin Core download page<\/a> explains checking a release against those signatures.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Even a pass has limits. <a href=\"https:\/\/walletscrutiny.com\/methodology\/\" target=\"_blank\" rel=\"noopener\">WalletScrutiny&#8217;s methodology<\/a>, updated on 11 September 2026, says a reproducible verdict covers the binary they tested, not later releases. It also says no iPhone wallet app has achieved a reproducible build, which is worth remembering when an app store listing mentions open source in its second sentence.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-can-you-actually-verify-for-each-wallet-today\">What can you actually verify for each wallet today?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Compiled from each vendor&#8217;s own documentation or public repository on 13 September 2026. It records what an outsider can check. It is not a ranking and not a recommendation of any product.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Wallet<\/th><th>What is published<\/th><th>Rebuild and compare?<\/th><th>What an owner can check<\/th><th>Source<\/th><\/tr><\/thead><tbody><tr><td>Trezor devices<\/td><td>Firmware<\/td><td>Yes, documented for six models<\/td><td>Release hash with signature zeroed, against a local build<\/td><td><a href=\"https:\/\/docs.trezor.io\/trezor-firmware\/common\/reproducible-build.html\" target=\"_blank\" rel=\"noopener\">Trezor build docs<\/a><\/td><\/tr><tr><td>BitBox02<\/td><td>Firmware<\/td><td>Yes, Docker build from the release tag<\/td><td>Firmware hash on the device screen; signed build assertions<\/td><td><a href=\"https:\/\/blog.bitbox.swiss\/en\/how-to-independently-verify-the-bitbox02-firmware\/\" target=\"_blank\" rel=\"noopener\">BitBox guide<\/a><\/td><\/tr><tr><td>Coldcard<\/td><td>Firmware, MIT plus Commons Clause<\/td><td>Yes, a repro build step<\/td><td>SHA 256 hashes in a PGP signed file<\/td><td><a href=\"https:\/\/github.com\/Coldcard\/firmware\/blob\/master\/docs\/notes-on-repro.md\" target=\"_blank\" rel=\"noopener\">Coldcard repro notes<\/a><\/td><\/tr><tr><td>Blockstream Jade<\/td><td>Firmware<\/td><td>Guide exists; 2023 mismatches reported, said to be fixed in 2024<\/td><td>Local build against the release<\/td><td><a href=\"https:\/\/github.com\/Blockstream\/Jade\" target=\"_blank\" rel=\"noopener\">Jade repository<\/a><\/td><\/tr><tr><td>OneKey<\/td><td>Firmware<\/td><td>No independent match in the open issue; 293 bytes apart in September 2025<\/td><td>Vendor hash instructions<\/td><td><a href=\"https:\/\/github.com\/OneKeyHQ\/firmware\/issues\/579\" target=\"_blank\" rel=\"noopener\">issue 579<\/a><\/td><\/tr><tr><td>Ledger devices<\/td><td>Apps and developer kit, not the secure element OS<\/td><td>Not for the closed layer<\/td><td>Vendor signed updates for that layer<\/td><td><a href=\"https:\/\/www.ledger.com\/blog-ledger-is-95-opensource-why-not-100\" target=\"_blank\" rel=\"noopener\">Ledger explanation<\/a><\/td><\/tr><tr><td>Bitcoin Core<\/td><td>Node and wallet software<\/td><td>Yes, Guix builds by several builders<\/td><td>Signed hashes from builders you pick<\/td><td><a href=\"https:\/\/github.com\/bitcoin-core\/guix.sigs\" target=\"_blank\" rel=\"noopener\">guix.sigs<\/a><\/td><\/tr><tr><td>Sparrow<\/td><td>Desktop wallet software<\/td><td>Not described on the download page<\/td><td>Signed manifest and hash, checkable in the app<\/td><td><a href=\"https:\/\/sparrowwallet.com\/download\/\" target=\"_blank\" rel=\"noopener\">Sparrow downloads<\/a><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The vendors that document reproducible builds also give ordinary owners a smaller check, a hash on the screen or a signed manifest, and that is the check most people will actually do. The BitBox02 bootloader is one of the few that will show that hash before it runs the firmware, which our <a href=\"https:\/\/getbitcoin.guide\/articles\/bitbox02-review\/\">BitBox02 review sets against the gaps in its published audit record<\/a>. A build guide, though, is only as good as the last independent person who followed it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-do-people-who-tried-to-reproduce-wallet-firmware-report\">What do people who tried to reproduce wallet firmware report?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We read five public GitHub issue threads, about 60 comments in all, from people who tried to rebuild wallet firmware and match the official release, plus the BitBox02 release record. This is what contributors report, not audited findings, and a failed attempt can be the builder&#8217;s mistake as easily as the vendor&#8217;s.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In September 2023 a user <a href=\"https:\/\/github.com\/Blockstream\/Jade\/issues\/90\" target=\"_blank\" rel=\"noopener\">reported on the Blockstream Jade tracker<\/a> that a build on an Apple M2 Mac, following the repository&#8217;s reproducibility guide, differed from official firmware 1.0.21 by more than the signature, and that Ubuntu 23.04 gave the same result. A maintainer replied the next day that this was not expected, and in January 2024 said it was hopefully resolved by a related fix.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1376\" height=\"768\" src=\"https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/09\/man-compiling-crypto-wallet-open-source-firmware.jpg\" alt=\"Man at a home desk rubbing his temple while waiting on a crypto wallet open source firmware build\" class=\"wp-image-168\" srcset=\"https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/09\/man-compiling-crypto-wallet-open-source-firmware.jpg 1376w, https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/09\/man-compiling-crypto-wallet-open-source-firmware-300x167.jpg 300w, https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/09\/man-compiling-crypto-wallet-open-source-firmware-1024x572.jpg 1024w, https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/09\/man-compiling-crypto-wallet-open-source-firmware-768x429.jpg 768w\" sizes=\"auto, (max-width: 1376px) 100vw, 1376px\" \/><figcaption class=\"wp-element-caption\">Rebuilding firmware from source is evenings of work, not minutes.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">In November 2023 a WalletScrutiny contributor <a href=\"https:\/\/github.com\/Blockstream\/Jade\/issues\/98\" target=\"_blank\" rel=\"noopener\">reported non determinism in Jade builds<\/a>: out of ten compilations, up to two produced a binary that did not match the release, even though the build itself succeeded. The issue was closed in March 2024.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The longest record is <a href=\"https:\/\/github.com\/OneKeyHQ\/firmware\/issues\/404\" target=\"_blank\" rel=\"noopener\">an issue opened on OneKey&#8217;s firmware repository in May 2023<\/a> asking whether its firmware could be reproduced byte for byte. Over 45 comments and sixteen months, builders describe Docker failures, clock faking errors and a script that printed success and failure on the same line. The effort moved to <a href=\"https:\/\/github.com\/OneKeyHQ\/firmware\/issues\/579\" target=\"_blank\" rel=\"noopener\">a follow up issue<\/a>, where a February 2025 reply suggested errors in the builder&#8217;s process, and in September 2025 a builder reported OneKey Mini firmware 3.10.0 differing from the release by 293 bytes. It was still open on 13 September 2026. That records an unfinished verification, not evidence that anything is wrong with the firmware.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At the other end of the range, in December 2021 a Trezor user <a href=\"https:\/\/github.com\/trezor\/trezor-firmware\/issues\/2037\" target=\"_blank\" rel=\"noopener\">could not match the official fingerprint<\/a> for Model T firmware, then worked out within about five hours how it is calculated, and a maintainer pointed to a tool that does it. That thread is nearly five years old, so read it as how the process used to look. And the <a href=\"https:\/\/github.com\/BitBoxSwiss\/bitbox02-firmware\/tree\/master\/releases\" target=\"_blank\" rel=\"noopener\">BitBox02 releases folder<\/a> carries signed assertions, up to firmware 9.27.1, that each binary was reproduced from its tagged source.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Where the accounts agree with the official guidance: reproducible builds work, and mismatches get fixed once a maintainer engages. Where they differ from what the guidance implies: this is specialist work measured in evenings, and a build guide can sit for years without independent confirmation.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"900\" src=\"https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/10\/crypto-wallet-open-source-chart.png\" alt=\"Timeline showing the key events in verifying OneKey firmware reproducibility\" class=\"wp-image-516\" srcset=\"https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/10\/crypto-wallet-open-source-chart.png 1600w, https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/10\/crypto-wallet-open-source-chart-300x169.png 300w, https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/10\/crypto-wallet-open-source-chart-1024x576.png 1024w, https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/10\/crypto-wallet-open-source-chart-768x432.png 768w, https:\/\/getbitcoin.guide\/articles\/wp-content\/uploads\/2026\/10\/crypto-wallet-open-source-chart-1536x864.png 1536w\" sizes=\"auto, (max-width: 1600px) 100vw, 1600px\" \/><figcaption class=\"wp-block-image__caption\">Based on public GitHub issues and community reports up to September 2026.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"is-closed-firmware-automatically-less-safe\">Is closed firmware automatically less safe?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">No, and here we part company with the ranking pages in both directions. Ledger says in <a href=\"https:\/\/www.ledger.com\/blog-ledger-is-95-opensource-why-not-100\" target=\"_blank\" rel=\"noopener\">its explanation of why it is not fully open source<\/a> that the code talking to its secure element sits under a non disclosure agreement with the chip maker. Its chief technology officer, Charles Guillemet, argued in <a href=\"https:\/\/www.ledger.com\/blog-open-source-not-security-property\" target=\"_blank\" rel=\"noopener\">an essay published on 6 August 2026<\/a> that open source is an inspection property rather than a security property, and that professional researchers review code when someone pays them to. Ledger does not publish that layer, so it is hardly neutral.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">He is half right. Open code did not save Coldcard or Libbitcoin Explorer owners. But inspection is the only way outsiders find a flaw before an attacker does, and a reproducible build is the only way anyone but the vendor can confirm what shipped. A closed layer removes both, leaving the vendor&#8217;s own testing and disclosure record. The NGRAVE ZERO is the sharpest version of that trade: its firmware is not published at all, and we traced <a href=\"https:\/\/getbitcoin.guide\/articles\/ngrave-zero-review\/\">what its EAL7 certificate actually covers<\/a> to a third party kernel rather than the device. Our <a href=\"https:\/\/getbitcoin.guide\/articles\/trezor-vs-ledger\/\">Trezor and Ledger comparison<\/a> sets both companies&#8217; security bulletins side by side, which says more than either licence.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-should-a-non-developer-check-instead\">What should a non developer check instead?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You do not need to compile firmware to benefit from the people who do. This checklist is derived from the vendor documents in the table above.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Download wallet software only from the vendor&#8217;s own site, and check the signed manifest or hash when one is offered. Sparrow builds that check into the app for downloads.<\/li>\n\n\n\n<li>Update firmware through the vendor&#8217;s official app, and where the device shows a firmware hash or fingerprint, compare it with the one the vendor publishes.<\/li>\n\n\n\n<li>Look for a public reproducible build guide and for independent confirmations dated within the last year, not merely a link to a repository.<\/li>\n\n\n\n<li>Check the vendor&#8217;s security status page against the firmware version your seed was generated on. A patched device does not fix a seed made before the patch.<\/li>\n\n\n\n<li>Never type your recovery phrase into any website, app or tool that claims to verify a wallet. No legitimate verification step needs it.<\/li>\n\n\n\n<li>Treat open source in an app store listing as a claim until someone independent has rebuilt it.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For everything else worth weighing before picking any wallet, our <a href=\"https:\/\/getbitcoin.guide\/articles\/best-crypto-wallet\/\">guide to choosing a crypto wallet without being sold one<\/a> covers the rest, and <a href=\"https:\/\/getbitcoin.guide\/articles\/your-recovery-phrase\/\">the guide to storing your recovery phrase<\/a> covers the part that no amount of verified code can protect.<\/p>\n\n\n\n<div class=\"msq-tool msq-check\" data-id=\"crypto-wallet-open-source-checklist\" data-verb=\" checked\">\n<h3>Non Developer Wallet Verification Checklist<\/h3>\n<p>You do not need to be a developer or compile code from scratch to protect yourself. Use these practical steps to verify your wallet&#8217;s integrity.<\/p>\n<label><input type=\"checkbox\">Download wallet software only from the vendor&#8217;s official site.<\/label>\n<label><input type=\"checkbox\">Check the signed manifest or SHA 256 hash when downloading desktop software.<\/label>\n<label><input type=\"checkbox\">Update your device firmware exclusively through the official vendor app.<\/label>\n<label><input type=\"checkbox\">Compare the firmware hash displayed on your device screen with the published version.<\/label>\n<label><input type=\"checkbox\">Verify the vendor&#8217;s security status page for any historical seed generation bugs.<\/label>\n<label><input type=\"checkbox\">Never enter your seed phrase into any website or verification tool.<\/label>\n<p class=\"msq-check-count\"><\/p>\n<p class=\"msq-note\">Ticks are saved on this device only.<\/p>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"frequently-asked-questions\">Frequently asked questions<\/h2>\n\n\n<div id=\"rank-math-faq\" class=\"rank-math-block\">\n<div class=\"rank-math-list \">\n<div id=\"faq-q-1\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \">Is Bitcoin Core open source and reproducible?<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Yes. Bitcoin Core\u2019s code is public, and its releases can be rebuilt to check that the published code matches the software people download. Independent builders also publish signed hashes, giving users more than one source to compare against instead of having to rely on a single company.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-q-2\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \">Does open source mean a wallet has been audited?<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>No. Open source simply means the code can be inspected. An audit is a separate, professional review of a particular version of that code at a particular time. If a wallet says it has been audited, check the actual audit report, when it was done and which version was reviewed.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-q-3\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \">Can a phone wallet app be reproducible?<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Sometimes. Some Android wallet apps have been independently rebuilt and matched with the published version. However, according to the WalletScrutiny methodology page updated in September 2026, no iPhone wallet app had achieved a reproducible build at that time.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-q-4\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \">What is a firmware fingerprint?<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>It is basically a short code, called a hash, that identifies a particular firmware version. When your device shows this fingerprint, you can compare it with the one published by the manufacturer or produced by an independent build. If they match, you have stronger evidence that you are installing the expected firmware.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-q-5\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \">Is source available the same as open source?<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Not always. Some projects let you see and rebuild their code but place restrictions on what you can do with it, such as selling or redistributing it. For someone mainly concerned with checking what their wallet is actually running, the important part is being able to see the code and independently rebuild it.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-q-6\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \">Does verifying my firmware protect a seed I generated earlier?<\/h3>\n<div class=\"rank-math-answer \">\n\n<p>No. Firmware verification tells you whether the firmware you are using now matches the expected version. It cannot go back and fix a seed that was created using faulty or compromised firmware. If you are worried about an older seed, check the wallet maker\u2019s security notices for the firmware version that was installed when you created it.<\/p>\n\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n\n\n<div class=\"msq-end\" id=\"ms-quiz-end\">\n<p><strong>Finished the page?<\/strong> The same five questions from the top, to see what stuck.<\/p>\n<button type=\"button\" class=\"msq-btn msq-start-test\">Test Your Understanding of This Article<\/button>\n<div class=\"msq-mount\"><\/div>\n<\/div>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><em>This article is general information for education and is not personal financial advice. Nothing here is a recommendation to buy, sell or hold anything. For decisions about your own situation, speak to a qualified professional.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Open source means a wallet&#8217;s code can be read. It does not mean anyone read it, or that your device runs it. Reproducible builds, checked vendor by vendor.<\/p>\n","protected":false},"author":1,"featured_media":166,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","rank_math_title":"Crypto Wallet Open Source: What Reading the Code Proves","rank_math_description":"Calling a crypto wallet open source proves the code can be read, not that anyone checked it or that your app was built from it. Here is how to tell.","rank_math_focus_keyword":"crypto wallet open source","rank_math_canonical_url":"","rank_math_breadcrumb_title":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"","rank_math_pillar_content":"","rank_math_robots":[],"rank_math_advanced_robots":[]},"categories":[3],"tags":[],"class_list":["post-169","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-self-custody"],"_links":{"self":[{"href":"https:\/\/getbitcoin.guide\/articles\/wp-json\/wp\/v2\/posts\/169","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/getbitcoin.guide\/articles\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/getbitcoin.guide\/articles\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/getbitcoin.guide\/articles\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/getbitcoin.guide\/articles\/wp-json\/wp\/v2\/comments?post=169"}],"version-history":[{"count":8,"href":"https:\/\/getbitcoin.guide\/articles\/wp-json\/wp\/v2\/posts\/169\/revisions"}],"predecessor-version":[{"id":644,"href":"https:\/\/getbitcoin.guide\/articles\/wp-json\/wp\/v2\/posts\/169\/revisions\/644"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/getbitcoin.guide\/articles\/wp-json\/wp\/v2\/media\/166"}],"wp:attachment":[{"href":"https:\/\/getbitcoin.guide\/articles\/wp-json\/wp\/v2\/media?parent=169"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/getbitcoin.guide\/articles\/wp-json\/wp\/v2\/categories?post=169"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/getbitcoin.guide\/articles\/wp-json\/wp\/v2\/tags?post=169"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}