Created
August 5, 2026 21:54
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment

SHA-256
3e2b7d4712e6033da6298eb06468d5f462a140a453b79a7a60c901c821944c1d