apogio (OP)
|
|
March 03, 2024, 02:26:19 PM Last edit: October 18, 2024, 06:26:34 PM by apogio |
|
As the title suggests, I developed an easy to use script that generates a BIP39 mnemonic. I implemented it for fun. I don't plan to use it for real money. The script:# contact: apogio@proton.me from secrets import token_hex from hashlib import sha256
# read bip39 wordlist from file and import in a list bip39_wordlist_file = open("bip39_wordlist.txt", "r") bip39_wordlist = bip39_wordlist_file.read().split('\n')
# entropy entropy = bin(int(token_hex(16), 16))[2:].zfill(128) print('---------') print('ENTROPY: ') print('---------') print(entropy)
# calculate SHA256 sha256_hex = sha256(bytes.fromhex(hex(int(entropy,2))[2:].zfill(32))).hexdigest() sha256_bin = bin(int(sha256_hex, 16))[2:].zfill(256)
# calculate checksum checksum = sha256_bin[0:4]
# final seed to be converted into BIP39 mnemonic final = entropy + checksum
num_of_words = 12 word_length = len(final) // num_of_words
# calculate mnemonic res = [] for idx in range(0, len(final), word_length): res.append(final[idx : idx + word_length])
mnemonic = [] for idx in range(0, num_of_words): binary_place = res[idx] decimal_place = int(binary_place,2) mnemonic.append(bip39_wordlist[decimal_place])
print('\n-------------') print('BIP39 PHRASE: ') print('-------------') for w in range(0, len(mnemonic)): print(str(w+1) + ': ' + mnemonic[w])
How to run:1. Create a file on your machine (example mnemonic_gen.py). 2. Copy - paste the code from above. 3. Create a file on your machine, called bip39_wordlist.txt and copy-paste the wordlist into the file. 4. Make sure to have both files in the same directory. 5. Just run python mnemonic_gen.pySample output:--------- ENTROPY: --------- 11101110101000001011111101111000111100001001001100010000100001110011110100010011010100011000011001100100011111100100010111011100
------------- BIP39 PHRASE: ------------- 1: upgrade 2: album 3: taste 4: thrive 5: country 6: drum 7: violin 8: health 9: major 10: catalog 11: multiply 12: ride
Extra notes:1. The script uses secrets module to generate entropy. It is essentially a CSPRNG and is the recommended approach to generate pseudo-random numbers in Python. Internally, it makes use of os.urandom as well. 2. For best security, use it offline, by just running the script on an airgapped device. 3. This is not a complete wallet. You must import the seed phrase on an offline wallet that you like, in order to convert the BIP39 phrase into a seed and produce the corresponding xpriv and xpub. 4. This method is only recommended if you don't trust the entropy source of your device and you want to use CSPRNG on an airgapped computer though python libraries. 5. It is similar to Ian Coleman's BIP39 implementation, in a sense that they both must be executed offline. The difference lies in the libraries that are used, as Ian's implementation uses javascript, whereas the script above uses python libraries.
|
█████████████████████████ ████████▀▀████▀▀█▀▀██████ █████▀████▄▄▄▄██████▀████ ███▀███▄████████▄████▀███ ██▀███████████████████▀██ █████████████████████████ █████████████████████████ █████████████████████████ ██▄███████████████▀▀▄▄███ ███▄███▀████████▀███▄████ █████▄████▀▀▀▀████▄██████ ████████▄▄████▄▄█████████ █████████████████████████ | BitList | | █▀▀▀▀ █ █ █ █ █ █ █ █ █ █ █ █▄▄▄▄ | ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ . REAL-TIME DATA TRACKING CURATED BY THE COMMUNITY . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ | ▀▀▀▀█ █ █ █ █ █ █ █ █ █ █ █ ▄▄▄▄█ | | List #kycfree Websites |
|
|
|
ranochigo
Legendary
Offline
Activity: 3038
Merit: 4420
Crypto Swap Exchange
|
|
March 04, 2024, 02:19:34 AM Last edit: March 07, 2024, 02:19:32 AM by ranochigo Merited by vapourminer (1), ABCbits (1), apogio (1) |
|
It would be a small improvement to remove zfill, since it doesn't do much other than padding with zeros for which it isn't needed when there is a SHA256 hashing after. That probably should be removed eitherways, if the entropy is lower than required, then they should be able to run the entropy generation again. In a similar vein, a sanity check on the entropy before the SHA256 hashing would improve the security as well.
Edit: I stand corrected on the point about regeneration of entropy; should not be strictly enforced at 128.
|
|
|
|
apogio (OP)
|
|
March 04, 2024, 05:02:23 PM |
|
It would be a small improvement to remove zfill, since it doesn't do much other than padding with zeros for which it isn't needed when there is a SHA256 hashing after. That probably should be removed eitherways, if the entropy is lower than required, then they should be able to run the entropy generation again. In a similar vein, a sanity check on the entropy before the SHA256 hashing would improve the security as well.
Hello, I just updated the OP with this: entropy = bin(int(token_hex(16), 16))[2:] while len(entropy) != 128: entropy = bin(int(token_hex(16), 16))[2:]
so now, if the entropy is less than 128, it repeats the process until it is 128 bits long. Thanks for the feedback.
|
█████████████████████████ ████████▀▀████▀▀█▀▀██████ █████▀████▄▄▄▄██████▀████ ███▀███▄████████▄████▀███ ██▀███████████████████▀██ █████████████████████████ █████████████████████████ █████████████████████████ ██▄███████████████▀▀▄▄███ ███▄███▀████████▀███▄████ █████▄████▀▀▀▀████▄██████ ████████▄▄████▄▄█████████ █████████████████████████ | BitList | | █▀▀▀▀ █ █ █ █ █ █ █ █ █ █ █ █▄▄▄▄ | ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ . REAL-TIME DATA TRACKING CURATED BY THE COMMUNITY . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ | ▀▀▀▀█ █ █ █ █ █ █ █ █ █ █ █ ▄▄▄▄█ | | List #kycfree Websites |
|
|
|
PowerGlove
|
|
March 06, 2024, 12:23:19 PM |
|
Hello, I just updated the OP with this: entropy = bin(int(token_hex(16), 16))[2:] while len(entropy) != 128: entropy = bin(int(token_hex(16), 16))[2:]
so now, if the entropy is less than 128, it repeats the process until it is 128 bits long. You've introduced a small bias by doing that... This is a pattern that I see pretty often: an attempt to make things "more secure" that ends up backfiring. Think of it like this: by predicating that loop on the entropy being exactly 128 bits long, you've removed the possibility of your program ever generating a 128-bit value with the high-order bit set to 0. That means, in hexadecimal terms, your entropy will always start with an 8, 9, A, B, C, D, E, or F (0 through 7 are no longer possible). In BIP39 terms, the first word of your mnemonic will always start with one of the letters L through Z (no matter how many times you run your script, you'll never generate a mnemonic that starts with an A-word, for example). I think that what you were doing before with the zfill was better. (But, if you change it back, you should be aware that that will reveal a different bug that's hiding in your code: bytes.fromhex always expects a string with an even number of nibbles. That is, bytes.fromhex('abcd') will return b'\xab\xcd', but bytes.fromhex('abc') won't return b'\x0a\xbc', as you might expect, instead, it will raise a ValueError. So, there's a ~6% chance that your script will fail on any given invocation. Try it yourself, run the original version of your script again and again in a terminal and you'll encounter that problem eventually and get a traceback that'll point to the offending line.) Please don't feel discouraged. It's cool that you're learning Bitcoin by programming little bits and pieces of it yourself, and then sharing your code: that's a really effective way to learn in my experience, so keep it up, dude. I don't know about you, but I really appreciate seeing other people's takes on problems that I've recently solved myself, especially if they involve techniques that I may not be familiar with. So, here's my take on a 12-word BIP39 mnemonic generator in Python: #!/usr/bin/env python3
# Don't use this for anything serious; it *probably* works as intended, but I haven't tested it much (I wrote it quickly for apogio).
import base64
import lzma
import secrets
import hashlib
bip39_words: list[str] = lzma.decompress(base64.b85decode('{Wp48S^xk9=GL@E0stWa761SMbT8$j;4?ZDBwYY7n21WR2~SMNj7l(<JR*YqdhorTGI^h5s6rBe;k9*`P;vADxW!uq6ud9G`Ryq|uIakUrKX0S{>^H;DHJAH%e&gKef~1EXSD0Us}VywB$hQSCL24wlCsKYCV<&UARZHOh)&>5DZ-~ie;w#}??6<JOl|mq9eKdcew;FDEcSIuPSsz9ifo`zRLj%vg=z0~cu*vAMA6cb5GSaXO7n>$%xTl&8@gxbgANqLq@*=qXFu|h=a2+%Xss$^84V29ubWU|Jis}ZeS^SfW}jww7nw9K&Cx%HKy;0Y37#)`+p3zY1bZ`7k+YIMR3u^uY^9ccN{kp=S?u6N4zisDC8gkIJo$^^m6wM^a$8H3bAYEsAIc966#xe{9j&hY0zxEiT${N!QqS^qX$el?jg>&rnPf$K-yrI;)DRZN%5N67_SO#n=yj@!>$4Hz)evAV5=sYEQS$z7tp@GUj@>>S;S=L=!OK{Jtif0|-p%w0w#bxt71|pkbpS}&H*pv#^=Et7p$ITGaVb29^yoJQ!zsLIcy9`T(hCW;CliZjzHlUHk5C~^7<s${pyjkGSHqDb$3%6Ql)O^B*BWTyC_6sP9t*>_nJ1EUY6*S-;1uIhueo7A2kS$(TLIuv1k&86r%i)z0*-`orY@!pD9im@rths?CuQ90QmSY+!iP@8%nQ%d2Mnv#BVLo#w-6V9c5aG@e2d9O-C!?y>GSCUs5I^54+iV}u#dj~6%nU^oJ!C{P_)~vNh$*wcdG_&J;l!^sRRdSF->(8X?IogT__SWD7&M;TnqE9)DN*23c>sJsQxXi3;5J6DDh(_H0P%C?nxktxwmY48AW19?`wuF+F(0QfgOO}Ojr7j3KfbBaY+=fiqt?}oNK#yaFde;{ARhlk@2*Yh;H++vcQgmvVuv4wKzHFO&Ru(cfJW=QuOvQu>M0i6U6^04<N3c${Mzk9WOh3^h|Bx=-7U{ac`Tojgk$wjm&2*;dbo8XCFVx7vftB*T#;^eJ6Rv>JXbcxiR-dL7D_vpPfb6ZAR`2uHBG)agXV6VC2h%>;B+vg*v^Xsw3(RIEe8ls@DQbyP-deH3-Hb)O_0nXteO2F_5u@cpMV^*D4qiuN<at4zRaHb_%+w4o!3z1x|G(S0Q4-8w6cmtHv6}zRL00x0v3*+4}~6x$RUNbD{rKGJe~n>SK-`U!)6zJN$A6{Hhkj+D@VTMcYtaNfsu>Wn{j~^6xu22NN$^PmEmEl*lr<Iq~?ayQ?Mq+_SAf1u3uKb)!FG*pd}Y`;RydQ_jhBV3IJyYbiO@nL6xCOD-B1(}2VUEWj<scNhas{F%K!5FcMIvQCKN3TK-vuB#u@5a)O*CA;1@r33JxK6ZcOff;l0Rb)DDIUIdWU2Lrg_nT~!OmX{$$FBD%4V7TQ-RCP1rhG&giyB}eTE*eNPEK0{F<j^TrG8NOn8ylmgdaL)Ull(-8%}RdS!ufXK%-KAb8qY!&DKNMg|}L7tSo2p<DN>~k&2zu1nq>@!y1*ypM67Y`iGod_AKq<bEWq&l@ZliOe+DODTaDj3TR|GzSq{ygZN5;^38}PCZxI0%CH=5UB@k%2Mgh6zQ_-s+Kb_kVRfhG^$Y0oOW2zmlc2Ay=$M;wjEA_a!!f;^Si^ZbTV#OV)if)Fr5(zM@iXYVO_6G(ooKRa&RFfYbR~k(4Pj|mmG17{dcNunx0U|P=4}B;hEo8nj=OBMER5&p=;yBKPt~g@_oib!r>_r-*jlOyBOA{KNrg}5Ac0RMtO%k2jizS0XF!`YtPkPQ`ImjU%rS#KF}#sq+r|(L72zb1TC2;dOS~ikeihcHtjY!p{N`Z*Q+Cx&oG&bsXVozbAQj&R&`HmZr4)YnH?nY^m}If@mU2OBIw{C4^z1meuyEu;2#(@;2Osppn@|UV6U2xJvDC)cQ<bcDUd=$-N=fQT+iEI(H+Q?DeL|cvnMVG}N$??c0$9l~YqMpU!4~#FrCo0^q9I#GD;e^^M&&}sesOl$cqh^&2$>`(jFILY>=0`+;VLBTS|!3}0QE&E(EzgOrO8NIJOzT>^p}ISTqQSGa0npbKH^kf2A+%8xf8FWGBc;!h)Vd2IuWu!v6lNZ{yVZpjQ$0$y<=)J7sK?*>Vv>n?iROPvbL3X*R&VFHz8cX8#e-z!_RyxYIU<(6gQ_4b(OE?b)!x=RJf;~4bi0p=4VlSVImRd!l7a7cE+)`2rKn4pAw_LqwZx&RWLHTaB*I{-F6zLlIF;yxKK4;IvNZ6t|?^jhonkM3>Vwmf(Vk0@EB2?UeVdB;Yb|Kd;W%R3JWCWlnF(sPu^{(m8)`sb8503#A0k-zZPD`M9T*&C+y`J9JD_0->(14<!m6W^O9yW6f0a!h*#%-1<bd5h^ziFcpp3$isdCU5BaIyH%tA&lv4a|IxD^z(ppfd%c?gp?Gm_dVP{9B9W#N5rKm47h>W>=Y!~_Y8&AfDjoh)LpK6g5a&}4OZ^ryKhc3JF+hme1Be32X32Sb{U6qNKLDpmXx~u;TgGnn6m@#M;b$CnfIq7~tRFqGO>Nx0>tP_m`QW>a`i!TRAZ%h<ymlLNjB`3I{j`y3}_zXntLR;@~&3)~<SBdxVe0Vg1MkQ0{lkTd<V%5*B=I_`q1$F~Qo;N&nXC7gDm!Z10oRnT>#-uM7BMo<Ke_<Kq)X1`x)B&Tdx7}z^L;s$YvfhGzZb!JuDEVsZ{LZI)c>C{dn$iVRKwHz-B)^APT2#W+FMad$_X#-BM+bg%Rf2oYfSf2eeAFo8OyWD>GfI>IduIKFJlGhOb<V;Yf~s`G!GBuy(f*5CW8~~c@!93tes7Fv+xF^laoI;vh^GK8{~-#$l}0k8xt46LDlynQhn%mpy?WuiuL+BT|KVa4iu(b)bcYP332V(60%B)Cf6xDm6x1cy=}#}NP4<RI&@N3E1HwLQ8&=yzW5DJ>tF)718Oq4s#zK*RIUGa7k|k$4HOG>9RiJM;@2yK=NBbEC6*Kd&2VV<iJZVy`q}J4-fIAR*IEm-W-NnbBMc#b?aqo*UzFd_rO0R?FJ!b=1U)c-%YZf(a552_x+NG~95C6Wn-z7rXYz#GCH#5xo1SKQEpVCZu?gKLp>qPUxacYwN@0mswW469o32{DJf=y*CU9V7)?7loOwYQ9_8JLIotvhv~&Wz%i0Z!05V1|QY@4L4jp;%<jo0f%IrpPHnzrf*|6Y7jJ9)F6*Of5>JOM*dix({v*!!8Z=C&nDTk&RqqACXz9fO2kwe61+}5DG1WkN7Hp0~$f1@=n~e=))pi@!)Tac?masVzC?^p0gZWN%vmU+jb}&3M!$|S+EHr+mGIU0i;)%3RPlu5L-Y@1H-swrrU`hjMq%4nK}+=J_f-N8;rWk>@YoZbR~hDP?=07HhnK7Yha4mRW0EOgl>Q5h&N?zt#<Q0@0*u}Wxt8X07JQtoelXwG`h;N8T#%3L&tG0GNEUaabE7}x+<Lgx`Z&u4!lj*=snoQ$;S(g#IGnq-t_g+aeXg(;3P=6(JW$!nYc^o*ime*%~>)JjrC*Ls77Nt7Cp(!Js+pn%*(NBy}Z)CsN&bjh^gC{rL~LauR(COgtSd>bE4IHnvS$>ovdGkG|VU^PL1*cw*dz6l9aH($Kat5LTU9BKbaVBD}e6ZT~A9kcNB{KYbj~K(7<a;8LX;x(jRm@9cLv$#Ubz)U+r%6B=(k#J4~F{f2Dp|S=!P9+VIP!E0)0ACz+VZ1ocmMTx{#U%TEl$b)$F#cXLOkdjKf96euOn^vW>{m$+D85|@#74?ms4(<invY(H$jEQ@mBDlK!7g0v_6PbZsmKeG&CZ_=OxUAWu5noVNzVGPSdTV|)SfD>7n$~>n>pTX`Y%sMm%<C%kmjvbs2e9LX<Xwf`1468A!!DT?({~n(tbR2T+BP&;2T-gLIDE6WyR!4c^kVJkqiP=y%%%!oFop}TE2PE`;yCH?lBC;}cOBDBEgoY8|c0PvpKy4m$AY+7VB#&H!!?B!AN@NJ|@m6sk4c&+a)?7~B)okfH0O<%izMeJF%U32P2``~k=g*GgtsAigqU>q37$sm}KmutW;E;=^VLusQ?lO6n`0TFjQAc`g)`IHG4G<%c8sRkb*u>;O8etvo0@=>SAhL77BuaNdi|LI0L>I=<SB0FAKD`FJgI`1dUT(!pnk|Fqh#3|&RGc&2Zn4PvdB#)D8VV=k>?nF^ZO%Qae<~ZG5pz#9ZAFRpFG%-Y8{Rmd*f!oYYX*iSsjQjl`TqEOaP?mMm@Y?2^|vWS<d{Wgc+ei9L|6u@Mq+Ca2Ye|jk<aZp!HU7svd6bVPRW5f2-cA#!#+?e5;zB^Yv-jciE^4X@vk&3PUvMV`XONl0g075#_BW}IKzoR+MK{H*tr?MSo$t78He^ujn2A-{c_4kuR}oR=DWs1M&H%)+tT7#;p6YG>Qqhp#Ro~xw3f(fI6x=i)ocaQIs|Dvg?AX3|G_enP}P7N_?{nab4X|xO;Bvcny;Mgm&n;}#^N?ht)QPa=pK=OJT&Nc@`)s2y%=xX{IUe}??&l&?LPPJ^bTBK*!;mW_~-<`zW8)>h0fy7dX3AT&!LmnKb*<tO`uM31<E}QIi4(}F}*{;`DAc_54LkLj<F~)gS_T9>*OFB-IsN$aD^LPcO{na32FZCS;P!p)sbnR{<1H8&Io*XH>^%{X5H?vtC_<5Yl)4H{;T?eR=xo4zMgGxrMG9p@1UtXLg|+1$C`Eo$g|L<Yt^E6lO6<?C7)VU#sV|G#;?62fC+<Stf5u}&+HgfUyeK(+kt_|hB~wI!OX|rIDi8kN5ZTptl?S!(d)!^kVogoZ;Jy6A0lGcjW*8E7^RvXr9`%t#!wlS_{uYo3bIrq*+sg!XgdRxFP?|RU?>x#e~g(<!|jcAl*qWW5zFc-Cp!f#!4`-P!JKI$%ARE-?%J#N76u(2k~%CFlA;w~_Ya!UPM-^Y-*@X9d&bT=b@Bwf{NF;xPk+}}5b@n^AL<#=X$kmPGY}~)i!?Id#94?7+&Q}rbtqs<e$ZmK#mehUtZ4_kmP^*D*5g48xD`p-tL`#h;Ejz$C~bzq0RK6<dTPL14$y`OE%LvEP-YHm0IeHmaf&pqN@c~lk&p>V3q>l<cvC>QZ}FvGh-tAjMoOTg=^RKYDoUIaU;*WhzRh@}-9nE11k#_w`r+2d7A@2gw`AU7O5Nt!32Lo0+$xOMdW67%8<KSxW5_hKx+mzyiB~Xy_TH`h4@;WRx{mgu5ISw;cds3N5;RceKJ2smo_&0j=uTKzWkG%Pu*a%TjraU^)b@IqT^Cear{D9!^dpbhC#I+yMXT4-ZH3gx6?#;j-QayOMtWK2>`w@1^w*jNQ!&uSKfge%M28%OO{Ws}_mw5k2)UX#8EE`$J&ery(PFRTPRe+8ME$%+0(8%omCTo5iz3vJ+obP2NWfe{?nUg>q@;gbs0zwWscj2jCr*C32c$lxQ+=4Exp3VPq2xzPUNKpQcy~h@N$a;FQxng%f-{r<UClnTh9ydm{A5_q{y0Eu!Gm=NpL<$<kbhpOSka0Fptz<~z7f9}|C(xH4#M#c*jMr(w<S_)isH-o4PWu3RNb$D*#lbp1>N>P_yc?!P-}$P_?H9E>1g+ea9y4H{m9vJ169o>9(N|lI$0M}Uv<HhktAiB(G5f;!YMp;EL|)g@Xd!c`ZQ!%qXO<i+qr%DB8lNVf50y0>$@25BS_d%2FJ&gv3Sf0%>(OD;tg#mF(VF6GK7(DK*r9%wu?|AusPFcGjp-H&mfb5eGek@LU1s(Jk*`7!ur*UU_J@K4Ie>Su?<+apZL;q7d8Z?MJJ{Ew(x%93*MI1EI#&X%iGGdJ9!(|UhI+i*`+>jZn?h;K-w}>M~lFsBSVmjSI*Py=BlvSgU<FZ^kK7TE7492B40FV>|RO*TT1Fa$2+@ie+S506rPh`84?h7$4MDe6a2Od4|YI$7k1`A|K_FixJ(d#P~6R>uB8!?^*`G~OIz3q_gyhleNle+a{7n>fPT}9i)}$yGwM~j_|YsLYVAJhKBb?=7uZ*07(_qNd_N5&8E8#c2Wq82=IEJY5s<tvxtANYUK>;WXJU|dRpVRJ1*7P<K2BqFho5sZb9;f#Fa}mkJyugV{pzo~(v;S*x4Ud@#36SMN4NU0e%Wuby<lQ~v!enb5G3wB+uzj^T5?dm0Q|lG{%y1t)C^@)jw**HtXvk`DHWfkux=43HqlH`UDhI<W9HoH!z&b20*n8<{YKY8YUCu+cW-jj;p$f4l7ZV)KPx8?pzgX4*5~JA^Vxi9xiVdLAJh}?MjU;xUVu1C-b>~Kwj{V<?j3e4K`8fl0+$0=Vv{vSC!7tpAn!|enyUN7FO2A&_W)R%XD@N=#HDZ}&4S(V7`PjSE#ElpYR7qEvP<b!_Zz<$)8b$6bIENcS`0ATm^j}wB_RSVl;Gr-gH^sJp+4a62hKv_@J#f;QF0~_g9TU2Gr(Mhcq{jaI`x7LV*`QpT-T8>%l9Wc;(#+4XM)!#giyS6FL_7O`DvOWUo6zO(FggIxM-;oSqeP>I^_M`6)E1WsCDM2O~XMf-t1*O+P)yvRgmiUb)kFO@;xD+2G{@qPq#);P$7Wt00F=#yJi3YrO+j%vBYQl0ssI200dcD')).decode('ascii').split('\n')
entropy: int = secrets.randbits(128)
checksum: int = hashlib.sha256(entropy.to_bytes(16, 'big')).digest()[0] >> 4
combined: int = (entropy << 4) | checksum
mnemonic: list[str] = [bip39_words[(combined >> (index * 11)) & 2047] for index in reversed(range(12))]
print(f'Entropy (128 bits, hexadecimal): {hex(entropy)[2:].zfill(32)}')
print(f'Checksum (4 bits, binary): {bin(checksum)[2:].zfill(4)}')
print(f'Mnemonic (12 words, english): {" ".join(mnemonic)}')
And here's the same script but generalized to produce either 12, 15, 18, 21, or 24-word mnemonics: #!/usr/bin/env python3
# Don't use this for anything serious; it *probably* works as intended, but I haven't tested it much (I wrote it quickly for apogio).
import base64
import lzma
import secrets
import hashlib
bip39_words: list[str] = lzma.decompress(base64.b85decode('{Wp48S^xk9=GL@E0stWa761SMbT8$j;4?ZDBwYY7n21WR2~SMNj7l(<JR*YqdhorTGI^h5s6rBe;k9*`P;vADxW!uq6ud9G`Ryq|uIakUrKX0S{>^H;DHJAH%e&gKef~1EXSD0Us}VywB$hQSCL24wlCsKYCV<&UARZHOh)&>5DZ-~ie;w#}??6<JOl|mq9eKdcew;FDEcSIuPSsz9ifo`zRLj%vg=z0~cu*vAMA6cb5GSaXO7n>$%xTl&8@gxbgANqLq@*=qXFu|h=a2+%Xss$^84V29ubWU|Jis}ZeS^SfW}jww7nw9K&Cx%HKy;0Y37#)`+p3zY1bZ`7k+YIMR3u^uY^9ccN{kp=S?u6N4zisDC8gkIJo$^^m6wM^a$8H3bAYEsAIc966#xe{9j&hY0zxEiT${N!QqS^qX$el?jg>&rnPf$K-yrI;)DRZN%5N67_SO#n=yj@!>$4Hz)evAV5=sYEQS$z7tp@GUj@>>S;S=L=!OK{Jtif0|-p%w0w#bxt71|pkbpS}&H*pv#^=Et7p$ITGaVb29^yoJQ!zsLIcy9`T(hCW;CliZjzHlUHk5C~^7<s${pyjkGSHqDb$3%6Ql)O^B*BWTyC_6sP9t*>_nJ1EUY6*S-;1uIhueo7A2kS$(TLIuv1k&86r%i)z0*-`orY@!pD9im@rths?CuQ90QmSY+!iP@8%nQ%d2Mnv#BVLo#w-6V9c5aG@e2d9O-C!?y>GSCUs5I^54+iV}u#dj~6%nU^oJ!C{P_)~vNh$*wcdG_&J;l!^sRRdSF->(8X?IogT__SWD7&M;TnqE9)DN*23c>sJsQxXi3;5J6DDh(_H0P%C?nxktxwmY48AW19?`wuF+F(0QfgOO}Ojr7j3KfbBaY+=fiqt?}oNK#yaFde;{ARhlk@2*Yh;H++vcQgmvVuv4wKzHFO&Ru(cfJW=QuOvQu>M0i6U6^04<N3c${Mzk9WOh3^h|Bx=-7U{ac`Tojgk$wjm&2*;dbo8XCFVx7vftB*T#;^eJ6Rv>JXbcxiR-dL7D_vpPfb6ZAR`2uHBG)agXV6VC2h%>;B+vg*v^Xsw3(RIEe8ls@DQbyP-deH3-Hb)O_0nXteO2F_5u@cpMV^*D4qiuN<at4zRaHb_%+w4o!3z1x|G(S0Q4-8w6cmtHv6}zRL00x0v3*+4}~6x$RUNbD{rKGJe~n>SK-`U!)6zJN$A6{Hhkj+D@VTMcYtaNfsu>Wn{j~^6xu22NN$^PmEmEl*lr<Iq~?ayQ?Mq+_SAf1u3uKb)!FG*pd}Y`;RydQ_jhBV3IJyYbiO@nL6xCOD-B1(}2VUEWj<scNhas{F%K!5FcMIvQCKN3TK-vuB#u@5a)O*CA;1@r33JxK6ZcOff;l0Rb)DDIUIdWU2Lrg_nT~!OmX{$$FBD%4V7TQ-RCP1rhG&giyB}eTE*eNPEK0{F<j^TrG8NOn8ylmgdaL)Ull(-8%}RdS!ufXK%-KAb8qY!&DKNMg|}L7tSo2p<DN>~k&2zu1nq>@!y1*ypM67Y`iGod_AKq<bEWq&l@ZliOe+DODTaDj3TR|GzSq{ygZN5;^38}PCZxI0%CH=5UB@k%2Mgh6zQ_-s+Kb_kVRfhG^$Y0oOW2zmlc2Ay=$M;wjEA_a!!f;^Si^ZbTV#OV)if)Fr5(zM@iXYVO_6G(ooKRa&RFfYbR~k(4Pj|mmG17{dcNunx0U|P=4}B;hEo8nj=OBMER5&p=;yBKPt~g@_oib!r>_r-*jlOyBOA{KNrg}5Ac0RMtO%k2jizS0XF!`YtPkPQ`ImjU%rS#KF}#sq+r|(L72zb1TC2;dOS~ikeihcHtjY!p{N`Z*Q+Cx&oG&bsXVozbAQj&R&`HmZr4)YnH?nY^m}If@mU2OBIw{C4^z1meuyEu;2#(@;2Osppn@|UV6U2xJvDC)cQ<bcDUd=$-N=fQT+iEI(H+Q?DeL|cvnMVG}N$??c0$9l~YqMpU!4~#FrCo0^q9I#GD;e^^M&&}sesOl$cqh^&2$>`(jFILY>=0`+;VLBTS|!3}0QE&E(EzgOrO8NIJOzT>^p}ISTqQSGa0npbKH^kf2A+%8xf8FWGBc;!h)Vd2IuWu!v6lNZ{yVZpjQ$0$y<=)J7sK?*>Vv>n?iROPvbL3X*R&VFHz8cX8#e-z!_RyxYIU<(6gQ_4b(OE?b)!x=RJf;~4bi0p=4VlSVImRd!l7a7cE+)`2rKn4pAw_LqwZx&RWLHTaB*I{-F6zLlIF;yxKK4;IvNZ6t|?^jhonkM3>Vwmf(Vk0@EB2?UeVdB;Yb|Kd;W%R3JWCWlnF(sPu^{(m8)`sb8503#A0k-zZPD`M9T*&C+y`J9JD_0->(14<!m6W^O9yW6f0a!h*#%-1<bd5h^ziFcpp3$isdCU5BaIyH%tA&lv4a|IxD^z(ppfd%c?gp?Gm_dVP{9B9W#N5rKm47h>W>=Y!~_Y8&AfDjoh)LpK6g5a&}4OZ^ryKhc3JF+hme1Be32X32Sb{U6qNKLDpmXx~u;TgGnn6m@#M;b$CnfIq7~tRFqGO>Nx0>tP_m`QW>a`i!TRAZ%h<ymlLNjB`3I{j`y3}_zXntLR;@~&3)~<SBdxVe0Vg1MkQ0{lkTd<V%5*B=I_`q1$F~Qo;N&nXC7gDm!Z10oRnT>#-uM7BMo<Ke_<Kq)X1`x)B&Tdx7}z^L;s$YvfhGzZb!JuDEVsZ{LZI)c>C{dn$iVRKwHz-B)^APT2#W+FMad$_X#-BM+bg%Rf2oYfSf2eeAFo8OyWD>GfI>IduIKFJlGhOb<V;Yf~s`G!GBuy(f*5CW8~~c@!93tes7Fv+xF^laoI;vh^GK8{~-#$l}0k8xt46LDlynQhn%mpy?WuiuL+BT|KVa4iu(b)bcYP332V(60%B)Cf6xDm6x1cy=}#}NP4<RI&@N3E1HwLQ8&=yzW5DJ>tF)718Oq4s#zK*RIUGa7k|k$4HOG>9RiJM;@2yK=NBbEC6*Kd&2VV<iJZVy`q}J4-fIAR*IEm-W-NnbBMc#b?aqo*UzFd_rO0R?FJ!b=1U)c-%YZf(a552_x+NG~95C6Wn-z7rXYz#GCH#5xo1SKQEpVCZu?gKLp>qPUxacYwN@0mswW469o32{DJf=y*CU9V7)?7loOwYQ9_8JLIotvhv~&Wz%i0Z!05V1|QY@4L4jp;%<jo0f%IrpPHnzrf*|6Y7jJ9)F6*Of5>JOM*dix({v*!!8Z=C&nDTk&RqqACXz9fO2kwe61+}5DG1WkN7Hp0~$f1@=n~e=))pi@!)Tac?masVzC?^p0gZWN%vmU+jb}&3M!$|S+EHr+mGIU0i;)%3RPlu5L-Y@1H-swrrU`hjMq%4nK}+=J_f-N8;rWk>@YoZbR~hDP?=07HhnK7Yha4mRW0EOgl>Q5h&N?zt#<Q0@0*u}Wxt8X07JQtoelXwG`h;N8T#%3L&tG0GNEUaabE7}x+<Lgx`Z&u4!lj*=snoQ$;S(g#IGnq-t_g+aeXg(;3P=6(JW$!nYc^o*ime*%~>)JjrC*Ls77Nt7Cp(!Js+pn%*(NBy}Z)CsN&bjh^gC{rL~LauR(COgtSd>bE4IHnvS$>ovdGkG|VU^PL1*cw*dz6l9aH($Kat5LTU9BKbaVBD}e6ZT~A9kcNB{KYbj~K(7<a;8LX;x(jRm@9cLv$#Ubz)U+r%6B=(k#J4~F{f2Dp|S=!P9+VIP!E0)0ACz+VZ1ocmMTx{#U%TEl$b)$F#cXLOkdjKf96euOn^vW>{m$+D85|@#74?ms4(<invY(H$jEQ@mBDlK!7g0v_6PbZsmKeG&CZ_=OxUAWu5noVNzVGPSdTV|)SfD>7n$~>n>pTX`Y%sMm%<C%kmjvbs2e9LX<Xwf`1468A!!DT?({~n(tbR2T+BP&;2T-gLIDE6WyR!4c^kVJkqiP=y%%%!oFop}TE2PE`;yCH?lBC;}cOBDBEgoY8|c0PvpKy4m$AY+7VB#&H!!?B!AN@NJ|@m6sk4c&+a)?7~B)okfH0O<%izMeJF%U32P2``~k=g*GgtsAigqU>q37$sm}KmutW;E;=^VLusQ?lO6n`0TFjQAc`g)`IHG4G<%c8sRkb*u>;O8etvo0@=>SAhL77BuaNdi|LI0L>I=<SB0FAKD`FJgI`1dUT(!pnk|Fqh#3|&RGc&2Zn4PvdB#)D8VV=k>?nF^ZO%Qae<~ZG5pz#9ZAFRpFG%-Y8{Rmd*f!oYYX*iSsjQjl`TqEOaP?mMm@Y?2^|vWS<d{Wgc+ei9L|6u@Mq+Ca2Ye|jk<aZp!HU7svd6bVPRW5f2-cA#!#+?e5;zB^Yv-jciE^4X@vk&3PUvMV`XONl0g075#_BW}IKzoR+MK{H*tr?MSo$t78He^ujn2A-{c_4kuR}oR=DWs1M&H%)+tT7#;p6YG>Qqhp#Ro~xw3f(fI6x=i)ocaQIs|Dvg?AX3|G_enP}P7N_?{nab4X|xO;Bvcny;Mgm&n;}#^N?ht)QPa=pK=OJT&Nc@`)s2y%=xX{IUe}??&l&?LPPJ^bTBK*!;mW_~-<`zW8)>h0fy7dX3AT&!LmnKb*<tO`uM31<E}QIi4(}F}*{;`DAc_54LkLj<F~)gS_T9>*OFB-IsN$aD^LPcO{na32FZCS;P!p)sbnR{<1H8&Io*XH>^%{X5H?vtC_<5Yl)4H{;T?eR=xo4zMgGxrMG9p@1UtXLg|+1$C`Eo$g|L<Yt^E6lO6<?C7)VU#sV|G#;?62fC+<Stf5u}&+HgfUyeK(+kt_|hB~wI!OX|rIDi8kN5ZTptl?S!(d)!^kVogoZ;Jy6A0lGcjW*8E7^RvXr9`%t#!wlS_{uYo3bIrq*+sg!XgdRxFP?|RU?>x#e~g(<!|jcAl*qWW5zFc-Cp!f#!4`-P!JKI$%ARE-?%J#N76u(2k~%CFlA;w~_Ya!UPM-^Y-*@X9d&bT=b@Bwf{NF;xPk+}}5b@n^AL<#=X$kmPGY}~)i!?Id#94?7+&Q}rbtqs<e$ZmK#mehUtZ4_kmP^*D*5g48xD`p-tL`#h;Ejz$C~bzq0RK6<dTPL14$y`OE%LvEP-YHm0IeHmaf&pqN@c~lk&p>V3q>l<cvC>QZ}FvGh-tAjMoOTg=^RKYDoUIaU;*WhzRh@}-9nE11k#_w`r+2d7A@2gw`AU7O5Nt!32Lo0+$xOMdW67%8<KSxW5_hKx+mzyiB~Xy_TH`h4@;WRx{mgu5ISw;cds3N5;RceKJ2smo_&0j=uTKzWkG%Pu*a%TjraU^)b@IqT^Cear{D9!^dpbhC#I+yMXT4-ZH3gx6?#;j-QayOMtWK2>`w@1^w*jNQ!&uSKfge%M28%OO{Ws}_mw5k2)UX#8EE`$J&ery(PFRTPRe+8ME$%+0(8%omCTo5iz3vJ+obP2NWfe{?nUg>q@;gbs0zwWscj2jCr*C32c$lxQ+=4Exp3VPq2xzPUNKpQcy~h@N$a;FQxng%f-{r<UClnTh9ydm{A5_q{y0Eu!Gm=NpL<$<kbhpOSka0Fptz<~z7f9}|C(xH4#M#c*jMr(w<S_)isH-o4PWu3RNb$D*#lbp1>N>P_yc?!P-}$P_?H9E>1g+ea9y4H{m9vJ169o>9(N|lI$0M}Uv<HhktAiB(G5f;!YMp;EL|)g@Xd!c`ZQ!%qXO<i+qr%DB8lNVf50y0>$@25BS_d%2FJ&gv3Sf0%>(OD;tg#mF(VF6GK7(DK*r9%wu?|AusPFcGjp-H&mfb5eGek@LU1s(Jk*`7!ur*UU_J@K4Ie>Su?<+apZL;q7d8Z?MJJ{Ew(x%93*MI1EI#&X%iGGdJ9!(|UhI+i*`+>jZn?h;K-w}>M~lFsBSVmjSI*Py=BlvSgU<FZ^kK7TE7492B40FV>|RO*TT1Fa$2+@ie+S506rPh`84?h7$4MDe6a2Od4|YI$7k1`A|K_FixJ(d#P~6R>uB8!?^*`G~OIz3q_gyhleNle+a{7n>fPT}9i)}$yGwM~j_|YsLYVAJhKBb?=7uZ*07(_qNd_N5&8E8#c2Wq82=IEJY5s<tvxtANYUK>;WXJU|dRpVRJ1*7P<K2BqFho5sZb9;f#Fa}mkJyugV{pzo~(v;S*x4Ud@#36SMN4NU0e%Wuby<lQ~v!enb5G3wB+uzj^T5?dm0Q|lG{%y1t)C^@)jw**HtXvk`DHWfkux=43HqlH`UDhI<W9HoH!z&b20*n8<{YKY8YUCu+cW-jj;p$f4l7ZV)KPx8?pzgX4*5~JA^Vxi9xiVdLAJh}?MjU;xUVu1C-b>~Kwj{V<?j3e4K`8fl0+$0=Vv{vSC!7tpAn!|enyUN7FO2A&_W)R%XD@N=#HDZ}&4S(V7`PjSE#ElpYR7qEvP<b!_Zz<$)8b$6bIENcS`0ATm^j}wB_RSVl;Gr-gH^sJp+4a62hKv_@J#f;QF0~_g9TU2Gr(Mhcq{jaI`x7LV*`QpT-T8>%l9Wc;(#+4XM)!#giyS6FL_7O`DvOWUo6zO(FggIxM-;oSqeP>I^_M`6)E1WsCDM2O~XMf-t1*O+P)yvRgmiUb)kFO@;xD+2G{@qPq#);P$7Wt00F=#yJi3YrO+j%vBYQl0ssI200dcD')).decode('ascii').split('\n')
mnemonic_length: int = 12
assert mnemonic_length in {12, 15, 18, 21, 24}
mnemonic_factor: int = mnemonic_length // 3
entropy: int = secrets.randbits(mnemonic_factor * 32)
checksum: int = hashlib.sha256(entropy.to_bytes(mnemonic_factor * 4, 'big')).digest()[0] >> (8 - mnemonic_factor)
combined: int = (entropy << mnemonic_factor) | checksum
mnemonic: list[str] = [bip39_words[(combined >> (index * 11)) & 2047] for index in reversed(range(mnemonic_length))]
print(f'Entropy ({mnemonic_factor * 32} bits, hexadecimal): {hex(entropy)[2:].zfill(mnemonic_factor * 8)}')
print(f'Checksum ({mnemonic_factor} bits, binary): {bin(checksum)[2:].zfill(mnemonic_factor)}')
print(f'Mnemonic ({mnemonic_length} words, english): {" ".join(mnemonic)}')
|
|
|
|
apogio (OP)
|
|
March 06, 2024, 12:30:32 PM |
|
Thanks PowerGlove, I find your answer very helpful. I will check it in detail later today and I will comment, if I find anything important. As you said, this is the approach that I like to follow. I code in order to understand something better.
Do you think it would be better if I generated more bits of entropy and then keeping 128 of them? Instead of zfill or repeating the process if bits are less than 128.
|
█████████████████████████ ████████▀▀████▀▀█▀▀██████ █████▀████▄▄▄▄██████▀████ ███▀███▄████████▄████▀███ ██▀███████████████████▀██ █████████████████████████ █████████████████████████ █████████████████████████ ██▄███████████████▀▀▄▄███ ███▄███▀████████▀███▄████ █████▄████▀▀▀▀████▄██████ ████████▄▄████▄▄█████████ █████████████████████████ | BitList | | █▀▀▀▀ █ █ █ █ █ █ █ █ █ █ █ █▄▄▄▄ | ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ . REAL-TIME DATA TRACKING CURATED BY THE COMMUNITY . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ | ▀▀▀▀█ █ █ █ █ █ █ █ █ █ █ █ ▄▄▄▄█ | | List #kycfree Websites |
|
|
|
NotATether
Legendary
Offline
Activity: 1778
Merit: 7372
Top Crypto Casino
|
|
March 07, 2024, 08:09:14 AM |
|
Do you think it would be better if I generated more bits of entropy and then keeping 128 of them? Instead of zfill or repeating the process if bits are less than 128.
If you generate more rounds of entropy then the amount that you end up with will be the average of the entropy of all the rounds. in other words: (E(Loop1) + E(Loop2) + ...) / N. Unless you select the bits from a specific place in the total amount in which case the calculation becomes slightly different but still follows through. It doesn't make much of a difference, and if the reason you are doing this is to cover for a potential insecurity in the random number generator, it will not work well. OpenSSL's RNG is much better for that purpose.
|
|
|
|
PowerGlove
|
Do you think it would be better if I generated more bits of entropy and then keeping 128 of them? Instead of zfill or repeating the process if bits are less than 128.
Yup, in the context of your script, slicing a length-128 string from a larger string of random 1s and 0s would work (that is, it would correct the bias, because the high-order bit would then have a 50/50 chance of being either 1 or 0). But, the way that you posed that option as an alternative to zfill makes me think you don't fully understand the source of this bias. I hope you don't find the following explanation tiresome, I just know that if it were me in your shoes, I'd really appreciate someone taking pains to help me understand the potential hole in my thinking: Let's imagine a toy version of this problem where you're trying to generate just 4 (rather than 128) bits of entropy with a coin. Let's not go down the rabbit hole of coin fairness, entropy extraction, and tossing technique. (I have a sometimes-sophomoric, or, as my wife would say, "bloody stupid" sense of humor, especially when I'm in a good mood, so "tossing technique" just gave me the giggles. Don't toss while flipping coins, yeah?) There are only 16 possible ways for 4 coin-tosses-in-a-row to end up, so let's put them all in a table (ignore the last 4 columns for now): Outcome | Pattern | Binary | Decimal | f1($pcv) | f2($pcv) | f3($pcv) | f4($pcv) | #1 | HHHH | 1111 | 15 | "0b1111" | "1111" | "1111" | "HHHH" | #2 | HHHT | 1110 | 14 | "0b1110" | "1110" | "1110" | "HHHT" | #3 | HHTH | 1101 | 13 | "0b1101" | "1101" | "1101" | "HHTH" | #4 | HHTT | 1100 | 12 | "0b1100" | "1100" | "1100" | "HHTT" | #5 | HTHH | 1011 | 11 | "0b1011" | "1011" | "1011" | "HTHH" | #6 | HTHT | 1010 | 10 | "0b1010" | "1010" | "1010" | "HTHT" | #7 | HTTH | 1001 | 9 | "0b1001" | "1001" | "1001" | "HTTH" | #8 | HTTT | 1000 | 8 | "0b1000" | "1000" | "1000" | "HTTT" | #9 | THHH | 0111 | 7 | "0b111" | "111" | "0111" | "THHH" | #10 | THHT | 0110 | 6 | "0b110" | "110" | "0110" | "THHT" | #11 | THTH | 0101 | 5 | "0b101" | "101" | "0101" | "THTH" | #12 | THTT | 0100 | 4 | "0b100" | "100" | "0100" | "THTT" | #13 | TTHH | 0011 | 3 | "0b11" | "11" | "0011" | "TTHH" | #14 | TTHT | 0010 | 2 | "0b10" | "10" | "0010" | "TTHT" | #15 | TTTH | 0001 | 1 | "0b1" | "1" | "0001" | "TTTH" | #16 | TTTT | 0000 | 0 | "0b0" | "0" | "0000" | "TTTT" |
The first four columns are: 1. the possibility/outcome # (1 through 16, so, every possible outcome accounted for), 2. the heads-or-tails pattern corresponding to each outcome (H = heads, T = tails), 3. the same heads-or-tails pattern but in base-2 (1 = heads, 0 = tails), and 4. the heads-or-tails pattern converted from base-2 into base-10. The last four columns involve the imaginary variable $pcv (previous column's value) and the following definitions: f1 = lambda x: bin(x) f2 = lambda x: x[2:] f3 = lambda x: x.zfill(4) f4 = lambda x: x.replace('0', 'T').replace('1', 'H') Now, the first thing to think about when looking at that table is if there are any "bad" or "low entropy" outcomes in it? The answer to that is no: as long as each outcome is equally probable, then any given outcome is just as "entropic" as any other outcome. HTTH is just as good as TTTT, savvy? That means that, tempting though it is, it's a mistake to use the string length of the values in the f2($pcv) column to decide whether or not you "have enough entropy" (to be clear, it's a mistake to base that decision on anything about a single outcome). If you discard outcomes whenever len(f2($pcv)) != 4, then you're only ensuring that the pattern will always begin with a heads instead of a tails (check the table to confirm that). The second thing to think about when looking at that table is if the final patterns in the f4($pcv) column ever disagree with the source patterns in column 2? They don't. So, you can rest assured that the zfill(128) that was present in your original script was correct (that is, it was only restoring 0s that, in some sense, were there from the start, and were "lost" during an int to str conversion). You can/should pat yourself on the back for getting it right the first time. There are at least two other bugs in your script though (beyond the bias bug, which we'll call Bug #1): Bug #2You need a zfill(32) before passing the hexadecimal representation of your entropy into bytes.fromhex (otherwise, you'll occasionally pass it an odd number of nibbles, which will cause it to raise a ValueError). Bug #3You need a zfill(256) at the end of the line that calculates your sha256_bin variable (otherwise, your checksum, and therefore the last word of your mnemonic, will be wrong 50% of the time). For an example, try manually setting your entropy to cc399b43e82bfbc07f2fe3fd1f13ed41. Your script will spit out the following mnemonic: slow smoke special space sausage then witness wise wonder weasel win lobsterIan's script (and mine) will spit out this instead: slow smoke special space sausage then witness wise wonder weasel win lionThe reason your wonder weasel only managed to win a lobster instead of a lion, is because you're miscalculating the checksum bits as 1001 instead of 0001. (I'm going to be too busy to respond for a while. I'll take a peek at this thread again in a few weeks' time.)
|
|
|
|
bestcoin_59
|
|
October 02, 2024, 03:19:23 PM Last edit: October 02, 2024, 06:16:50 PM by bestcoin_59 |
|
Hello, Thanks for the experimentation. Although it may not be a huge improvement, i think it might be more natural to replace: # entropy entropy = bin(int(token_hex(16), 16))[2:] while len(entropy) != 128: entropy = bin(int(token_hex(16), 16))[2:]
with # entropy entropy = "" for i in range(128): entropy += bin(int(token_hex(1), 16))[-1]
Cheers
|
|
|
|
mcdouglasx
Member
Offline
Activity: 329
Merit: 90
New ideas will be criticized and then admired.
|
|
October 03, 2024, 05:28:37 PM Last edit: October 05, 2024, 04:08:32 PM by mcdouglasx |
|
Since this is a topic about entropy I made some interesting integrations, I modified the script by adding a random secp256k1 point, random passphrase, random entropy salted methods, using random hashes to avoid predictive patterns and random concatenation of their order. As long as the entropy percentage is closer to 50%, it means better entropy "in theory". What do you think? updated to the BIP39 standard24 wordsfrom secrets import token_hex, choice from hashlib import sha256, sha3_256, blake2b from ecdsa import SECP256k1, SigningKey
def read_wordlist(file_path): try: with open(file_path, "r") as bip39_wordlist_file: return bip39_wordlist_file.read().split('\n') except FileNotFoundError: print("Error: bip39_wordlist.txt not found.") exit(1)
def entropy_words(wordlist): random_words = [choice(wordlist) for _ in range(12)] phrase = ' '.join(random_words) hash_algorithms = [sha256, sha3_256, blake2b] chosen_hash = choice(hash_algorithms) salt = token_hex(16) phrase_hash = chosen_hash((phrase + salt).encode()).hexdigest() entropy_bits = bin(int(phrase_hash, 16))[2:].zfill(256) return entropy_bits
def random_point(): sk = SigningKey.generate(curve=SECP256k1) vk = sk.verifying_key point = vk.to_string().hex() hash_algorithms = [sha256, sha3_256, blake2b] chosen_hash = choice(hash_algorithms) salt = token_hex(16) point_hash = chosen_hash((point + salt).encode()).hexdigest() entropy_bits = bin(int(point_hash, 16))[2:].zfill(256) return entropy_bits
def generate_entropy(): entropy_sources = [ bin(int(token_hex(32), 16))[2:].zfill(256), entropy_words(bip39_wordlist), random_point() ] entropy = ''.join(entropy_sources) return entropy[:256]
def checksum(entropy): sha256_hex = sha256(bytes.fromhex(hex(int(entropy, 2))[2:].zfill(64))).hexdigest() sha256_bin = bin(int(sha256_hex, 16))[2:].zfill(256) return sha256_bin[:8]
def generate_mnemonic(entropy, checksum, wordlist): final = entropy + checksum num_of_words = 24 word_length = len(final) // num_of_words res = [final[idx:idx + word_length] for idx in range(0, len(final), word_length)] mnemonic = [wordlist[int(binary_place, 2)] for binary_place in res] return mnemonic
def entropy_percentage(entropy): num_ones = entropy.count('1') return (num_ones / 256) * 100
bip39_wordlist = read_wordlist("bip39_wordlist.txt") entropy = generate_entropy() checksum = checksum(entropy) mnemonic = generate_mnemonic(entropy, checksum, bip39_wordlist) entropy_percentage = entropy_percentage(entropy)
print('---------') print('ENTROPY: ') print('---------') print(entropy)
print('\n-------------') print('BIP39 PHRASE: ') print('-------------') for w in range(0, len(mnemonic)): print(str(w+1) + ': ' + mnemonic[w])
print('\n-------------------') print('ENTROPY PERCENTAGE:') print('-------------------') print(f'{entropy_percentage:.2f}%')
12 wordsfrom secrets import token_hex, choice from hashlib import sha256, sha3_256, blake2b from ecdsa import SECP256k1, SigningKey
def read_wordlist(file_path): try: with open(file_path, "r") as bip39_wordlist_file: return bip39_wordlist_file.read().split('\n') except FileNotFoundError: print("Error: bip39_wordlist.txt not found.") exit(1)
def entropy_words(wordlist): random_words = [choice(wordlist) for _ in range(6)] phrase = ' '.join(random_words) hash_algorithms = [sha256, sha3_256, blake2b] chosen_hash = choice(hash_algorithms) salt = token_hex(16) phrase_hash = chosen_hash((phrase + salt).encode()).hexdigest() entropy_bits = bin(int(phrase_hash, 16))[2:].zfill(128) return entropy_bits
def random_point(): sk = SigningKey.generate(curve=SECP256k1) vk = sk.verifying_key point = vk.to_string().hex() hash_algorithms = [sha256, sha3_256, blake2b] chosen_hash = choice(hash_algorithms) salt = token_hex(16) point_hash = chosen_hash((point + salt).encode()).hexdigest() entropy_bits = bin(int(point_hash, 16))[2:].zfill(128) return entropy_bits
def generate_entropy(): entropy_sources = [ bin(int(token_hex(16), 16))[2:], entropy_words(bip39_wordlist), random_point() ] entropy = ''.join(entropy_sources) while len(entropy) < 128: entropy_sources = [ bin(int(token_hex(16), 16))[2:], entropy_words(bip39_wordlist), random_point() ] entropy += ''.join(entropy_sources) return entropy[-128:]
def checksum(entropy): sha256_hex = sha256(bytes.fromhex(hex(int(entropy, 2))[2:].zfill(32))).hexdigest() sha256_bin = bin(int(sha256_hex, 16))[2:].zfill(256) return sha256_bin[:4]
def generate_mnemonic(entropy, checksum, wordlist): final = entropy + checksum num_of_words = 12 word_length = len(final) // num_of_words res = [final[idx:idx + word_length] for idx in range(0, len(final), word_length)] mnemonic = [wordlist[int(binary_place, 2) % len(wordlist)] for binary_place in res] return mnemonic
def entropy_percentage(entropy): num_ones = entropy.count('1') return (num_ones / 128) * 100
bip39_wordlist = read_wordlist("bip39_wordlist.txt") entropy = generate_entropy() checksum = checksum(entropy) mnemonic = generate_mnemonic(entropy, checksum, bip39_wordlist) entropy_percentage = entropy_percentage(entropy)
print('---------') print('ENTROPY: ') print('---------') print(entropy)
print('\n-------------') print('BIP39 PHRASE: ') print('-------------') for w in range(0, len(mnemonic)): print(str(w+1) + ': ' + mnemonic[w])
print('\n-------------------') print('ENTROPY PERCENTAGE:') print('-------------------') print(f'{entropy_percentage:.2f}%')
|
BTC bc1qxs47ttydl8tmdv8vtygp7dy76lvayz3r6rdahu
|
|
|
bestcoin_59
|
|
October 04, 2024, 05:02:18 PM Merited by mcdouglasx (1) |
|
Interesting code. You assume you have a better entropy source when you randomly choose one from three random entropy source. But is it really the case? Moreover, i don't get: if you don't store the result of the function. Do you mean: entropy_sources=choice(entropy_sources)
Cheers
|
|
|
|
mcdouglasx
Member
Offline
Activity: 329
Merit: 90
New ideas will be criticized and then admired.
|
|
October 04, 2024, 07:01:01 PM |
|
Interesting code. You assume you have a better entropy source when you randomly choose one from three random entropy source. But is it really the case? Moreover, i don't get: if you don't store the result of the function. Do you mean: entropy_sources=choice(entropy_sources)
Cheers It’s true, now the three of them combine.
|
BTC bc1qxs47ttydl8tmdv8vtygp7dy76lvayz3r6rdahu
|
|
|
bestcoin_59
|
|
October 04, 2024, 07:46:36 PM |
|
It’s true, now the three of them combine.
Ok:)
|
|
|
|
bestcoin_59
|
|
October 05, 2024, 06:55:30 AM |
|
12 wordsfrom secrets import token_hex, choice from hashlib import sha256, sha3_256, blake2b from ecdsa import SECP256k1, SigningKey
def read_wordlist(file_path): try: with open(file_path, "r") as bip39_wordlist_file: return bip39_wordlist_file.read().split('\n') except FileNotFoundError: print("Error: bip39_wordlist.txt not found.") exit(1)
def entropy_words(wordlist): random_words = [choice(wordlist) for _ in range(12)] phrase = ' '.join(random_words) hash_algorithms = [sha256, sha3_256, blake2b] chosen_hash = choice(hash_algorithms) salt = token_hex(16) phrase_hash = chosen_hash((phrase + salt).encode()).hexdigest() entropy_bits = bin(int(phrase_hash, 16))[2:].zfill(256) return entropy_bits
def random_point(): sk = SigningKey.generate(curve=SECP256k1) vk = sk.verifying_key point = vk.to_string().hex() hash_algorithms = [sha256, sha3_256, blake2b] chosen_hash = choice(hash_algorithms) salt = token_hex(16) point_hash = chosen_hash((point + salt).encode()).hexdigest() entropy_bits = bin(int(point_hash, 16))[2:].zfill(256) return entropy_bits
def generate_entropy(): entropy_sources = [ bin(int(token_hex(32), 16))[2:], entropy_words(bip39_wordlist), random_point() ] entropy = ''.join(entropy_sources) while len(entropy) < 256: entropy_sources = [ bin(int(token_hex(32), 16))[2:], entropy_words(bip39_wordlist), random_point() ] entropy += ''.join(entropy_sources) return entropy[-256:]
def checksum(entropy): hash_algorithms = [sha256, sha3_256, blake2b] chosen_hash = choice(hash_algorithms) salt = token_hex(16) sha256_hex = chosen_hash((bytes.fromhex(hex(int(entropy, 2))[2:].zfill(64)) + salt.encode())).hexdigest() sha256_bin = bin(int(sha256_hex, 16))[2:].zfill(256) return sha256_bin[0:8]
def generate_mnemonic(entropy, checksum, wordlist): final = entropy + checksum num_of_words = 12 word_length = len(final) // num_of_words res = [final[idx:idx + word_length] for idx in range(0, len(final), word_length)] mnemonic = [wordlist[int(binary_place, 2) % len(wordlist)] for binary_place in res] return mnemonic
def entropy_percentage(entropy): num_ones = entropy.count('1') return (num_ones / 256) * 100
bip39_wordlist = read_wordlist("bip39_wordlist.txt") entropy = generate_entropy() checksum = checksum(entropy) mnemonic = generate_mnemonic(entropy, checksum, bip39_wordlist) entropy_percentage = entropy_percentage(entropy)
print('---------') print('ENTROPY: ') print('---------') print(entropy)
print('\n-------------') print('BIP39 PHRASE: ') print('-------------') for w in range(0, len(mnemonic)): print(str(w+1) + ': ' + mnemonic[w])
print('\n-------------------') print('ENTROPY PERCENTAGE:') print('-------------------') print(f'{entropy_percentage:.2f}%')
Still, it looks as if given an entropy, the mnemonic generated doesn't match the one provided by Ian Coleman on his website...
|
|
|
|
nc50lc
Legendary
Online
Activity: 2590
Merit: 6356
Self-proclaimed Genius
|
|
October 05, 2024, 09:23:45 AM Merited by mcdouglasx (1) |
|
-snip-
Still, it looks as if given an entropy, the mnemonic generated doesn't match the one provided by Ian Coleman on his website... For the " 24 words" ( not OP's code), that's because its checksum generator doesn't follow BIP39 standard which should be the first 8 ( entropy/32) bits of the entropy's SHA256 hash. Reference: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonicChanging the checksum generator to something like this should fix it: def checksum(entropy): sha256_hex = sha256(bytes.fromhex(hex(int(entropy, 2))[2:].zfill(64))).hexdigest() sha256_bin = bin(int(sha256_hex, 16))[2:].zfill(256) return sha256_bin[0:8] For the " 12 words" ( also not OP's code), aside from the checksum, it's using a 256-bit entropy instead of the standard 128-bit of a 12 word mnemonic. Just adjust the code above to match the correct sizes: def checksum(entropy): sha256_hex = sha256(bytes.fromhex(hex(int(entropy, 2))[2:].zfill(32))).hexdigest() sha256_bin = bin(int(sha256_hex, 16))[2:].zfill(256) return sha256_bin[0:4] And set the correct entropy size: def generate_entropy(): -snip- return entropy[-128:] Those are just " band-aid" solution BTW since the whole code is specifically written to work with those BIP39-incompatible code.
|
|
|
|
bestcoin_59
|
|
October 05, 2024, 01:53:07 PM |
|
Just wondering; can someone explains why this # entropy from secrets import choice entropy = "" for _ in range(128): entropy += choice(['0','1']) print (entropy)
is bad...if it is...And why the code given by the others members are better (if they are). Thanks:)
|
|
|
|
mcdouglasx
Member
Offline
Activity: 329
Merit: 90
New ideas will be criticized and then admired.
|
|
October 05, 2024, 04:12:46 PM |
|
-snip-
Still, it looks as if given an entropy, the mnemonic generated doesn't match the one provided by Ian Coleman on his website... For the " 24 words" ( not OP's code), that's because its checksum generator doesn't follow BIP39 standard which should be the first 8 ( entropy/32) bits of the entropy's SHA256 hash. Reference: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonicChanging the checksum generator to something like this should fix it: def checksum(entropy): sha256_hex = sha256(bytes.fromhex(hex(int(entropy, 2))[2:].zfill(64))).hexdigest() sha256_bin = bin(int(sha256_hex, 16))[2:].zfill(256) return sha256_bin[0:8] For the " 12 words" ( also not OP's code), aside from the checksum, it's using a 256-bit entropy instead of the standard 128-bit of a 12 word mnemonic. Just adjust the code above to match the correct sizes: def checksum(entropy): sha256_hex = sha256(bytes.fromhex(hex(int(entropy, 2))[2:].zfill(32))).hexdigest() sha256_bin = bin(int(sha256_hex, 16))[2:].zfill(256) return sha256_bin[0:4] And set the correct entropy size: def generate_entropy(): -snip- return entropy[-128:] Those are just " band-aid" solution BTW since the whole code is specifically written to work with those BIP39-incompatible code. You are right, I have updated it to the BIP39 standard.
|
BTC bc1qxs47ttydl8tmdv8vtygp7dy76lvayz3r6rdahu
|
|
|
apogio (OP)
|
|
October 18, 2024, 11:49:34 AM Merited by PowerGlove (2) |
|
Sometimes time flies and you forget important things, like responding to a fellow developer. Bug #1 <~>
Bug #2 <~>
Bug #3 <~>
Thanks for the great explanation. I have carefully revisited the post you 've written and I am confident I have realised exactly the bugs you 've mentioned. Now, having corrected the issues, I kindly ask to review them below, so I can then safely update OP. # contact: apogio@proton.me from secrets import token_hex from hashlib import sha256
# read bip39 wordlist from file and import in a list bip39_wordlist_file = open("bip39_wordlist.txt", "r") bip39_wordlist = bip39_wordlist_file.read().split('\n')
# entropy entropy = bin(int(token_hex(16), 16))[2:].zfill(128) print('---------') print('ENTROPY: ') print('---------') print(entropy)
# calculate SHA256 sha256_hex = sha256(bytes.fromhex(hex(int(entropy,2))[2:].zfill(32))).hexdigest() sha256_bin = bin(int(sha256_hex, 16))[2:].zfill(256)
# calculate checksum checksum = sha256_bin[0:4]
# final seed to be converted into BIP39 mnemonic final = entropy + checksum
num_of_words = 12 word_length = len(final) // num_of_words
# calculate mnemonic res = [] for idx in range(0, len(final), word_length): res.append(final[idx : idx + word_length])
mnemonic = [] for idx in range(0, num_of_words): binary_place = res[idx] decimal_place = int(binary_place,2) mnemonic.append(bip39_wordlist[decimal_place])
print('\n-------------') print('BIP39 PHRASE: ') print('-------------') for w in range(0, len(mnemonic)): print(str(w+1) + ': ' + mnemonic[w])
|
█████████████████████████ ████████▀▀████▀▀█▀▀██████ █████▀████▄▄▄▄██████▀████ ███▀███▄████████▄████▀███ ██▀███████████████████▀██ █████████████████████████ █████████████████████████ █████████████████████████ ██▄███████████████▀▀▄▄███ ███▄███▀████████▀███▄████ █████▄████▀▀▀▀████▄██████ ████████▄▄████▄▄█████████ █████████████████████████ | BitList | | █▀▀▀▀ █ █ █ █ █ █ █ █ █ █ █ █▄▄▄▄ | ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ . REAL-TIME DATA TRACKING CURATED BY THE COMMUNITY . ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ | ▀▀▀▀█ █ █ █ █ █ █ █ █ █ █ █ ▄▄▄▄█ | | List #kycfree Websites |
|
|
|
PowerGlove
|
|
October 18, 2024, 04:19:44 PM |
|
Thanks for the great explanation.
No problem. I have carefully revisited the post you've written and I am confident I have realised exactly the bugs you 've mentioned.
Nice. Now, having corrected the issues, I kindly ask to review them below, so I can then safely update OP.
Looks correct to me... One trick I've picked up over the years is to do hash-based cumulative output testing. In this case, how that looks is to run your code many times in a loop on a deterministic sequence of random bits and then add each generated mnemonic to a running hash. Here's a test harness that I used to calculate the hash from running your code against a million deterministic inputs: #!/usr/bin/env python3
import hashlib
def bip39(sixteen: bytes) -> list[str]:
# ...
cumulative_hash = hashlib.sha256()
for test_index in range(1000 * 1000):
mnemonic = bip39(hashlib.sha256(str(test_index).encode()).digest()[:16])
cumulative_hash.update(''.join(mnemonic).encode())
print(cumulative_hash.hexdigest()) Its output was: 7827bfa44f0ccc8dce1e490ffe6f3009b6ad30c55b8578ebdd1ee327b36c28eb If I run my own BIP39 code through that same process, then I get the same result, so I'm pretty confident that if mnemonic-correctness issues do remain, then they remain in both of our implementations.
|
|
|
|
|