Skip to content

Instantly share code, notes, and snippets.

@Frankenmint
Created August 5, 2026 21:54
Show Gist options
  • Select an option

  • Save Frankenmint/7d27a1eb98b9f0bb1fd7f10b5f969e1f to your computer and use it in GitHub Desktop.

Select an option

Save Frankenmint/7d27a1eb98b9f0bb1fd7f10b5f969e1f to your computer and use it in GitHub Desktop.
MRNG WALLET CEREMONY — radiation pulse timing to a 24-word BIP-39 mnemonic.
#!/usr/bin/env python3
"""
MRNG WALLET CEREMONY — radiation pulse timing to a 24-word BIP-39 mnemonic.
NO THIRD-PARTY PYTHON PACKAGES REQUIRED.
Linux/macOS Python 3 standard library only. Serial support uses os/termios/select.
Windows serial ports are not supported without adapting the input layer.
WHAT THIS DOES
==============
1. Records local monotonic timestamps whenever a detector emits data.
2. Computes arrival intervals between detector events.
3. Compares non-overlapping interval pairs:
interval A < interval B -> bit 0
interval A > interval B -> bit 1
equal intervals -> discard
Under an independent, continuous arrival-time model, either ordering has
equal probability. Pairing avoids reusing an interval.
4. Runs basic failure alarms. These detect obvious stuck/noisy inputs. They do
NOT certify entropy or replace NIST SP 800-90B source characterization.
5. Conditions collected physical bits with SHAKE-256.
6. By default, mixes 256 bits from os.urandom(), creating a hybrid generator.
If either source remains secret and unpredictable, SHAKE-256 output should
remain unpredictable. --physical-only disables this safety layer.
7. Converts exactly 256 conditioned bits into a standard 24-word BIP-39
English mnemonic: 256 entropy bits + 8 SHA-256 checksum bits = 264 bits,
split into twenty-four 11-bit word indices.
IMPORTANT SECURITY LIMITS
=========================
* Output length is not an entropy proof. "256 output bits" does not establish
"256 bits of min-entropy" from the detector.
* A Geiger detector observes ionizing events, not necessarily cosmic rays only.
* Statistical tests cannot prove unpredictability.
* Python cannot promise secure erasure of immutable strings/bytes from RAM.
* Terminal scrollback, screen recording, shell logging, cameras, malware, swap,
and physical observers can expose a mnemonic.
* Run offline from trusted read-only media on a clean machine for serious funds.
* Never photograph, copy/paste, print through a network printer, or store the
mnemonic digitally. Verify recovery before funding a wallet.
* This program generates BIP-39 mnemonic entropy. Wallet derivation remains the
wallet application's job. Import only into software/hardware you trust.
DETECTOR INPUT
==============
Serial mode treats any received byte burst as one detector event. Your detector
should emit one short message/pulse per count. Example:
./mrng_wallet.py --source serial --device /dev/ttyUSB0 --baud 9600
For testing or adapters that already print one line per pulse:
detector_command | ./mrng_wallet.py --source stdin
Press Ctrl-C to abort. No secret file is created by default.
COLLECTION TARGET
=================
Default: collect 512 retained comparison bits, then condition to 256 bits.
This does not mean 512 certified entropy bits. It provides margin against bias
and extractor input imperfections. Collection needs about two intervals per
retained bit, plus discarded ties: roughly 1,025+ detector events.
BIP-39 WORDLIST
===============
Official English list from bitcoin/bips, embedded below. The list itself is
public and is not secret. Only the generated word sequence is secret.
"""
from __future__ import annotations
import argparse
import hashlib
import os
import select
import statistics
import sys
import termios
import time
from dataclasses import dataclass
from typing import Iterator
WORDLIST = """abandon\nability\nable\nabout\nabove\nabsent\nabsorb\nabstract\nabsurd\nabuse\naccess\naccident\naccount\naccuse\nachieve\nacid\nacoustic\nacquire\nacross\nact\naction\nactor\nactress\nactual\nadapt\nadd\naddict\naddress\nadjust\nadmit\nadult\nadvance\nadvice\naerobic\naffair\nafford\nafraid\nagain\nage\nagent\nagree\nahead\naim\nair\nairport\naisle\nalarm\nalbum\nalcohol\nalert\nalien\nall\nalley\nallow\nalmost\nalone\nalpha\nalready\nalso\nalter\nalways\namateur\namazing\namong\namount\namused\nanalyst\nanchor\nancient\nanger\nangle\nangry\nanimal\nankle\nannounce\nannual\nanother\nanswer\nantenna\nantique\nanxiety\nany\napart\napology\nappear\napple\napprove\napril\narch\narctic\narea\narena\nargue\narm\narmed\narmor\narmy\naround\narrange\narrest\narrive\narrow\nart\nartefact\nartist\nartwork\nask\naspect\nassault\nasset\nassist\nassume\nasthma\nathlete\natom\nattack\nattend\nattitude\nattract\nauction\naudit\naugust\naunt\nauthor\nauto\nautumn\naverage\navocado\navoid\nawake\naware\naway\nawesome\nawful\nawkward\naxis\nbaby\nbachelor\nbacon\nbadge\nbag\nbalance\nbalcony\nball\nbamboo\nbanana\nbanner\nbar\nbarely\nbargain\nbarrel\nbase\nbasic\nbasket\nbattle\nbeach\nbean\nbeauty\nbecause\nbecome\nbeef\nbefore\nbegin\nbehave\nbehind\nbelieve\nbelow\nbelt\nbench\nbenefit\nbest\nbetray\nbetter\nbetween\nbeyond\nbicycle\nbid\nbike\nbind\nbiology\nbird\nbirth\nbitter\nblack\nblade\nblame\nblanket\nblast\nbleak\nbless\nblind\nblood\nblossom\nblouse\nblue\nblur\nblush\nboard\nboat\nbody\nboil\nbomb\nbone\nbonus\nbook\nboost\nborder\nboring\nborrow\nboss\nbottom\nbounce\nbox\nboy\nbracket\nbrain\nbrand\nbrass\nbrave\nbread\nbreeze\nbrick\nbridge\nbrief\nbright\nbring\nbrisk\nbroccoli\nbroken\nbronze\nbroom\nbrother\nbrown\nbrush\nbubble\nbuddy\nbudget\nbuffalo\nbuild\nbulb\nbulk\nbullet\nbundle\nbunker\nburden\nburger\nburst\nbus\nbusiness\nbusy\nbutter\nbuyer\nbuzz\ncabbage\ncabin\ncable\ncactus\ncage\ncake\ncall\ncalm\ncamera\ncamp\ncan\ncanal\ncancel\ncandy\ncannon\ncanoe\ncanvas\ncanyon\ncapable\ncapital\ncaptain\ncar\ncarbon\ncard\ncargo\ncarpet\ncarry\ncart\ncase\ncash\ncasino\ncastle\ncasual\ncat\ncatalog\ncatch\ncategory\ncattle\ncaught\ncause\ncaution\ncave\nceiling\ncelery\ncement\ncensus\ncentury\ncereal\ncertain\nchair\nchalk\nchampion\nchange\nchaos\nchapter\ncharge\nchase\nchat\ncheap\ncheck\ncheese\nchef\ncherry\nchest\nchicken\nchief\nchild\nchimney\nchoice\nchoose\nchronic\nchuckle\nchunk\nchurn\ncigar\ncinnamon\ncircle\ncitizen\ncity\ncivil\nclaim\nclap\nclarify\nclaw\nclay\nclean\nclerk\nclever\nclick\nclient\ncliff\nclimb\nclinic\nclip\nclock\nclog\nclose\ncloth\ncloud\nclown\nclub\nclump\ncluster\nclutch\ncoach\ncoast\ncoconut\ncode\ncoffee\ncoil\ncoin\ncollect\ncolor\ncolumn\ncombine\ncome\ncomfort\ncomic\ncommon\ncompany\nconcert\nconduct\nconfirm\ncongress\nconnect\nconsider\ncontrol\nconvince\ncook\ncool\ncopper\ncopy\ncoral\ncore\ncorn\ncorrect\ncost\ncotton\ncouch\ncountry\ncouple\ncourse\ncousin\ncover\ncoyote\ncrack\ncradle\ncraft\ncram\ncrane\ncrash\ncrater\ncrawl\ncrazy\ncream\ncredit\ncreek\ncrew\ncricket\ncrime\ncrisp\ncritic\ncrop\ncross\ncrouch\ncrowd\ncrucial\ncruel\ncruise\ncrumble\ncrunch\ncrush\ncry\ncrystal\ncube\nculture\ncup\ncupboard\ncurious\ncurrent\ncurtain\ncurve\ncushion\ncustom\ncute\ncycle\ndad\ndamage\ndamp\ndance\ndanger\ndaring\ndash\ndaughter\ndawn\nday\ndeal\ndebate\ndebris\ndecade\ndecember\ndecide\ndecline\ndecorate\ndecrease\ndeer\ndefense\ndefine\ndefy\ndegree\ndelay\ndeliver\ndemand\ndemise\ndenial\ndentist\ndeny\ndepart\ndepend\ndeposit\ndepth\ndeputy\nderive\ndescribe\ndesert\ndesign\ndesk\ndespair\ndestroy\ndetail\ndetect\ndevelop\ndevice\ndevote\ndiagram\ndial\ndiamond\ndiary\ndice\ndiesel\ndiet\ndiffer\ndigital\ndignity\ndilemma\ndinner\ndinosaur\ndirect\ndirt\ndisagree\ndiscover\ndisease\ndish\ndismiss\ndisorder\ndisplay\ndistance\ndivert\ndivide\ndivorce\ndizzy\ndoctor\ndocument\ndog\ndoll\ndolphin\ndomain\ndonate\ndonkey\ndonor\ndoor\ndose\ndouble\ndove\ndraft\ndragon\ndrama\ndrastic\ndraw\ndream\ndress\ndrift\ndrill\ndrink\ndrip\ndrive\ndrop\ndrum\ndry\nduck\ndumb\ndune\nduring\ndust\ndutch\nduty\ndwarf\ndynamic\neager\neagle\nearly\nearn\nearth\neasily\neast\neasy\necho\necology\neconomy\nedge\nedit\neducate\neffort\negg\neight\neither\nelbow\nelder\nelectric\nelegant\nelement\nelephant\nelevator\nelite\nelse\nembark\nembody\nembrace\nemerge\nemotion\nemploy\nempower\nempty\nenable\nenact\nend\nendless\nendorse\nenemy\nenergy\nenforce\nengage\nengine\nenhance\nenjoy\nenlist\nenough\nenrich\nenroll\nensure\nenter\nentire\nentry\nenvelope\nepisode\nequal\nequip\nera\nerase\nerode\nerosion\nerror\nerupt\nescape\nessay\nessence\nestate\neternal\nethics\nevidence\nevil\nevoke\nevolve\nexact\nexample\nexcess\nexchange\nexcite\nexclude\nexcuse\nexecute\nexercise\nexhaust\nexhibit\nexile\nexist\nexit\nexotic\nexpand\nexpect\nexpire\nexplain\nexpose\nexpress\nextend\nextra\neye\neyebrow\nfabric\nface\nfaculty\nfade\nfaint\nfaith\nfall\nfalse\nfame\nfamily\nfamous\nfan\nfancy\nfantasy\nfarm\nfashion\nfat\nfatal\nfather\nfatigue\nfault\nfavorite\nfeature\nfebruary\nfederal\nfee\nfeed\nfeel\nfemale\nfence\nfestival\nfetch\nfever\nfew\nfiber\nfiction\nfield\nfigure\nfile\nfilm\nfilter\nfinal\nfind\nfine\nfinger\nfinish\nfire\nfirm\nfirst\nfiscal\nfish\nfit\nfitness\nfix\nflag\nflame\nflash\nflat\nflavor\nflee\nflight\nflip\nfloat\nflock\nfloor\nflower\nfluid\nflush\nfly\nfoam\nfocus\nfog\nfoil\nfold\nfollow\nfood\nfoot\nforce\nforest\nforget\nfork\nfortune\nforum\nforward\nfossil\nfoster\nfound\nfox\nfragile\nframe\nfrequent\nfresh\nfriend\nfringe\nfrog\nfront\nfrost\nfrown\nfrozen\nfruit\nfuel\nfun\nfunny\nfurnace\nfury\nfuture\ngadget\ngain\ngalaxy\ngallery\ngame\ngap\ngarage\ngarbage\ngarden\ngarlic\ngarment\ngas\ngasp\ngate\ngather\ngauge\ngaze\ngeneral\ngenius\ngenre\ngentle\ngenuine\ngesture\nghost\ngiant\ngift\ngiggle\nginger\ngiraffe\ngirl\ngive\nglad\nglance\nglare\nglass\nglide\nglimpse\nglobe\ngloom\nglory\nglove\nglow\nglue\ngoat\ngoddess\ngold\ngood\ngoose\ngorilla\ngospel\ngossip\ngovern\ngown\ngrab\ngrace\ngrain\ngrant\ngrape\ngrass\ngravity\ngreat\ngreen\ngrid\ngrief\ngrit\ngrocery\ngroup\ngrow\ngrunt\nguard\nguess\nguide\nguilt\nguitar\ngun\ngym\nhabit\nhair\nhalf\nhammer\nhamster\nhand\nhappy\nharbor\nhard\nharsh\nharvest\nhat\nhave\nhawk\nhazard\nhead\nhealth\nheart\nheavy\nhedgehog\nheight\nhello\nhelmet\nhelp\nhen\nhero\nhidden\nhigh\nhill\nhint\nhip\nhire\nhistory\nhobby\nhockey\nhold\nhole\nholiday\nhollow\nhome\nhoney\nhood\nhope\nhorn\nhorror\nhorse\nhospital\nhost\nhotel\nhour\nhover\nhub\nhuge\nhuman\nhumble\nhumor\nhundred\nhungry\nhunt\nhurdle\nhurry\nhurt\nhusband\nhybrid\nice\nicon\nidea\nidentify\nidle\nignore\nill\nillegal\nillness\nimage\nimitate\nimmense\nimmune\nimpact\nimpose\nimprove\nimpulse\ninch\ninclude\nincome\nincrease\nindex\nindicate\nindoor\nindustry\ninfant\ninflict\ninform\ninhale\ninherit\ninitial\ninject\ninjury\ninmate\ninner\ninnocent\ninput\ninquiry\ninsane\ninsect\ninside\ninspire\ninstall\nintact\ninterest\ninto\ninvest\ninvite\ninvolve\niron\nisland\nisolate\nissue\nitem\nivory\njacket\njaguar\njar\njazz\njealous\njeans\njelly\njewel\njob\njoin\njoke\njourney\njoy\njudge\njuice\njump\njungle\njunior\njunk\njust\nkangaroo\nkeen\nkeep\nketchup\nkey\nkick\nkid\nkidney\nkind\nkingdom\nkiss\nkit\nkitchen\nkite\nkitten\nkiwi\nknee\nknife\nknock\nknow\nlab\nlabel\nlabor\nladder\nlady\nlake\nlamp\nlanguage\nlaptop\nlarge\nlater\nlatin\nlaugh\nlaundry\nlava\nlaw\nlawn\nlawsuit\nlayer\nlazy\nleader\nleaf\nlearn\nleave\nlecture\nleft\nleg\nlegal\nlegend\nleisure\nlemon\nlend\nlength\nlens\nleopard\nlesson\nletter\nlevel\nliar\nliberty\nlibrary\nlicense\nlife\nlift\nlight\nlike\nlimb\nlimit\nlink\nlion\nliquid\nlist\nlittle\nlive\nlizard\nload\nloan\nlobster\nlocal\nlock\nlogic\nlonely\nlong\nloop\nlottery\nloud\nlounge\nlove\nloyal\nlucky\nluggage\nlumber\nlunar\nlunch\nluxury\nlyrics\nmachine\nmad\nmagic\nmagnet\nmaid\nmail\nmain\nmajor\nmake\nmammal\nman\nmanage\nmandate\nmango\nmansion\nmanual\nmaple\nmarble\nmarch\nmargin\nmarine\nmarket\nmarriage\nmask\nmass\nmaster\nmatch\nmaterial\nmath\nmatrix\nmatter\nmaximum\nmaze\nmeadow\nmean\nmeasure\nmeat\nmechanic\nmedal\nmedia\nmelody\nmelt\nmember\nmemory\nmention\nmenu\nmercy\nmerge\nmerit\nmerry\nmesh\nmessage\nmetal\nmethod\nmiddle\nmidnight\nmilk\nmillion\nmimic\nmind\nminimum\nminor\nminute\nmiracle\nmirror\nmisery\nmiss\nmistake\nmix\nmixed\nmixture\nmobile\nmodel\nmodify\nmom\nmoment\nmonitor\nmonkey\nmonster\nmonth\nmoon\nmoral\nmore\nmorning\nmosquito\nmother\nmotion\nmotor\nmountain\nmouse\nmove\nmovie\nmuch\nmuffin\nmule\nmultiply\nmuscle\nmuseum\nmushroom\nmusic\nmust\nmutual\nmyself\nmystery\nmyth\nnaive\nname\nnapkin\nnarrow\nnasty\nnation\nnature\nnear\nneck\nneed\nnegative\nneglect\nneither\nnephew\nnerve\nnest\nnet\nnetwork\nneutral\nnever\nnews\nnext\nnice\nnight\nnoble\nnoise\nnominee\nnoodle\nnormal\nnorth\nnose\nnotable\nnote\nnothing\nnotice\nnovel\nnow\nnuclear\nnumber\nnurse\nnut\noak\nobey\nobject\noblige\nobscure\nobserve\nobtain\nobvious\noccur\nocean\noctober\nodor\noff\noffer\noffice\noften\noil\nokay\nold\nolive\nolympic\nomit\nonce\none\nonion\nonline\nonly\nopen\nopera\nopinion\noppose\noption\norange\norbit\norchard\norder\nordinary\norgan\norient\noriginal\norphan\nostrich\nother\noutdoor\nouter\noutput\noutside\noval\noven\nover\nown\nowner\noxygen\noyster\nozone\npact\npaddle\npage\npair\npalace\npalm\npanda\npanel\npanic\npanther\npaper\nparade\nparent\npark\nparrot\nparty\npass\npatch\npath\npatient\npatrol\npattern\npause\npave\npayment\npeace\npeanut\npear\npeasant\npelican\npen\npenalty\npencil\npeople\npepper\nperfect\npermit\nperson\npet\nphone\nphoto\nphrase\nphysical\npiano\npicnic\npicture\npiece\npig\npigeon\npill\npilot\npink\npioneer\npipe\npistol\npitch\npizza\nplace\nplanet\nplastic\nplate\nplay\nplease\npledge\npluck\nplug\nplunge\npoem\npoet\npoint\npolar\npole\npolice\npond\npony\npool\npopular\nportion\nposition\npossible\npost\npotato\npottery\npoverty\npowder\npower\npractice\npraise\npredict\nprefer\nprepare\npresent\npretty\nprevent\nprice\npride\nprimary\nprint\npriority\nprison\nprivate\nprize\nproblem\nprocess\nproduce\nprofit\nprogram\nproject\npromote\nproof\nproperty\nprosper\nprotect\nproud\nprovide\npublic\npudding\npull\npulp\npulse\npumpkin\npunch\npupil\npuppy\npurchase\npurity\npurpose\npurse\npush\nput\npuzzle\npyramid\nquality\nquantum\nquarter\nquestion\nquick\nquit\nquiz\nquote\nrabbit\nraccoon\nrace\nrack\nradar\nradio\nrail\nrain\nraise\nrally\nramp\nranch\nrandom\nrange\nrapid\nrare\nrate\nrather\nraven\nraw\nrazor\nready\nreal\nreason\nrebel\nrebuild\nrecall\nreceive\nrecipe\nrecord\nrecycle\nreduce\nreflect\nreform\nrefuse\nregion\nregret\nregular\nreject\nrelax\nrelease\nrelief\nrely\nremain\nremember\nremind\nremove\nrender\nrenew\nrent\nreopen\nrepair\nrepeat\nreplace\nreport\nrequire\nrescue\nresemble\nresist\nresource\nresponse\nresult\nretire\nretreat\nreturn\nreunion\nreveal\nreview\nreward\nrhythm\nrib\nribbon\nrice\nrich\nride\nridge\nrifle\nright\nrigid\nring\nriot\nripple\nrisk\nritual\nrival\nriver\nroad\nroast\nrobot\nrobust\nrocket\nromance\nroof\nrookie\nroom\nrose\nrotate\nrough\nround\nroute\nroyal\nrubber\nrude\nrug\nrule\nrun\nrunway\nrural\nsad\nsaddle\nsadness\nsafe\nsail\nsalad\nsalmon\nsalon\nsalt\nsalute\nsame\nsample\nsand\nsatisfy\nsatoshi\nsauce\nsausage\nsave\nsay\nscale\nscan\nscare\nscatter\nscene\nscheme\nschool\nscience\nscissors\nscorpion\nscout\nscrap\nscreen\nscript\nscrub\nsea\nsearch\nseason\nseat\nsecond\nsecret\nsection\nsecurity\nseed\nseek\nsegment\nselect\nsell\nseminar\nsenior\nsense\nsentence\nseries\nservice\nsession\nsettle\nsetup\nseven\nshadow\nshaft\nshallow\nshare\nshed\nshell\nsheriff\nshield\nshift\nshine\nship\nshiver\nshock\nshoe\nshoot\nshop\nshort\nshoulder\nshove\nshrimp\nshrug\nshuffle\nshy\nsibling\nsick\nside\nsiege\nsight\nsign\nsilent\nsilk\nsilly\nsilver\nsimilar\nsimple\nsince\nsing\nsiren\nsister\nsituate\nsix\nsize\nskate\nsketch\nski\nskill\nskin\nskirt\nskull\nslab\nslam\nsleep\nslender\nslice\nslide\nslight\nslim\nslogan\nslot\nslow\nslush\nsmall\nsmart\nsmile\nsmoke\nsmooth\nsnack\nsnake\nsnap\nsniff\nsnow\nsoap\nsoccer\nsocial\nsock\nsoda\nsoft\nsolar\nsoldier\nsolid\nsolution\nsolve\nsomeone\nsong\nsoon\nsorry\nsort\nsoul\nsound\nsoup\nsource\nsouth\nspace\nspare\nspatial\nspawn\nspeak\nspecial\nspeed\nspell\nspend\nsphere\nspice\nspider\nspike\nspin\nspirit\nsplit\nspoil\nsponsor\nspoon\nsport\nspot\nspray\nspread\nspring\nspy\nsquare\nsqueeze\nsquirrel\nstable\nstadium\nstaff\nstage\nstairs\nstamp\nstand\nstart\nstate\nstay\nsteak\nsteel\nstem\nstep\nstereo\nstick\nstill\nsting\nstock\nstomach\nstone\nstool\nstory\nstove\nstrategy\nstreet\nstrike\nstrong\nstruggle\nstudent\nstuff\nstumble\nstyle\nsubject\nsubmit\nsubway\nsuccess\nsuch\nsudden\nsuffer\nsugar\nsuggest\nsuit\nsummer\nsun\nsunny\nsunset\nsuper\nsupply\nsupreme\nsure\nsurface\nsurge\nsurprise\nsurround\nsurvey\nsuspect\nsustain\nswallow\nswamp\nswap\nswarm\nswear\nsweet\nswift\nswim\nswing\nswitch\nsword\nsymbol\nsymptom\nsyrup\nsystem\ntable\ntackle\ntag\ntail\ntalent\ntalk\ntank\ntape\ntarget\ntask\ntaste\ntattoo\ntaxi\nteach\nteam\ntell\nten\ntenant\ntennis\ntent\nterm\ntest\ntext\nthank\nthat\ntheme\nthen\ntheory\nthere\nthey\nthing\nthis\nthought\nthree\nthrive\nthrow\nthumb\nthunder\nticket\ntide\ntiger\ntilt\ntimber\ntime\ntiny\ntip\ntired\ntissue\ntitle\ntoast\ntobacco\ntoday\ntoddler\ntoe\ntogether\ntoilet\ntoken\ntomato\ntomorrow\ntone\ntongue\ntonight\ntool\ntooth\ntop\ntopic\ntopple\ntorch\ntornado\ntortoise\ntoss\ntotal\ntourist\ntoward\ntower\ntown\ntoy\ntrack\ntrade\ntraffic\ntragic\ntrain\ntransfer\ntrap\ntrash\ntravel\ntray\ntreat\ntree\ntrend\ntrial\ntribe\ntrick\ntrigger\ntrim\ntrip\ntrophy\ntrouble\ntruck\ntrue\ntruly\ntrumpet\ntrust\ntruth\ntry\ntube\ntuition\ntumble\ntuna\ntunnel\nturkey\nturn\nturtle\ntwelve\ntwenty\ntwice\ntwin\ntwist\ntwo\ntype\ntypical\nugly\numbrella\nunable\nunaware\nuncle\nuncover\nunder\nundo\nunfair\nunfold\nunhappy\nuniform\nunique\nunit\nuniverse\nunknown\nunlock\nuntil\nunusual\nunveil\nupdate\nupgrade\nuphold\nupon\nupper\nupset\nurban\nurge\nusage\nuse\nused\nuseful\nuseless\nusual\nutility\nvacant\nvacuum\nvague\nvalid\nvalley\nvalve\nvan\nvanish\nvapor\nvarious\nvast\nvault\nvehicle\nvelvet\nvendor\nventure\nvenue\nverb\nverify\nversion\nvery\nvessel\nveteran\nviable\nvibrant\nvicious\nvictory\nvideo\nview\nvillage\nvintage\nviolin\nvirtual\nvirus\nvisa\nvisit\nvisual\nvital\nvivid\nvocal\nvoice\nvoid\nvolcano\nvolume\nvote\nvoyage\nwage\nwagon\nwait\nwalk\nwall\nwalnut\nwant\nwarfare\nwarm\nwarrior\nwash\nwasp\nwaste\nwater\nwave\nway\nwealth\nweapon\nwear\nweasel\nweather\nweb\nwedding\nweekend\nweird\nwelcome\nwest\nwet\nwhale\nwhat\nwheat\nwheel\nwhen\nwhere\nwhip\nwhisper\nwide\nwidth\nwife\nwild\nwill\nwin\nwindow\nwine\nwing\nwink\nwinner\nwinter\nwire\nwisdom\nwise\nwish\nwitness\nwolf\nwoman\nwonder\nwood\nwool\nword\nwork\nworld\nworry\nworth\nwrap\nwreck\nwrestle\nwrist\nwrite\nwrong\nyard\nyear\nyellow\nyou\nyoung\nyouth\nzebra\nzero\nzone\nzoo""".splitlines()
BAUD_MAP = {
1200: termios.B1200, 2400: termios.B2400, 4800: termios.B4800,
9600: termios.B9600, 19200: termios.B19200, 38400: termios.B38400,
57600: termios.B57600, 115200: termios.B115200,
}
@dataclass
class Health:
events: int
retained_bits: int
discarded_ties: int
ones_fraction: float
min_interval_ns: int
median_interval_ns: int
max_interval_ns: int
max_identical_interval_run: int
passed: bool
reasons: list[str]
def stdin_events() -> Iterator[int]:
print("Waiting for one non-empty line per detector event...", file=sys.stderr)
for line in sys.stdin:
if line.strip():
yield time.monotonic_ns()
def serial_events(device: str, baud: int) -> Iterator[int]:
if baud not in BAUD_MAP:
raise SystemExit(f"unsupported baud {baud}; choose: {', '.join(map(str, BAUD_MAP))}")
fd = os.open(device, os.O_RDONLY | os.O_NOCTTY | os.O_NONBLOCK)
old = termios.tcgetattr(fd)
try:
attrs = termios.tcgetattr(fd)
attrs[0] = 0 # input flags
attrs[1] = 0 # output flags
attrs[2] = termios.CS8 | termios.CREAD | termios.CLOCAL
attrs[3] = 0 # local flags: raw mode
attrs[4] = BAUD_MAP[baud]
attrs[5] = BAUD_MAP[baud]
attrs[6][termios.VMIN] = 0
attrs[6][termios.VTIME] = 0
termios.tcsetattr(fd, termios.TCSANOW, attrs)
termios.tcflush(fd, termios.TCIFLUSH)
print(f"Reading detector events from {device} at {baud} baud...", file=sys.stderr)
while True:
ready, _, _ = select.select([fd], [], [], 1.0)
if not ready:
continue
data = os.read(fd, 4096)
if data:
yield time.monotonic_ns()
# Drain bytes already queued so one detector message/burst counts once.
time.sleep(0.002)
while select.select([fd], [], [], 0)[0]:
if not os.read(fd, 4096):
break
finally:
termios.tcsetattr(fd, termios.TCSANOW, old)
os.close(fd)
def intervals_and_bits(timestamps: list[int]) -> tuple[list[int], list[int], int]:
intervals = [b - a for a, b in zip(timestamps, timestamps[1:])]
bits: list[int] = []
ties = 0
for i in range(0, len(intervals) - 1, 2):
a, b = intervals[i], intervals[i + 1]
if a < b: bits.append(0)
elif a > b: bits.append(1)
else: ties += 1
return intervals, bits, ties
def longest_identical_run(values: list[int]) -> int:
if not values: return 0
best = run = 1
for a, b in zip(values, values[1:]):
run = run + 1 if a == b else 1
best = max(best, run)
return best
def check_health(timestamps: list[int], bits: list[int], ties: int) -> Health:
intervals = [b - a for a, b in zip(timestamps, timestamps[1:])]
reasons: list[str] = []
if len(timestamps) < 3: reasons.append("too few events")
if any(x <= 0 for x in intervals): reasons.append("non-monotonic timestamps")
identical = longest_identical_run(intervals)
if identical >= 8: reasons.append("8+ identical consecutive intervals")
if intervals and min(intervals) < 100_000: reasons.append("sub-0.1ms interval; bounce/noise suspected")
fraction = sum(bits) / len(bits) if bits else 0.0
if len(bits) >= 256 and not 0.40 <= fraction <= 0.60:
reasons.append(f"strong bit imbalance: ones={fraction:.4f}")
# Repetition-count alarm on extracted comparison bits.
if longest_identical_run(bits) >= 32:
reasons.append("32+ identical comparison bits")
return Health(len(timestamps), len(bits), ties, fraction,
min(intervals) if intervals else 0,
int(statistics.median(intervals)) if intervals else 0,
max(intervals) if intervals else 0,
identical, not reasons, reasons)
def pack_bits(bits: list[int]) -> bytes:
out = bytearray((len(bits) + 7) // 8)
for i, bit in enumerate(bits):
out[i // 8] |= (bit & 1) << (7 - i % 8)
return bytes(out)
def transcript(timestamps: list[int]) -> bytes:
origin = timestamps[0]
return b"".join((t - origin).to_bytes(8, "big") for t in timestamps)
def entropy_to_mnemonic(entropy: bytes) -> list[str]:
if len(entropy) not in (16, 20, 24, 28, 32):
raise ValueError("BIP-39 entropy must be 128, 160, 192, 224, or 256 bits")
checksum_length = len(entropy) * 8 // 32
entropy_bits = ''.join(f'{b:08b}' for b in entropy)
checksum_bits = ''.join(f'{b:08b}' for b in hashlib.sha256(entropy).digest())[:checksum_length]
joined = entropy_bits + checksum_bits
return [WORDLIST[int(joined[i:i+11], 2)] for i in range(0, len(joined), 11)]
def validate_mnemonic(words: list[str]) -> bool:
try: indexes = [WORDLIST.index(w) for w in words]
except ValueError: return False
bits = ''.join(f'{i:011b}' for i in indexes)
entropy_length = len(bits) * 32 // 33
checksum_length = len(bits) - entropy_length
entropy = int(bits[:entropy_length], 2).to_bytes(entropy_length // 8, 'big')
expected = ''.join(f'{b:08b}' for b in hashlib.sha256(entropy).digest())[:checksum_length]
return bits[entropy_length:] == expected
def collect(source: Iterator[int], target: int, timeout: float | None) -> tuple[list[int], list[int], int]:
timestamps: list[int] = []
started = last = time.monotonic()
while True:
if timeout is not None and time.monotonic() - started > timeout:
raise SystemExit(f"timeout before obtaining {target} retained bits")
try: timestamps.append(next(source))
except StopIteration: raise SystemExit("event source ended early")
_, bits, ties = intervals_and_bits(timestamps)
now = time.monotonic()
if now - last >= 10:
print(f"events={len(timestamps)} retained={len(bits)}/{target} ties={ties}", file=sys.stderr)
last = now
if len(bits) >= target: return timestamps, bits[:target], ties
def self_test() -> None:
assert len(WORDLIST) == 2048 and len(set(WORDLIST)) == 2048
# Official BIP-39 vector: 128 zero entropy bits.
expected = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
got = ' '.join(entropy_to_mnemonic(bytes(16)))
assert got == expected, (got, expected)
assert validate_mnemonic(got.split())
assert len(entropy_to_mnemonic(bytes(32))) == 24
print("Self-test passed: wordlist, checksum, and known vector.")
def main() -> int:
p = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
description="Radiation pulse timing -> hybrid SHAKE-256 -> 24-word BIP-39 mnemonic")
p.add_argument('--source', choices=('stdin','serial'), default='stdin')
p.add_argument('--device', help='serial device such as /dev/ttyUSB0')
p.add_argument('--baud', type=int, default=9600)
p.add_argument('--collect-bits', type=int, default=512,
help='retained physical comparison bits before conditioning (default 512)')
p.add_argument('--physical-only', action='store_true',
help='disable os.urandom safety mix; research use only')
p.add_argument('--timeout', type=float)
p.add_argument('--self-test', action='store_true')
p.add_argument('--show-entropy', action='store_true',
help='also print 32-byte entropy hex; normally keep hidden')
args = p.parse_args()
if args.self_test:
self_test(); return 0
if args.collect_bits < 256:
p.error('--collect-bits must be at least 256')
if args.source == 'serial' and not args.device:
p.error('--device required with --source serial')
source = serial_events(args.device, args.baud) if args.source == 'serial' else stdin_events()
timestamps, bits, ties = collect(source, args.collect_bits, args.timeout)
health = check_health(timestamps, bits, ties)
if not health.passed:
print("HEALTH CHECK FAILED:", file=sys.stderr)
for reason in health.reasons: print(f" - {reason}", file=sys.stderr)
raise SystemExit(2)
physical = pack_bits(bits)
transcript_hash = hashlib.sha3_256(transcript(timestamps)).digest()
os_mix = b'' if args.physical_only else os.urandom(32)
domain = b'MRNG-BIP39-v2\x00'
entropy = hashlib.shake_256(domain + physical + transcript_hash + os_mix).digest(32)
words = entropy_to_mnemonic(entropy)
if not validate_mnemonic(words):
raise SystemExit('internal BIP-39 checksum validation failed')
mode = 'PHYSICAL-ONLY (EXPERIMENTAL)' if args.physical_only else 'HYBRID: detector + OS CSPRNG (RECOMMENDED)'
print("\n" + "=" * 72)
print("MRNG RESULT — 24-WORD BIP-39 MNEMONIC")
print("=" * 72)
print(f"Mode: {mode}")
print(f"Detector events: {health.events}")
print(f"Retained comparison bits: {health.retained_bits}")
print(f"Discarded ties: {health.discarded_ties}")
print(f"Ones fraction: {health.ones_fraction:.4f}")
print("BIP-39 checksum: VALID")
if args.show_entropy: print(f"Entropy hex: {entropy.hex()}")
print("\nWRITE THESE WORDS DOWN OFFLINE, IN ORDER:\n")
for row in range(0, 24, 4):
print(" ".join(f"{i+1:2d}. {words[i]:<10}" for i in range(row, row+4)))
print("\nNo mnemonic or entropy file was written.")
print("WARNING: Python cannot guarantee RAM erasure. Power down after verification.")
print("=" * 72)
return 0
if __name__ == '__main__':
raise SystemExit(main())
@Frankenmint

Copy link
Copy Markdown
Author

SHA-256
3e2b7d4712e6033da6298eb06468d5f462a140a453b79a7a60c901c821944c1d

@Frankenmint

Copy link
Copy Markdown
Author
Screenshot from 2026-08-05 15-01-29

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment