From 935cb59a2611052e7d31359edee07396a5c46085 Mon Sep 17 00:00:00 2001 From: xiphon Date: Sat, 13 Jul 2019 19:28:34 +0000 Subject: [PATCH 01/76] seedconv: omit unused 'readline' import --- tools/python/src/ledger/monero/seedconv.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/python/src/ledger/monero/seedconv.py b/tools/python/src/ledger/monero/seedconv.py index a33eda2..0c592c8 100644 --- a/tools/python/src/ledger/monero/seedconv.py +++ b/tools/python/src/ledger/monero/seedconv.py @@ -15,7 +15,6 @@ import sys import unicodedata -import readline import binascii from struct import pack import hashlib From 3eb4a2bd2927007c1892882b78864093b2f72ba7 Mon Sep 17 00:00:00 2001 From: Wegerich Date: Fri, 22 Nov 2019 21:00:07 +0000 Subject: [PATCH 02/76] Formatting & spelling minor changes to make the readme.md file readable on github.com --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a9c736a..dc2bbc2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # ledger-app-monero - Monero wallet application for Ledger Blue and Nano S # Install from sources @@ -16,9 +15,9 @@ Note this is only for testing. For production usage, use the application provide ## V1.4.0 -Add address display -Enhence protocol security -Remmove double ask for view key +- Add address display +- Enhance protocol security +- Remove double ask for view key ## V1.3.2 From e260979160169437aa2f11d484d37324030a8a42 Mon Sep 17 00:00:00 2001 From: russoj88 Date: Fri, 3 Jan 2020 11:48:03 -0800 Subject: [PATCH 03/76] Change 1.4.1 to 1.4.2. Spelling fix. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8d0ee89..69a1e0b 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ Note this is only for testing. For production usage, use the application provide # Revision -## V1.4.1 +## V1.4.2 - Compatibilty check with client version now discards the fourth sub-version number. + Compatibility check with client version now discards the fourth sub-version number. Release for client 0.15.0+ firmware LNS 1.6.0 et LNX 1.2.4 From 158202d1fa80cf1b130269f106fb0acb98383ab5 Mon Sep 17 00:00:00 2001 From: cslashm Date: Thu, 9 Jan 2020 16:22:53 +0100 Subject: [PATCH 04/76] Security Enhancement - Add state machine - Add per type hmac key - Check scalar in range [1;order[ - Check random non null - Add display info during TX - Ask user to confirm real TX start - Ensure sc_add cannot be used with arbitrary address - Remove sc_sub - Add device lock when a protocol sec-error/spoofing is detected - Redispatch SW code Other: - Reduce RAM consumption in the global struct - First unification of UX between S and X: donot write secu twice It seems there is stack smashing when using FLOW on NanoS. Stay on legacy on NanoS for now --- Makefile | 16 +- doc/developer/blue-app-commands.pdf | Bin 353709 -> 355239 bytes doc/developer/blue-app-commands.rst | 83 +-- doc/developer/blue-app-monero.template | 2 +- src/monero_api.h | 33 +- src/monero_blind.c | 7 +- src/monero_crypto.c | 20 + src/monero_dispatch.c | 270 ++++++++-- src/monero_init.c | 52 +- src/monero_io.c | 99 +++- src/monero_key.c | 106 ++-- src/monero_main.c | 9 +- src/monero_mlsag.c | 26 +- src/monero_open_tx.c | 44 +- src/monero_prehash.c | 17 +- src/monero_ram.c | 6 +- src/monero_types.h | 137 +++-- src/monero_ux_nanos.c | 399 +++++++++----- src/monero_ux_nanos.h | 22 - src/monero_ux_nanox.c | 714 ------------------------- 20 files changed, 856 insertions(+), 1206 deletions(-) delete mode 100644 src/monero_ux_nanos.h delete mode 100644 src/monero_ux_nanox.c diff --git a/Makefile b/Makefile index 72fcd41..84fec55 100644 --- a/Makefile +++ b/Makefile @@ -38,8 +38,8 @@ endif #DEFINES += MONERO_ALPHA APPVERSION_M=1 -APPVERSION_N=4 -APPVERSION_P=2 +APPVERSION_N=5 +APPVERSION_P=0 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) SPECVERSION="1.0" @@ -52,6 +52,7 @@ DEFINES += SPEC_VERSION=$(SPECVERSION) ifeq ($(TARGET_NAME),TARGET_NANOX) DEFINES += UI_NANO_X +TARGET_UI := FLOW else ifeq ($(TARGET_NAME),TARGET_BLUE) DEFINES += UI_BLUE else @@ -59,6 +60,7 @@ DEFINES += UI_NANO_S endif + #DEFINES += IOCRYPT ## Debug options #DEFINES += DEBUG_HWDEVICE @@ -103,13 +105,15 @@ DEFINES += HAVE_BAGL_ELLIPSIS # long label truncation feature DEFINES += HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX DEFINES += HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX DEFINES += HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX -DEFINES += HAVE_UX_FLOW DEFINES += HAVE_BLE BLE_COMMAND_TIMEOUT_MS=2000 DEFINES += HAVE_BLE_APDU # basic ledger apdu transport over BLE else DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=128 endif +ifeq ($(TARGET_UI),FLOW) +DEFINES += HAVE_UX_FLOW +endif # Enabling debug PRINTF DEBUG = 0 @@ -169,11 +173,15 @@ include $(BOLOS_SDK)/Makefile.glyphs APP_SOURCE_PATH += src SDK_SOURCE_PATH += lib_stusb lib_stusb_impl lib_u2f -ifeq ($(TARGET_NAME),TARGET_NANOX) +ifeq ($(TARGET_UI),FLOW) SDK_SOURCE_PATH += lib_ux +endif + +ifeq ($(TARGET_NAME),TARGET_NANOX) SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl endif + load: all python -m ledgerblue.loadApp $(APP_LOAD_PARAMS) diff --git a/doc/developer/blue-app-commands.pdf b/doc/developer/blue-app-commands.pdf index 7fb36d678b5d85f73479989f480b5bfaa81a2f3b..72a9b60b502a25bd85a3115dfae738404b0bac79 100644 GIT binary patch delta 167879 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�qJpfJQp_+_Ryr%kEH(aed2+In0}rJB?PUM14%N=@j}8OLnJ0DGW#wPu0F%md zfv)tH=sS0KgxU20Utb0zUxX4upP7qm1HV@f#-kc)I8$}IbZ7AR)g$dz_s{7 zAuo#w5A2B}Sqfxo4T+04X*ZA5Oa7FngQ*4#azK)GO0;7#7si~h34Ac;KAeBFi+;Fk zyI`*lpp0gVZZGtMa3&u9S)%BQ=c=r3{8O`sdxz4Uj6IK_YbzC;F#kSf>HLi?ebYq|l#;e;sHQ=*97`vy!V~uk2`(dZX~#c6+1#xPnqY%8Lp5J)Q@{os{A*19h(45nMm8XMt<+(+APd*xud z6z%I*cg*Vz*vVGIP$qq`CzQ6C4KUnL9^~ugLz;V}bYQP$69>4Xs8~-ubDguR%=Kz~ zzWBCcs*0RQ)jm1=wNg9(V1ddS1%MSpuVP#;1HZPaa=?A4FH(8Aw46y_ zC`O_%hH1+K7HsQprS5748s)8?9&EycGsJA=rd-#i1sH5XPT&stsOmR`G_^_$X zPCz{Rb+THw-B$zmC`hwH^t`otV2#aHKA2nrSL_$8z7hl=HbFJ=r!P_-f(A_giyV0g z(iX=hm>DF862G34y%tq;cN-uI_9K;F1Z#tohG`^94VfyS@O&O&6~*dGF&h!Do4b<-fnvQ8nK7 z<-v-C2{Y^g!e1DX5hH)fDM$uLW7>`3n7g9zB(&;Hjz#cNVC#7?M;%p+aHU!X<@-DB zor6TS&g8*-MxZ4d8Dj@qIBt!5*_5Ngm0j^8lH)O%#gnKy!~EJAvnGc9)jd1%e3hFG}AzrT>cvhJH1FapWyG?`KHSaIAJrRPLh118aLLe zA8cs@libv0#`dWv#4bNv`}zmz z1e;Ruzwd)6s;~S+N2BQOq&|f%LuTdzR-)h>o%gvwo)VyK^aJE*jWYZY>BS= zNo9wBHk5J!{Q(OnT0c5+>FIJ9$t&3=Ft5oB`YQh#uAX*C@k5Far5!nJ9Uhk_`5o~N zS10Olp!Yk2x(S*kr_pA@X7E!uJBWV?P=!PkoespgGCeNP308Ow*Cr-CZ<6Hwn0HX= zJ8FF0>&(hH_*+Spx5&z4gr2}rA;z}{a<_Y)rt(Vu%;t6jz4a#RJ4z4El?#PT2xOpFy&MX%D%DNnJxzf{h6@Jla`2Q1HD%s4FeikJ=@ASaa2n8j zG8gM)d8@bJJH6;-qy9wmktky}hM&_ws9b>?jL}Lpr!&6%vRQcc*5Ehc$T=~(?lR=4 zKCf9=Ta{M2g`JjLS7Fel`FNiRKnz8@Fc6QlXF@H#37eII`vY@4CINne@dB9A_t*F@ z#fX(FdFvb{x%QY5@Smo1&$-4$36R%}yNA04A+WF1=91eEh86uo(m|iHtC%VpdX)Xy z*Be56G5L5(#fo*{0A%f$m+$?QNq8Pb67*ZL9L;D6ka^5*T*>gkCS?E7!x=p4hWCU( zYY?~!O#4jMKUsI)SFgJ%+q=i-Ii~4u;6ZZ_9hC??wuV(ePIt6@!{VqnvSNt~#~Zsl zjZE6J)`!uP`Q4`N-6=J~W&5o+VdT>lx73Ls7hwVvwqoitR_Zy=k6eco3(fIIabPAI z2@LTgRhfj^iHd2#^8d?WBLa;4I(;JKI+@I4m46XRw4$ngrB>#tp-xt#s%rMPf&292 z8A~^Xzl#Te2dUj3`gr`8rf?r>dXk33(ZL``Z33SrOxu}FPhEIOg-}&7q||;UPD-Butu^b^a6h? zRwxSu(uqP!1v;(h!IelMcEiHFdn~F7#;vGOJh2!Mo7gf$_8bv^r)Wf?mJl*;bVfWG zNIx11HFOH>lrmS1^lN@)YkN_%Zp1m#8}GdjO&&$QjYnsBnRJYv6G0*o(a(4vHSgx# zx#xzg=SHl@9}8YdQ4A1-mz%|};953z$DI>JcS!i>i zRT~Wa*-3dKwCDb1CyO>4&)HgQvL;Y7a8@tH5iNO_Wmcr}#<5U>oXK^Gfnh)t z`vD4lfazYXMW5nSAlFx%h;#Qt2adyg1yxgHmC(zpSIeDo4rz(+!#a|*x{~_u>DAH#z%g5^{uz z!u(3210^T&OQv|MnRY0c>(IH%FimB=eciTeUiU_4}%5ml;_V#0@G| zy0M9X7c1Q0hYc$Ve7CG{6FoY+#ty(5>9UB!1c?Kr^7+dVJ3EsM<_{?7649)xWn>_nkU-og$RXN@A(7O%*nwjRV@_*EW1&M>*d`L{BoxDg8HgTqs5!hf2!62BLMo&o@jal^?PsU`YK^T`)!b&p z5t%H?$UPs?M3ahKnK^A`wU1xc{N++4|u|&{`QEEDcGVUud@3_MR znlo6aFc>jf7?cs>)u5KSW8@Nb>8*W+(IZ@GdrFNxN;Z+Qctf(rD}fLNz^n_{@EO9) z16&au`LjFVwUOe6VYE01gEyn8MyfHYtW_70&!ygqwwn65OHM0=@U*R5&VxZe5V^Hx zo!d*3k#~GI4#oxi@rWxmN+%~u%Fci2cwZpjfRJK3Mht~b zD^SMz7uaA0#=52v+Y`wt5|R{a8=#cq#xO(@e*)L29|1O&<_BqZY#||{FILrFLG1_* zmB zYI}F>k}rOd>L%i=0zU3`2d}d+8GB&O_uz)*jBa_3gVIfb47Po{z+=Ra^WNlhxSK}z`Nc29us_<$p1ap$SQOV4^-bvK^Q&BnH=$5rQy&8};6 zd*`L^NAQEyB-u&F$?jC7@J<;GYsUqd!AwR(xKS(VfFy@{0uDiLGW*;F*0qWXgcBtr z9~085mu9S_Y$`>yeZ-_}q8YmjJ?FTYG>Q*&2@Q~9%VA~AzrGtR3@!3KssgXdwPmFQ z>3uq?$cqIFFHU966zR2e@<5kq+7tL7Gi*3&rh&{UHr$f8h{E;=Bnq*IW4*?Z@Qy03 zluSpd{~Z$d0c8CCw7qOJDmdM$S*bfA)_FulE0^7qWh5whb_4l_br)>;txc2dw`Q8q z!#P=16<%Q*$g-rWB&}N~y^~7mx5>uhKUPf#qAw25rS?E!bWL|9Kr;kki^rXjVNi@t zd9K>AHgr(X@LfJSEGU=?bCAQnQ;eDPCQeTn4{b5y1C|A!xgArfc-#=^{_Jtx6x6}A5fvV&6XN}!_;icD;@0{HszzWO=500s_9o)?T{6$aC) z5Oi3Oaa+E?GsXgg{*N;K|9ma8HEy1l0|Ufiu=x^RjH?v<+dF!&WW7FAmnE3r8>+^= zRN$v%1&@XwtkhHAZ|7c_bjHWS7Nsta9}&HDmzAw3bp!>a*$;=GQ^idqk}mKY zv(ssl7dZZklrs3$9thZoHYb2h3O}I95JMhFv%y@;zfHdF$WD-mEu#2n$Z}uEKm)o) zc81^2wL{z{a_i@uVo4lF>;tSePYMy3hN>CnK_h{ii5#pttUij;Qo!cy7SSpN-+fIC zZ8%t3^P25$9M@Zc0$Zc5d`=XUfL4u85b~R?8tL=p_(jJl&><9Ig|<1551#30pPyxvSE2^qkhy zf|6&{G4ox|X5bw6Ltb$_l<8)DHAoVt4$fYWG_X$1Nt71dG-!`|bFi=}i2!PuWJ19_ zEaPynA6jS%{b>6oG?)SwY=zS`Fj+*Zq5n?z>Q7KR%Lzc6jInG97`fH`T|D$R)z=p1 zY9jvdFjUA2bpR=oU7ur3>Raq5&&r~lKg1FxIj1W;B+K|*sj|)8%AM4{ehSnqbvhZT zY>a%q{zcWzmV|GfEjlH%+XXQDC-&DpW^kie?xt_7Y`a*$)s|L#BKYw_l$5twcXL{q z)V_1mN2tep4D(fmqxOAxkR;pQW2{JE%=mUr>ZyjnK4X!znS|;&Dfwe187*m33F0BU zA-QS8CsQ>-sHPxtZ{(zRdc8{Mzf3_679w;eqW|=6`1oKL<;?6YTrG*X+5TUx?ePDv zwY{jG08M}Y+${_*wZ{RYPbd{(s4-&N(>9k+q>+|J*dg5BO+Qq#vTzIj)y5mdhUCS7qxjA;ILbM=KL`uP zy1RK5&T1D~T{Mt|{TS3y+@xuZ(nn975IFQ;UnMvLqC^pxdeM(a(gEYw4BLjD{oRP76dQT*&UO{otK=W1C_ddo7zB7Sav>`mr{4 z^oDLM8Y(#cmeTkE^oEtHsJ52W&_r(x{1@XGlUkkxRzVSEz!HEj;tqto!1lgjX52N< zpvz<+v5MR=_p+JnJ4+aiHa8B`A$%;|^XBO){pR8Dp1L57!D86g?dN!SU8SuqYrTIw8W~d}<@|n^Oq!SB!_2;Nbu z8RgFS?|@3`9bb%J!Vyrot3Gtfa2l-nOiOJ^k~5kzk;?=ub4v!B?zj&IH7o|l?^4ts zJ87uqM8E~w&%tB$0r`gUkODb)!<21==3o$tROTRn&}o55;sI5)wNRZ;3@OL_VRr~YV*blh&dKtB!^<3O|6_^$qh?T_u*VTb>bghwiEP4a;pd;u72_srsHUP6Y$I0! zGsaI27Y_I>b~kE~W@bUirq$*srQrCdI`gciCbzYyf3>6`64IeWUcv*K2Xtiqm9!7hYtO-N%lKnUj0606Uw=f0D3#?~&b#>RctWq}2! zDLi_zw$J$>tl3GQsZ8Ui1o${{?a4Wspl`O6AxFfj%8^DCQ~mM!vA@+duC{t2;-ZiE zI}@m3t#cSYyr0$B3nDzb!HBJdFf;nD&pc{YfrVtvY4p{$B2d7vti|M#QIiTF)g|RyV<3O&W0O4(<#WEBrOo?*t(vo{R zy|Jty$?EH9@2Sbjm35NJ51qzjP_^6G3D6t`W&^mR@*ov)%MJN;2SVUJ60P)jdtfYR;Z3jI(eioN?ls_@5Zh zce`|R#>5JNI|_2V&te!?MXRgccg@URo zeK1rICf4)%e=8IV5eqj9XZr357&1-6 z&$p@bUwX_+xv;;X|Q*B-ZuwiQ! zgtF`5;}I!*mx;@$$m+yl6VVqGl7j-BGJzg6u{ZmFFMzCyKI$-osy5|+ZS&3aZJ&;_ zXrpMqS+n5T_WA%h0&NH>{Nw1g`j*6{SH<3mXS1v*K^?jx1Oe)9j*bo{!p-jN3wuSw` zD~)l-ScdPKGDG%f^H6XgKiKhA5A~zgF@P27!GLW^=osLfy0DM}LZB)>;P9C36 z_Uv!_bv)y1+x?5ZkJ>1rfD(AD2c7QwYu3j4@6l4afk!Y289|TgX>=W@7P~lFF+U^M+`IM;reJP zfC}0b9T6aqZx+$ikM&2usN(lDxmm#0FW3nF6;wmu1~DU*z&0U3df?Z09`BQ&ZbOY> z9n1`Z*!MQ*=QdUTZ}^q_-u*91G0X=Yn$K_2)e)_w5m3_t^L=Cc&)V{@iJBdd5QgrM zZgea#{zQP>TJJS7R!7}Tc-+*r1x7$04&Xrq@>_>K zKwe8)LsmBX6tDz1%p=0~aft|x?M4qs4#xy?bq4V+tP}P489?q#0qdE;CX5Qof*6`K zhqno-03p;c^Z?05ppIV$9~=VLNq=yB!rp?ckbM&g0A()_4j~#Qe+7d9*|K~K`(6NX ztO`be_E9`TI)SVld=oJMW&c!oGr&Eh@M`K85mo0MApp!w<25+{qT*Zl#EQa}~pFOb51=HJ42IWvXkLS=&Xf4J_yi3lv~ zxWVA`g1*V>L?5+3zzg<4J|n}&Uo4=%VFKJuUnzms5=&y&4X}GRku=JBQSyn^0m6TT z&cB5K_Lj>_xd==7z39Sr8~|a<&X%7Y>`pIHlVGP{0_fUQo-*Hzs?Uk{_hEPo0i*) zed9~8Hyi&M2;B{00J)Hl<0q`hy2m@L2=7nG$oWITDj?BGWWwhQykOJo^Ath*FYLhs zW5svRxpl9B^Ecn%>bLIKx6k``FuPt3Q45hCF6@bc03ehKR4>Pt4{o&p(GcMe{@dgA z_SP3B@Km11sKIJd5)z^VhW*J|qa(}>5SM2G<#d1l@Zq~b`_Z>I^7s8sxIoEQECA38 z4os8)IfiFuMPST;cE`}nTs5g!YS@66C=u}Z2Srg;k(t1V+OOevh7gZOGzytI)(T#N zunv?`5~lBYI-aFLl_Y8qtiQI6^c$O#Sph>2{DaC9sYH4r*3_)gBEtADeK@M?VpFbP zlYf^PcXR()#yXILTE-K|KsR}8in{>({rvqBMXXe}-sll$yDig-phux&4(lVN7lgaG zR)Np&e8W+3!7oxZjdPf%5C?J9#GFq~2iOc{rj}XCYqPdng`(azL`7`Nq#fc*FOgqf zDeXr{J}ALiuE5It|7ujFFo5nR@9pOllwixW#yIrTx`8kGODWI4^3(`~stW?lrGwuS zN#tE4HP}tMx=&9-05=|4iIBV3^48gXm8x;jRk{TNM`&DXqop8*$D*or*tZPZVmwaM zzc)^MCaKxd1|(@)djpeK=zt#_Sug?vNC%Z5$I^E7H-+$EyT(Y$fn}xt)`%D*w<3}p zWIzg_pK1Kw*dLTRN%g&M0m%W(w59zHFjk@y4pQrgSiYkrX>|dn9u8xzvu-L_4m$Vc zqsl348u0jli?K1HNn(Wd5o}DKTWz_PThOBAmw{(Gcn0>o9f891DF#pguxd_f6=kNT zjoc!iA{TdcaP+z6?IdrrA{-t)Bl9mr^R}1u(jUhZ)d{7)JMuYlp5*~=#$Pm4`Kbh4gf5@~2JxmRl*A|k9< zm96F#a1aXjnXD*6lXNOnix(HNdl%Kfl16f2FO-`%zT|saIW=zx?R`SSQ+)ptdqIB^ z^>AUPGgoV4c$%#Slk<|rRV|ewlq-8eI`3qYLmX>`^8~IF^e4b+;Dl;b&4S&G5tQ1J z(EXk<1Jbz@Yj+6}H!eNHg4A!G@J!{d+lZA6{HZ)aFstKeIpvLi(}_zEaij?F$}XaK zx?lGp*eqwkr-&Ls<-3d}4PB1g@Op23DrZf8a<@{Ohrb_b>wGOO;v{X8P%tIkIccqy z=SC!pDH%1nO`QO@Jv=1a3n84O-twmHy82gjrfcOQ!_J1?A9K077PIV`ovYbPtKn7mFB})L6k~rn1*9`FmIs>@zDOl`I z8xfS@7LX&DSB2E#Qu@>)!)ujOA|mL7TWln^a}#bXdy)y=cEJ6GRkG1;5A>OU>%IXK zjS;r_1ePtKwx`rKiws{3-cZHRXzHb;l!pDB;g7}aZf78-Js&%e_1ldbP~Sk(*cv{k z#3tbgodf{uVtt(&G^u;}l4jG)11WQ2h0=!d5>}}Qdr4&uHwlfedI?SKOx-ONf{6xi zuVSM8^eKxVT~CZjdytJ}jdljnklVL26cb&)jNOlH})E4K&W ztb#>A4qfF^MWRybBhj1Qr+L=OW5D>CgEcRZmJuF*Lj-;{S|V)5_z#IL+SKSsm} zXvB*b%5BP)SFsB>uHE;-ji-UC_cy!I*bJH}mcg}t5h5LnK7ydHT^b7Q})*3cbL zqo`VW_43Kx0J^_VcZMJF@rAus&3%aDbkW9yy^RZxmybQR=}={#$6=aYn7THl0OZNL zUn~IfGoj!~Rj~MU>RtMF>w7^Wzos!LCZ64(##O1;KZuXDO zsh32+kJFdgvw(Ehq=M}iS6bAEwwD3|t-7XCVpdO}kcm5YfEtFhcFkNIlt)lX1f!er zc5(l&G8Et%)~Cm*;dAqMB0*)hz>DEr#uPng0 zI;sbZbGTLxNuB$y$eC4t>O{mxOSnnSgKk(XLG5dT=@HBT@X3?ASC8vacJ_rQOwt4e z!uYzwyx7<>NywIRsq6u%(%K>FdLb&&(rJ$Yj}dg7h-{Z&lUqmRJqA^HCqoB3p2vFm z;X${00sS`;%&XkO*yMu8OND|k0YBhq_wd>3%8iR~XNMZbIf80ijBwF@7-{@Qgrw$V z^y`gRtG0TLB>QtALMmgj>+t+AT)UiV_B@rGdHSpPuh=$5FtQaz+y1KVA{ zS@^zH4Sie7cuat*lWY$$=7I|nCHC*KZO~$$#f>oYS5`9(W3_D4{UKacNg!bN_l{oo z)1Wqvei?Z(dT=+_qZg>sGAmh)3?G9RM0Bw@-8-thc9Ev%!>eq(f8xJxW9+6K6#e!p z*!MAFx`ySrNv-A|)}}z}Nd~$9rQnV#NPU~?(ng2wgip<;)WED92k`C9&U00IO8iFw zOXz!&E<21ETP%c!k3PS5We@OBFpAIXX#UzE@kMueuAW;{U3wzR;w6N*9!D6SCdsjZ zTFWHaMb&&;v$P)i#)xd3+tBT;o-S@e*o2kphXwAwVDhvR5>^u41YSxg6eM0P2-jD4 zP8%#ak!zf#K?+>r0-?oeTE}-Cf8?W;%Pt=K5d!(96pU+)^z^Z5=M6X&aHJkVOdj-W z_FgYqp>M4gxlXli!YR23*_vgGD5i6yLvJ;CyV6~QSI|$$mJ+NT<`Q((Y>ps8-h#@^ z#pGiDnkg&BOudt#Nt0P#ZfBkT*WT!=``f-%7W2v8-3gMg{He-6lld3+F)oKAot90g zS_?#5V|UZGBZ#Db)G}bK8ENb0lvMqyp`}^R^JbHiyTR)(T6!d!F=P zy+kr-C{Y($N;>$p-KCg@5Pjp(Vr@y-HFqsS_-DPv zSttsJ(4xgRf522^=DxFmo&!WPN}FsRB;Sb=mj86+(5;(=y7nl)+ide zIfP?1XQMv@wz3Fof!pJ0awTKY$gdmn@R6y{m=oPDujrY!BPQul0~?6Pb_G0e*|lMI zevlc(aVgHQFP0b@REE@ti6TNVo`8ek;~F`5%21jILR^_TW*tmyJRI; zCJf!AjX8OUJ)PFoIsty>g_}B&y%g!nK$cK^0zKl$!kwi>th}7K&5so&){X()4s@$m z8^QsXcvoH{V^@s3hC-iVPkjQ8v!dtYYftjfKB321R}-M2|3tE>c>8;Wh95jH=JnNF zkHb>}To9lFF+R8uCZ~+G7{TCc&09hhX_W^axB-JY@~G40g+G99aSkRZS|QKn_xfN2 z4$5+~vvA*Pze`G@0KQUJmcLvie;Q^$-2u~&djHs#h-=tNb<8^*xnN1tb!veMo!c#% zh!J&7pb1cj_w{!hk!sM@Y%CxSZ?k&zcWRn_5qWOnkJ&ptj_`lN#FNzL|65CzWmDSU zjCF4TamaQ5hI7S&*IvvhFq$Q<^NI`V4cZmTInyEE=M}9_)}K`yG*foPwJE&DUK%df z%Is!xCXq2fDZhAzw~kfqV~Oh4v?}xA6-~^CzX8AyIeFviVPt^o6w?6b!QYa=!plb5 z=3CnBt}^YCcJN?ye<0!Mpe7fC*|}FP!GQ31WT!*;fqA>hpbAdHW;LqdO3y2kWG>qI zR#_p@-=Va?6kY}!L_r)QCW^=J=m&z4VfM|KN=-ACy2|QsSY(Van@$+H{ZEjDEqzIP zUjV#hW-`8$)QyNP!h!dJy3? z;gLvP!Q`tgY}UVcqfqNFg-K|IjfV$uBQiuk#{%!pSl4LR<`sQHycc;Ku?`c$yBAke zuLgDMk8u?T2Y4~Rojh=|^)~sto)kdIbN~`)mpPV)Wjcj$cPOlrblr6|lc4)@wpwc3 zL1Ajlm}@sYHc!rF!~Hw7a%Pga^^>i?t*-$?Dm6;TrHCxl3ESgZzrJ!}VP(%}afq!r z^KsrI%}zSiBu)e>*P)X_S>Qj`0qB}t}FRm?%5*JY#6zbOn73~%NN z+?fJD>95oZ)(}vxQ@nI#IJHUZ&zqBZ(qd9`wrQf06^1TR$;0(-`BX8QuK{qnS=HY4 zf@IjQ(eZHK~~`ctB71sK7`C1 zTx5PR>qmd`68x*3GIcQ$u6p}2@W0x9m+pKL*O5Y!=95yS$YdHE29HY#@z)Km?|2;v z+(p>J_Cux|mDFK$jaE>jg#vI2mx*m^YTz}jO(}@DXYDg>i}UbUNtTG;HR|e)kF{2; z?KUd0f5KLKqASxZ2^6JMUS=&hOLZPPT1~=)W4+5skeo2O@eW)Z8E|p6@3<{2e~tMp zc{Mpe%X1bX)sC+4+}A0}%ltMx09o>C02Rpn#{syi21OCzLt}|#LI=2X84f(Z!rUsz zMz+PDifnvIv?C8#QF`+hJ(ygDL)gf1gY?q>ID{o!;qRMkqP(ZGZ?cYa5QF;ZaMO++ zx2&FeD8ibK2UUOYKk{A`)IK--dz|FA45fRcK+|_r+RLV7fCs|{M>;vsgb!>f?k3-A zey2MArMaRp!*mM|qz_n`WM&cb82T1tn`!4CyD9P!AIiMO1WbQkMy)1PRFffXy_uPeA3q` zWUIg=;NIHfkiZ8mV&c_hjDCt+&|r0!33_MS!f?aXiFgZmJis8rkoeX0FDKV&)LDF6 zuX~!X*Xwbo=E`9Qa$=-kT?wArE}nw?{4|=16&xjOk(&o**Ipq^?mRdVlte`K74o)C z0h?GOGFh;q3i%kRt{%yF;rvOAO0UA}slb=1Tzq$lS?pGmV#2G4GXk!hyc5;3@hw*% z|H#3^_*dyD0U(!2EM1w@3mk3dw&%wRbPX zwQ7XW(8A1t|7~jLObO4#p7g6-hScruiJ~=ONq#UmKG?f=qDVxRWS8pn!SjNv3f=jb zAsl)(!c)x;3e}mIm94C-!$I;fqEPTV(m$AoJ+9#{1t0?x>@SW=x)wAmTNrz611*y! zrU|!b-&FZaus+0l20e_2=WsBY)r`#H_%Flb&;hI*7bn@KvDiYCKt`c z{^oJ$6ab2gXqvv*q$gWn(0fo>lw7ARsWtvXmiI%78G0re!R+lO*-g>cB+0!VJgz5{ zb1KRvj=Y40UiGd}^?Hex5%`q_Y z5nlS04NXw*%0=q>_7pn{J~V4QK~15Fr;iXS=g>OX|1!eaDbzMP^e|#=(J~+SdEUw# zY++CYly)!B$1zaSgVxGnCmo^W@#`e3!{2noC{;FBf&WbE={&$u3TXRY0WZdjz$;E; z07|#b5>Q;lp6qi#;jyEevKd9Sou>HY za0WOurP-Q}*_@(ZK|Zz}%NZ9LCD1GA$?ZB!co}(S!Y=*w`HDrA(H*nJOKmsRA|x22 zP|#rFZ=J@rIyaZZE&}RpQ-tR4lE8|T0ZMY*Q!gFBRU8hw7-hJ&`a1s(eZjJnu8Hly z$Ta=?FcHXElu5`xXe{U%L-#_FSPql$>=I}ATfnyg>2kk*|2C1ls2@Em1J3ULhNV3= zhAOJ;-e4z3uY9E$6d8}=X}M=1ewH*qD|zSpb7*2rEpbe_ia?jT=@1@FsQrl!1{m7V zj%}Iqwq%iUcnpLujdrF#gN2|vYAomt+fFamO^z`R_!Jcl4Q|?kM|ckZ0y)GMC}*|l zPo>a7Rtyv?0z63#`Eu_h5}Fy*=`pB%LCoG`F$Dj1#34&{{D)%g@!P;TBdK}!YH@p? ziV=^v+}|VjrQeJ3I7xfBKZ1R*9I(<^ryi`%6o2LC5)P{0sJT6=gK;_I)P&HTK1U1# zyxWt>yzEs`QaRR$QeuTnrW!mYjaA7`Jp#LJQmR-Oh2jMns^-^6rW9J~n67>)FGuPQ z3^~=7!hxMfZ#kNiV5pbvJ6aee_L=UX;B?hfG+}V<%Q57>-Gdps^f{&_007vn!)%ZF zidNj_dxp}5vZ-&yT{!E|d58Stb=&xBqL@_4o}bd3w-OtRk^k)I&#=RLL;c$6)S^X^ zld`u=q^zx943aJ(zCvFl=P(Km&d^R7)5QL`h*?Ryf9Xy_5AlS1`LB;n-1L6Ve zFcMWHJq^>qOB2Q5K=TWO2GEkIs<*U(~fC^+M73wU!$C8A`(z34BQW zDi+sLXgtl*7skoZ)y5;Wk_p&s+zul<=Em}Y511C})j+qR%@j>mLpHe?Fr+v;jAsy1 zykb?O21oXlk=7=`j1vLn*Yw{oGG$h}T3@#Ny(1B=+>a{B0F`)e4M@*{jmi*p2of9< z-gka#eW_7@XUY{`dZb-qcq#A|SPDGWX4xZRu zg~K9{v()DJSMyXE3*LVjOUod4YZj`}BqaRM2s{m&M=*@hj_5~SC%zRz)fdgRuC-50 zXkO&ZAu$Mlbkkbe3Fu;-kk`X_CumuKL8&s>N!o)Ga~xLe=}M30MB+}H;0P;=FoN`qH%fhpUeJAbY$yOJRGP;gi2PFKZrHUuB{(205kjNaA zq2P_)6MJa9e~-a_Gb`;*M=@bIdu_;LMj*DwfFAI8@1B@KeJrU(9-Z07il0c(|H8{K zw)di)izXD&f?`nq)vn$fS*wH~V4%1pf7+*aK(NE?GXJdb-039P(^J7stAA`|k%H@j zij;i6^z_%j2mp_OU&UH)l@)Tb*3Kih4D)nE7BQQmoGS6kovhoZsRkUjwg0WKv15-VIK) zI9EeRhIfri1q4siyesMCiakpl>xBW)b)~CnC+{z%1J?a1HmX|)gfb*$4=DzIm!&4@ z^ztx-s>BsansqZ?{8mbyv*=mh9fO~J`N<*4;llceENg{PqA)1pWqr>hcth^h=CidzCVeTg zpcVQY4sczwNO-QG(?pL`W5-AE9{NQmn1F~{{>ArQykNh|UzP=>qM`~rDVw8#%m?$0 z!}6Vy`Y=>rL0iODFQM#rHLY|WAn(-4pl~y1!kXSZeG#3S=Bb^!akVKCzzV+{zIlAR zOeWqt(o>S)B({?RWciWz4?(`;n)O1GuN*gu1F*#I=AuTbI}-h($ZqbzZcD%ZS~;q0 zi(#>b7?1LBG$z(@X#h3osDoTSBhkVz0d^c1>ZT#5tD<&(Wy&FQPN5@?qgADZup~`Y z#+ari}m>+YDH z8z2qQYeS>rFBm~IE9lhNgFn-9fjk_EVRyO;0G5{HkE4lZ7&x4yd36MqiG0;8g^QmW zEqbdXM!t+e(-GCB@vZ$*$z{j4CH!^up@}S3f6kV6cvM_&rfHvxEwI8LbQ6hIeDhJ? z04}+J&6i*aSUSp@h(zqIImbq#tBS3;2SCE;!_MFUd9Jd~{@UInPa#)QK5~n782MXc z%G^t93UgMlO^L&5h-{k5!rwED5e;yV%!i!g@@@h~j2>u;LfO2aq%Bhbn8&Hlw+soD zY9BsEqCf4{vv_7h5rKu2iDlFO0#iV&zaRZN)&1tSAfDC(pJaq$YhuIqQ1+|EVri(b zjgh5)iH!Y$$(HV;@>>eZ&x8-4^PL2Vkyk=F*a&Z>xgqL^f-eTtDbW-8jTn0On=Eyn zUSwWW&kTFgq>Gxhm-=`SqTs)xYR#xQZVWA7P1x!IXn2GnuSj=W#}agoP=$6BAj6+nwUkiA%Ub87hd^(en9(C&4V zW^=H(+(;iIuBJd_eMZ*l9PZ6e@+E#(z`y`xy zWM*gkl(U>YJKFYWWFAB&_vExwa`VcFZ`hcG&SNd*s5 zQ6^2&VWyeK21^VHJW{$n3i(n@4Gx3C^jy?Zs8S-}jM5Ol*4u89YK~62jo&}BMB1L~ zCRL4uzaH>pR^nquj-%V_MC7kLSyu84ppnM+04nn_A@F5=8+=#!u(2oG6L0v=c`cD6 zF(PyFSS{?kx!%&-Nv+1i z+YuMen$w{e^u0YiVOTi=RK_r2tPTqY-0Y_(S0gwQU$~tnukSq15IzSEUFyi8anxTc z7VW01$8lx&EKYjolIWp0+!CES?1ws5`bNHhU9b7cs15!E!H`m_yx}E(`P{sL1qPTd zO>iMOxN%&6!Or?_Uuvqg#o({KSeUcA+H4zZ<8h_(i3}_@Ruk6ty^pIaRddfeYL~~( zcKSw8mV(N9EDlS;EzG*DlY#9Lz4Zc7Oa7zDQT#_D?w3V;Bif9$6l8X-3k!)P-}|lZ zMnPxX=oVls`j*;@6nbobwO-*9(y+`o;c8lh%%Dgb|BBuTw;7X9eOg}=ZRVQ&7w){B z^jr1Lar2m81&769$9}%yzSwR;QC2{Z3bE?v;U&)A;)_J30^vvP+-ah}qEqx~sN_c_ z1|Zc&&RNm)zL!SASY@~4N+k)u1M7`{?jS9tyA`L&FD=1&E}~O^Y5nnur_z&v!P>=t z=$C@Y=P0zgge;}yK&$SbWds}>rRTnLh@K*Gs)Znw*l(;FiGKI-IS=rA%=oid5<%2^o=1>~C_og*DCdE%8s1vd zUS;x*Ji?3>;T$N+n^PWr>c?1-Xz$W}dalTy5GQ;4;UwRGAh!Mll}MA591TaAjHJYu zn)Z!ZY3#TTs>t<;$zzbcWa@*I=p|TLp66pJU#8%v3&ECe)?OoN8MhGX3o#q@S$*#+ zWjUB4Ioc&8TBV@WDXT>Z0UP-{{P>qY^6}3%?X6a(7X`dUnab(R!L% z`gCmi0-?!&157FBuV;#_mn-$TiiiVd-UCvTH;Fjsc5iW!Bl@$c{J9!qlD;mWgrkgO ztR(+Zsbq7!qul^e;@Ox`=?FtGE;Bo(Z(yZ}^?E&RyB+3TGe*o*0PnINt*e{LF+DF*-^B;8_~zP*#hkKt)7F_aF#Gd=W>!4>qM>q|72j*-L$ub|HwdCCnNWPMBok7$*Qh4$e2phug{*e{JiBbJSb zssSUTFSG7U`&sjk0|TTdgq?cW0y0o@>V(R1O&hmS7NXmXZq7T#{Fto*#I1uM6{Hb= z@&yjDwt{(UJaDT|iLi!49^bn(Do)Vy`rCZbx*k*R!Kew;3&^6Q8mSlG;6`4 zWZn9BKq{y#OR;L4q&W_Nrnqi>CPyB9pTn+%)T0D!whhWIYVByFUyxg2z2#Heu7-8& zqr_xOTV(=(&xXRiS?(U*II@1tBhqhwij37ke z-kv7qnQ1Z1szd8gbus3D^ybG` zb*IX?-fR4@WWp3&GrJP8F;#>2bhg$>a}%R}F-6kJ+6=N^sEDMcfbKAGGmHUzd9UzJXArHt)8z!dcufSA_YEJ$`<) z{ucGU)&3M0ubVVtg6&!8>s-4%JyI1fT|{_w+&VFsQdW;{Kez7bJ!CzZG&cL|os)Bu zwi4`yMyR|F9Ror#oBU5QRE=19=ORv==2xCxP1^~*mha-EU?%W?Q;UYig*lLzU-M+EheO^9P9J*eoIo6lEmEY^`dJ?vDnu+ zUSoiT&z+5Xbg?>rl{i+U{2<&9-@jA`vJdVO_eILRRWhU)qhHk7Oc0G-J90z?Ph68kriRqZs+Xxt**78eO_%5iA zlDODrC0c#Luy}%gYS*B+F^1gdZufChG{b;}mw55NSJ!NRtRK7ds*W5K$68EJWT+hI zIQ57rJ7hx7VuN!XYkIx-Wzgs_Mk3OOVJU`2W1NdcWw8)s;!&|ux&RyE7LDojF(7-f z{|oBc=SK)pu>3^d(*kt@Z}&&~X;^%tTWEl*d9?V&(MZ zD&>@g)xhq1c*~$FG|7CEePh*XcU=bY5dEL0ZH#7@Y^)3hpd#KrpW0pnAsv4 zY9Fj}cBQkXT73^H4(a>16d><`-R2+EgwS%!nWZOxH|dPoJH;l=0*H@)5Ldy4yxD_) zO%aVn>*1AK@JO{4j>Mg0{cw$V2j@3rpzDzTuIp`FF>r~C>AeG`7cwZDQ3;(}9B6eZ9Ts}84s zJVSmU2hx=lla&&Ss>mm`C6u34>3mLa?Dc3}hwe2h@^=G%xhM>&uL8qmKVoBx)BsP_j|CvV^0yq# zW8YJo>{5CCczapvU4~*N?cuKrjvV2CJNCTRB(|w&O_lUVV1YE*^#aUiyA%&Uf8p)E z&+*s0D&PdR1)>_^KP0ABzA!Taux8Yj;=1RyzN7R*{Y=lYVA!6|rzw!&iKHpkE%4Or zy3C8RR$h^ulq{ksal%j+aiQ>6$u;Vp3d^|U>6sEU{zp~ zM>c~uLDb8j63WfR{ZeD_n!(GJKXE!!By#7hzsFX7w&}sY1eX!!t>iCmv>NR2KIE`) zKZP=v132(*l^BN(pTs&1CQ_GQr>mepQ_!>P3x>Ne)mC*3q>gD>Sn3-A-`{5=%k$0FRj%-#*y3V(9 zOww}OJ;Y~SHTf~O$mVmg@_==1o;#N4{M4Yl_eS6tm+B$#E3tPaUOXCvTPN6a^Sn7A zYu*zScYu9JmA_v~gwhUicnWcwKl>1Ji~b!QhL_D8KzP=;(tH8H%0{Js9x79!Z9N9R zSQ39YZZsO+D(rkg2=?HvDUz;XOLXEDaZRxNW!!~ILK1!ohw5cHPv3EbfPCuXMXzZ` z7}W!>P`Bq*Yt8bL7bLx@37DUYpCaYCOa_0O?l#R8@{^PBGE5j;>Apt{Q?t!_XV&KS zap94t_aOwEIKNhEXnP%hYvWK_f%;+4v}l_-(f3gZt4<{BuB(}nS)X2xVhirG&LuLu z3xVBS#sI$>3&Bz0$foD;xEbYXg_DJ?1drI1g5k%}vUjcVa!02O^BFU*$mhloGoH{# zmR)u?;mqz}>`d?!d~SLP5|6$C(9y)538uh&opkRA`%~)&>2N52!MJvYMIvmUKIOk> z>DwA~E!%)Rnj)XReo6jne!!QDJoTeXu{c9pqwJ$?$j?f-QFh81S29~iss~4LQCtb_ z?OrQHjA0p$m8$?p2>IS)u1Kg{imGHz(8ZwW)3Yku6lyrv z$3JSE4oA9^omxgxOTEN9);r}DR}qS5y6W15z-I*fx~OyZGUMFw5Unq*(rFf5u*CMh zFg+FF9mAME+Bk)6Upg6b{5FC>$np={4Az+FAAaaLq%l*0y?}4vXxR04` zj}(idha;PRERreFxG!g9a>WT=jcVWlprLQ&ea-H{*X-QPGaM_)H0?0RiFL?BQ|Ejkn;M?MOaGbH*m?N_S zly_8DhpQUsR_q1K+wInew4AgE=&rs z4k?;SKM=9Hrgdl&lW3F!)sU0o!rt^h(}taQ(Gj3SGGNlk$%gi6%s(pC+f`2x1p}=z zS@wQ^klV@X*&y|8867|FdV{aIre!d~8PCkGn zW93U^R>R!55B?f{AI+9JxqT2lYdKbb(zj{B)ZpY}_~WMyTXO-qK_^dM+NWx5BUStI zstXD_FO8w3d^ct+t|qsHe2@0wS{Ot0Rw>+(G{E@2XXL~i|8I7d;Hh&M>VcByz4!co zK4qj~g6UB?r4!z5CdU!gLP_*;kK5WS!UD;VoOtEV*UZ6*Bxk{w4Ej|V>Vp&YS#IawJU9hNdH8Cyj(^#E<7oOaqV5yH}M|}3QJW7F4dfC7BXeHFjnA>eJ z?Liu+lTIy7Z#1p%UL@Tx&EtH0;~629`pcI7X1$3`FS)qWb>RjKTEVBr-DhK@quWWTmGs77&{JtwE}IJl&wlMm}iNAe{x3AO=^bS1;+d}dBf zB|N`^`?}9fPBfYnIvB20(N}wa3q(_A-Zhsj?fejEWqNr8+ltj=u2y!vh5JKZv*GqT z)^{Dxz(l%oPmUoShQ1f>_3$3JN~Qdk_;rIOiR019Aa>((81j&{1QxKL16F>PrdH}N z$mcU8x3|}1Ci}eL+gQ7c#0l9c>Q0O>i>G2o5oIL2lM`Xbd2d9b%^*vE*P@#-ZK`Si zI`0=7A`KO8B<`FS*-X6q6T_UJS(fSNIR^Aw@zlOHYh*RZt1a%Y1l`^EH?*@1A%LxM z2w#Fyq?vbXdt{ck8nI%)@xmYQ1v$_r1yPnMzl8Ay%L!pWjT{TKP$mCV#wXNF*Hh#( z8fxi(yzynn3m6T4(&)N>Dal7@iA%z1=h*gJ`>6MzG7|jUxf5|x?1^=rUCp8smgCp{ zF7c#w&_Ycz->L)IGHA6ukYMQ)`IjJMzg~`O&#-tm2vvDD z!{IQ3;#qpa!6YymD|ubG#Q4!B?-N1=b708Z?@Ijsg4>6|VP&I#x-W;Q@J+ljnYEtB znSlxqYdGt20{AdV;knJxnnFVeJwu2e=_|Hmq_1#omVeMy68?ar5&_L`xaHoiO$e&i?G2;LHnbp;Wv!*Zi((ET^|J&WSd0R?D zpuk$XLc}Z#-3k@a&Z!r8xN-v#Cu(x{^N}$WA zcS(cDQiEsEc%7A<4}}GJGHHVBS)lncyJbJ%xF&_VL+GY|P?bhlrCk?cCY^$9%cnwp zG+5RpC;DCHRv2rIOQ4%JB}7AVB@tGSHMspCdTeFJq@3lpy)2QM{}`zCMH3OGGbl|n zI;-X0n|H&`Mcrb6xQvY27e!St&l2ak95<^0aYmW_35L^h)jd0@{tC9wW12X;ToA_y z?>vyP_7)F+-p^?-FtK0tAm0Ef0qky#cK7T4K^SZ{MTcG&{LT9P*LOxwU*?=gdr-GG zFpLWJepz)Hz5Uq$g0aCZJa~&qG4jG3a_F5W`NGo2|H10C^+GL=E=Y5ie1Z9B^n5pm zl54(au)o}YCQk>Zv^p?SojxMnZ1WC-hMIju-5t??bqT_St7L0PpE)XK4k;ULQ5kD3 z&OH`=Ez^bLZ<%vXdp;fXqbEUkW$1Iyv}ND8vaobPgsiq;L}>G+;*)aTkzntv=*K0M zkb0mr%;bi9xjDU_1b$hEbzCfJ3~ACQdqvMWQ1s9D!zHduz~oEE$q~9c?w{qlXytD! zf0P}6d}?_6BD(!<9pimw_s(V*^tlp%_q`{!b{u8+5cMgR>1#ss@!)CqQl2ktkBnc6^9%C34#69oT9H;L{`#KgJK6H%E zZjq~Z=bb<{qfr+zq+$DwAF9+6EXh7D1}UU}ZH-TQ<|;qS*g)cd^?6T{fYf6PllIMs zQ;^BGFjty;BffPvNCI*ofre1@R!{do8WlW zf5eT!8f6`|%+p<*dbmCC`n=eCIBd(E6D3y+tiM7VBrT)RLzX<1eZ>bUHQXS_^Yw3k zJ{>*}yE1)a#qBRiVjF?P6pXGT_+by3Rpn>hC!1TBMT@?XoAI(~U)t!}YEjM72IszO z{AWh~54!61u*7yMv<-eFTCY6L$5iIwIGX6PG5tdOrz6b*skT%EXwpIr2LVih{g{iQ zr`JZ0;RN2z-_?2!_NhF^%SQX8Q^-ZhE+1QI9Zz1`?VKcKZ&0iqi{q zn~Ru6)>)s~^=%Q?pJd&e=s;V_={`F;+;;uor464%y3l9zb zfNUoB268Hs6)nJ@W*IqEz4Qk4Vv4HOpA0c_Uj_v0`wam10z1@z^VgE$U<1_DR`3S^EZ@VPDPj;) zs7~$GoUf@ma+FYQr{EP&SmYWn zh$_bUrTp-WBg0g^tW&H?R`7Q*PXn%8l5A{krJKj{!|UtJ<$`g;vW-e%R4_ z>_>Ld1=B9+J0g-T4djY{q9F|IbiM*Kg^Y=&BZ$Zx5)*ThhIT!*GOr{%1TyD8XyOl{ zHN)g5;dc^rQ%aOje?5J%nxhdSr*Q5BG6|5hk)uEN08I9lnMDNkPG4v8b_pkY0gYtF zv_uVK6sdX-TGgFzI3n#dVBEkrStp7*6? zzYTpLWfeh7K)mR=q#@6?zB46?Sz{%&7ADiUg|B#{(M(I05e}_hzpmO^u}gz4IVJAe zUBVFuP%QBa*9KHQOso)keL!8p$f`G_ly~ z=ettoWz2PQlAwowBAvtAW1mV2_kpl-`>Ri!WB|*9Y{)@nbVXQ>an0zfRi84rH%WpP zbOi`Mtp}7`Eb$-Jm?k9v;q;DTdG)VV^D6WCFtIqB)G7r=`T%eQcKVq`W?nIsWm^K$ zSy#nx)l|hoy`6;`6|eP)b7$n^O+KrNF^dWP7)OnN5i7EPiM?+j*`uV2mtf9bJbFBF zpZ$(U!wrovtxfBRbn~bIC%M5o2t4&drtWU`6oGJLtJ|E@j@v7cp9+&fg?lCbeWXa* zXZ1{dQ6Rg2-jPv98u!hqo?<)>_d6BQQpYWcOPplT{tw0itc!qbe~Ih+EkNLf&#I@o?pkJ25>m-?|3Oq<(=047=hsBFh@mJZsP%r&bAb-GM zg@3#?J;;{MwhZ@a&05eXI!-eh=ZpYd?rM}ax-Ck780`vg@a|qWo$C4ho|Fyi8u04V zWfV!2&`9RyDPdk)HdYXe&=Q&qS2N#wXK+3a2=)+;ezYH1-qnFPSI8TINp^OELPkxakRLqa5&cP}fSZr!a>n`?4p{`2i&X(W2jB?xKaTzuF4kvVW zz)venWXXMV4>^1G)8hTo+wu8o?LAh7^JCn91clBLf`(Gn9_xd`S*t1{s>dnCtzyg{ z2809d@rwr;IvGfzx!#2h6?FP7jC^z`q|;!!3M?k>kquXA&VfaL_!W+m7jW;4Guv6x zyk=IkXXScG97&o?Y;;c>GaiY)Mk<}X&n1fXna{KBz52k?XUEc*-|yq-FYZC)XxJ=& zEn7vrA7H$=i)AG~OJQXRemp`bAbq9*1JgKkoAYPBG5GMB zBr1B7(`-XSP4>NACcjb0x^nwlqO{s(F={*=^e$!zp!kf}*Lr@@chdd)PUG*}P6GLM zF$uLf-jmPa-pWr8YG&1lW&}jbyYrcU``0Q6j1T}@f6UYTeCik1m-s5&9BIM)wM(d| z&H4%%6vE4sNe%2Q^3NQ=>KGuq&`R)#SZ*(w4p^^GDZaP|3((`i$=2xi%TgI}|X zRNd1zsVzgM$;FjP)tEg_!p<7KF!D08fwkKNEyq)S=McE64Y~u7=|c1WuZ?qB5=BvP zXxp}J+qP}(wr$(CZQHhO+qN@ynvKnT_>wig=2~dSYped;b+$c~w{($!q zeAt@Es988AP8qNQRj0xWj(IVEg(2*j+!ELE*tF+Y)i`9LPbcg}O=q7df>31DLShxacADgU`LntYWl2*Jw`vk z(t?Ga;NnZEh7++Z`DQQZRc{sEq*7FPzHi>rm9Us&m`wCyA91-9WP>RxQvIFhU#h{EC4uB z<49#JM5;%14@`CP+V7NV8m22k_qowJZ^?3<6JmGr_15%59_|E8qq^y zGtfBiFw0VJnEaoaaZF=nh^y(f=0=34-HCf{oS|g zSBjE>f23qFOZ_2=uMCNG+Tc79|4sRmvj;_=^9x}IUXJwEEQ$7i(;e^PSY)2NwJaG# zB3&V$^FH|Sd_?hHZJ~h1@>5~;pcUZ$_((ka#$&Htba~;W;8-tAjsiICWW9yTD>LpA zmW-!TPHQ60VkRLNQ^qf@xb0IQY4{_Oi}glPha+ zjM15nAp2dG?EJ%jkCbn6gFg&F^k8MN^b$$KS9Z!QBs;;78l(E~mE{|-n&P2R=S9c| zqfSrS&GPRi=%!<~9B1JlI~^m}eBiBfkY4c$P~G#@Fqyt(?{FhZufA|>DgOsRP~W8r zXF}A6Kn9{j<#y)dZk?nGZo<;ANpHP-Emv`t>6g)x=@Bh|e>(4gkM^uBo6o%IP@mcd zXkl!~39MJ_X-jKmVl!nVX2A1<1bP&Wpa0VAze{Ie(GfeO8aY|_@&A8SCN7?b$8jEU zv``bxoaFG-FX174dnSNwYt}i=(V_idlTdJ9j)Ohy=kY<}nxh|+nNVj3->_2&bif}x z>@d&~q<+MIhOR0|>R}sR$F1l5RPcBSWyeNL6NE!BA~q0_Mw=Dh_<7VIr>enk>1*;N z%{28vwlYfuO{AOY4V!uU(7QFdj*o?^r16ZA5m_|#TJ`?Vj*Y*LAs=96JL91g03>e zEQ9lZr=s!2G+Flx)}pL=8VZOd$>u&)Lxpd>JMHG_$WF}P_}qnU~5WNpGtqOJS4cc1;$ zDJMYR+&gHc*$3ptngHQ6jxgjwqV@yO?}=}JSqv<2H6e@*$22S5xA(-FCbe6Sm90zi zqQ;Bib#+aY$oySmM!O;#_spcJvQeg?I`r8Gs~jK4S3GsGW1DnpE%Wf4{D;#vgdnv* zG`gInDi8`7q;%QR_K>3U=?)-vj8pG}q`Jrbtcwg>r8X}nWW_iQLIEmmfrc|8CC#dT zH+$&@eiw+hS<)X&*hv3E=p)pIAf5?9>2!%9w>Wi|6@88B`NStM@bCfP!+cJJrrh&d zUe)bn)iY(LHc)b(L*3Ytncx+$khS<08o`4tLmQ|E#_0FtyDlUo*kN7~s4%S#^*!L| zEMtF4{$TSNR{IS5ueP@wCCqyv|EHWO7!xfQl55WQHgXhBzK7&z~PR`@3)7 zH`3Ehv4=Dky}8H}F4C9o+foa@LIhU;>F5#~f&X`ECiL zyBy`5b<4OV%>v}aZR%QwLEwxb+WO-&K%Mpg-Xy(S#|3ky^_ z92#|qoawf1a`KQwEQvM)&qRm_YCkELjWpgxfDrC^z_3lj0hEykY?A29I;;V6LnuZm z48W*MAhRQZyin(pLYatrdg&a0(2mb+%h4zJadJ{!5y1r(6Oan zihoT=dBB9EA`VGRTm#$L{czvls-rQsQuJ^T+T#{EIx|iaBU0mWY2U7f50;9vzW%U%a6T7g!}n()EhB29sV~ zawS9=>{W`5&B)Z^A@vxEn%!u^s2EU3AD)`hC2YCnX&=A=Qra@yD#KY^J;5B+XHU*8 zQM#$xZ$e^(A8BJf6)60FfWVe%0~dj4BN&mKD5e^S6kOub^`(B_ZBpz`wVvpHuZJI& z4NwD`aIx;!tq*riG7!gOz;DyqTr)THMp)obhtyd zB@+?>4`Ok)hQ5ahP1+L(&Dz~!-xc^|h+2YUfTz~$;Xo2t+Z|ax*mwJws^oVdxHDe~jvG%d z#b!#)8^AF0Wj&b^nUK8{z3dJrIMP}aa%%rooJesGiT7E5v?tEP?n^Dt9Kh)x4^hXj z%Ab&ertTm9oXZkb`pM7rB4W9yhX1a;#uK#qJox4sIRirmX)8v}X-`MSumDBqGKJY z=C)c~o9)zpDA3mxV^%-eiAOfnn42t9-1pxrN1R^(QiXlwpaA=Y12q5Gxs&8`zp?JJ zx}&uhqnArZd^B%d_+)N=#rK!G3EO+CG#B!BHY#fm4e5EZ6_6o4N!C1DQRF#(Gh_lNA?T$}>$k|R4Jd+}sos$l|EaU@9%c2`z z_Dpdz#)OS7Om9c7;M*S>N=^!pqCZLa1}D&v6cBJ~-})d9iXwpEyhyj<*Q zj6vXkMY%;m)m0>aBmDyi(4DN1{mVbOjE<4ooM3ZE_3XK$msiR>UOh?*^dd_$XYiCa zWxcFCJZUZE2H@-kc`*qUwG0%997Yjgse~qCM@CZ$K=<@qly*rNCIeA`$QfV2j<~qq zRsVu7#u#<}T^ci2$uqS%Qe3~Nizr-(NDL9$UA(9++UuWP4A+z6jY$uALSHRc#?n)V1n= zyuMc(_S<{AIG>9kOOEqSFIijEk*G&Yqot`u4}VarK}pfJcpbIb#1fn2#*gb;rXpUQ z4~)AZ&)$d@*)nG>34l8&l^lGGR1dp;eK|XBI_GJbL}>_jt>CoNCR6&6w5i;_6W9^q zv><0pvq7O2YGQ8K7n=SE>lx*!=k~CFE7m!f?ewbHmfok1ASZ|XD;=r89<&{yz>`ri zb^u2l8b0cK>~%8}a1?nTvqwgEcx!(E_i7O%-h;4;wS2vj6qi4xNPzs3c9CnIqUc49 zj#mqyL08ePL7A_>dmxlsd91`I?oI|ke!|6_L#flLfT5SC-ymos>^(q5v3|;bA(wH# z0Bbi6ZG=Etsg7B!>Od>rvm6X>||uIRagt*D~ZJ3c+Z zB6iaIB`U&{(nK+S z+JQuY76YPd5CHq?piK~S8O_+KAB||@eHIaJZzb{I-u_yc5Pu~fQ9ULm1|TWiQLQvIhHiy?E!y3DrZ!`OX)lWs|lIKOMz zQ<9taN!yIbgT!0NgCmo^nuo@~K+AWw?vu3NuEp=fwg1gHQxTvm0};5E>ifH#UbUmiew{Z=&f@*2b|N-qJD79wJhUTZU2XvPoa#FB5Val>2>Rpc>Vh;G z)}dtwgHydM%QF0>oODfpL;n!bG;22?4%Y}iAATEqabM&RJGv1#Xq_sYl&J2I+zYbd zFh+Wx1R7KDh|AP@YY!2fOO~4I^77O%2$ptu){!VXM|yGj#;{qNr_gD?d7!xJm8G^7 z%MAlKx~5DaZQ{oky6+vXQ#}GL-XLRiSzQx2#ln1t%g5v)!?xOgWO3Ps;$!M9@h8k< z<+PT$PZJV<>-S6&O!htNc+>tO%~D0vDz66B_7nTd+!6|SCEP9VV9X*Zu4Dr%!HcX> zWFiKwRgO}q^P;h}8$gDXOihkf9vjz>DRb29Fij&n<9ohow$V;vv`Hw9wQ_~6Ee!Rj zMUdah_IHc8neQ`yhRfR9_~1o%=1Gd+@WCyEvT>yjqkLQ4?7q}^?7o{A(0T{}Trk0? zh52&*E@D3`AW^Dp(OT$vu+)BzvMKeBvkBYa29wUw9) ze3jw470pk06fc4}z0Oq3=3CJpCR&B{0-ImK7!5gY$ERk$~|IgYHZgb z%U9zzavr35HcgFp4Q}s(IuYA5=o$b%Ce;Fbq9&Y5yeZnF-`w{&c9J}D(ThDF`=dm4 zb2?ecW!nWq{@S#xthU!%|3G2fNSzm}FkM!i)vx1!8G>+t~xoeeH+n27$i7h!g z9P0T!HWxiHH;jaZdH;!Z3;j=tD~iq?>4@|)B$9N{vTJxylTpG&tINUh!oIX(t%hFM z(-LTa3T1yZ@W|UEca%GBL;hu>j5cP(WcixJQnu@-_4{hXBTE8YF$*|K?a@+m6AV-t zQ7z(sw^r_Koz7#wq$PvD_`1*yr$K^1O=Fg=^tejRSMXOCG=g-&CJ2y_XM!n3Arkq|sA&rH9Pr|3s>Pd=DrTi~E*p4>8dW3hMUz7u9 z&71G=rl1VXh>%R@EVS#!h{#&C+%T^}t!ld0BeDC=RjV4|c;(og@o)3JdNR}zPqFYy zM!$RDeRW(Mi^Qzr=J~a;+ue1e*@lz*5q?fxy$BOef6&It{WS5IBySo{@( z6~N?)-vCP%pD;)VmU_0l7Ni=$2uUS>o1PXS2-G4+hJA#~w_DKwg&Y4WvXKQbAc1$Q zBi1<_(mtH6aMi1IT%KCk93N%pYsDpG%GkE^B15s0p?;&-Z3`Vi&0KDcmb#Rf-)HdB zUOWHbi&;D#U2+?%?82^-TZ0Xt^=-cc`rq1&;Q&@!E-44VlqE&9{FB;BTU{xC1!`Nm zj+mf7n&De7D16~r4`;~`Z8IY}XNua0W2IYJ&ha-$c)op^>xe17*M9 zIM~Qe$$p`Bwzs^F;DhcNAsbQnNgOS=c-}L(^Rf$84~j$&C<9?{^3#5j+!RU2Ig%W| zou6Cw$@BjCMIEEt_4%xR&n1zDD^s}f#2`uFKZkqT$#V%A(h6Nc) z#v7hiKbEF$L>(jQb1S`!x5VQ9v_Oa?z;%zbg4SJ?XHa`<8X<{MvQvR;`$Xi3D4ok? zE0AH?Q4@%wHz{(ln_3ZJ7ya26fM2r(lA2cN7dpAD{vY776xoReYNnwCBd#o~Ia(O0 zcT0Xy{FDJ91+`8dd9G@IOS6VkzgLLjhxXDqh*V=cE$PCIp>-A zCbqTJCW%JJ)0KCvRo19VyT3=MZor}-h19VqK)g4j6{$xhgx;Gh1(?)O0Nty%qb`&{oG%2=9vU zEXK;Xp2OnVcgj)ob_m>nh-C6Z-<(ZO_fw;zp41Me~M%i+WVX9i9TW0>uV(X(mEUy3xYB%XRl;$-k}RLUUgJ#Gkd*^ zQCsCGos7PJ`d*+$&?h17CYD;IIjj;%^{ z$5ctiuISrR)U16y;ECq)LE`9#GAdV=NV-Sg;w>zHpm%9?CmS*!b5qxPz4+_MI?Tk& z&$bh}%kQky62l(7UU`p6fHpA!8Oj`}kJ@aPOvxs=fR14fGzSn%#bRiyK$@zn4brWK z>bS3tNoY=bEsb@^lz&y{4ckJyc}x`>`f%}{CnCn0%EZZQs7e{EBNij_b$~T|gkAv| zyCW=r%1)@i(}o77HE#qeTy8wgQvG=aey)^+R^;?G(mtJa>V!ETU?uEmS7njLHo6pd z!BAnmJp*+iI%?&AOXJ2_FDdscTUYRt!@Xu2uWV)7&*-n44LihEET6aFbowv;Cc=uh z4vv#Fus{T8#`05~$vm}nad7hxL2Xd!(64NNo4m{_9N(ZCFjkvxoo5=PSyZo_l8H+AhnWy%pNO?9<}sUjOep3s-5<8UcFGlgldt&;hz zT@bkYBHjlSRuUfU4bH3FYAkw|PEn?RqfgIEobQXiwth${U~~!Bkkb;YOxc!Cwp42E zwAY>A_(sczekAz8?kB~5J(MC#r_F93!L{?FTIf25%@YQj5FN4~-N1hpf}h%h+G35O zM7q0t?f;f6`DlL5rXYyNLC5lj0%+9hm7qV39BC*$a z1U9i*%8d9*hC{z%7X#j-a(+pFMBM&kj^U92Rpr0oHC|7vPuxG(>pg$%^8Ct*#6H#C zc8n7{DT`&-gq~FXQ@{0@rr%E;Kz`Mju@gY18;*1~}M>EvwCa zA~dfO=sS@@o9adCwjQWo)?Bp6m01WGhzYqcT zebKq3EniywQ5=_ll|4A1i2BwZ42_?BKp6loP0s~+9@Tb9p+Y}5jFM2s{O@j(lLrJ{ z1oQ5&d3e3Ix5%^#-+>$kl60{|6_D=InurkIWE*7Kk18(asNg67gxX# z*t2WnoMwiJvL#4Au}nl5dBCB0`WRj7DKx8hZ=KM{x~$KCqout+QE$9!K4y=5#ZBV} zMb9-yF1$~8AzooqjcZ8D`v?kLPLJO3?1KFRfc=A40YsNLa9}(^)r;_h|=K ztDfTVvY54E+zt>moEuIf_b5Lu>Ceg?OA^jX(z6nP0O1HE@Jp^BdR4BNx*i!UKh^{3 z)qPAV75kxoB{~=wxjHctHq=c{BKfuN1|q)_Ck(Zj^dUO&KC`%6K3N=kBM-gg%UMW=mKHl$UAK3JZ9ZN9@jC!Q*lr5ZYAAb$+FTs#Bhp43_k8mae5 zlzC$G26b*hHC>*DPLuQPYvryJJdzozXxZyfG*Xk~flz#8eB~&^Qz_+kHVcrw2Wx25 zu9P*LdO&C8e;H(p$7%@UPI+sac~FXMh)M-cod{~}MgbCT%4Pc~80$uOKTb_pkS5*} zC|wz20K<6SULfM(JyE4meQ}BTh_Spvz4G_c<9B?rxGH0C)x?*C`l=f?+j^#zGc>28 zRpi%6iNOHNG99;mbX6g{N0#we^gUuGM_IhmhzV$ce-oGMJ`|bwwRQ(~1L*PV<3GqM z>jI1s=t+?nyL(89ujq+%;j&?r^sPx%g#VG_PtsQEpf+aJ1~gJ$MR~L>nQ>mj9~rgn zUme?Ru1BP%V-Y+v4xsK(UBW+WZ!TRxvU4|QOt=q7J7w+-C(uJ}BDIGv12AYJw5?6J z>GA-Mf0Ycp%q!v%DJ{$aq4EcJQ;H6d{x*wKW*}Gf%N#jK=n&BM*foJUufFVsLL>V> zuXVFsV1(s0X9^5nYxGYbs%@ttKA49FIGoq87nPY`TjJ5KJ{xVQ3?uj0hl>U{AI35i zPfyM4beO`ICfOvA{^7kudD}Rf+eX^40u3z{e=ZM>y?%2L3-N$!>g{|4@yuQa(z<<9 zUn#IJKR7>6YjQ{oiPQ=U5hZZrJu_l3OxK2px%U1OU^cPX`QC|8$OeFHs|SilQTG1G ze$XlrBw|;7yMBoq@6EF)gN>Eo_U%rU+`i9n)hyadnNk2v4gu*f@l~TXPG)htt|y1L ze|L-W0!uUx2?Afbmgzlowf`%dUfJJ^j;G93f%SeLdPfbW-n7@K*?>+29Ea($88Zi3 z3mHzC_2wGv2@!1YH_SWP>L)7N&n5#Grir{WxRJy2i_?&2TEj*PX{@b_#{Xgq{YZy} zs7V+TJ3#PmsTE8eiXcr;d(qff@6f0fz4 zc@?ZjPfBpZi4#GQ>s$=H2r3g--6~hFo~QaEeImrlyVFL4q}g;hpI?S@25g>Umk(X= zrVz6j>+D%jHSC@JCJ`0>jN33(p<8*u|6FAfq-79m%NjYu7lI!%t98gFd&pJoiAeK@ zhJ!MJn9JU`ShV1~kj8kP>#I{He?H&uVD2FPg%xAa{l3o4rT&1&?4RXLZB+42r`sM8 zJW`Ow*^NVi9!{<bkB-G%w>{)luPL`@HvPi?d4LQPF6DgKA!psF~R&;mH-;@`&8ZZc~W(6p zn@RV`XuXLE+?acEGck)le>NhHWOJh7+MI$bqlE-_EU><)W1_m|o7U}d<| zM*B2%NS}#paWk8dLPPLgV$jbasHRzPksp_VTyJ1CuXqg&lzrd2zgx}!K!0+D+x#OD zsC*)n9rjUokyD*D37>)A$w9@hO>a8eg0y3PORg6rkHznH8O}O;f2?v1(_la6Af&kA zI?n@pyi`9bD>e6rI4-Iw?-xih?!ucvlGKRCn8%G=-oFR89ED=?18fXR!wZNQ!fYpE zdXZ4{K@q!j*pI^26-Bj437ph?FHRRb7Wle@+#T|57Bk@{pi%7*D-p4wTv{af6j`XZ zIvMAJO&qc%Wkh2&e>zp_eryyG1yvdY+7$`SZb3|+TmLqWW~({8QNtWi@tPHDs>Mc^ zbqQBR!P-Uud+Kiu#U9r=$FMHbl~~@&*1hAv1t~!|vjrIZ*kk>9MwReOXTky+IGzUw zT{7<%M0_@h9hnHVqylGBy9;o~_EX2}+Ey+mCpGWsCGtk?e%wNjqW0#x*E+Pi|7uCQaS3_TYKv2XY?7HsA5ZMS2EGNh*bicfk$_^ZHmi}q z*eE9T!GJj`uhozhTh2;Ieq-V`GVO46!_F`$IANY3wsI&81XObFucf4|w;MVaOygDF z0;^oH9r%2Yr-C9C{(7Z7!}=FLrh%v4ncnZ7_Eha?>4h z{K|5%ItQ1%nABy1%HCjqTr@&@p97|qn~qecHzME33mMW>0CDjjohH<>69Slp%Dx-F z1y!uVA~P{(3@{_eXUjqeF>di8dI4RAu^B*09Qu4t6a&!LDhL(iEk0T?wc8T6E*YU< zQNHdae+*tM5K{8jdvI-Z{KG@MOGsKCs{7TiTYOoi%-bK4>tK8%@b3f#vyQ8A#tg2rGd{-Q%n?F+U#^>@q_bA7udmm z-mx0i@M9#6*UcJQ6L%nVnXne>w%+}eE<*Fde-!rudAqEG8Ptyg?Jw)Qh?s}>BDnHH zY5^6bJHB+kxy0K;fQ?cV$89DJeRJHsjB7J7oQCY_a=D)Kv>AV0v4Y&SNHk^n2RGZN zQ70pFstR`J0Zb_ea}|k}%cs8E_=-Iz^`yew71CgD;h>#s>~YV)C{h!Sr&OW<6*N;q zf4h=QNu3W0*gNjKEA+D~e|)S5D0bAbYKP5<<~vfEGSbbawJ41)BA)I}89qkEzyk+bct`t~ zrR#^#2YG!OS-|PFdvxW7i32$an8_F-e^RuEyju?u&x1(+PamplHx4~#zq|*rP589* z@^)}b#J-j2CI<@lAFNN!8n-_5RDyNUqQ;na`VIOzE$xlU@|kW$i2Tr7Z6eA=fNgyu zK?$JhsGxXR&gou9BL%Ioc-z6E{ZU9l5omw$tk&p``LF5gR>v93xgNf5b5l z`z_>bHS%$J7GrzxgiNflRt94icojP2LD8oSGOvB!+ZmH)s+AZD>mNtD_m0fUBTDc1 z5c8?*za!~7q3H^lCX~T4g;4OEfZ{hV8MwP2C=d0FRiuBdf(#~ z{yIOXne%jo;p#BfF=7A~8+}lKfBhuLrm0CrWSaqrNUl#`Urp+pCuad8Ci=L7)r8sa z@dnhI&C|4)_TJQfjGu;0vW$OijAuxh#56SAy~MkHh1 z-#hQh{fJ)F`$_Pz*47D5&YL>G8e#hN86KpGq&*u!-m>v>e$!bFS-w@We||+=(bnJn zIb?_f_E}@UC6;3)db~9!THXpwb`y6fP;mln>mnRoK-wJw!cpb8gm!Y|^qm}-=}FgNIj#BTj}GC6A*ESC%E)Vri6Qf6LGct}-+j3=p1m5NL?`ga5EZ)N(|N-Tl^=y=!m zXX*=|qEY3e9 zT;OYhOkm5efBSM2l*1c2%T`(i(50kj^QEh-j}GoB)7LwUAAJu z(ERX4Y*4s#Or5{dl{jvFJU|pvIL=$>{0TpQEqvrD%Z2rQ_noBSen`rGJbW;0oF1_g zocCIM7DaZMr=~+p7*NNaxKI3#ccw?%(1e$??4X1De`=eIP%dgsTQ^*|G%7PhKc?p~ zd+sl96=GXo!nIiGA{$V>XBQNA&k;eHf<8`loLu9BokT@Bl*T1Q1LEOT`5LObpZLyB^{0+^N+3aFM&6J-A5Ylh}&^r^wwR6Xt1j9;F8t9tEXVY4# zz^|)rPPL&pph$az1v=y$1himAYz%$1;u;q=Vys?g`h z8YRfGeBa=(5f8(z47SjB8+!FNWE`I}Cpbche;PJk0Z~vs!Q0f~G|vmVfm1n?C;(;h z&5YyZjRRSM+D0Q2Cuzn?go*`y0=SGDu3t*CRn#+naGquj0_=v<1_RmhkzF?R1?~;! zL%EA};CqC{3nzHYF%zNG=yVv|U_~KQcvU+Kc6_!ch7++0X{cp@4I>pKVLGFsP|Nq4 ze?|5U#CNe0zDVCqS-4rYxg?_Q$NqKCwidC-7Bv;(Rg6-U<-pvU>qG2XP>A5mE55X0 zZ)x}DfDnNx9a!r_p(|Kl1yE1%tJbaEHTlcuFoQmQHTNtOG+5q&EzF?^#S;;9#&T_} zkj>+qFf{6oE;3|CW#Cx(faB#={h*e4(A{NCiS4;`I*rP|m? zFh5Ac`D)BgrM4in&|oy<+oS}rI&9O{G|vhq)rNqWVy3Q#=uD_Y0#+PL((?rCf2yO0 zx!`05?simd8ss^B8CNV~;+o)rzn9CW#2cFWSs3{w&2~rK#T0w zsm~s&gME;a&O`1>3YJkV$*oI&e3LfAFY7MrYegaU5A9!>8Hs?`=79ZBLU0VvCv&z5_{>8!Y6_ zp7(KW`E2eNpDLOy5h|f;otnPFnW%o?ANT1EDX*G;U^WN9!l%iEVtAYN#wVRDyane* zzwF_g64B$3fuZ(S&L9Y**F5z%=*z-6z#kap$u!5PhS}g|J>*-Df6mE)u%g<6s-#L7 zreZwQ|7sEE_WAM!2<<-ifHV@^52Vdd@qHU!BRF z->wwB&@*_@dEF;De_Eg0Ovi1#?ddH~k&JGrI2(-~hlzML5BVo>`xc427=+Stl+o;V zCqH3h6EP#pMv<&hlb}QYz{qHa7Xr>l5dk;UCSIwR<}swfww#T8Du0z ziYVmS!D{Pahbg|#5X0$y0naNIL=;PIuLmi*&`G1L)WmO z9w5k;p!G8dzcfr}5=Ix(K^C8oWzyHiv8uy6ihGu6F}CVR;5-(%7O!N#=3uNQ^6Z9a z^+?6yK{^lWA;%Sn7Dr}^KeL)>(4<|hu=k6aQq3Fj9#)Z6tb+QBuTS3ozFk2x#djZ9 z>}^b#``zv4f868?t4o*Lt2Sjfpai?pzG%GfmB3gkrWMBPVtu76J9bsabFlkiNjY0$ zAU_9CX!uy4>LJN&4PF);f|`aDS=vjrMNQ2!(iqEwy-BR-dHP|W*>S)ZLvev!lf9s`an>@oeRx+5#HG}}0(uB<9 z1Eg!Ydl;60T$rhfW2J4%GvB1aSm(r!>RQ;qW*(ba#OKaRa)NpGsRBGEltd`9?9HsL zzo98bS&l!)gOC*FwyXivjTwmuI&nHZ^l3;?{rFz+hXE0{?A3~!~>?(1KcNtX5KZ_ z$Ou239|wWNc&~=IRvXy%j;T@9QnpA7H_ef!B$R?VLV8%=Dz5gFUDB9$Gw!AnbDK|M zpkZh+pkc`4)#emsm&;Q!{66`vjV~UUaR@_9f6K=tX_*BJ%W+7+ZCLMWPtMiZ5Wl6e zdEJ~NrN?{II=bG>i(x4D3VP2O?d(*>3M}6Baj5+y(M{AAbR;xhSf30dJCx3x2)0mPy*V;2ewy6|L4|IK!1VYR?-^)a+ ze_F$9)zB|;I?`TaS=}PYQEUI`rS^0k9#$5q3W#nOdx)_XMndc>Jg88n1)jUq9iE;< zm1~W(GWHR4e1s=;f;x6nG6O&6T+t6(F65EqHa;{SNZ*CGg7n6dmC6y5zj#1lNxkp* zc=jd09eWUwc#t&k1#oh;zrM_`>GOhzf5BJR+s~3#9wI$ph;JLUA%DV2OlczgFl!(#7w4@t*kv2oa7aey?;!_~i5v`Y_Vqa*FBGvX?_AWP6E znb6jIKf4(Eh<&(?qI((;78p^hDv$h%n$PBN$MsU7hP9;vK)_A4r>h{|>Z9A*e;g2q z6xNiO!s2Q@EBy8qp9NHexkv*3*)lJFX{mM3GOAm%kf~&aOnoY$m4&bMUsr^Oo6|{7 zk+m$g{9Q#@_NApb{h;`6wQ;Mh4;l#z>pIfXv3Zrxn zoj{tWuz9304dN7>5a_7f(4PYpTH+sGSpw_)tSLIIxnFsmFyio)Wh=eHDN0PPuY z=6O5mZZzHEow>96rirr=oM{Gk`hj_YwQ%egSv` z1}&nfq9jDOg@FN00e|1+e7Jf59sU*jKX-=0pG(4DCmzG!TjhVQm49JnCkIgOwmbP zs1Q=QKE^U-eWOL}IWc^;gsSJmJMD`VPqsN6SpBG!ApgYS8h%8AjT#;NkqVZ*n)al= zdZk;n?Si9IBU~QW%-2pO>bJO7055)9UPw#Zo~LpbU9B#iAlMGZpp#k!XK9+ABj>)?iN;Xo=pAK0Vvu>x#DQ($`_Iil zbQG7)2y=!-zW3)GzN2uFTeAc2kM7>vBQ|AHMUr!45uXhCsU&xl&QWPv65GZI1sNS< zq?AxOXGIZGMEcS^f00JfZoqdQe>O6Q9sqgTgudawlCzGi(g7z470)YLuDR-jZHO-a zhM;RlS$hfz%>%#@c|ca`&VS)C@hIcmh3Rptrbgpo68O;%oRiyx$hBBWQ8Xp%__M`y z#>gnzbOBGHgZAiMpAG8iWv6&lg+^L9tc9N1iX}SZO8PuB)1>O%*_V9=e|p!XhO3zk zup<8huvm$8chAVV3ZC+Nhorqwc5-c?D}6>~DV#NLRcUd4U?AKEbTDZOJ%pZ9+T(}D zjHYdL5GPzg>AkpBe~DCLe8^J$=hfmKYV6(mJdf}7#qMzw|LC365XLdBUoK;c zyqAfK4d{60@<#}hD1#ZS&`!O58Idv0FX$w5;vStXi)O_0tWMqny;>=6kFQEAiyvIB zj>Ww|O8x}Z86v`=R2d1tf3fWdnpv^NEb7x!O?!MUNmf<+fY2Bzf4?VVXz~4ddSblD z@1j7B`H$yfz50awXksrisJ`-C)Yow3AxzZz!1aLOUX}3I0kir869hw`YW7>ZJme_s zKQ+b^Ptl*d7#zjW6dE!Gs}r}Y#HXeq;LFlqNJO#=N?{Ahu1wU!rcdD+XkjOzHGbJU z9tj`jmqFh)fYooTf3MHmKYa^WQrU49tD)uynew~kou1*GCk>X5n|cFVpVng}Q$kx3 zVumYvbHE$<*CwDtL$LWP{fqNj_20$AiH*W6U=>@ZG&+liyijqtCH(EAh14eFtM%`- z8dW%|!x`(~5l+9z_+#SDeMaBjI||3E82b2<()Qqj4l8O`e~8$ha!}H*PNkrw+y8dC zvz;i|$v93c%S?3ThaA*N0SFWW8Vm&Go>1@x|GAZOJRCa{`GBZmEFt8tHg}+MbksNT z_H1o)kk7efkBC2*3DzWnj2`#6Xv8pHdFvr{ouy{_OckLIds}Qe_YFp-S>c336cZMyfe6XO2Pj3 zSZKLU5Nn8nr6y(9b-m4OV*`CQaY&Gz4Z>`&91A?$6fx7cZa2gM0|l`h0YXtGoG3Y* z!h;}}bf>EA+Gg(aKUjl*`lkM~WjtDjvwmYcy`pP1)@n-dR3&65tg>hgFFE_}* z%h+qq>EvFssGdTy`tZUa2N%jPUK8vArR@|G5>=4(AGk-OwN;5R^qCFXp}Ep91FPeZ z(U217xi{Vs85i~Fl@E>w;h>4n7!cR8qgx7)e>x-v^5LVKO5ogcBAhKLUBky@h7p ze{8v>udI@_i1afP7l~Rn{fBN}USkaN-vo7l>ryL6>`2zo`XnHx$M_%?@t-UQ3bZlV z{hRUmDF`g7fxEd*z!1921KBRiv63-ILHd)+FWw0AR3;spUW>+qImtf!$3etB=hu$0 zm6^T{u1+W}bC%qy06P2#;qxZ6LuwP=fAP&Zxid}?BQWn@#}yg>sDEGF*=xX5G_ZK0 zfVR1qF+Y2O`zBya=?j=c$n03*$3rZpH25h04)z3v?KzwFX5X;iB#9OoV=*g3ClMLF z7IC(p)773Kk@zrJbaM||a7IJE)fhNTOe-C0A%T%AZ0HJ~--_3V=#l;0%D~S}e<<5D zD%02t?F%ZE=deP*GVX=Xli8RwP2=j_^Ud8anj!TX(h`NVwj<#4iX1vI=LT6W6o2;I zAEjD}MbPxPZwjz~yFXvXd3aqd)4tSpl#3rvPSaIHV^0zXziE7KDDk}7;uj8Yh+Mi) zgKipEc)9_4)_rYt(t$h>9Nt_cf42qJfQ(~uT6iR)*6AA0t^+&WxACtPNZt?0RD`lVSLUBp{$dV_UnJ959e%G0-SRCV7_ zB%(4#A&h3N9Z;$&KK*hH@eM)`q>y#kjdy~iXgRvtx=N201uKCN^fxhdL`{LEvlPtO z(n^|<_|T-Wr7@$CpR~DGe<&;!m1zYWROKALicv&U`4#qDR3R*A(A`?7_0H)UTJYGn zPuX7oj??(qwlZd;Grp*HJKiomHuvs4!hpYdy+!0+KAx zmUf~+q&*TyPTiTu6cAX367l?5-o_O7xh!eOih5?tmv>Ch)~2rveN& zF@7r+3_LelEZaAEV+%pOWU=^);d?b)YDf#-&EzZTafq|Xp%7d8iQp+H-lFp3GgMRf z`_Sv>b)@2X^B?$zJ18YgN|{9wHCmrVs0XxZO&u+qNBSS{2UEIUf}Vt8r4tXl$ZH^k z&W#^tV7sn}i+=szf3osA@ln1b&zzf%`Mz_HwS?d`N##5VjJJW-6KL`4hGc9CM$b6^ zY*KQ&ClkLz{7VB$ruqta=mc$N{mLls1#^|9EZz7Dy)OPL!JH5?BqLBen+AR^(Jp51 zJS0YPL`<@-{(F;foRyIl0NaZFTSXuFB?5Yy7+wXRE6Scve=bk7`xh2+kt@~nIE=(t z~}U?^di^`VLuih+wYvkAut@?ypDtmyDcldYcrPBNfTG}-Ll zdNrg_ktvY!wt=Qf06O}5x#R~>`0w#CVkHVQBRN9TL1jyR_2{`+r(qf`hvT*7 zc;e&S>SZwff01t!F#i(^Xy6klS$6P1i&b|`6RttT|Ck+4)bqtPl%0X9`?x|e{|80r zL#m@^QJ4$OIAg&izPHtG?%s3z!m;8$*dtBhbqD{Qktr*r=xJFCY<3BVB= zZXu~*qLT2GX$=;O(Y}+pn_u_Z$@{Ah0eO&AwPswWB1yS~VI1umCx(%8Jwb{N8FR0+ zNko}Le9&_CLpfB)t|}70JL`DN)6iPrODp4t6ke z{0TvOM*Mnosu(k=*IOITkI>AjhI`l1G^k0VfBxl5TD^UD`p;n}RGN?`fB?ifYlXW) zdi9lw;XcgmX6I-4;IE{b$ZyUati}ho--l)3^XiMzKv+pPe_4UhHit6C60j!c`uLQ7 zrq5^;!XxBB#_R_|y_vV38Z1dKOBPp{e;3Ehye(!9ZK(f3Ek`%DWrtEDm+!&*8hyUS10)>(6D;ZNg%* zA~14De>druq;5(}t8st~Z88pn0skpze`{*4^QBjvV?XE~ z+x6ZZ^Ct#DhDYbNnyCes!04a{I)IV5soda1`0AjogN#a-QO->XJP4-8+t^4>(eQ2u z(1>c@#!26ZVU7ICj)N9W&)9T3?*X-;HtO)!&};;;qJbTaOH>T%F?w zoVtO$Ky|va9TM&c*UOnIe;N>w<5HUE%+$SE{!&-l=gqWKw8ud2&5I1E?H;9{wzNq> zRJrHVpIo7&Dz#2LTKc(H*R8NHK#b<3D7V4v)#Iih~Y*S?xnsOk@` z*F5cCI9I}B$Gm*;=;jO;Z)O0{nh6cai_uCSBz_4TNE5!AsEg+pf9%L4hFri{gD{)k zBtyst?ynVbGWv4=39a`kD=l5!e z66QgN&T9`0aWxwf*hfF>sLGfi4M|i6R^S50(|q4RIL#F-fAcj~HKwn+{yFMNE?k?> zU(V!86Y(Os$lx&p0+q9NNP4tV$FTpYGMCeU;vPNaGurES7D%roQRQxIbQmEk!|lhG ziNzpkT6cNF7Moz_+8m34YuVZSzfBW^Eky9Z;W5jwL`QvYa-?Fw3t=o&zS4gSikAwW z-Sv07dS*1Kf5?ABNxQUka*B~WUMX_jgrywerGX&Jgk%1~Ri7`@(NJSl4_X%)3|FFe zi^{3A-L%9G^2?6mb}{ZpL2odZY>xyw;>&f6TTROa3Wp}CeaQ+|t&RoOr` zgqY@X@&TEkI`7<$iRs_?#=A&c3H`ZhTDmgxSHDFYX~J z?RxtIiLyuA`Q4vd%BfrCQe~_GydVU~=dKvy?9M?)*PMpNdF7n)i`V1YX(o!RfzwT9u?ATDIaf-_7J**PXU?Edi;;1!ey<8e`pR{*HoLV)P>S$9-yn6k#ilLkjVZ^ zD<;BRRoXCXOHt&u31WO3FXYB7p^MEId9 z$FgPRw_u?%SWkI!RU)yZ6uZ~W|H#w@+GDH5ub<>d_&XPMfphOD)_gT9K`e)#j>skh ze?qO}JI!d120>HF+lEd+I%g0<2FcXD^wEyY3u@oy35CptE{*-RIjCF&b|#`w#Mvac z>X7h+NNlji$4YWjwZR81f-+)aw;>G~e9nN`z;f#fm1ox`A8Z6bIH77MaG#Uole4s3 zF5ARgsb)ba*LBP|L8gHzl#x^rYVt`cO?y-E8vBNP!8S$gWlf?Pz7uA(_vV-oyw2s+LL1wd=)6CRj6d%d#SB3J=%RE9SQHKCwSgx$dn6rR zSL+g~fw>;sF$Ev6C6>O+tD%Q*dUB1a< zi#U0h_3Ms;JThkW@|qd`By`!GEpCYgrZ804@R|*NdsdUW2+Pc1D9Bj1e}#s;WFve* zfGSVeyKBycvOd=7^^r>1&z3E1q)jsk#u5|rHRDnS!5=AqhlLheB7Wk_G@ce$3?^RR zSc;FI3X!53+1je%&Xc2lh}yJ~y6>P)x=3W{{P`=D?*-Mzg9V$G-Mal5w!~x7R#u)u zU696_4<3Wu8ZAv@)34b}%<#UbiO;6lN*Z;W*j{ey^sg-6M;di?zrF!7(jCbE3ZkME|^ zkJpZQWZ|K3vB5kADIaoRL}C1o0J_aUsT}8x{)o^~LJ)$i!Y;Yc zdX2vo?K+)jhCzw0f3v)VerG!sWTHeb65e`yHhAKXn5`SstUAZ`RR zb)2Nh{bV`;7vYX&!yq@`ulUu@7URqbs~6stC}W0{rrg47Dwny-^?JOD{;fcy`-o8e zyo5qGlLm6>Nx>t42?lyHuNTbRM8oA*kUI=uMYsJ(8H zt2R|z?}_s^v2gxln|7|?_B=~-B$y2PA0<-9IiVd81RuL%UGjPi{1--L`41;hDv*!- zwwg;vM;8Ch9k`ueE*SzU7NAlf6tnVuf2wKQ0U&Z>CwSqccZzR zAL9%9LMs;^!)_}|VTp^2!3(5maFH~Myq8A;R!~`Vx5cBXFQgTm?h~2*i`1PG2`h>7 zLH2JoZ|5v2iDGF4$v2gdB5ChG+GJhO>?0`Vl!I5x7f|*fbHaUzwLg&1tYwOVbRq)P zFGAHbf0~)NT0?s!NT0V zkFe_Ih@?=tRz|4M4NzTCsPR^O)Z1SC_<0tIxa^egsy`$~oB-TdpF|9oi{Kt#QnG)UTQ|ukw?Kba=>W;pgDSTY8^`KP3fOp z_nP*hTSkQ?lI=aP#`gZvTF@h)@GX!}e`QGA_yWaT*IR4fIdm#qU=iE;v`XKg_V%6< zaJO~!#mz_)WZc)lK)D~>TN@e)@C8{)MC~FeGc_gWQ2&$t78BRC#f4u(QUN5&2{Qf5 zAtLD$X^J`7=hgH}DjzJF3ueRT$#9}dSc!go(D;$if>5?D<|K!r!p;PCRa-5*WJo)mi(4_r_^6`>*HDNY^QUYr)+q-42KgThn*NLH}-y z;l%xr2ca?*AI?-wHd4DHgzM!bK(RC~g1#*~@-F#PmN3qFNxNUwxt>!WwlU3cPfAi&3W32|J zAH-k~JHd6I8~J&G&L>}t=8h>uZ{T7;T@cn{<_-r49i%tF6xX{k1n97vKfo_hFtxUv zpUFuHTNsc|Uen)CmJa;+`s1)$!AVaA8Rm{4O@32 zEB}|6*OZkcCQRb5Rob10DkL3u8&p3ZR}Su{V=Qr>RGBBUD1~!poA9Oq&-CT zH_AGu5gpx>`z=(g# zFoq9-8q8=sYUnoew2z&)Lg?|^S4-a)X}LzEN`-sbb-8v3cdMrJ zceCBgyYNnkf2p84dn%%F#;FhgjMi|2={00(;6I}>88lPUeck2 zENWG6xf;H?HB9C__u1cD-Y~ziS!8I%XCSyn6*ukg@4fNPH3|u-2(5bj6!=4r^n;8z z(n*Z3qc!Gb%JYXO2sWWRl*IwN-EIxUN?(I$G!j6oe~~}Uo(dNq?d`+?`~~ANsCqgW z|F%?Yd=XBU92A``CYS*hp8)1ibss2ek#FlgPAqo8&3IC8f?BuSom9dk{vrDu0Y!&i z7m(Q>>*`eaaxn>typ|A1z`B#J&@n^s54m^N#bz2DtmjQbqSg;Aco z7Uoo8{!%%vJ;m6QeFE^{%_HvQ0LjQYPPV;ghIx3w{-RHBlytO*dNQ4^YrMt=3R_B| z|BZ{?MFd3m+o!ctvf~>F?P*Ue+)?y3X*3ute;1&#mK3lYm7a|+qExL$PKt*W);6{3 z6;PEjtAlILy9)epAOSoq{zMN!5w>k<_)OhMADcD zP_pMvH@im<{!8g1f>~nMf!HMD$PV>pn(z!`Iec)CX)Rl}pZ}$MWQve>djWrxU^-yJ z&C3Mjd0Of&XRLVQxopVEf9S(KQxmd1f2iWKp~sC`)c)SIR#xAM^+F-Wp(lWTO0z~< zrF}p3T9T%m!qXe_opfT6?5wC6?uL6~7~|i+(0?VMdLL2fb-z;lgvmtF&!sOX1Wa)cqOwjO5g%EQZW{q(6eGK95mUND|+spN-&e{e=EKZ zGTX85^xc>kR#N8=m(lOsMy1oQ-W)>M27GkZ)&Hy|CwoI|)jr$0bg@d+Vg7y2k2Ls# zj}6RC#5XfnrxW19{iYDMi5?^YXIst`YFkxbYxSqpZNma}>ylYYMnjFE_TNvFwfw<> z{Y|0}#(7t-R7%}LiOaUuwNna)e}#H&I7ja5Wd23f(9-(Dnit%K`4kAAo@~cINK=+^ z!54i&9a6#2Usoj~i=)847{(&GQXH}dzRB1&o91yc7YsWv_hCj~)Lo~9tW7&x%F60H zHw|?oYfKHHUwSXH|GFlCO0_=7Hc;-!>}Qy>^Y42<%~ZS9_!o<0VfyNeeFVc{rzQ0G*Efzc-J`67G#E-TDG_!d%R~A$e;G(wP3I`cr;|@bd%;btn42SJ3X(`j(KK~=p4@wuDDqV7F=87{DXj`YpV+;%R zH-u2AWgO;aA}eUjy-(8M-G@+E`YbKPO2&yfs6T^pd;Kh3e^)e$TbOkoo~E`VqgG?K z!EjypWTXN@4IZBFrA3xqG#Qx!oZ36D8EFx!?NCm4`j?cV8H+YfmLM3>F3v%V{kPqK zMPE3q?WY-8(;-0;psO2HUW_ySnrai?tz2&*%uN$Q1l2eiIz=^Ubmk$ zj*d~EtsMvCf9iN>*+-7Z|q?Kq{d-)4^8-nDT#VQ60Me@nQ;>r<%^CbTm*o2y6XpCdfX zzT}pCpAPXaJo^DzbgIv-MU=7W)_b#{=k(GMA4%$BcsD>ar?bDZwafH#wPQU1`>Yf- z8>4x9gD@^)ycc)BT5o1lFY;C|Tln3FU3ieQ`H+36y02Tq&(V9H5LG&C%UUY)VWq`4 zaS{3Qe^!1*c$I2edQp0MYn$nuPVuvc(7GM`2Z1jOf5(Av?H&B8z7J8|&&EgP63x-^ z=ft(5X{76#>*P`0Qp3Cx*JiC*Xih?S__<1~_*c*%uJCqdW^Kp#w_5vrs{4e6)TWVb zB1+ucjxi#Zz1&yU4)NC22XO#tK$gD^PR!5RGD_ndXn!=|phiFOT7|2u6q?O@v^ej_ zi~ST8&XdV}pJW-(usGKM{N-$XwOTxR${g2ngkcVC<3kg|5zYJPGIXHFLYvV7e@KU1o+mB~<-(=N zYzWiCk$;|Pig;|qo{}0uq%H+d`Dqa;WGFp5yZ{~Vrxp*!s36}DC{n4nq5IefOt^10 zB6hR>QdLCeupK=^&$Jz9Q<(D$v6uTS|c0}nYzrhi(|ek*P+a~0B@3qui!3abY`QmO;M zE~2V1kv@HzI)jHf3?AjGv<6?!Vv04&p_WMGHsER}<`jk1B8g2zATi+=SRC{29c;m; zkIs3nyCtfy~E%=*70+B~XaW(Bdab%m`yG)0Th zlQZw!b>g#s7XdpNRT6nj9w`k81yhkOF@LHa!Fp%dSH_=tjNt6127gP0f|4rb&_HP@ z7g!J;5;uA{>1$78elBpRHtEF6Qcv19211b9yWy)LpAaA;tRtvHpQJz_SR#*86K;MuOr`3k< z)IL~}cE+-dB-6(dEHk9g^Ncn)L(c%tj$T{zVqq^8{NbJqcd(KrOYi#ksCq|~y0|ls z;*mpDu;=~YA;iL&T-E1~M<2U|)J4h*B)+dmu)Ty1oR}t^r4`d34YhERF%gm%Pc&io z?DF}r?6>+d)@pzVDb<8haF*?}H`_q)%ZnJcqn@9r$o~PGndEnu;a&t20x&t3p?v`p z6EQh5Gzu?FWo~D5Xfhx&F)=kamk~h%6$COfGBA_DTPJ_CdSi4PYTItuq(PH3_QbZ? zSdFd56Wg|J+h$|iP8!>3Z0DqVf8VR~o}XvU8eBK8=f*Q@)*2E5Sp^z?11mkCh?S*1 z4FfGbCqPn4-pWGPQc&00j)9&=9%$rXu4@Zmq@|~4h9Mylv<2$gn^;*2>DmK10c`fh z02zJz5C4A;3wnAE7!rUe&=P3-p)>&Kxd5bq_PUBL)<6aTx$a*;*2>PFMo-u7Lk+Yv zGO+|we7Fc&S-aSp7#Z9DiNQoe^C!|DZvk3>gs#4sm6M&B2|(A<03bmtMGKI&a{3UO z0LZN@0eV1VU2{W#l_5Y8s0vV05SCW}h|0?-$tr(P(0&9~aIm(vvi%<}f(nXCqSOE( zerZKv08p74AgZLG_~%s-X!*h4h#DZR_@V!k=fm)iyOgjZzoMF~FazD6GXNL>jzC*G zlRs(ygB$4wGr&KoeYhIhT3P&+06=bRZ*R>>N9W|^L~G<=XHRQoYeZ{p{ue(*V-q`o zla+t18Q|mH7HAIqt1u3h1|Q|LHwOM4;7?}&k|z2H#yb2onvU8n!-n69ORo$J5U=HHL4Z)Is`VrOsn zcSIn-(8L`0hrQjOo|#zwC6nTp78em#P^6Lk7(7cFDXWj_Skl@%+y7<#Cmz3$BqxBK zo)y5r!3?1P7*b(N13@bbix1XzFn`J?Wb#obdn;QPy8k}hW|me?mTvzKrJ;$Xf#H9j z9vC=S(ih5pCoLj?0rnGw()Ko0=g0D#W=#&mxY|1~#%#0-DL9|iGnv$nDZ z80wna0X<9%fgf)$Zg#qkK!Cli1JJ|mUlsq3U>Mi{1}6IUA0z*LD0&x5- z{$TRIs{a`V^1oJ=;$vS8tSrr400w_RLl`<~EBlYpApiffasQD^#KGKLTGs+d{$K0* zpT@ctCgv{xxA}jVsQ~}5CYQFdwa_*H51omfh>0`MK-R=w-}vtm|1B4{*ZtTBeoG^B z;75D@5-I&zG4qdE{el^ zlm%53g$1bo`w0DID{QH6Wnf}y1Yl%g1?bw^>bk(tf6N6V3k$%F;bYhhfX;sn4uFo< z(#rnB1z_!9?*TBhvW5B6TviqU9seKE--sPRC-e`*#tNX5{3l`q&`JFRu`>ba6#juY zH~@6||3D1%^Z+`8e}N1DI^cgl;0H~*zsmhD28;kY!+(Jvv5o!(eo!>|CuH~tZuu|p zBe>N+;77ITtbw*BR{xToff+z&`!De0jE?_w_&}~e+rK0HdlzK?+)RJ18vQ@p@;_Jh zUs%E3*2)a1Vq);|b@eYMQo8oGCe9l4AA7>^A^&*%@24OCmVo4+EB=2k-U0$v&Tcf! z9IOBuMvjk8vV6FHoSEL^-+cA|zCHii(U1B0U;O9l0sw)|Kz*2{Usn2D{-#L{fhC^8 zc~hm3BpkFSIxiw(wPP<%wS(HfKcYm1>-O+3QO@e*2BM8h ze6sHDMBW7rFW{w5I9I>9z{;)fiIY|k#toiDS`mp+WL!hw^Z9>Ty0WeaPHwFH$!^Mr zBFBZxYHfOva)hOrkS8Z{A)7vaJr>pC2du!mxlk$w(Zs!y_DG$Qop0)1fjY8}4xF8c z?pH1Ho_@Rv#BV%viFe1=iDyx>9Zxdtb}<_=rBCcHefBTxM8%oD@4PQ*-_%%!w~?d0=bKoKZ4+L zA+Pn78L!7kqX$gls+p_RB5Mp!TY!56^H=&Z&U|BYbB|INi783!ZB8Z;;j1#-hivhlFrJ==hBHYA3Adpf21= z2`sWFg>$u$J+C?*a_>Y=L<<>L(8f?mSIdRyl)taS?2MErkLE4UL6u+nEK5wEDZM4Q z&OU7xWcC6{nJ_W~_QSM!pWHiRcD5EoX${8?P04>}-ds|v1($K`&pYb-D|LyA(7^&x z^y@9$hNIY*x@yx&4CDkV!uDJg63qOfeB=X~ylHpaq0!s;Bq0{C5jO5tg*D{rAhRLY zoD${T7nPCO&YPqAJjQ~yvjtA(l5LQ1ZJgyDNZje!boIll&r!t5^lo!T-R5jBs*zCS9rgxDwm9RuOo-rLFJbiTLgsgs;Rh zXF0M|2N{Oi=tOXtMF0M7P?DM0QKz|bY6VioclHkkaBTg52qte zr@hGdN&#az9EPDc0^5S{=CiC%=1SfWg1f39J1e>ZMa;r^d8Q5MQL-mVnBg8ej*O@$pSI;U{?X6w;%&{x74s(YGQUkfLrrgURv_!)AT`Py0Og_&%%1q z;F7X3d`Netas1BKWu2*O1f4pqcrUbe#o4^v z8l$D^8%e|$(x~_4Ee@8z^Ivpt=t|cL2!6~xNWZ`zbRdT3irA8^1FE|V=Se;@jXd{^ z4nnmihUuPd8V`iHw;-8SHBwgp4uZm9VPP^}L@NXgnobEw={U{zp;o>f1s8uQ7bKVJ zjm|gA0N!g$piA+D;XDBcn$!d8irK>MzLdMFt!Aj#ewCUJIJYDgHegID0UJ@UxXg6< zC_CcmsMp@sP{@W?;6HsQ9^64o~Nl zPj!3Nr=qLP_5OeHd|GZM=l!I&8D%Bbqw!6n<=0q|?V+`X8vFtKrkrSar}!yN;-*=X<;dbRFPdLI4`xyBdl*iOv9K#1KfN3x3YzJI%~c{>|EgmK;s04M{mgP~P9>+JOe$Lhwm%%rTvH67>*bad9pn`kGmxu6``ZT$HTc++6rLo|1(p;qF zdv*GhxL4zG>|d?VtmBIv9N;!U$T){2_D#WGcw2zm?)iZ>MYkx{0t@oBR?whNM?%<< z%g8abpA`3TaMP#g4n%*vQMh)6i(BR1K63O>Xp+Pt`BYL929Sy8e%T<$a^YE}xFl5C z=%!m;^qVi*K>BoUE!y7JOQxM0T%)obgD9iCB5RhUH+fV1B>|s2FXqmmZ5JhiEowBt z9A{xYGaXvYgqnSN2h{B5=YIGY?JOo|wX?YcJ$?%RrWxbXbV`5pLPF967Bx7KJ#o{8 z5A4UUaHOGg$>&SPVWgds@^lXb|23r;(<^JH-;S52sML=SAR!rwMy53I0cALL%ma#_ zJ_S`~}MW(5V&UXVK(rR~OR z)YQ|=%B-ElH=Tp&nXjQ_Lm-t+^2&v1pa?a6^Z_o|_85OzXkb^&1X&JwJQ}9^;`T=M z7A%MW&?F#V_oUw7_A5Mz48+)R)#$p?YPRU!cqT;-&C*$5%+5aHnmZqf>KCjh8q>8I zeEg`B;bB2(?!-oRCyC;@ap8^nZ(GpT@l%BtG+Yimoe^%;RVGw!zR0E5#+;USIyA7I z=v9dGm>RK z+{IU|hlAH@yijSbk;Ev~M*pOi9Qi)oqrWujWjn6YkN7G3wozMJK60?oy|=QMx72<*?|n&%AmU8JBux7ClsYn+* zRUv#BB&LDX1vQbCw?fMPtaq~S(l{kQq*(yoq-9Ec3`fp%6Ex{{?__DV9t9U;udDWwPq8$^5C@p-wl(fExXpN%r}zcdDLe&oV&(W!{M4dawD-|=cyn(Q^)z;CrtQmH{P+bS)m_J{G%+=#_)sd5 zqM#c41~svN=ii&c%x|m|o+MDpu*HYzld?2JlvDc?Yr66QjJDN-60|Y0vm z)*sM5Us9jz$1ia#v7JVBIsO`XsfK@bJGT@qCu#=V*Rx>qG~JOO-%W!lYAd*hBbH*1 zuc}a(1d2f{lU>6Uwtas5nuefy(2zSegujstt7VIrV;cb_a6;ON^yqX^Om4!AYZOl1 z9OmI;ht$AkSn+I~5-3D|%n~j#T2fIMBY&8=hDq3{(4+Ok)MC|y>CSE4nwNjx6ov~z zC0XMn{2O@=n*|X{^njm9PQ&}}7j14Nx}$Vif0NHJYrmkm<5u**P>gyLxPC1h4f*26 z|EOGe)?#{iw z)!<3rJ4mPx(bH5N{L?);0akx<{fuf2fr3d6jFop0M;zJQ9GBILngD3zPZv{RDKc`phve)r!Hq}R-tH*?iv6*i#eLb>dV z#Mf8lA{DlVMwz10$c>UY9MpFXjG4;%;Yt;FhOeQ0-PpJ!eqa8ZyMM0CvB>Wwj*Mtq zn)QTE6$15Wf|5ilcuff=m1CNe)!{R$r&P@6+_vp=D6D@?%=ny{6~WKAb60}HRE0H8 zrv99^kojPsRC_ybk>=geBUe9CfRpBN{=a=tJFC5Gx9Fo|Q=Ac%?Nr0DzCs9p(?$g+ zzGV(OYq{;vU>943oQ>?izey9GHlOGA2IQ#kaZB5i80lmwVXmJe#8P500pn~~r$gKM z_D>F+C#ZiuNBhROSR_@XPcw!{G^U?ER&`u3euI9`K9;;#OGqvOH?xlxCrklr5P3rr zX*b&&;)IXwwIpFcW6|H2Z@a1$5{z9eJhykvzp_wf1Tp!IKF*l55UhkCDttDG8Ce=@ zT^R;}L%*xjdQ1S~G-SheJsRS=wf+#@NWpL1n-G6HD1}}gyL(o6*r!qa?BNkpMtRFk z;p(F1*jVz6q^Gjuz@7d=Wm&^4D`w2Wg^rz03DGp1a0z)^~SvI8vc`3qP)kcQc29(+sL3=+X zA%%a9D{Lj|xb8^)eSo=pnr|UV^;NR9-&V%}a@(jQClN^ozO??3OtJPAWfBZdavp7B zS=o6~seQEHp=#Yrk#UWEl)jEw!2S+8cc5nJqgUmQel--+m$1)WGEx3IR4WuN%mv1N zmXmYE$g`t5*TfOkfgT4VO8ruNDt2~{NGyL%w_1()d;@mBNKg^KVheg%C(JL7khLQgYMQiFfx zlE2K3)7NoM(CLYisp~g?4Xi=8fcl02^MVGB&(#}iomGl){bbh46Vht-W(2gm%(;ok zc?Y`CSI(iXo00Hj_!>;Ps1P(im?_1A5hcRv2Ub9>mC}M995oIH(N@A8rnPS4rmb|l z294ue>1y(^*X(LYH`#4y5QfFp&%}SeVR8suoFXEO2;a;A&Ck1G8p22=RvSu5Ve zY`w}jck^oWh1Yr!hd-o49Bv$At-s0+GI+bLtrweN%fhD%haAb2@NY?$O&G2*&8x8~bY~EiT?(35zmTkTrpqv6XO`qw~$v#N= z4W<2N*X&C|uZE7wlh%xUerSJ8J?+6=4!uMKCw70`Mx#+p)KMM2%uVhkU>xE6BVO z^d>Yd{IS#+Q&lkHGP2GNAdrr_aFZkel#oRKd|2pPdE>v_ z?AqUs#M{b;yu!yu5P^$%q12ElBbAkLlwB? z9JR8RzP=3vB);}Fmc%5)!byVH>usEgvG#`6T-8SybN-Hz)vof%RokS~p-Z_r%#;6& z2vIr0OoEd#&*ZhB>0wTU8U}BQ4QbqLSe+cFU*2T=I3|b_ z+ZKoP2qrp zj6u*DMET+o_O-wbv`5+iI$J$au9rd_x0&8zsl(N;>E$zb$M8~6Jwa(z#J;$*6R)(R z@JQv!!2Xz@FV=rbxdNqVTvm$S5e zqn#uQf3knxKx?Qct*8HCf4Y^9my@9sl*kAd(%3KB+w{ca3GeU`p8*W$qaKm%E-(2;!AjdCR6p6{6SQd1H3?+}E7C?2Nh~9~#DvD#$Z; z{TG<|69!au&~e>2!m-1)T^uo+yNMo3Rpw8naHfCnLaj=v*)dNcqSHjaHD0T~+kEfv zwC*i~5^msHCVi!A4$5`O3L`~#VA|shq$LT;B&NYfPK$iOs*@7LQpxljsl0R;Py-tT zFmEys2SZBQy!2(^LhXKBqZRso1E@%SA|(CkmR|qUz`X)mLhkgcsKE!rER!|73lo3# z#87`7L77YG$}S9T%Bo?A$OS7y&LR-`E~HU7Gs7J8l7C3 zU7SIg9xJku%o(&{7lVI$JvtKTC@8_fzLkFjADK7KH`w$n1W--Uvua-Ea@_{Jh4;nts<7<|Z-$4BI8YA4$z8;haRAqsz~ zui(<$Dz0~>%LBiNGf_)^Q5QDe^fi@YqgZI57W#*&&=33|N0tgyGOwG(R5M+u=vQB3 zYzUUY<8Uk(u_YT-G!?{wXR$r`s@k45VRDt{R}MuN6`PVRrtO?=FSC%RuBk=ev=qU> zDhJ!|61e0irJqVmc$}>jhSTTrTjPJM5YsrzIP+1Nv9gF1S(x%d#evmu0 zYi!C(t$;e=PE`_C$R;Qyyd1UYNVVC-mNA1)?&n4KADBo7cL>WO8lcUbdAr}*&yQ>y zh;&%PFm*wy@a<)e|4`Neua^1*E-ML<@HP5|D_?2-qE&Y1r*(lhtjuX&Niu)Y$a~P9 zQ8UnCAeX49F=JCoi#L;Ed!RPg1Hoe(%IP!o*3_EW_E!EpEuf8lk@1}+nq!zu+y%VVmXt(MgY>fue5Bb zAYU0N88yz)YMu~G&|hMU-5Gz<^cth-lm;WhsLfiT^mX3A?d*cPu>D)QANprXIeE>- z_1l+^8oN3Rg;P%(21|@Z&kdU~4MAi2k;;u>ok+m#7TI|o^+?`cho9y`O<=`?HOh>epN2s9pS)=@-2TV#5|6(fYsx; zCvmAhinCr$Pd&bQ_xq}iw-u{ACaVCZjCOWTekGIel;pMLDBAHDTy4&1f}qqJEPEab zy7CimJP8U(YF_kf6t{!~F?l_P{M@oPosD8%==3#2)nt3SSk*xc-*2WlM2l#wZ?ERL z7&)WhFU`U)#B8R*SL=Up1k4`acREk&yuqwO9zzc!>=w|!`#XE-IFK9SQ`*9^_0_I6 z-sh+q958VV_fQ|nus-hRYAci>kh~PO27a+l^glMzvSk~a6-K01d?SQLrf@kA;(H{# z1e9%I7xsy0cwhS>f^$`YE?$y|zPawAuCPKL{n8__Z{I27K-+)$o>}$dd{ChnjJqAd z=deNb+r0Dg%vp~vekWuSG=Hw^Y{1faH8z-xW0*#82`Bp_7sb+O3+X!?S!BxGA@)G= zmtrzoxqMSdX^sTa#(~8XoxFN6xM<k~oOcER2lrMMp?kXbT$&2z% z?2$5gu36z;8PA-8tPEJAjd72h|Ar~iTiVZZ)Ox2%9M@=hD)m~-m)cUNQH$E>#u_3` zP$(l?>Y?x%LH9RF^K07JdGv$+eTQXi8ac_q;Bd0=UPPoE!U;J+uNK-RjV;dI(n2#X z8D%{tD;Ix5q-sWXLdPU@Ps^NpzW*JcccX=i==lSKSMH&__a^)2DOPs+=~V}?i7iWz z<_ht0*)Hd%pQPenF;Y|Bf9FS;@Q@>no4IeB7{CeM#xhat^5wUE^=%Hh3c4K%vZF7> z0M&Xpx{@SUIQdap3d4ODCP(gy`<9$%Qg#(x8R~zmxw!VW_(XBov4eB*^_wSSeSe{@ zczcNF?ZpPyS?=mDnf@Altsk!Zd22YQ*bvax%^H=e3z9ykcS~yQ7hXF^-leSfx?8j0 znHppkkAd?$SUftN*C8EAjk4o>=-rAeE2e^A$e>ex4opPTww8olVhMU)Ao7NbcCr)e z5kY@|V)$mc>7}QkQ{J1on%z|Q=1nA!`0w*+jp`!pcXrYPL#^l=&^eW7Oqcpf02)>t09`;pLqv-}yxuE=u{M?5UFCcDBdIWrH2JL9S~CUZ91VaQmxb8N1NFTgT4XQI^Vw6%`qRBk@HAd6nub6+S ztW)zY=HF99@HsPzWivhZ%aVV#2H)9Ms2#v==vrkXc8F$)&nXZ4boMy}o;vB;|PjUf|V2m11H`--# zJAhgDXB>k{ii$WBkLuv~ja zcl3`TO;@4=`4VQ?VUA+$3+V8j9s@@xBJK3)i0UT-w&fknP!lDC1)59RKn3Tq?~Z>j4V0Ei7mH|1 z8q2UtnSF7zIMvC5KH=HO;2e-{d_y>0E73XR9{X&2?h%mJ3Q@|x(y(yW z@q~ysyJ206h|JTMg}dwkYWRU3;ABgiv`akD0-G$zKZN8Is%9TYHf|k%smi`+DJu#s zci*j4Fj^diqL1h*)`Wj~KvGYz=?E)qO46r{>oS3ky+im2`=EzLdhq9Z)!z-7GA#e2RRB*yG-iEZ|3ryID!7aOUyR0tLvKeuGuOs~Zu&{=VsX3*n0&DcGtk z+0XHUkU4;ax)v1SHE~{v$~TygvqDXu$yNJT#$gVts2pf!4oFPbJA{ypH!ylS7B3D7 zOM0fZhacG5z@LBf1;+DH=)`;EHFPR2`@a9~p6nJ{JBfDRydjn6E!(}f5&ZpYuu@$X zrXldL(wWW_EBD(1+#4L3v7v`h!Efg9K;CtRBi9X-rAu#Sp4`laxtO|ARV{L*LZ1Wo zgnf3V6~;%VsFbgp>_?r+9r`lUZj^L_pUTs}P#b&TrjCDwsIcBqG`es{ElbI+7?$F(W*YJm1GxM#ZssQ0Y@WT2SXoH?t`m}_z zgFans5vzZZ@dM3t!&PIC4p`jOY*|adPgyFLNIdtQE^i}o61NGfFA}Bj-(4~CPiI3{ z*I);<(9>3W`0HvvsiW%D^enX;Yt9QK^lx_Ev{MD$nm!LF7kAz$t;{_PE@AKBc+EaLZ{uzV__n>ekr?pYbzFx{kbT zVZZBcViy3$S1T5$L)Y~Jc-WJHoa7mAv9A`Lt1^@>0ht&-dLJvQHo zQb?I#h9nGXwQp~}7suT9{SNP8%xALWPRu{v#xod=;&65BUlfPhR#?w?De4^cVhdsv z{~AspDacP$WIhI2$}lDf zR}b;}d*|&ae%sCu5hqz-N_?K8*X)mEpy0_k&@tvU9}6kJ$^oD%vz3hJj*yVXjTxqY8}r)=_{bRa&d@GejDYq=041XvlUIGBbNToTO{D!h=|Mn z9b3%K{YdMa$%%5!Y_@DbhI}`rK4WxoOd9h58`gc$*+Vh@vlv#7>;*^qM3Yyg1i9a3 zd0Vk_>Sj}jSs162)K-A%Y%_lalBU>U-h@3H#r|L+*m*HCZ-WqeaecX!f@?Ns!z;YT z&2SqdazP8`jIH1zO!|vt3-x|txs(m;NuDlM@E3C!wuo#f2;blNzG~U&k`PCau;YJNYGX2BhPjV$E5 zV#Zq1yE#b7qaq4OmE5SO zCan0mLO+Y#?(g4tTF{C5g^h)DQmM#%*el=bbzw1fXJmqtW1MgzosLHX4E*Q`{VJpD zOKgEQLj^KfQK?D-Xx4v-MmF7S4!QE^cdg}^!nXS^^$>D=;#j?L(@OE+~I73obN4 z_w=?2Hr6K%9OldTj&q2+PRC8*Lmvsr^qcqwHR|oR`uQui?ehMg%Dpv-m&7sW)Gd)S z7L38$7ib6-2%ByM!MSnw8c#vTysV4RxCXL*qv0tO0V{tgOYPKV;z-S&+DSjYvLzej zkD<&Wa*N0Bk5kzO@x)ELX$$A67Uk#}Wy%M@P*8rtGC$j>60=f$yOQ^>uh$JV(o1wE z94tT@k#`WeP|N!4G`Ud_k7OxRGtsYJ4a>x-jh6qImfP3L!@y6Z+-*eC z5cX0-21723AR5(zZJ~`b$?}DRzM@iIyl#GE8)tv6@%5lnzU&Mxr>`|KEu=x6m)~kx z-}G!+ucyk$_&ZU9ukTz+uVq3r)O#9nXEgrZ${Vg_%Zg(3suiQP6NhJl#+-4EgBx7N2o-UpK5KzkiI6YgOKN#o(ys1Oe4T${B4^EF>nzN*4qb@KjhJzY?|B zAq$*awQUr^m`EPyjM^iewz(6L`&?R}$QC^dr3QuqApCW}LRF?SnYQ|{99vF&TgQJ2 zNGDYEhq8;@g-bdpN@u{yrnNlwV7IlR#E4;ryEHb+CU!+lA<=Vn5@V*N?~i%`{!xhSdlcIg0ObRtvkp zQ6BT8q!qQH?q(x@j7TGzLefFNL_3ug=z**C4syaeH_6^ccf|YE+LqSH14Nec&5*%Z zv^X7#F1s8*-jS>fy)$KSXls0_kqk~mZO+5kyLqIlJ+bLaMlr!VR~ywJbYy?L?f^fJ zO2&dAq>T$z@pVEqaV&+z&vb@9Fz{6Pra2+^cyN;KK5&>mFBfqMjAZ94g!`!+RGYgr z>0#g%%rWhnN2AR#CDc@2AVU(%^tL1vp41b23 zU}o%#OjyXLfufzsFpOW&Tql16+rXKo5EE7ClgyB@pGr6KdHXPC&#X==5AhWriZB zH%^surIUQ$rC`1eLOaQ`ep_hu6)7Gu>P>uOOS;Xdqz$p)lXYR4*vyn{9>+OD=$J6x zx*xg?6tjs(bl30HG`)-Jl4Qf7Q?X~hp5!Ce^Hwgj^(OK!*jpjVP3OI%dYoW!Zc_fjH9$} zb8(b*8$nnC*6I>71#;!WRT>Uet^4@59k#{$x;5 z&EC!GbWSI{5E*~+ZrpOp5CS<4Q}de(OT>Y(%<8nN|86H@V_9zX_81~c^CcBYnKC$yI& z2vP(J6YpXcV?ov$*lZWLSvScMLdRFuHRoMS0{fU9*K=xRx2x$gQwrzZBv@N7ieqLC5Dk^s1Mxls z!_+XTe1&FKHLxkQ^i9N=I-@(t+1L>xTA*<+_g&7KrR*E91D&pfpwqhJ(`rSH&(e^| zO#MPmL2!RpYnnq~Xq+RoQb<)CoS71s1m{=WUoxde{_ip2ImF?|l7S%gd6BTOP zM^(UuB8@9oi8&M!&p{wyz+@`TUoT`7nkK@r3o--8r;h>{2xzpYnck}ny0zy5qPSS; zcL6^WUZDco;({;O+ywVlGakXHN_(r{s!*Q|IU#>Qa0N-6woeUIzJq&cu4}q}!W{C6 zz2OhwtGJar|2*n2;jNua@V|THOTZS}o?XZ zdpCc5ah*)BNX$n0Z5{(n9Q1+bx}`Atlov_36P3igBwoLq>sf26we^mlUJTjKJK>X5 zDT;UBiaqlWG6Lx)w>IwbpH=$;za2CMsk&wgBFYyy(bSsM8@`zJc_h;1nXOC|F3ErK zC|}qYU1JtXgiI0*%tK-cO4QzWmks5A^Z9=rK6&%tgq(P~Wo_13%7U(MvRPaY708nK z4DPnKPpw6~*<59nV2FBs~sZ5;z& zzL&i;PVW^Y2^3lclsI~>LZN<&yO6euH$w#ip6B`0eTm#I&R%$ExVRtgF$m>AYWDAD z#g{m;6dgkuhSXxe0xkrW9dA^h`44|#u|26imnc>?^|b@8w0m2B(W%G;U~&-9)~IRb zc|9^l^`5XT2SrlElq)s4eqnO`QIf26zjC_;MI-fntZshy zvdF63zDJx^K0@P2CU*^lb$A5&!3+#`bx;EtJGMkWng+37By^^Arqz~4rV4-e2frGh z=@nzHhJpbd;xA|>bdzObA5}nM?(k5!R&QnEI9Sp1Ll%)iECyFd!5uli>Y7{ILS2$_ zndHL)`5|JV^1K^@QpB}O%qNZRm|-Zlhl9A#Ppu<{jC3=fXwhPtJ2~gLMX9^^9DjyV z6EFB2pPx6BidVB<7yPQ-a~*$`E{l19*p^hKAltw3kvjnFa2Jd8&p;lc621|Fi#{$) z5m%TD&|^SKHdVS8uM-^DliRuN`}_=_sb}M*ssz$$^{*W!#D^c#Rb!iKhps^MsRqxg zW%wtc5DG9feIK=n3nM1r@jt<87c_c!H*b6O+cl73FqAR5!xtnD38{Z3*PO(pQs`oJ zz^DGik<+d@*V>;O)^fLNQ#|N99sc`xb#cE9jY+_z^9Is83k=sffQo4|PDqio8cX}- zpl^@!$7dgLw`aV!BI!lxIb-RDwc|>QOz9_0Y$`1czI_a1xQ)~TEhYbIfrP*nL&O(6 zce>ken4i9w32xN3Iq83tJm1%oP=nd``{aUX)}x}so{%$KGPxmy&wmyipRc1-FDs#e z9!Cx(&JQqkxweP}g9$;Lu$HA9#pLU)(8AdGp1&x@rskh{7koZ12K-WrVobY~gs5HCs^>w(I%$|~{nKAolEO3O95mO6{%IzS7i+w#iH(2k?CQyrauq|07H=?>A1sphd@h~b(hwtLmi>(-al5YZRrKF}%cOs|yv5@Z1${c}*XzOL``!%O zGFfhI8r))aTyPFAe1_%XnYEV@6<0x_fbM4z;W>KuaAkjLi&7JBbCR-PeYz!*HP7^D zR0-nG?k3;UNro3uBAjf3?<52jEl*l5QONmBpg4UBt0_s`#T9wG30pV7SKxYoODWgK z%Z==aM70~W-5~-T6X9aUWa5@Y7eBp8zHMJ~8x5NH!qgNMc zeTq=%gvW7WYZjPg9-F%HLWd)}UYsCDDTz-s-;=aN=IT1i?PzZAj+vCeL$j%bf_U8- z_zLP8^_*YhKj3^nzT<_7z!w~BCNfbp;=#>n0N#IGWb|Vob5XYx!nqIzKu-LVf?WO6 z?_=FoWIm-@1;)Oss$tF`_u1az#X(v*b6W4RDjD;FliN>^$Q3kB1XzIxaBs^5DeLrm zUwWN9BVg{Ebo}1rF@LKSaBIudSCd4k1l<4mM?vSXU@99wmWb({r-JPpi2JI=RMwsl z$ajCltTCD!tJv?KuukwHv9Syx-U+)bwh+ji1p6gfpYg2fJ*pAYutPj z1ABPle;;Ru%3%mrvQ>sjRzd)-JV?1*1UY;AG#qQV( zHe`YnN!4z=k#Jim&^XZ~+2UU`DpL?iQp|^MUQCMv$9q$;>ih&!FY^x zQYA-~b2Y?D;gVwlD0P#8$@fHqLkfo~sNkrLy)VwUfxQXWt!5SD7SZZ_8U2$FN8~v3t z%xJ$7&Yn%lwjcRW;D+jXRt{fcz2+!-%6)DjC?wJ?YY})?{`(9>ESspx3CYa*)c94b zshtlK5|IG`cB?7U`(dMcqR%Ky7W>$mlc=%uzD-sbm`^HzRde&rDw-qeV)eU%+YSJK z46hrJ64Pv3$K?yHY2<5R^uaB(!`hAIA^$u<-g}QlOZVsVyD#tAK;iRly?6?wIwIHz z5$alap_|3D6Hi(G5QZO{M+_!JmM5Ovu2Yy9)tAGtQiY+ZvOPjaw z>=T&vt?fqWzJ+1vN%;h0h7o5hT8@hHLzPoow{EA<70*mb09_JDOEv#8?23@ z4vhjlRMjU)a#SmSaYgOKfWs*`pm=^>4E1@FB<3)iV)khzul{B^1kpCDNVDs=Lv0ne z3QyhI)W^M8!Dxk2b3M?$meA4=(@HUUtRRTFl|UMmRw;xXZxFO6GO){3(BJ&*p^mom)-)C2$;n)@}bsO^UQ;2&(&e$O3m$Y4o+jr5#uMWIiY&>7I$U_ImesDSZ=T7r7vp@-1&d4x$9B9s+A3o79+AJ{M%*MgHEh#=aIaLEiq@DB zCmB-VNh}k_)9L#58!2=Tu^``%+t?YIJKN8L_}aoT@EaNkxZf>C)Ygk5E4V5;2Dk#F?BuGp_o z-PMLErx*9DnCw*#rG3ltaG$aZcIZ*2H|3?$w#)_5)mw;JP#bQ4J{o(k!K}rJPSUnt zl5T>5lbWGoZ=lVkE8m)SnGlwGkJFPx8U$32JodmPdVcwh<4!Lm4lRN7c$2t;Kzdpk zZHy|9j-t=L&LauT@k5V7#%b9^%p&Ue0yUJ#TbzOujQ9IFEc|SB z*zTR*g4vSIK$Y!($SLcKHn%Z1h<$Sqf6G+*e&sIwFR<=@LuEb98p!eSI-x8Jn|rWd zDo-6a^`Cl$h&>D!L-2l_P`L+T(!LhQ9+u;!!|`tmAJm7Qo`oO%pEk{bFAj$Rz-e7r zw(V70wwAFjyO!(HUADDsE!!;{3(LH8*~a2>@B0D%-{N_He$V63vYJ=*K6%sxZ6FL9 zo+QW_9%djK+s;%(^TL!fQ~)25LB^r7Sh;~>5($>ux+@CW7`+MKmDf{gfY0O2-lo<+ zBnz`n=)$Kiz(AUUW=iCeCD^i#{A+Yj52$a}YZ(_S(|$u{r3}v7MO0joTC5w$Zm(^J z#GnHaq)0`7F$zJN;)gDw_!RP*RoxWFPg-d;UvA%Zg^3(Bru%%&IWNb=j>6{+mcCbt zS*V-Up`p*$fxEa1q{;os5f2c&#gnIjs0S2ysGK(axzmUX(Alu6h@qu(vN(+Hd^DN4 zbJ4lItDO{Ib-^-@>YBL@=rN^UfITmDZ&MLK;VKS)SK|s&R&*`!;|)xPvh(^5rcVEP zsQyv|EMLgdGFLJ@kjkShpJ*O;M#g^`=t=1*Ik zJa>j$mvDmCz1i;?1<^T4PLw{$hs@#P8S5XT`RM*_EW^g0iXIprw2d(Lz+9aZj{J0{ zmg&rYH2I|P4#R8EQ-97&GQjbB5FG@~TBTC4W`+5rKeFc?#-i#5`30Cn(2?X?k17TP z)u43SD7~SKLaDOa@8vumUgtH5JNzzS1J%`3zT^<`W6@oK{ECHS*-ww;6a#b2UH^{r zlHW+rwA1mOtlEcqOC*hF7uU>u+m?-1Z!i>p?k_`|`E;<^uu`7a0XhZ(CWwJX-gPB| z`TEWj!8GoSg#LV4e{|@#_PwJq9ZspH%bZT6qrOX!3gfI59jmJXw_QMnf~7m0WNJi% z*`T4e(~*7%!G^`3c)OTIKJIn9QerJ1>x|=n$p;e^{S~{2rR}w(2QIbGRV-m9jmtlO z$jSTrThX#6>zzBAy6_dgHbUQ!j}7fWSU6 z=TvD|VwCC=!sZ6kdYXoxoy^r!uOx{8GilzmL{hjZ1^)Fl;4cj+fyub^4R?RI3i05i z!cS@q748vQTBqNcbEY_5e1CJt0d*Vr%-&qvk4PZ?$;psNozAmO#=BS#T$cWSqT)mu zO-3N5BpNuN4E{UX;^ZxzVN%9rxmr7gzUrjSHUL7A&Hg6(Ob;h()e*}3hJ9wn(L=aP z%RL)ehOM_r&9!wLqhadC|D0&TNL!*X5$+hQ{VG>$7vOx+s2M$KlZt-TRrA!*6Ohuk zmFtk#`?yD)NNX_LVEGg82o_3zxa6Fn5IIPbpjJquB>YMe)!!NJP*f^uMO3<2w;A9c zof{b+w0Jh~>k98e2480Tu+Fr6jU@`=dJz?>z-hA_Bb#Q;Yg*u4lXNLkhqfn2*$Y;L zp%1-_v_l0-tVby39C_el&xq1Qht1Kd6yxe`ONX;}^D~=%@`Pr{77k>80H2BLB~-S0 z@h<@v!<*-x^Ft6>+Cx39cQMaF9it#j{}1qk@pFRQFW)vQm${CT`?4d{CTR3mcvy01 zTg)~-1R9N>yy%4?`_F|Vr{%mH5{oi%-oStVVXaFpkYKnRr4gi3 zAJ}0%w#bgq-^%tj#Pg$n7y)huIxpHh}|Ui?lvHk-jg_nL)luY9s~u1hvlf#|J5*x5}=ffCzI zx~mO;OOzY|JtJtS$zh`xSHY+9yRg*gko#$;XWKVtT}H~j$U>DD|0g=(t1F6n(?mDa z=O6RFMKD1y*NM@8=~GQLXz;TW-iJI)cI7@&*X&+4o}04n+?%IPq-GRyZd`!mB`4i} zpp9)Ka&G7askf&iHe7S=HBT!xT|@dA;)<}i8xEF9WAUweCuO6YrP|t=4*Wm_0`)0( z=5eBLrFMu!tk4U0FkEoRWEYy@z{ApZl?f$Cu(PH4@C-0W;`}!Z}9&f z%YVwR!b1sp`;UBE7iul}9ToFxjr^A@x_Ci@s@3Nk$^@&41B#_VP3>_$DlwIpq=y}5 z=jlk}!zB*gBJXOUee%osF-GxeSQDwz)e@cxE9uk*#w(isUN+@V(%97M&E?OjR}7vV z=96-F?W6#I6N5cg;VZDfT=kf*qj~aXMI_t+`AOCT>rSO%g?hf%_HLn?scv5AT;e86 zjA$v=?zl5x-w`sHfU4_T3+9EC6YZ4EzJ;c>I;zVTjhD=33b9##B&pA#rB*vU%5n1T zsGHtv<4y|kK1lr=gInVGK*hHqVne8tcIF?k_-uuLVfm-?&>Z|%Agg{Y!HDhDFUpVG z@N0d`OR6QPXPQ?2TPHAHrf_aCh(TTrjRDBCOs-7S{=+oB#x%@1`>doe(EP-C)jp2v zi6_PMcX^7P=azvLiKU5tpPw&O16G=2HX10{RbboF!?P;AnW}vMB3x(@Bt$ zFkhB`V&_mi5Z8RwZ}@Z)*0`)fzR-iRis*l%%Vvs@o!9(vU+K65)(Q-XTu%lCD(^G! zq~bJBY?08*xQZbwYLtYHJjjm^MoEU7W0YCdM(Hd93mLa;u1w{+HvjRQhAHg>d8n50 zKBUwg~uxXrGD$Saml2PWqI+bac+3|rcBP{U2Wg)=HL4-3Adnc3@Y^vf9GJWf8a zY7Sfk>y#Dgdzo1w9nL$?AJ<^F1a}v6M}0zQloG-)z3*HxDE}t(ZgITQQ>ERdV6>Tk zJT~>RU!fP&8z<$R$NJ1!uv+q5c6;j=+B03g>K2rhL9Qp}e)*SHw7t3%&?Ow2tt=Vv zSpG{*hC|%Ru=@_GgfhuwR$y({GVMqVCXt)3Mf_Gxy=^Q5$Rmsp))gEG=KoeTToSIY zK6DEuS7;^NCVDTG3Qj3Z%lZ$||4WvChPdcU*`yWN_ECw1Up9ejz%p`v)KXqXLgeG& zs$Mb$*wA;=T*_T7*=<9C{BrKWwYW+X#?S7~qZuX=u=D>U!|px50qn>TZQf$Gt1GHP{yF91|e z8dN^&b&xhO^_(M!Ey_1Vcc2h*a$B<#~breT(0gP&jMb;q3xhs zVT=#LvR8UR%ZBEyN~~`>sMW;eT|4rL!J=!1@CnZVAu0nYI07z5&zk1~&7HjRJgshJ z-J)E8i=LI2m!P>(&O?=}t)-8BnSet~|LcOSTQEb+~KR9o%bG9)~I?M+tOuh=;( z-=&OUTv5A=@WpVG;$YM~rh9%+s5 z#Mrt8K&kIP&cG0SaW!XOo@GN{FoV?E;63~NRLJtL(gx_#`7b?~^%0552;EONdSQu2 zoo+2uvZNm2>6=v5mBO7*7dO8w{26b($@ZXQC}rCNZh|`(qHMc=pC(p7>k)@R8#^ho ztt=QB2uzbH^KNNTzydVWt8=;tvsi2fcDhwbm?58CYTySk2Dp^)0gqCYzg|-fxLdcJ zPN^F%;EC&l2+iof2z16sEgofxgK<2#dN1pgvB}I5FGbF7@6&%E0Y)j$C0yx#d(H&Z zFgXX|t}f3}x`;=ARwY+Pb`O?@b(vs!L5Mp(ld_p07rmG8LSt~hFb=H?xr`p=X2u*U zfJNKsZTVDWgBc?zB*HE6qmfb1Cg+F#ha7mk8Ra2U#)Zw!gvXe=FgGSq!qYw-!r$AH z{s_O*e_`+C`N+nq^{L_Qt-ESTTXHK(5`X|GB*(EXp=;NFXrDvUM}A`@eq`{Fswmj^ z61FL%4f+DbEjZTCoA!e4&rr!9a42Dj^fXTO4?0^@>ZO7Ol)}c)Y88hTo>RI`nr+k?|(!e zVB>ab%n9n-UrTHIN~kir#5S~*?th5hy8pnq+7tKjoY2dNI(F8)Xp90BW1ql5l?J*$ zDUH|u^ADZNtEo)BRIx9RJc{jwKTE#2D(qA5aJ{;J@t;cN5ssJ7z;wfsFC`1wcax=_ zS-|fZ^ty3C6VQ+9!hS4!v^dDIU_wJvxQ*l{d}RyDQr}|dmog3vsDy!Gs+k) zK?uJiW!@aPmQ5S}Sa4dUfZhE|=$^&A;bHxMXdZbW{F)W0EHZqFq19S}C${OQ}N+>^cW5*AfZfu zjA~VlLa7MCSk`{~AR3;hJ!!$vre$70N^anElW}7(v=V~~-b|iA_L?*d z8x#uGnrRW4^8gWS&_jk#52Z_tEN`AgJQ(le${vsZ^8J2Q?coH!Y1a!?V0za5_+-;U zANg%+DwZ&8^OUorwmlI-9KADkk~j;+wj z8AP=e`HsIcI^<9&NbA~H++GsuHW_v4nC=zfrR5aA>D`q~f53~F6YZH4SS}Pxo7zlZV4s zlXzQB0fY`vf>>AsC1pmI9Nf85d=(q(E?S)Cj4g;ftfIitKU?{KP~!?$Kv+v$sKt%; zrQ=zuw8-69c}ziTi?*lQmYRK1y)d_>D{gmoSL|--js|zC{q`kVSK5-cKJHpvf^08+ zn<4%|*@Z5mLc=wV1Z5P&IApJJ;F z>0jkA9TkNIk=u(*c#oq08~kw~0gA8_COojbX-tt&jOvy5N>h zHM(AODSgsG)C2TRi*#leG{ZYnEmykX;-HbcZo{Hw+U_HYeOaR3Bp>fXPSu*GbV2bo zxY8&!pLNH7a7RWm6Z?F%t`%`-^!Gwt>2I^HC0AdDSkwy?Lv;H_&B)FMJ;AYE|DcYC z$y8x-$p9A!sj-7k(NYQU#~7%8Vp*PJ_+x3lJ_z7}un=FS^`PUUdK7k2P^E0nh{{`=&zIH<&=kDKUEZyBnfqOf}ZNu)!HXEFE8{?Q;{QhL2(_UP3Y1LYF|_l(m| zG&~BJ!l9xo8j-=#=uA!wzKIh|a5%W=!8zA;bWJTVbdZi>`$_t-jm!MH6_&_RN)KemI=G))RGUA`dY=HFB#$7xmf9w_;!a?%Ti=l&IY6HA3RvPU;TC5HM} zcJPR^gwx$e1&yN#takz!R_$<8Uqth%K)AVG6>nS}7-siDmngPvH;02~ni=cq8+%>v zR@4#rJ`@s1PwV9Iccv-C2>UT8GhiZ*Ij4b{3Rr-_Q<{r7Pd!7LHm}aJ$7P6#&dUmA zKeXFNoRInF5C6&k0YE*-vzOss1QY==m!W+D6Sx0B0*49$F*BE;eE}4=oJ#^JB?2)u zm!W+D6SvxM0>h62F*lc?eE|~_F*q|Z3NK7$ZfA68G9WTAFgP@q5kUeK1Tiu&Gn2tv zCx5hhWmH`2vMmYjPH-B6ySoH;cL~87r*W6y?(Xgq+ycSf-QC@t0FUf_&pmR_-#2>D zi}JbVS5@7t0U5D^GM%82wEhLEA3mx5`On;(<=mApth8EV& z_7-LUeUK4Aie8o;AZP9Tt~3KsT7v)vKvR87V}P|WKn17)P*oOHQU-`C$*U?TQ-9IF zCsuZ}v9Y%MA1uPkDyrhN01-hs6;S|CjTRuTs;u(oT?Ghw$8SOlkW+cL|Ecrt_$ORe zR7FrlQ$du8;mdJA1P~W&Z;k`8zVeKcT&c8rxZ0{Z#-!Y3kr$!_C0p?CeZ$ z;%M(cZ*6BnZ)5ovJ{40ldw{dGoqq-3{b~oa1pY;gBgp8TP6t!q-wFPl3P8rp5D2md z{s|JZ{@ZBvPUU;hyWZh{fxQ#q@F%C`-|hf=An@PHnCjdA6)U5lAOo<{Hv>5ULHZ!W zcSi?(2S(_RaT*sd2c)rovigc zJ0N-o7l*&R|Kt-Ck>Lh#GO`1hxL5&U~C35GXBH8k$B`_ zmkIbz#D5@uXa5gg0Dps`jGB~$Jk9@Yo4@=-L59{wW*`#)GaEZV-_B0o6^`+}7ns@D z0Palhjco*U`Kws~4D=vthxZVGjiZAnz}VUj?$3d8Z~++P|ET_wxHtg}2LF`aZHE6V zy(48Xd~bFA|J5)tG65KX|0}X_02n~;H}_9F)4SdJp9$X6S%3eV`8}iEKf~Do4E8`L zAn0El*Z~X<|8!t{mz@74-#b`0pxxh!e&$$m%=TnYm~)zPB~gyZ-(4kKexjTLH3vPUOEt3kg}fxYMyRGXvaFET&Gh;$e$qZ;=26@_m}T)5!C!WhA)2dL{UP3N^_5F)d%$n&T_RZ;TYbZ0mc; zdf_373Jzzf(X0BV$u4-8wvkXBN@eA07O_sH{fzlZ2!Bu=J`(0?ZGVhl9(zN*N+i)K z{kf6@>0B_w)QDkciRQ9^g8K_^VdB>Zhqp(JsBrx*!A0t6z1%>oVJRq^&UWJ&NPa92h*?OI>_PFxJPZ(- z7*2$*!hf26{nRncp9bPVvd1JP%M5d2%FT5jojy5i?4ut`$q)7h<&h0y#Z|N#tOKbC zbQ^#9!Af?st;0}eL^EKb(JGK5ozaMI2v=m!lJ?8l8l(s+Q@}VT`sd=tZsnj1JgHCs zSI|V3$3ysr)IB1p?`2xjRX|a)MjCbY6Hl7V2PT@g^sDa zKE((W;1FW?N>U_cr_xGjuEavSHJg+6Yfk0Rh2L_UOlOsIAmk;?$2%pDZ~6WzCD#r& zp2mUYmNvKeJe{VV_VMMo>Z!-UN~j5UFnCj57i-$6a`IJIdp|AVPI6-VBcGxc^E`Vs z|9@;jyuJdE{(Yv;cI{ zmG)U8#Ze;_e{8&gSokrg!uG`ltyS1CM0in0weSXq{gUNb^ANhaB$*We?rI6Q ze}pJ_@_y^tjfcs7!lWAAydr?7?pNKLrhiFD{NWD3mH{6<_|UAQzk*q@aWxKhgcR+= z=_jH75!Ii*qOwxHsIO-#_X-CZN6`wJ^{Gvm0&DifKNwNL-$<&vE00(MYd*|v3SmR| zT=|4t7JNNjrU_4>l%BD}aK!s*h#jA`*#5l^n7Sr(k5!>0kSTc)9%ikbFK{w~Gk+Hs zGkRYyYjGS+7GRP;eKw^&Zq!KfCgmzn3QhLmb78SnVf^sr#g_)u2Amd8-ZnaoakS6F zx7qWlJ{~B~6oFo1vp@0pYm7Hid!Lpk9uN~gyM1FF~Fvl15mR(@um@r!Re>v1+#X7?QqjO*;e8sxB! z<$f12iDLVKuT&5>g(t#6Z?Hb@P4ag;@YS)nPY{&DD(6dBtwqdgoo@lO0e>l`_Jb0x zP-Qktw>xk2EjkV%Y<|EqI88=OM7TOr8nT7w78Er3-#hL!y^4W-2Qu{d=M%qd$)SDQ z?(Fc*9TGiFTwz;WJykhr3wVE0sJrJWl3646aNJVdRJLqwrn*QI0A#EDHxwl}V18mH zZoPye5$KJ!nVza{7kf_$#eZ#|T<}OeuL)-vofeMiPhu)gCWR94kGAKipXbKh`+3|Y z<#_WQ%%VBG76P#sByoAda}glwZ$5r*BtM;-g4W`{qD6_46ERA?Yk7VmgyGChyfH$% zaG44Rwh|QEhBnGM{a#~$5t6Q;c5;Pp!9FbF=I%jO^Y@21unUXVNPj_aMHjL>Af>HazES8|fIZ=9O@I_%}eTi(>=b4=Gz#p!c-N~V&oh?g(0?Gq{EQ0yE z2t3Xod^lR1%x_isW`FV9;tD%ST}Al% zclDsjyB- z!MG3m)H7Ca+;15_x9puBG#aVf`BftA~ex170u=QWgTdx z8<`VC(`oid9RlU)R PuZhw!R+BG=ibr-w*Hg4%D!di;CW@u2davl;$n|MQ;6|;! zhm}C83V#BZS^U_`*qu^nPD>YC*4}UO^Qu=1Niq$aqEXU<>Uo6sTKRX8wr1I`*JBD`|&b3;fW z2sadcH>8{+oe4ZH`-?M7IrZ66_LWk8{dvoJLp}~?7n9%FJV80duwymW#N#n3QFEmq zh;R&9dccU_M09@GUz7a-HW{AKNnBqMPk;Q086|+>N||0(M}#|~a4ttr#p?RrVcJK; z=gkg3e(0X>>-hR8dc`1_V*Pi0PxQ&?;&Ou^BVmV?k+tM4+UGANSsSK|Iu3}F{5JWG zZN9&M6MoDzqGAhJ8F<7WXgZXo3)>@dcQ3(3s695nsBG0}sW=K$A%itvb-R+2aetIt zWDgQ7P)<@qg3sYvifpReC8;0q++1|53V%5N!F$esUG{Tjw2Q3yqpApDjB2-)9riKw zViZcueISWD+Ed9K0F@hi61&+Ih-73A_6w=wTl}}_ijGd3=T$HaMn{lM89{MIi?e}V z*xgOs*iVWC)R9kMORTvo=K{fdUIhHX6|(aJ{Vza8Th+H|SgN zoFYxd9-#43>aI}4&I8-R<@@_!7P&WT=^@06&`k(dRqWE5+dWJT`^0ar$$iC9q3 zP1~x8$rl0AFM4+*4#wx~(5mb0?#vBGY9kM-Yjw68>QG+V@j1yw$b>$8D}SSy>dF)N zUr&&&(sOZAJ|<6L2fR$D|DHG~B$0jsKj@nmkdBf7r=OR|TYqY0rluA!XxE#&63(Pk zSZR4gfq3xya8P8a0mYy8Ljs98+mV7F)Ranvh`b6>Z>*PTD{Z2r zU5TZ(_Q6q1Zp$bT9>fHr(tD|`p(AcHketF{*(Sr6FjHAiB5oVOHy5rGMOuDkkZ0}- z*%$i0HMZ*Yl!^EmFn>7TXp_g=7AKqBBKisD!W7w}eU?=N`B8H*RDU3r>L4GzO3+u% zU|y2BJ6nj;%X}LhMXd)-V^j>QaEo!DoF$d*FcnW$X%FwEUm`UmUg2&(`2Vbm-WLAC z+SH!TV&xjs>`9A48C_-WM-Vb4sLv&~ku>I9%wRf5mqT!S?0;X5rYTy*pvwlIN3kv- zZ->{25qBD0-?R0Taf2rK#N{o-5+rLvs+7-P+X%Ydbva?A)-k*lZxzGYg}{&IS1l_H zy~9(kEO5hUyqfXRg=R@g%i6zW+2(U?+=CcIJMt`#WRu4~N_Uz>RwrP7o5fR!U6yPQ zMyZ5fQE4H7aesipEn%9rO}FcM4)b*=Qz;DK>vIuKE^LmWee~=JeN|}D=j?BOtltW> z%X442YyA14b6a88c2OWP1|jCx8TN)@ZNd^|mFgX0kG0PSY5VaB2b+8=!rRa68aS-A zvk+%4ZO&!jR(ubn>C$*`;x9bmFO)Km2e**3%kwQ;LVvH&L`Pup1-f(H`10PGAsi>A z6R*^nT@Et-gIxX^srfcWg*Q@7wrL3iU6$#&7KJu5XKW~lwCqgDwb*))hqE^OIa|kS z%#}Phf#fraYxIs4XRdUf`goI&yfkU(Nxp1)eFFYJVBd6HKjw1gl0#o=yzO9)zD>VL zWQz21!GGtaBJSFueYE);FFX0bw+(0PN?n1d=_@yx9k*!dEe{S4v7k^Xu8PjXUC%%- zDj8GAP{nFr5(zcvZ<8IlP{F$p04J`J*#1M;6GOAryN*`FJULy$Um2uTvtmV6b`cw$lIiSqg2+e8<`P4 z&VO*Edl90060LOjGuWKzn&3>i@-zEXa?>R0g*wjD6|&y$q_!d4lCsdm7#WSe5AB+w z=gJ%i?)yoZ)4{zR5Sc;Q>SMgDK=Q84_Pxf|fd8ByJlN(lkppk;M$(CRN#6TRCG(Y zgqizdw4;sY6X1O>*U&bP>jfh0Bay~V8aPbmFU@U)#UR*Uk>9}aw_q8&?2o>>4)odg zgvI_Gg!shb!nWNFw{OhR9re8D(Tg3O!-3gYKhi{b46|6G97!ati23DA&K<+f9e>7} zVuv5ZUwDqV7fEEMTF8;UFExWx<$U=g0xPXEYQ5%IgNH)Yz>DaSvmBR$g{2|V2SQ`Z z(fo*z(mi1=ZYrp&Ubxa9EM_j17hfm}F9BY#>_8mm_^n53qHn!4=}1)id%<>+ODwv_ zk-Oegw$jcX)2Q1-Ak+cYopaF#j(_2lLmLS!qE$ZE7A%QhT$`$2e{F#!5=@+q?Tvjm zpJ2;p`|v$-#j-Ms#8;4(R4j}x__z6N5PJ_y$@zl zD~IF|PYgU0RbMfI5NI#WnP=);UTHgDf|G7T$-I(sB+dNf&!~1sZ_>xceeAw`XAk!` zCX5hAh#DIY+7#UMSs*|9SQnHVg3qf&n&cjDq`K!ojtk3tQbo`DTj^KM$}cF*SGSff z7l~>vHTMZ6oD@4;9)HnWCV8XK$_d0hyt^2ErAN-JFr-rcXb*z=A>dY0)p8mJ;tep9 zr5`P;7x1=5HBm`J{wPwnp8J+OiM^#9&o^WWJ#RYxR5zSx@pVDi3ins{@ulBG#%*;g zJvyY$k2#_TMJRjdr>ctT4ZSj)AwlQ}T_qT3)&BW1-b1&on}20v(=1AVEoD&cm)Dif zU$dUU+Wmszhcwnr~j>O|El9(I9f#(6`2H>SI=kJ=`F(^s*ao zYm=ad+Q^GNOeD>6SIL?178XgStwDWosTASaPl7O{dsMU2qsLSOxJS}nUuQ3iCOug+ z*h7-X>IRK1b$?&oW!gVrK5Mr+uBGm%pPRs|Zrz8@`9Wu?$y*P^>z`z|`3w%sH^hZk zs1~4VA^y5X(*9Yx>lZj*I5R5^Yh<{2-Jt8*Y{p!s+@)tt*_{&cUG3{PRjwlnf9F;L z@Y_8zPRi=wrY427R1c5%NDv4`3I+!80nvWF=6BzaB7Y)QQaRB@PC5s55{DVK`ks~i z+VUe=! zy;1}&JeOoq2fAIn<0eC4m(>&^@!N*&Fwt*1M6FWY^pBiU7izQtW-`yK?aRaQ*%QRU znveqaet!h)r-NO(l3y5?S&0W;H}ydCAHh` zcsL!(Q|B)hvDkZP6|2ZX>fl}dsOOEvRr^ueMf@-kJn_R_<$!(y*Y1PZ_yG>;qz_$V zpR0*WRZKWZOn1BQw{)lUIn-<6{*wLiXi8eUYs+5t%7aH}$*UciOYK9t`fb!^oQ`?smSC z!+#}G@Fd`GFhJy`*^={k@ctMmmvEEbT#|LkWOx68ewBB+rnUi_fdp%@%2&*jFgR_o z4^+5>OSNp*^6lW5N8<4>e|E(iluwbiW3+i)v-<_%?h7{gErJFwrBUFOF~q60Q`-^> z_T04OLobWjIrTXX>A86$X?EGit8d`+7=Htb9aRr2? zog%9z(H-WVyzG-Y41j0%&ILO! zHmNEhnT}OV;y8sZ_N{af|JvYW7XA{S*zs+nC3=8Qpusws=+TI_4eDFp@Y~St7o(BN z1n09}lkus-ZIQ%c>`16dQw3^*@+u#+?by*KzdkRKq)ZI?qA=A<2 zwd##;C%kYCMN}79^Y^T&w#1-Ib4sx^3k1^06YqG-+L`b)_fNmNKN?jspTyVO42yGPw)Y zV=rX}P&i+zFHTp$`qBp_$_$YP#KTq5tZV6ivvt9!k+IKP)fbBA?`h_6`QiJw07O@v zy4X@>6b}-_akR3qr1Fe1uycpuERr7)dP?v3O24%R-;`4&3nq7 zaXkgDO704d<<${(%Jv6x0ssjUq6c5Izgpek*i2pb@9U#F$Y?cVNww$mCErcH`8#M7 zSb}3W!*+v$K=}m(@PBUHT!fND2?TygbSLw0#mTm47dmmN)>Tfu(pcOW=OUh7usO6Z zLmR?WjN{#M_a6$wqx0(lmy~jN9)PLPmF^vMYicp5&e24KB=w&G48gH+i@5j|+*11) zEKWOPR-V;hhSV~j1Z4N6n1?ViYR^JgNX!wPO`AiE2e>}@O@F#RSD}j=vP~V6#Eggi zK+xhrqcDIgZOh{ij6KEsG5sUm4&dG&a@K*mU6TrZY599`99aB`zqa`sS3Utff`8f6 z+X9e`nR`si|3>{qqm3HNTS_vi;&pEW+5ds0-8UHa>sN@h$Er?4%7+wcw4mpm51^sZ zrI=grRla;=8Gn5(;>|Awm<$zgEZOz?)NI9KS>BmEkieXpgVEhOY~=yjGOU+v=0<)r zu~fhqAH;{EYTzvep2pYPCUZ#{vsQ5iRmW;}OFx<_rfe+FxEelagwZza#S$>S%Of`i zl0XxtGy{WcdJq%5m``s6HmmNQY$q-{PPXG)5Y_{XTYodkV_}gn!EI$Q60}ch4vh6U zbDFa){uL}cq9Baz24yUQc;l+Iz>nzPsjQNWb*260=(Nl$ZKEW%to!%kKc21Ue{C&+ z5HJ>d9%6Z$>5ToTV7x6VGNV6a#vycSqfYq|%@f<>CmDz1&lYIy7O2aW$4QD-99h-v ztTaehIe+xQ%9xIL9pg+YPDSs+!Dn7c>CT(n%(DD?mHSZc_QM&Q7vo9WsVbZgQwW$# zJMDg>i71&AhE6sDl(U&1AOg>=$aJWgkQh2Fj53-UPm?=Tv+ER)wMnPURE%r+FHa3f zF%)LkXCU~e%Vn#V&|q!L23VaBr0MF9amz$brGMo`yTpQ++7OzbgDKc&XVHGL9YZaC&=P1{edI=!spum5O zG3nuB3wAiY-o;3<>q>`r3(`3(M;-VYy-p^L3&31!=T9pWPZh{0vSf;|f=&hqoO*yy zL4O55E>h?ha9Nlf3=%sMJx}=3yCEvhAK-?}IQb3}^)Etxek0N^YKnVRav3NA6XMDe z%l#gi1gm5(Zh?BO9O+X_OuBWl${(|cufCwVfIVP}v*ZS6)p}y1A;C!2rQ5 z&a&O4UU1M0b;9nCFXKGS;Rs2cNb5)QIDg%&uAr*-VJ>uGhl=0H8()<-z}-t9xRiHexEAAZmqNUdrFrs%l}Frkq` z?gWc|3teqU8faL*S@fAiS(DXCT7Noi`f!1vse**}bN2&gOpuDRV`7f?xonGHZNCHa z8GuM|fQOjBr4`B2&w6H8$-=mmvuY&KY+<_f=pCaQ0vRDB|~_fT_)lelee>d!9KMNX;s6rkU><@QF-!nTB2Fksvjr4}UmhMj&Uj zj!~52)$fZu0z0i*YH+?iO7wo-h_%5IRG!zhwbDf&qUvw8#EIf*S*~Z$#A0R>shos` zGBt<3kWEAIwRj))HsLK`GbFFlFM`hU30>IX4Kr!2{Reo!^!{BgK(fjPuE_jYlP6TKup=kef0 zN zw~P$ri`pA#9X7&Fb%HGd7=*4Nz_kvxAI z+SK_7HHz19@KI$}K~MQci>hH!k3+_3tzH*k+=am3 zg0&wrG;uqEnTsfLmxV%GOPO%R$#*i&K;s5$Hz+~-z;S-W)X~1nUWa{~Rl*W*BDU#8 zJ+qM(hRL)0Zp6jU#edMS!IZdxfAP)5okmSvtPAxT2HgCbuv2d~6~(eL}3d7yts02a;sc$$AC;g&BJN~fmK;6S{N8#z*KMF=e8aa0?WQf9y!bj}zi!tqj}z7Jzk(&<*a zy;A;Wip>an$jN^ug*0kE!CAX_hir(NIr!wH3t|e-TRtWhJ)SQQ{k+SUX#`(5)iv91 zirdyFSzd6~!zD#Wf=x%Z1vfJjiJm#eYropnr64@Vmcf&I`o%7#esEjABQ#fy4YoU< zeAYY4@QFyoXmeTe^NuuH3d{CG?hk!@NBE$r62Ns4tL}fb>OnhoFB-A)$lUZ!Tvu|e z1$jV;q~Df(l?x=xLkg6g!uZo-#AJ6FpVBw0A~+UWeFMHhd`d5qJ9N&-Yr0swv~*$v zp4qp&F9@}qgU_kOAFsivsm92o4(GML_ohL=X|m9K%sH_&$LR6&pJL9&vSyX6FYdfxYe9jevpvWIiRFFj4HTw0{%4l~&Arlw-zFKv><$~ugC ztQ}wHG~9Ud>US4dzSY0C@ep(5Zzj3?4F_3}NG5+akUtje6Y*=G4jeHuSeEIVn(7!1 zpSmp)$>n1(Y2`JKsY?2`)asQkASk;gb_JcTr@VT#2NIe%OAt>v5T=vuv%0wwAB$zc z=hBg2Ulq4LoJLGem2-Q}gxzzDaNA#+M%p5g-yx@dR}&&`D}Aig19L^dCxFww=rQ0) z#%zC@ZfJn3GMa`*iD(iHNnDcvQ#Je8QxU7bNgd`3zxOQrsb%F;=v&oVog^R4*#ek$ zx5)C$FO5-8h8>wm&teY-6E}Lxej{|K@n_3(U398Fp21W92 zmyRlfK7sz_v~RpL0Yyo7O}O>7@i-Dl;j(`@peP|N9N~%HIK3#^P%ZQ8aq*#)Q^uRo z*$!{#&F)<5i8GSoh*Tm%WFJ+U30GCjr|Cz6&H(-->o#owEE@SQWd$k0FDXAG5%bs% zOF72i?t4xJYwj=k>NE=;R!$^6hzibqIfgPm*%LqBRZCr=IaOW#q}cN$QEv)+N>+co zO*iaAilv<`$)EN=lM>vMmQ%m-%~=CTRvYG1hMJv%)&TwD_{MQ#`JfxWNeB>b*53IO z#ZY*%CMu&M*>QHOXQNT3zcwz3n!F)D0q3*orss35(Rlg`gb)S>-e=F-bw2wREFJze z2)5hrBWH$=*tb6PwTca@J_-k>VEBLD1>=llVAIz^5y5;|rc^bb6AC!Fc_SZJmSW!! zLV};+?xekDRdHFZzm2d!@MQ_h|KQ@(EQ#uo|A@uA~zZfBg72y+u|VfPN-4Uqy~ z2o7|otvwyM^6j?m%03w}4!RQma~JPUZfb%os8CCb?_ak5xoMX{FJik7E8|l7Xj2z_~ zK|jueh{xVdSHa$ff(so6LLa0hVMbQmIrj%=zjdG0MktTc+8vB8@fv;9GoLgwGr)5X zjE^`^F|?LnN-ilG5Pu1q=CFSb7oy>1=6>A${*8kH<4_xhWZ`9VQaj7=Ne9g(Y&5xw z(kKrv@PH|2IV}XrJ<9959W;p!EZApCR6N<+o#O$>?=1xeiXp*+EB2Yzu&o9gRd8{? zA8ZYA()n=?Flff~Fl-w_p5Pl$?u%&a&Ti06J8c*BDS5X;>l`^&=qP`tjoE2HLDPMX}qIGxmEFz$Y z-B?-`rM)>Bzx&wQNe3YO^+1b zYTHinag}9+Ei5m?)~OR!znqWl8CP?nFOn$3edSJy{*C?}a<*j(lnVUD z1{6`W@M~ez0NWAOMrp$=QGBrbXw5N5j&Oq8N=Zl3imQLr&TmFw@ie3^Ej0Sq{@dY zdAFxrH9b;ANi9=`EY)C-1F9?)i>2>wr?z+XqZHLN`AjqBE(ZfEO;!6szL7khpVpPY z=KOJ?qMd)}s62-BJ6QRPho;XtV95dag1=|eWePj_&M(@lM+wNa{xQ_Jyd^a$XeNdc{F^-sLm&i2F5=})JA_n z>`UdLWKdqJ9)i6mhn5NUlV@=R#^za1uk;3~{>!L(Q}h}_<|{%hv++0G5WWDgVf6+4 z*J*3}!lW_Q%G4_^nt_Y&kt46PyN}1q;;PlM}I*LE!y6?uc zNj3oCw4-$EIPSZ?S|0P=scL0y;zn;}706Osm=2>+-r0cUtc#LRw%|S)9|}pAye)r} zVQQEID>sdy@NB|4W0Y&rKl3zkxKzGqD1wyuNW9tTWgLBk#tsp5-w6W8@fj8U7;(7; zr4St=Jpe#FkE>GezcUZ|IrGXhHBT?>58D&wU?ONp4}mBerqN=7L4YtQZd%V7g8Q4L z`Y<*<{mi$^Na0%C)$`VS2?y%6q`m#w%&Jsfc_+ z-&dkpLkiw`Srzi(cwf&AF0l7=H)w}GtT+wi!x#Ix9PGO=$v`!;_cyQecP zJA-PdN0Kw@0Pwkq^YL>6FBiWkNrl5chL~Q;$gzNtOUR?M;(0`IXeMw1JyN zi_%^=e0=$)K_v?_4|H+C?C5_h*O!jGW}!6*G=mE{>-J&&xl4=-^6g{Z-_im{sn%FQ z@DoWe@Ka*XW-Xd3qARKj?3~t$u$(-bZ77`U*Bz z3vSDr83UH~L zI;)AdaBRg`7SA9sMSg$Q?UtZ<*}uJ&8)aj_?uBY3MP2TH-gNV>jwPoI%zC*?1hkx5rC)sfJs z7Clavba_KVSPOq^Bj?_ajMzt$U^*bK$yUvS%tm_`@nD)WEHk-rU_;buaM2U#iJ~^+1{lP_lJ!{!j=@X&mQCIujK03vrYD7ObXSKx9N4wU$%0EN6=E z{3t06N3pa7>1&sO?`S#)Ibpv+93&Hw_nF`GBDag?jw*kX`N!_Bd&1}?)>-lLA^kQh z!4sxMsVjerd(RA|yWt2khxK|1>tbEg#R(c1QnheFC8i+LSZMJ{M9a(}OxZWOrbA^7ySH<*v-ah;)H z01xzEj_%Vl#r;d+{D?x+>OaVeUC_Bzy3qX)HRbk1{Vrm1hw+*OBuCA`Q&9NG}Okiy}%n1VU%aY%5Oulc;_^EBM&Hu#A#CIe&QQyp)D)=x~4j?24;7-|LKPFsMg}PG0C*+}IR} zixzIfgZf?H8kA*g4UWUn8MR9&?B*f^N%)a~9fR;29+I`hBkKd}M0_Aq$Wxg7@7B2~ z$GglQkEP)%l&%)42ry8K8-`eQ-kyK+-3VysQIl=yKz8jNC@&HwVot)?58 z+pDI?^Ukt+c%A&NHb#U3DR^w;HO^wdXXj56ff}KDkFid!Si~o2u}9xIG7=^;3at7xi;sK@6=E%r^1& z;3;=`691b(9@oI>K_7v__Wko(G|`FZPFBkyToYLOst%TQQX7^Iv%@SM2FWFv?os%t z6eceO?ad$8f#v6#>ro)triwsB`xaP^l3-Fvxq2FO0oG3*Hkr41V>5qhlw~HJwnuKF zkqrjO!&L=Av=sA=vJKQjQ0kSAXh*MmCk+*9y3W`vHytsSKU)=irL$FSy>&$SJIs3v zuT6)WhNr+H)?zQ*gaY^DF$VeSxxVWl5%07*b$OC!SBE1oPZQ!60u)Rq(3t5gFT9%E z?8;T+;By&1R*g1(@MM3>yk2_=(M?UB*!;A|S#YFb>Y<9%ynkr?(01ghAX5AF%+`s1 z3(=>M)0|5>6N%*dQ?!CbV2~&1^Fm@dY$mt&czEfH71GxZfFM3+DCBPcYQN_*@4?WR zXVME*s3yUiWRyRIg>7~gfNelW|MT6!58qiC_{Lw;4$S$!FOq-A(lI9jYqsF07pNji z51PEb*F=pAy&5`9OI%k-H)t=6Y&2pG&sOkB?$`3QN3>rm++w(%mmkS6UtDZvzm3MZ z>m`M5%nl@K>^(*kuRk}Kk1n0wMBIuo^ir-bfRkUX(T?r?T!O0)s(^4ko~w$lmT#+z z_h?p z3b>V_nkDtgxNCi!CK2mjHqG&BLj910K~dzviBpZ)^Y}|$Sg0C(I^WicVPNQD>DjQj z%ZG7OlOQMEGxK92vIpV^-Z3a9l?|v9zV=1*i?bhkrNV#4*j+vw+1s@xjJ$&s#{A-< z?#9TkPBm2*L35%}Gz9U`!^-R*kVfzvU@g6IuP5B5kyDs*((xmw44^e@SP4oyB2wv% zDV&`zm%yQ>p|;aSwng|9v7@Mbi)d+VO=hlXmjmaFsY>FukH4nMiKi3K6)k00z{}sn zcNvG(0A7D25!JZ2`UoNUH}EhQ(6sJbju9#)hjwK&Eb7W5#r$ zNNopO_Kd{!tUj=a!}bc++Uj%jQT4PxrF&JSPdwV^cUwSW5j_9AC_6wya&yCZpdE=u z@jOxf*2pS&CLb4m<2~$eWa+|{3B?OnDdMbag=v3t5ylwEUY4Fsqv2f7AwJDSKC?sd z;`Z}Z%f`Y&VfzQu!(vz}Vb(@z2VB`iN~!$(RES=*ODoOek%9pqZJwxNJ%K^ccdi3j zGURb$-WaVg+4Hl(C0x-ki$U39WLkb(hwX7bH!JYB!zTJZoYCrsIOHt!A_ALr9t*+mW+e+CrI51mDpM<4baA*W>S639%PLUWh%X=RLe6B~RPe8CQ zx!1&=NZHO*Opx>3dq@+X{St;#>*De7l|X-XEQkCwm_j`4MEhe$vH7*6CW#i4o#?rF z$E(ZAnVO!LEevm4PRnTPK9}H2-P<14Q9_RTN1fwxy|Pr<&C5S8;gw5&7&^YOU6BP- zgR>fi=rWW^(hwXE6fK=zFsUmhc$>LhfhOZIune5^BO8mu}2`?06a)Eo6A<>9qs=D?TVY{CsmhcBt$o zH%ED$tpvNddg&@bzAJ;-fGyq&S~-8eAcv&92G<|ZW}#r%29R7& zmxbK4rMa8=&L(yivuaUi3)Uej5CS*cGawhCdD3(t5xGx6rXQK|^Ow@g5(q4!wkaZrC`ovR?)EL zwreG{D|qzZ|Evam!nKn*a)`YB_LL5(Cz{Qkbsqz|Td$LhbSi90j_h7Ro)UxFi^?*f zJ&C^A$Rb;CthZ=6L|IbNH1MR+>(ARxl`))}0zjjsVqHpe&jv%TfB}{gGH*$n+Qcs{ zk@Or_LFw*aBe9Oo5j*fTGpL!*Q=gNh-jR+i3fmI8AN=C2#-s3KHtNTulXtI4XC9FyT`q% z&Zj2`3GZZ5@3QfkB@0N;DSUqO$SU$B)KO)?7;BP7S4uN-&X#|IbBU|jpRPYbVwNL4 zq&F$Io+pIEYE6=jf-zamDxD4$bC<3-?Q7U0mD3rBYb#SibDTi&A?})Y*|OwB48s3P z8&l4e69G>=whN;au4`~v)cbuj^5bUI_R1ZpVTCs~kvA)s7QRAoa)C)KbiZXv3V33I zX&t+LF(QJRLx+D6+>8YBl}=s+MH3d0YrcE_fOQ4Bj%!^`r+AB)FJ2+lEl(F+WG7P3 zgoooLI0j#bthTQ5f?A|2s$XW6PLwQ7fUZjFbq_|eTz8K(3@Q}Y5H2JZNs6x7yunMS z0o}vA0XCZHS5HhnSUoG#FTS~W?cOJG_rG6#Zw@v|%@n)^PNc;baW;3)I`oMOHKYrwNNs9v$D;e9$-g z)0F1H1gfq)zb&z5C`CJWbhUwkUMS{S>@ksQV)h!tRuki;sT=eeNuV&B)Md8@5(K@at|3md=yC z_Bw}LG$n$FDjhI{0j(AlAt7#l$txH?FcHm;33Y;4nT_~TzVPi8n?W0848bR|sz=(0 zYZD1@pfP>3Ww1G8V)%ana}kX0p>y%+zg@~}KsVVsdUX2q_3*NwC7Y2!NpQEj{oQz0 zJ{^Bf%}v;_WvpqSA!*;ZYG#wnEVlibU6rx^(mItzV0#KqGboSqP>R32IPshk-t-GWuQbrHb$`HiNgbirbBT6o6)Exoe6gPs&9uf3FwhhBm|_#5l|W? zGU?sd?drvBQGs;weapz%xEMl* z$k#nd0W-HTB#TgfH+JzI%W;3nFf_a>nKezXHwD1&FUnn^iVu`W-mEzk9!iXbgrkmE zN30V+-UA+?%I9O`8uNg0C9ciIuuW7 zeiR3J9pZ429wmP_1ev5I-DR7@j%}vphr&K^Bp;kIHgw=&`oYw*?Ad_ajJc`VNgebm zhIqcST{kJ`7n8pQGwyXFH#xGz4-15jUmRSir7KlPiWuWKSBWRf$8QE{o3MoNN#iKl zWyXm63DT(PO$yr+iBqY9r>Bj6y=f`RfItX9rDM)UNV0!t82%Vdvon3}zsDB8YBuy{ zNoUz!{h7VPz7`)$AG(``NSVE}spiVg7Q0AaRre!C4c5sz z1>!othv_=nwnN1RWK164HPW-U`u;zKzbGa_S^?YxwiDC5vv@=YkWwbBN~SvdAy;$n z>VtrmFBGM~${sB-Lj(LT4!urd1e4)j| zEWrATvl3eh%e*qvhVa(NK~*(8HVlRm3+Sezu*ud{2ZaC9QUDRrlisNUxz$omNuqaM z|JHFDWecyRk=-)i-F|zi90hvk2CcJm@1T%UhqKSe&9HfjGE;p#r-k&^g;BxTWp zKn`fX&Cy(=%tZe-7CeLHvj@;Us!r)$H9X$ZUat2sVy2gAjU@uQ%*nOK9SD%7HfPLQ z=VnolmyW<$-sBaqP`Oam#I-lf+lg zSAK&3BAP|_`ru?%{{^3!YlHjIQ4)2cpR$DWfk6McGlSP7`&E3rQSAUHrwbXN1D)@m z(4fWCF}qqW8=MzFbsVtBEsh*?Gdt4~S|xu{FwhN5V)2MtG-b9+zOr{6o{g>vahzc2 z$Nj~kKb8`><2rsBe^aF#HK7EQm1Oox8X0~nmd*TSe*3>qI%*dSfhHZI^`Vi)XFA5&)KvZOYwi# z5}EVK?$oH~2mRITXZ0~SE>1GikomlchkN8O0)wtv#X!_#cNA2tOsOb9-xzI3YNk+j z>-W0`jGE8nDw0~%D1)BCR~;iQHSAwC)vX6KduiLnUTxBY^%UJCM@r09k?HELQe*1U zAT`lk94FjOMp;M21%8_wHssZE-?D!f1`qOuJ1f{C-0HRcxlwM91Xg}<=<=*$n>mzk z42^cr7(86u@CN3c!1dn_XSstia~=ESRpPcz(-zp_uX8xS+f{U3Kt&(0m?f~R`6AQX z`)({pLbh27jU2>B%_gM`ELUV-k8*_M^Z+fNyGUskE$M1Hm7GrmGOIqJ_1J$`oT}i< z{Q{~b!m&o`GC z2|vVW83w&qJbFhrhmi`xW6Hql#9Ez^@0LPe{S=Wr_zWClI2^7KabM6&n!R7o1$T?8 zA91n;EV(MAS0UezWg=Vty>EZmX(w*1I}T8JphuDp)XG$YHU^I!b|gNDk0e5`g@Kl> z(5rsMnl8?#N@r$eMlBz-oYrZprTrp1L2P-CA6<6s9Y|9M;Kx=*6s!}1l+rD|Ob*=F z)EHUE`FWE>aC&cQ7cYd`ABrbI_GPW)NQ5X$g=n)xGCaV8+12pOBgcP9J(nDc*EziH zJS0H059086ZkLpo&nh!vvw1Kendv8X-U?_~?p>QI&t9H_&^pl>5?qSMBtk^BW;)E5dCZ&~^brJ3y1X1c+Iv*KX-l(~uHzER1x|#$G(yv6%mKdLlM5jwb~L>()bwRVC`)CGB2y{2F86`U1F(w%>$DY73Yl^kG9=KoIp;$qed`(JJgrg z@#6IxRe$Shqb`4h_K?2n#H8cU=UryuLWWCNdIr}ceRhjO*Y`GXf`I3|1EBi)WT(ac z*craDufo7(pS!^mDkEn2cWVqy-i8KVLtUFjy$DfoYQxTd)kriP&zUg}L*`HV{g z`*&zs3e+atkw=hpMCu;Mp9*g4D#iH}l=U4{{l!rg+`#ZcX0KV=)_~SYMSCuF-O>ZG zD@t1VwM}&@EdEvr;ppBJC_2J zeJzbu&uRq?HoS;Vt+}V953&hv<=V+3&SI#r_eXz0FCqj9Ey9i6AeU?}!qdDrPu(#l z!ofBdZp;xy%sa*%+H8sEfb_%?<*dhT68yGnOz8v$2w8ae>h7sHY8(1Esob#p3*S@n zfFJ6t_gyJdZ2b_+fY7xMsB?B-tW+w`JIRP?JXCwChGKf5OFV47P?(7}t<1IV%JKl= z_>h0Lu1Nmc!#VsKozehx*UACQR^+G!9bFdXe*CE3v^l~C1fusaQ#7wcZ)r_eK$)dx z_TdMQd!ICkRMnajy6pPh@>jVk##P+k!U<^gy%Ml^a2^5mImb_Ty1af_?IO`U{q>pM zLpKg@DBE4(3xG_q;&1W{bpU?=?;r(a`&55|=Lv<>)8H=v7ecpr7ZgyNF^ySu`;iQIg%3hMpe z%#;C{;oXcs!k`WX@YV-vG^DsQ?66zKLV4~!F(esfX-F7yIlxq~G^S7eb%zh2ivg=W z?=(8sT!2(<`Kt?*7BTw|7!7_(pxiKE9l(CnS>}8FOX$*z! zO*BWbt?bVkZ573)*mL`ZhM#N;u`BRQ;tZcjXMK~h8w135M5ge9j$jDm=zzE1*?+{ zm;`Ogmx5Gj#^oxUCHM&(phXPXAVV=9Q=2vU+kq+o4}>-kSD^Q77JjYV)Og^t^VQSy zKFAF!$nLECGc!a~gNRpH{p){Ykj0@l7SqkqLw!r}cnoc1aeN1lZ6e2#9uNSrTJKEk zQ28*0I~|XSgY&26hl}fuNH(A#<<;CF>&$+UPSY>r3(+J*CRl(g!|I|CR;}vJ+ zl(RGQJYSxVvsoes8gqX=MM^eZ9Y7{&ohXcBI=zf-$l~SPo9i?g|29|}HxIC=l<G?T$wCx5iLWmH{Fwl$2q2KR%z zySux)b8vTecXxM};1Vo&a0?J1xI=IYkdLJA?L6K0{re3D9OlxwR;{YqdmM6NB~^N1 zGY1o(goC{+Ju?Fn4?s>{-of61nTcKnXyImS>_AXW~ zt}g$I2n3j0*#dvFclo_$R`!2n^1=$z65^_A^m6ZmXHPHh@ZKGJ23JqlKi0qF35&_S zk0K`*fQ5wx!1O+(;`U~u4t93$tX<%Kw@=LKy-%(V&R&fFXRdAR9X#xP{;%BJ%HGWU zcYo*2+#DIz?X8^LfYM_BWBo3I`zx~mx&oL0Kqmmu)6|mjce+2*@>|UOTm0SzKOaX2 zM}WDptqai4${hIq0q5gl><$FDI=cb=eEzBUPXx!z2{5xVb$uW7_ss(LC%Uw~xdVXv zU*dNr|Ev0+5up0BQK{c|shNYltrx%yXnzjJsNmrGJ`hy@|Ln8>$|d1uYpY;v2c-J% zmj1V$v7MEz*MC_4mxU(qH&-eJ2WLBD+yBy8xky-f0?m}HTum+ibn#zeX;2bLi>M4=8u`Uy{UtlmAwUkg`ERn?Cfmp1;_M02Q2LD03YV}kv0Q*{uvYi zBZIwz>$?lU(aqHlVD8`y_j@rp*a3{fzeWE-oB&4AzlaOKDE1d|0~p2sjW}5Vj8cCQ zD}Yh@FM5wD`xkKl80G$=_YCrX(SLgeg?}S1CIF+-U-TYR^)F%rFsl7U?_9M1jkw=q z8vjM_F-`uWcT3ZMBW9-e6lVW`%m7B<7s;QUWo?Le_WtDX4-HkLp^1tWqKfFam96WvK+1Qx@^sMjQ;ACa~Jxd&Z|KV!-uY2szx%s}m{}=zhL;ygb zC(smbb=kp`H`F?#ExgKKykxc-lAN32tQJ#1OC||oHG8%P4_mBohkpbpSQ^w3m_t$O zAScD6@1GWAU#>+SiedY<*_m25b8Dt7v}^1y?~jKhF8rlHgF!twM}8%+x|f9dSf-#@ zyMk>#rz@$85TO3$M3lRy?m$*$?UkQ87Ewf`mVzUm&?jm)*zIx za)DQ7l1)&l8TvS7x9H1RiCOsr7z7efGvA`P=C3gFE8BMx4(viP4ri3 z2yQ`xFgE^(E9X43ZlP!_J!!=B*TBZH45~h*mqmsAkmqJeiH<5FV_e^0e3!jEN8`4j z=1Lel4rmIKlWxQh-*MR#kcixzDuPDvP}!J@omB{dx5I} zyozs@Cx3of-K)t4+A9#Q3+4y#RR(K{!a!q?{)W-QUCcr$z~T^g3G*Y}lrU<0Ee-u1 zS|lN-;|sJZD1K%+t+$S+`d4Z_zCGnk;gMAiog5lTq43b{8!*Jr6(6DOqjcISaq&?K z9T&tOqt)8*Vb^X`9l_Q+r-r#(q^|X~O)p{ml7HPl1I@9OzS<$@l-QML-nAhR^*Z?q zs)_0<{A`I=Uf6eqBN&E0$(jVp$`@kJp%GH*g_k$WxY zQdX&nPqXezo}n1R%?h~L$u{~l{2C!jYqkw1t0f!Dd9q4G}Qg^_(=%Re;aH zOF=tmfN?HFgDe5&w`qJbD-d^hA%DP1^?i%EhK)o&xsuftIv(6%<}$NwURRU?N93## z_kiPPgqV=Tx%F^vUkWKPc7FxE`G2hTvTK#^ zL-BcwX*^^KI)}WOj{shpy#w%gr%$VoeD5}U4u1tZyi<3a9b0hQA;Tl#{7=csL*xdIge@gpdQR~{ZG0v2 zQaUED_~Zo{oHK!CCk~i{`i7M6n9#%gnlupH`p4e$3kAt)T{taTMIj4q?YWrTz zs<5PTl;ah1YDtcz^^nh`KHsXlSI96UAS2o2y>H|WddyrsM{gv+ynpf1cT1w~>UU8O zn;R`oMSYDy8+66x>Y*>zmc`H=$PqLvb5WBNc-k3tF0byePFVy>hl^& zPEctf&ExKB8l7YPY%C2WhwI|;_&5D{_~jUk#BK@UIMeViJukD8ql23~VH*}rBsD(H@6@1esH@a6CDWe8ir&S2c#emy z>6>OHk ziEAyA3`IjO}YN4I@#zcV|$v0t-lvC z$xI5it8Dp77=oACqGDgCLOIPf1$%17bU0f)^fU z{r5yUKhd;4Vhl-f^YowaR**>Zk|u6TTymd7Md3(-OQVAIpgJL!A~mXVPIincXF$uZ zzY1FGynpDm8x(}yuliF5T)ptEKb1^cjoRP^^M?7|{&-r;lTev9Hx|Zb4Ci15w}s_- zVB=})P{bw^r+mfD3|`!iggf9^!Nncgx({|pjCs0zSPgGlWG59YM>#2&LAA}IV3I>lmgC`<$rXEbbcPRX0RtWa@8^W6WG}~*X_Rf zNvmOsHdLPgqn6x&6qiF@;8t5myd1JLvhs|Avx!h!ubtye7JNawH;N!BPcScHC3(F+ z*ym^%noS7&m8fdtTa3~eWDnPt)FQ5Yz25oNud=TTJs!JuS&-6f`k!duzWp>1EfFZr z?SG}|j5Y9_wOi*=&edwbJ{D5RC)wf*bU_saKNH`yYEHstgY{cGBK4PHTOC59hU6OF z$@oB^hSdCx2ZUWzwG| z4hnd^L6;moT1-?59iu2NyxpYX!~^R|D1U8qyl4gk6TByhkx^0I>3~uoXexLL%wfTv zX*rW5;J-EOMswDs+T{5fK)|>nb%EZBN1v${R^XOQvCk{R;9hQ?gx=Gm3+Ofy?^~*tEw&peprOA9dz{JG+Ag!TnSXeKKcu^yv`?2@g<^7%sicnVYwL{RKQbvIrcmJ`dgX=XtJCnuRB_# z#wvW`=bI7W9)9;~F9`=V8hxH$W>jcE{9bXf7h8zLXb+36Vtcg6U<4~-2f4U^)@L(Ew0DufGU@$LWiRoO6TQKv04; z0sDeen+HfvIFfvd8uesWTM;CI>HS2Cmif>cro!O8{K5_wyV{!}=C=l%=88`Jc`o^6 z(u=o6h9^XPB)AWfs1rMVgUwJI%7TAZuZ2qV56M~>VT%nmCt?zHm7lqXt6On9LP;6& z0B}O(@se4G1{&+ma>-4u0-Awspp9c2}3g*=;;hpDA06@WSocLcur8+9p>yG z{coQgww$7is&it5dapS4i_dWQ4|#Q-O-2ra=SprigJ20`ik-^&>y$a6FB5-Zov1-y zAryJY!K0x~D`%`2@_LG%Afe`XiH@pBPzs~6hTN*-G&BqM$`+n%suIKsZJB<(J+rMB zGk~+qMoYeksX-@0M)_~Rb{(ZTGNt-FNww9e;Q&u9wH!iIC-?}Dtz#8R!_3)-e;w`z z?_<)BA}rBOR-$OQVHT^p>7jo+)XwiW=|Ymvuk-!<^hV{WnVo(2@Cn89vtyu0O+E5{ zh#S?2#v@xUS*qE>qDQ}q2Okx+n+`20-2^%-R&pXC>occ3&0$P<434wNtpY7`D3J+4 zdo5}!(M#%K=}4`|6f>Q(r|MSLK~vNUP}<+XKK2frDI6RdV4JCWO@n`qFKdD^rvhIK zhc3rd2xkupKR8pH_IT0tz32)Updv(_PP&_lTdi^cZ!j0dy|n#Eqwm4wCu2xklgpmpvZ$v)# z^!y~%+4IkK>{w{?+nax~{ATLCSy%bNn;({&+9GkP{Eg@3!nn zEM?zd#uuP;i6gyo8WY$k#RC{WB@&g`0~Z2B7Tx&}P5w?YyUDn}@9 z4zOv`30YNv;vFUIQB0e@qwK*(QDIkBa>d~a}GJxB0II0 z@Z9SRwZ3S1#1nsL(=4=bx?>hi^{tlq$}%TM(V?`hd%c&^Wvn+Hud_-XYdnb%4bvFu z22j6NlGNZje~G!PHxhWX&~ApT-P-R*H(?9EAE9=XynS#GGOwnKw8h3x^k8-lp?{H+ zRDFK_=mC+8KU3s@IAwH4a*>Ew7m0%1UTRdlDVBMy6SRLh&VkTb7l}c~#f}WBcvKNq za_{imu`+(RV=vuncz~bd;t9?drcHyumQl!uiG%d1ragHA7M&foiA@=ArAhEPu+Pbp z_0o!gzEPTHS{R~dl_M#YAw@4Fo^GKeqnGxn)=TwKx|Fgk0*r0fa&w@(W9hpqo*@Uw zJmP@oNuz&NgEMPDm9+lE*mUT&oU6QKmT-0$?gJl==Rw_xNHNz8s#0sG7qZtycD}7qnaS(r!r9Y`_QV?XFBjC|rlS2ETT=?(! z9}6+3!LtzCv>UeE>xoD{ue}b~%;akjRTfbmAH8_K z$Oj5~w0L6TGg@>ILDpQgd|lYwV$_N1*sf(=7)#cAK7Qz8(QXz1%)2Uv4X|KXt{y$a zr7;ZVmnMmOmTEDpQ>BlQH1q$#H-qNRR9{<3&SeR&N>6t>q9sQ9cPd6;(yo-w> zLOjmGw;oq4Kxe%a0@l7J*Ie4M&6HR=*D@oVr(h80l0GDObcRw~S>}!2eyx@QDTy=U z!KX5ZV)yf#zMc6A=ioS7<`?PWGQNx5=_`M!dA{WWqUu57ub?B&99ywo#g}z>^5FxK zS39SpI7G4cCO)5`An_Q?qt4xguuwCEc0NJFui-0&AnyniJX55 z*fG%3ealXI5;&hHg2pThtSDuJ91p@+4gj^0PMqm%XVqzF`lwyel-HlxN3@gDvIKGo zJ9r)p@3#0n@uO6#fJ~`1F5U~*#RlsN>VsTgYZ(FJ3Y-ocM2&Q@{s5MGamh(n-L4JN zAq?1%p#Jr;BV((8kH#E$T~JDJ)hT}=0C)Kg!0EjuZv=aTt_eTuFa^}2oK(g`I)1{H zuk{?TDBPdMj?CR^kpahmQPk6Hg`d!{8z#t7wqXoWy)XFH`jz8R-!qea-|4Jwt$wad zM@p($$6^DSXUNcW!Jaykjg4C)k1rNxx7sHDm>At?@Q`gdYqRe8`&iGBK8=6B+!Oc4 zkQX_9s=5?jKrpMBQNreDSFSSM*}IhQWD{}s+F=m+VqENUBW=l1R!-MpXxg+?*mFY5 zxRo(0d%5a8c$OpZZaX)i2m=NYAAe*&Ua6bcQY&DSYL6BYRE;1l^cP8-dYPg7fDKJ3 z!a#MYJXR)E#xN=;kp_guMV5cKD=T?&Km!kwgdd27&5#$|fsoY?tt@8lQCHV!=9>NF zb!6sp`EGcIWAKVa)uAr4nq+CG2`z~@)ys+kO<3oyWPWgOjzF(3>`ye_EL)8s>R(qJG@GR@ZkO}u=V3e zPG}4@9or1o9{dgD`F42Z3<_-BYMc)mPDxaxNTTdYbW}LY6q*(o2dy5Qat=|r(%T6v z=*V0tKOUvu6tKz*zz%;~Msn+*RXSbI5YO?B(|w~S{UL-$rW8-&3AU$UYpK|O2sYrf z3Wz;iGz)_$N1!Uj$9}@BWHc+z^*yv$tKyOD=y_CVn4@bk4?I5JucS zvT|lr(kg|JDE^ccUHFT@KF}{oe*m6xm1}S39A~veA_HEqdT663ch~4%dG+DkEinm- z;hZKs9z;RdFUa!v*Z<=s)lM%6+vp(O*p38PF+4&o^hDz=VYjPE%&CbXXz9Jt+dDO_ zuPkQFnfc{D;vs)+?oAE8*fTbT-Zs~ey52WRaFuw0``&5U7o_&{of;M*9*ICz_ugoK z0O^gUSGWa;9`>t2-=Ui`aeho&s{0QT;=y!l<2atl#tX!JKF#3Cm(s*zb<&-0T%zIk z8<~1SExvoHr|bNzV{L+|YZi2Q5CG+$22fo3ObD|)aBF`gBjFq&lDued6X)EWq$!{Y z$l|R{lA?>S9CkTSn$h^AEzybFtef55I|jL~7EV~F5)in*$oDSI5gnl7S?8K#WHkzP zXXKG0QJ4o1x01TrPicg3?v+(AhJ50Zpbiwx?KHD0JM}R}PRN-eOG0p4zB zlt`v~ShB9yQjXxo`3WZFC7@@2FuNMI{! ziY1wvrGD+fM2J~kC?w>jqvQ2SMn2^ucZe_Dx7yoXX?(y?bRe3C5EQ$>e7dXg3u>pX zcwT?GFFDl_G}qI~yz?yNmI@{mnlp;U?3)#Nyw;)|(U2Hm?=oFaF>6|#6pH^oCXR5J zFQS(9t$XXuK|TpDqI`D-hAN#fnKf>I+_<^gQHRyz!ud&C2$>Ku=S=<3M{0@RLcg9+ zJsM}&GSkY9i(>dx<4n<|VbR>Sg>~fE3K@S>>HX!J;0|r0C*ea1iayt&OpZHvMp3P2 znT%Nur40Jnoy$dc$idSl_b1!W?ij($6O5S`R3GEj_L9+V$_=a_{LmSY^t+QY$;U>> z+9h%`kI=T7M8LdUw@&hKlDdg`R*fj5OEar1RuTu(oLIb;$3iEgQq@L<*tRIFbY{HxB#Fiba6Ct}oHR`W)T*EG1$rWtcXoB2u>HQs5c> zbIQ^(jA!!O_H6sbM4(RmBqXY@uFKJNSe1o|%txUiY=_o8XAq<5>3(>u+9iJkyQrw< zBg;4%*W>Z0vdVBpSOv4=~cq=6-FxoAKN_6+z{MrfsY`%RHQIESRg5A)Q zHJvF?YVvI`?`I_vS4Mc@k7j?Iu0d|uady3>OvjGl07m~)n+TsIPj#ZTpxH>}^o?Cs zLeld?qwW)85RWfmBV&)KwT72viIM_Y->du*F`|Z$6}40AB0c810%+Frh$UOl@G^D#y~JJH zRlfL}$6$W=>=laFFIRuIG=A90Voz4M=-U`WG5qft5mX#%^rjj7su$Kh#cKJLdXAJAIp?O(qXhIYa*a)ABUDSSY^M z(Uw|gk1Nie!DMI@23bJe0?Kg0IO=@rg@4kKc_@}y-Erwmht7Yi#JR*|T_`9Nw4znL zFO$_hG^Zk7>w0=?C*^(0jgp=FIJRP3!_T#<-7w7v#PDQsOO=74b`=l+9X7a2c1%hv zAP(WzP#v|r%2n2rB-{{jsxRS*4^FR!-iHtQ2sis;@SNYz$3u-MJz5t$k)|-&CrztZ zRSOhR$y&IHe+hqq05iUg%{f26*hl=&W%^7x(8NS=fuDYxtR@C z*=i~KBVu))c`WP&B5Ogrs<9APC<3_M>+2#s3;X? z0(Ur~bT9DJc_g-`=H&b$9mS40>NO;c15a7(4nWaY)1mZOT;tuc7C^;^j~Yn=%5=2O zHkewhB*TxosPB%znIb3~g+x<$|q)=APU({imMq-MSu&}R;IQy*+mu-FFS0cfMuA#i?4?G+ z$AfO3`ZE$k9q-H@d41SAJ6m>M9H@z;TvOJ{^jCi+KZ&CbYw#&RKcVwghBXgrcmAdn zs`ZP&TEdb(5Vzhv=kPXXS;Z#rW>w)J(5R9S{ep!l@b=q-H;$Ls_DV}9aVQv|ql+Qi z06G=P8J*5}#WxW@d;9w`wG@5fCO4ZAVU;)Kn7 zqOFk~u@Srs2d^~AAX+LtsES}H5i*^mj` z;wrOMHt3q$`?TNXN}Gq!k+@;w)JoYQ@+Wh(3HeNjXsYKK5h|!x|KkIVeKqWI(x}9#t z{cfrFwX(dOZBj2r;4nlzu$k>heN?gs8w@xx$$9tUxko({RVedgGlM*>le;6 zdR(LeSH<2y;pOS*j(XaYpr=8XfVVn|sQTSwrruw|bI^LG;|887!4ia_Lq@Y5K1S=t zG*68W3)|U>`i`8;1OD3jNLB%7w%O#L7@MA3yjGjc7e(645Vq({hNhiMbRU1^s_m?f zg~#iR$;(XGs%)(n1U1Guy84d*+)C%KwkLpDvqUE`T58PGX+y;f6(@)E4yEN^#^{RH z`O1@c(cvXZf&P3`Y~T?2^9v;qy?zf3=G|J*Z&Z<^x=?h8usB5VNGCBgHFzB=zp^81 z_hUKqDN%WO6T1?horRooufcyL8;&R^4^ZwCM`!R?X6%sA*h?Yf$?sQ-u1NHLc4zgC4mXQzbHisaz_WL=IK3le>z)i+xcjEyq;iTK#= zkg;12e*h<3-8DU%&iuChQ$jf2*zf!WNytOPPCy?}{eKh6p2)1M;ZR-h$t0yF zOl%4A-yEc~sn2|*cAhfz?kcur=mGUCUbiM=has(Q=HbQ$oPmGKDcGiWx)O+vm|{b= z7h4Z3NFr+xuzV>gy7(y>=JoVMo-e-+$b8jLpZ`FVmior5Wg{*(vx(n$6~O$_5smn9 zGyTE`iqIJ^i>6?Fe9x$yF7Snh+mIzrhdczNvs+xJ(m4phORDKC+%n`aPX1Gu*r0|A zdZl9P5R;EZI81+}#qmJE1ar$&?on?D6}{_h`1nmozG5Lm*o8R5i4Z>2!qO4Gv=i}B zo&{B;krq`wiZ`qN@S~?`?#b@Xmf$d(8$Q_?e#{!@f)h=oHWEiO1UTFpBFz-7X3gpj zr>0DrvvF%>H9*YFWK!2r4GL$)M`#=K%p%P>H_ibf z+k3v53WJxp13M(0IQeCYU`_|Ak1l~K82y8ib3Mk8g%WA-Io!cd| zbM9%7^MXZPX81>kBDp~_Mj!Ji5v#8_p$SB-v))J5wQ$YO{@Pb&lub z@?M6SgQd}pBr93a`>Il6`I5^*%2C`{@JKZC3jwA>ye9c_s7js&K|1|`nx12^?b96h zNIpD3stv&y*EcBOJ4L4o>PE%5qL``w>bHoDuLFOGtX!5F&ikz!;G#GETO1!Nl9JD0 ztP$*1kG#TsY`72CiORD?SC=emdOc_qudVtP2iJhPAdM+zcQ_0S^Uf}!mL*f5>0eJs zqq%!(1QcC&iOh2Nm9R-wC;5M%p>CyiDwkV*GEBz$iL}I7Q(7Qqh?3}{eoRsKtf_g( zcSV0Xd10E@9^SFm=Fx+@*F7uRl{LftT4oz&57UmoYR!1Ud<@^|1`xfe+(tY1-t(F* z{n?iYJrzD*4Pmfy_mNm^g)Z@K_7-8elP=0Hq^8RqMfaD#7j;LoH8LL;57gJ_?2LI2 zh1;c{W8l>4<~a~nyK0@$XoM&_D{0iHJzak;Xkm{M^ayJN3e3C}4tt;GdcP>bXo)YR zCcU*}Pt^D){Dl0)^yxU`n`Q2iq3MV6Dn9Sc{ zC6I`ktG6@jazt;B0rQO$47gN2=TW*QD&+R40$mk}uXus@~7Z12c~t) zX7(jZvF~p(Q0Gs^%H$J znvB|-VTQaWiLR2$LRw!{_;aS{nGJui{OQ{xiO)^wt}Do2f9xBF&tu%^(OS`|&_3?} z6lZ2d0voS9owDWmic1tQsoYZSY9r^3C(h34WTpo?xLu?Z9O;+|FbL~WiNj@mY<#dL z905hMTwieDHLL>^=ikJlS1b@givx=k+F;W+)MVB5%2^F65sQZTo*(Y$NEd&WJrI`1 z-9F;+3%OLw5bwY!!($fn7kn@yYA4P4&W60gI(<*lh0?_najn#l`*R>M`Ak5m-_d3G zUP;YEVj(@nMHaJ0ylg({k+Y=uZWy#dgoc?{a{bI3Gy`%=XGr`TRje&9A8w1y%SKV+ z@iAZABOZAae<9M;(_62IT%3QA|9w}N9eVKbu*RtF{wgu)GMba^3KXRhL9fy#ZE#;q zJ^YviySCVaHWx2~UXP!mug9)|w7W zUt&am*Ok)6S0x#1>^7L=$G!cW)aE8-^jtICdKFi|#7Bo%m+xOS2-JVaw*Dv3{tX$3*)OAQeBXb4e|@<8Kuw`-U9`jn#@%4Bq=wnv9pPwbAlO@3VdRCT8sqdx>|`gzJB2*7iiM)rpBV=5dqz@s_wxMj?ZH1dY{cL*N6?QTOv4!KKQ=7BeYT z4f5X#51%gFGs|9EEcK|r)(mHT*})d=p7hqV*;doKz)~kLWb1zTd8jrT_%p!;-KMzI z<^#1_FdgMnBtm~FO|91vxg;r|Im-#knzwhmp-{E&^R1E~l_6-Bgt48dbu!+~>&u-F zQNB@}x&EZksKntN(&2{={6?S$3CYq5YcRVb3<^pmM|515ZXVTNma#(n?FIis; zZTrSDO#oX0;L5SUwl7v-QgD(ZlME(Z^Tk;=k!Mxbw4;AtM6KREQ%s(TgY2yt1-w7P zAh0>RAK5Xyr={7JeTG3sqzS#>w=uIlntjl;mA%=x2rwmA^RO2qe+5XQudnzXJm8p@ zKE*Go*G%LV3m-4?A@0-Fomeih6|)xEYsU{sP{?f`azuaeM)Th9 zZ~4ktOGSS!BWXYAVl~AC^40i{fMLK;0Ay!1!R;aZTWe@lE z33;JriMrMDxb8W}OL{+WHeQU$++h98X`Ne3cC>%oJ*R^faJ|-SOySu_RxFNepS2m~ z5FjA(JvhPp+h(1L=$Pf;KAoQ02J_h652JX33_jQ}0J|yozF0yuD z8_y`mS60YgU-$B5&N5L5-xeryyq05{%T8=TayX-kGA{F}Lle(u4y%<7YcDBACJDftwsH^Q>6M+r=suC+p1_=QxM-ua&GZ3}e2sLKV z_MK<;kIXgH4ahSBfmk4Z>3y5C~O~frCvkALD2|wwF!@yrH9IxkPnV zTCEUtC+x5~_lc0;?_j7s1$9|P9GE|BySjfHG4lK9lv|H}&}2ff`MTL&^t_SsiEj{k z#YCXBz((*p6B&3}m_JQcfy_p1Q!eF%4GwkB{T*$c`t0fBoi!9$#~ZWpwI==gqwnB`MIjd>4I%}~5dFUoRk`Otq_ z{FNCx^ope4T{4ZMKgjid;1zn{t%c}jvwk@b*$TdZPA0^Ou_OZmBiu)5!zW)$_ZTsD zPBPM=uKQBaF_>&Nl0hcr#DjPq{>z9|D!JYJXM3j+cg3{kCGbp2udm!bS1S=S%=Y9i ztDrfJJM?v>DvyASbN!V@#GkeRQ9ge~vgg}ieo}oL=rq{`9j5zQX;0#{xUcM$>)$lT z_o-bNv)223-Gc4c#!MkR2AhcGpNZIC6o3HBSAvcvRD%8UlWV|6PG6-y3OiLrmgXh= zV`#QZ=VGSo!5CuH*JLH|Zqp%?^fy&HF2?g2h+&iUDMN%IM$kG*Q8+3;Ke&IKR^y+2 z!qTcbU!S&R32LTFV^uRii}jn4NE$B$ezM+7K(ma8e$M=6={Fl5p0xv|{!CUAIxY)N z--Vo`@0)lr(rD+`(%ehlGrNtLHbhif*5D0l$LApc0sf&HOz2x8z6h&^%(oz9+TUQcCK5mtYl>IHaEyrj3#IGm1aAU|1aph$=l?Ddcp`rUI}>==hG z$LZXt;N)Vhj#E$}aM-hm%!9*gI_P>9xL;a4KNBWwU0BBl9h7uG4XDUrD$-5oaH-2+ zCQCO%*D)t#h#ypaixa;(Q(oH5rK!>Hv5_ZH?EeJbvWEeRf_n+R8X13vbi0v)-v3r? zj{j&G%ddJ(Qw0U6;wY)ew@-lEs}y zpIi(EWA2?unpti;KV`$|5=!U`P@tZ+P(>x)o(}rBPLfm^5;>J#l(g&|b(OQg&o*k@ zay4ouoB5#Q&S2b~%T|9xlj&F8(MJ)|yH+sO&=xk&3RQ@k?iG8P>i73dzd4#W=bh&a zBKQF5gpxIp`4>h%;aPc(9BAciT;(Eo0$cIs15FF47a=8Ev-!L7M~9zfBNW~=fR0HY z*`+n88rpsHJwS0|W!M6#Rj?Xw)jh1Vf_XJRLN9wJGmKN{UM_!lEcpi0#;{>zQM%_~ zjqpLYDb>VfBA;RwGHud=`7`(6(R|V4b>B-f@frxMO#+O^T$Xft|A*CYkwuPi#NN`T zX~%OglhwHGlphAS;h%0U#-_8d5(M*Szu*a(cYN61s$M;7ol8HWZr@JwqTId!8@?5* zC5)Q1VSMqY-KBrw#(|J%{mUmPA1=}(`J^*JNY1_1VOC5W5+z>^ZrCommM|(I&jx=z)uA7MMz8I1R zs@#;=tOm_YA*QU-2WewAvN**3QjKRiu{+6dSO0xnEi0vIIZ85EG)#d`?8i9{%@AGfEhZ+dgnS@1lES{sQE2(pN8MT)Q5e}zl$EW4!n_7~eCKb-K zNTYwhsLGz1ZvxRI?k*yjQ|=X2KKwvZQk$XE2}Qon;>y=CS&~szw~>n@7x;G2DCUu; zG*zJH72S{d)>Zn+I)%5l_U_gV9!xDEILb?^QKQn_%Q?B$k#ur~XJ|=lF<^F<6(!7A zvC(QGE)X&a<62WK0FaxK7lw&VS9Y0h`{jSh>1~tz0D1XB0;Y^?=&D@UMFSKQ)2c1v zG{J>{?Igi0nB;qeCbC?UG3}3{FHSx9uVNxi+qO!!m*Bzi0jeJrU@a@uizRhTS||{w z{o?~akKrFm&2^G$r-X7RquWGQRAle&SIwe5L3qFuG2}S9sy<#+wX0ttY5v4|B%Xh@ z_yxZueyE`L6d_CRb)x~zs>N;!tYNQSRnr9Vrh+7UV|7^28SsxUP@|ZBv_P-_@$t;t ze9nz~!=+Sxl8(`6KvK`@BzB8i&$8`RFDZN#rF5J=nlpvYFrmo?$@-Vl0ZFuR+3wA_ z5rdx8g&|Mad_0k}b+SfjZg1gks0e>v-lk&?KsHqfVQ}aQ|Agxm(?pM()laG_&>le{ow7B$BjkB4P^GW0WAxj#$S=G@q1dP;o+mgs*QpPe=i-B7M&9sR;h+j)p*@MyDey^Rq5Id})Co;K_R zn!^k}$So-STBEq4X-)PPFV6-FY560e@}_#Brda&_8LZ1*5k|Y=tAaD8H$U%+0~g_Z zzx=@=uWCz@3$(kfJV{VW=wOE2qQXp3!HNy%h~#0?Fotn65}v3=5<`DrM;s#Wax-MG zPE>5UYRmYxw<;Ah0{K&$vv5h!Zf0q0Dy0K~Nhj_F(}R@ROtrpA!SU7?msS8&R>^|h zPE)|IhG+|+J*k_ib(563GuTgA(BsTES!z<>r48E4r1O@r4slWO6MBuHov%t6P26SQ zqM^|6V{PazL}igin@N8`+J$SZHdR1b!b9D`?RPsA$e~LzoD}EfD{L;Royhvw#$KXa27K6{PCvtx^uTXfx1JfgqPmN%3 zgsGUDo|W4JTN@!(2i>2IEzzrDgQ*^p%1w=G`eblkb~A@p#W#faZS?#C$rREwCOaR* z7um7nWh>T3>qi1VmZ*CWc4dw2=g`vF{6a)fP?0lLL~5)Zu6?Uv>c+H&4ticV$6YC! z8q^k+sZG3_%Wr$**8_|Y z*YS1>6PIX;`8eMbbUuZDL5^KZjNNsL$u6jVpuAq8GWNKj?3xuCF>YcYa5;J|wYs~f zeZp%3<4-bt6Ga%((4;N#UEkbWzaQH@X>)(<;PNE{;TM0%T%TLv&>hO%XiVy!o4!C6 zjqdkFBCJJUJ)D4Y7vn~3dh8%i1y})$zn}saiTSU#Xk8f|PoH#B5HO{D`JNr3Xl^mX zUPR8<(ERnmQF;TZLq1T0G2vFRv3_O+zM)&f0_+mT>nk6_DB3E51$e+cZcQor#VI>7 z`<~%IGp2v9xIUEIu;qTLisj_j&#!E*G$9=iO+1`vN)B$Un-l?Bh%JExG~LrhjvRl9 zU2DjK?+*^ERafGvdfbFN5Ra^>I*uIoss?Cz@v^qpl=b9>adBfNbGX}o0o~2 zS9k&PNyFaL{xdb?Pl}sOK78)7Udy%q#d*Rx|yR#c$V*aJ)W)*kiv zVOGOXc2ubT+Wt+-BSzbj8M$>3R~Xq9ch&I*4$6L^Q%VoJ1Nw z#pcGZVR5EWpjbCbr`fcqSbyh4@BVdq43MEhT*iZmsxRvw=k)?*m?VoEf<4QAG;KKUoQ|Op!K(`l1|~e%zHfy;HI? zz?OpsOzSk;)ZmruOAjs-Ewg4|&uk$bur1uB*h=?xyF3Pq+DJAd#E5@HPGwGRsyX_5 z-1C10vKED$w!pmao?b2S;L>b^F_pkIUyMe0^tb+(OzB z4+eIOe1{%(amb?TcV((yeqnB4`;0>!4-D~e>|uVL43`b?B;@6mSYlepCh#hjr<0}l zqR$)#L&D*mT7~K@xUqi@z-~N!D`8xp@j-T{G%Hgo=cg#3St>EP&7lF6f3v(9VN#mE zA^BV+xFv1giWTRrt=8%K$hIX?d~vsgw?#7yquz-TDfARjB{y(HmW5Fxj|m^<{flTK zIJKH}pDVM0`Z|z>N+#yWjH9|_M8)2&Z5$HPJ5TmboSo^%;QfD_;R-~08~zW)8x8W| z`&~p)Rm>Dr?6g!q+kJ&m)b;-_ktJ^0@hU{?>B-U+igV1%_I<;nwgE(zD=`k@$3$z`xoNE*3s(uk+7)yCBLUQmJLw44EjGAHTPf8Y1CC|8Kh( z)%9jH9@yr*Ak=?mgKFIZeBV-L;;@yjqmi8&_5+4TiebS69wj=nA~s(x`F&`E^iZqO zd7p)Opf;*+Pqqn31Xs_FWY}U-wwfs8JIhZ-W$=2qC%Y~fr=Un}_5EXaA4+yq$o<2m zGvrS}&V`rZgZeG4mAh7>JYRoH*zQNqv)BqLrtv_*;P-z{D)A_Je9aTNc0H?qZBe(+ z>c<-Q-YkxlKtql!2hhE{J6FJm>g&~t@1M=PYKY=gD4JnCo zlkfr!R3LxDMVxuEOx=d$%mq1MRrN9idihh^Nt}o!{Gv^t4;=6=jb=E~Yc-gX#%YpF zEL8cm=(;(}@@bUW%6PEiGRE6N-OIgLl(3}h1jIxkf|S~ZiGit}Bt-^%_;s4OX$scE zrhmV$!;icGhCZpz^ZAfQQT}zyI^#sy-PYD?WHWzIyY|()_?`4Hr%Bk>)W&JDVuMh* zqgpkR7V2;}65Df2{?Zo%Kc)>hXY{?40Q+$?p;(XW=`|9#Zv{s@euBF$A(KxAQ97-X z4yP)k<}tTHeCVgiwca@R-=yJ7np zAO>YdgQp$!BoW~s!qWciV_KgcXVrh{8QKqxLp-kDlV+%(NjHgiK;&s|ki|xASsa-e zLrNFh%J&UlNM29Av|23%t}?9)4e3pYMrD8DXNp)TIfr$7(Y2d;8WSYu{7)wpo1IDI zqkaA-KlsvUhkQCeP!UplvWZ9n_Tc5Su(T{rDP%suOF?=aq71?pE{RnBeoE%O-OSKB zIva63jfESXm}Q84PWY;N=Bp!1B`Q#EqVJN>z|Sj=pTqw7-#A{3X%a3W3U8tQG)vh=@f!EfJLZyf%p1>@ksw5TS{IG(BYm2jA6J z_READLZ%QV z<-M<2mpONG)*9Ot*2%GfL8_Kxx+8)p!f4|JUeDgX(mk$M36Qro*@z}g|l7vo6azGD&tB+o?`dbT{< z>vJ`PAU^vZ_e9^Owg~^>K1d%$*=$euuPaISoN#3L+=i1XMUNkSK%bV`FQ5viV&MT< zv9{2|I`U@irXh#W{MICz2dwple4mFfN~Be~(taUD6@+;9ZP{Po00$>7V#uF45C-xk8i7kEO}s=0 zrS2fGtbDQCz#A3*kAi;>x4)7WFdiAeCFS)=e?^7>v8a0E9%Z-h=dsHIRukngK4^Z+ zTYd>08ximKKMP)ZS4haOwNxm`3Ek|tT7*Olj{2HXA{AMH+N%!{bmhKs+ji^SJ8d{B z0bg`ploMGCo+67ayiKgH=`)%Dm!Lu6KoNPM1+8b|59!M;h8BM-*}Enaa#KdubuUh^ zTYO>{MQ_x2<5K&?XgdeixxdR=ZAEoR!qfU01oX3eu=T$PpX4MZjai3`S9&h z>RddGtWtP>H{^e_Nov(;vbvwt25NXLgKUQn)MQoND@%X(3c`+lbZE3DE6_*)s_>rC z?IgblLk_4iHs3Aoe>ud)f-p~CD^9D}C(8VG+`H5F6Q>aS6@>2_rH^HH;<7U{tMY&0S^oZs%5I4CJ}isL0N5Fb2u2WN zg`{V2SQ39L!J&afjn|;A^?o}Rq;4U(OB`x5@j=)FY=rsHr^6Sg8TyFbW%=S{u&l^} z!)Y|V?qcbhVZF%6+wTRD8)F`x;WHQL`?Q4>8bUf0qrj1%OI|p&2MNj{q6gU42{UCx zTa@s=iT<>>Ou(b|z8jW~Zx>LaH<~Iorlc|dVKINy%h-qy_m#5$@CJSI?GXT?hAz~A zTDu1Nnz=$;^}@Y^N1+Pt_c1`c(c~W7-nYC&)!Cf4r?}V`OVJ6E1=Z)1_S#_(MZKQB z$ni}~!gf2d=P0A2e{$jQ zMC0l3MM!-+ z*N9+b;!il13xDB+N>_0=Tlo~>7=Y*>F5^RyPid=@v=mD+z_ z)=Zjew%%$`BkH^Te;#gJlD*CoNYyu_<}BuN6a@92N?E5SN6eWm!F5Ve$>&t3QEqFr zH&)IKSFa9_WVP3HxS}=K5&D0GYxv8|2C9WKjwmbDeYZA)xpIKx_yL9+Hz;^X17fV_ zz&0Z=CSg_W>MlT1S(q(=8v^?cve$n}6Qn(RR%n$MY~x3+D{d4e_#nyB(_!zd_G2NX z)1{DZAqd}IIQ-egU-T4mKyRz+uWz<^Z({dCX^rl^CjUh}!A+gcBXj(izLl>63a&P_ zd{RUKFcJ4~ke=Zh3;hoKRV-h{5tIK&ua_nv@89QSYlb>-=QRP+nuCg=k1T%?hg|Ac zz23x5YOk z#JE&Q4v{L2sJG7QXO5NT`B@fZnQV^Oa86|=Gp_gW6!zF?Mnsd}M_>CI(+|)Ilo{zu z9;&!A3NDI9eOi!%*`d zZegb}F+NxOKOtVjO$*v<-BTw1X6($&BJD^O8Qu!@L6@GlJ5U^IYh!WG-zAS4UpP;e zaj*2b@q9&j z-8H=YLJv`?#fZdOCY>2*>E8K&_e3D!6`kh_jwL<7`nAwi)&kM`)Z|tk=^oiK>++Hm z==}9gw^IVHL^P@it5{hO?4+(*2U$`--C<2(LCSxu*=yVUH$EO8hln57ygGR=(o`b2 zTsA=)t37hu7y50cNTz@3REIwLNB1OM7%N<5tOV8OFR$$Wd>_%eMng4bZZfu*K<|GP zySZ=ODs5kxZJz52h_l5E@`ka4iU;O?*}>n@IARL3AH&aTrru7H0TZeNf(>jEv(l8(K_ z$LBSptjlXa5s^3G&TAbxKse&|+WYda->PbjgI3hDKRf6iK!{9Lz4k+SdKx8Mp#Njx z8<>}ypF31KQ4Mk#R`(zdu8))Lm99dae9x@Kee}QdQ~qH3$s7+HV@t>|KZuha z7rpjD1d8(bRgv*iS6}++wpSF6iHBu6%TMpT4)3V0sripGEcUt%%&}djfSvhJX|=X( z^u>l=^S9_8`u8F}oqIXn_aHJzyKP?Cp#{PNpT;aV+bDnQzw-}e6+@vBCnul!O&KumD0@Hw+Cc8WrjJopnkBWbM6;)NDdp*-?(FoX;^b zOA}$~pn)`u;&IfncZa{P{h9`BOzPn6SR2Ku1A0vsx!V@IW!Q>(t&^4+cHWTW0f*{$ zT*Fv!5Ndzt-*#|6xveL2mnwjD_o-GguUxc?0c%*~!y>d(%~5BEL=ZJ7p1{rwbzGHQ-1m_jtld2M=M-=wF?0Oa%1?!`)P+_&yS|Sa3~EB zvF$({9CMrQBAOT|p6ct^QREw@P;LY2ElqD|4L< z<{pXpu%`ky`+XFR4m0?iioCGYseF0;W2E1yTpVM~BID$!=KzB6z=f{^*q(MyMt2R~ zb25LG{Ox|LH}jGdLks=k1A{|Wrga{X!OSF`VE*0BhSnCxN=eYMKI~vQCZz>A_8)Q# z=l6JpnX2>uM$TIOp$nmTd@sB#DwYs~g4Eu89bE#)l*D5=$)t2Z^WU5mbEplNvL~NS z#MVDlztEuF@$LRJ@R9A8scnRvV{jl*x2|KG6Wg|J+n8`-n;qMp*tRpVZQGgHnIse4 zxu?E+e%v}$=SO$#wR%-`wRZ1bz4rT*Fl59@@DfD=n=)`9dr;H8`V7YH@Sc>oj0atc z|KK+{{S|+Sk$DKOxHE_H5`j&=5b2YU?v;O;sMnMxQk2lbp)l{3`@ZDmb7vBXWeoKv;s*HYbw<0}rvxv#Kj56{OPzbTm zPq!tax1IMbei3Hop6_LR$M?;ay%RVomxnt92lakCTs`!#l73kXpYD1GNt>?<<4^r& z5|+Y5ZMS;Dky3Ho@{6%H0ZojIqCe=osuWHA=b_EuF46}gBnPBE-1WGiZHD!Pm6IZH z5W-vkWcwKF_?9tv75qF% z`;5`6D-X6)0%P6JtPCtKO}8JvgjEauH;;NxD(#i`%H9r^1I^Z$ZawP}bK!N{X5Y~w zb|ZUuHOfjy9haU@&0eyXc#y1T)Z2ao6|(}Y2cAYk+c9qPrd~8Q9H%Sg>1m8TGutFP zkF;=;F4}O?OS19&|s8OYsVkT#uSCNK&`s^=Qd&8Pr{) ze8eXZEvlwKkHqLsf}1wk)b~So;WJKeKy2!dwc>*^-Tx^|8<9MoxUzhEdJUxnFwhaT z^ztH^A1!{IxmySQ6SFC04|*fGeNqbGD?Wxjkn`$6HpQ3rbPO=wT@JqTZNdG8f7Qpx zlF4#$?U~5ryNE_*?lhz9!mwQse*#K+Z*z3DQvzW}8e%f^8K{Y4~IYL^KN;1XB9_NRO_{V>{E z#eAsT4%N@DTJGw1U3yy~_`TgeVr#~4u0HP@=!#gk2LjgpQYvgYDfpb?c4}pUvo+O^ z=V7GM24-z#6;$jhf%6KoPzCBV6igRuU!W+uP+IrqZ|s}Yr?y8?ki%BsRQ8(0<3DZI zY=mzH^E-ghI|;lLpoRg}x!{2Tt<8v@)zP7X!5e0_k?z7vuP|onpWE3wu$++WAB(=L z&_K>9uuZdMF-$V)7barKW36*G-p_^iY`O@IN~WWn(PWx#6vjhF*UVIh|IlNRP0aj$ z#k=lI>cTwzXGHQ0DA2hg}<^Czcz4y3RfbH6&%%m&_sxT9qM1S?TV7I9xR$R?F z$$oY7n0`EuowdOl@xiky^aWKQld12&Eh%5;)1K{*fy@|BxsA%FdCgR56qmR&TfoyA zgTo1#irNzl9FVwuu<+;n|koA9f_R1T6=6i#F|HJV@t@BB3^o+U^& za#1bTM=s!+ID5pwokMg%6cTCwLnolmM5NKpIZ?hC_?9kHrqGF0Fk3ghrZ$oxz0GL< z@aLPDd*+Dj7e%A_l1jrHpS8;g%>#6)9JMW&a56KCkJ1?PlZYC1LHo8%k*pZtgj>o< zMp$dUIB^jmF3YCHRGQ)cvJNNKK-uMY)%-JYY>}QWh^zCxEX59#XRkUKO7zSDOFvY2 zU7D&65G-i^Qtm_(%+*9xx|VY8gq2^l?n+Y3M%V2RBaHF+``xxJ;3QQ-(}%$iCb5y1 zdv>-9R;h3?6T-pcls3d`(VV-XY6`z*2`2ot27htyLUq&OS}h~PEv?$NSGbay z@5Ih4=F7DB0V#q=E?*<809;5-VsXN5*%RaznB(oQkywZ^xw!f|$V%{#J zM|6|bY;d-qy=&_6r5QRa_uxDq&xI|%gX?Ose&<}O@KK;|sKH=B3yM|qz zjw3~g$~bACv!GVh@3CoX#ootSH0|iUX(7#h=owAR=<6G|)6G-ZiqXR7G2+JSPDAg( zYV@i!&F=?PkLw+@Q{7wZvPJU5Xs!i)6!Jh?XWJyKkJMEP!Ju6hTP>fJYbcSGlD+4x z@}Tk`^*t2CsRsf+$n_PhY>NJY4JGq{2-*U>S})G1|rOWMng1y=*Uy)8X7%dcUWO0IWvAn(XxY3 z^>$;PzzR3GMS{ySVUY=WmeF&O?X2}?4qlYYeBg`s0Ax(SaIQGaOk5e`;8-4Tp0rq2 zDd@_C)PG)ouzj%nG&Q@6Zh$9hK~NS{Vb0*UEGEahhG9e@E?cvyBnY_Eq$|h~uYefO zB8L3yj|?82)P~aKwuj>IJ*9iva4^WOAjR_Kn?V`Di3zATg}7A+D`Amm(l8l=w1qE; zmWofGY<*n4;5`XsEq24L00|n9rTx1N144Rpsat#6kLl=yUswBgmZ;EgM+lP>rxHRyA##w|GAIYp8e`>G4yg4Iz6YyUv@m)=siRI-I6%xV70e$)k1Z4>C` zmYhat|2v8F!`D+<#tB6moV}N!4NE7YNuEF}j2e$`D(MgYyBnA^0Idfh2gxFf@7X&^ zKX_K0t|rFaXCSpLI~RFWkAU5h+*p{v_!|64U{e6czv2Ch(vRlsH#i2>XQMeLp$_X` zGhAlwohP%hunM3NaCZ+55k%{3BHXDrxwkYDKbc>HYN8#uJ44DnrWX3n^+_F^9MT$e zO;bfi)`&wfQ;*&3OCWst4^Zu`fd0@qA=Vt_&szvv5|oY9Zc{LvqW-o|_LRQdynNLIqID6?;l1WH&~mZ=i>7*#Lmzy`a424(MDQbPt_19; zuhd#M2-Lp=aDm_eXIMUfi#b=u88i;6=C^)_1>(?B5s$;L2;QP~3tg$Zxy8U;2RjyE zlq`CRbUTjq-@AM({NK?9r$KJt>Aleus~W&a1#2Miecueqz;<;Litl@7O_buBsxlFt z9396f*|9mnJ{y8B{nKA^x`G-CQxbX@7pa(4%QG!^oEK5?<(~WxOC^doPrC+~14gM)5}k*Tq2-A%G2(B^paI@O~*Dm(B+EKhW_*iE1Kk4Z(M%#HjB6zJDwTz1+d*Ep^Q#)F51UON(eo||pQ6$d}* znki0j)SOX9z#U1_i52QdZvwsXZ^mOe^^(D`Os68dM;BnI6wXpx1wo4)SNBx2YgBl< z)hNgd8?jT8HDrZXGZY&7t}&1=LW5jC;>r$Rue{*%Kt2;<_@dQ1?pMXKrn zO{N5cKC$NWlnJKy7BG6m>T8p_EQ9Wg(l^l@sqPP#I-KC7MxGhAKC4MTV2m=Q+&26S z62grmP~|@;DgW8C0d*42l$e|h?8D1OgvE=QmhJw+;kh-?ue7Rwns!*pForQ7Zn~Jm}(2=NaAG{sS<62M*JX1kI=On+>{rW_xxq z!!|=9Wr*6j`4SZHC{qkOInhK|hp?p%f6AP6DQ9S)XV|oLagcpSq4Rq`-}kTo7{c4dx>OHX{i6PoCKEMp-W>jz z%+FIJb(2p&_0IwCS`tEfIObJ2Z(;R;Fz!2asXsWRzw=??#)s_&?aaP0ZRuz#ix@^#$|Y7P+rR?nYj0N zjsszhsAgu)zc`1!q(yJa_>9Zc&Bv`C5BX$`yyV994JIqiNeAW3Z!>tbTiGpdnAP2` zq1CnRh@3j(?RcE!oL`Lu{j?ACgSP!IZaP?%x%j!Si1kw78lvKlREEw+5&_Gbzc%Ev zj!V-NfKQA;ERZ925^?ZjjoiF(y4u`G+FF#=oH0l@LziV@@jcC)xByG<(=$YC+`;gv zS9RoFk_K8$#r&fgB>e_L?B?Wfq(8=^;bY)`3P@$b4W~f{Nps|LygI??WF9Kg><}Tl zB@W2D2&=wT$JJi^Y*P3VeX&d-VKAnl5v{tE0qn<6^cmy(vELd9P#@E?z4c3$nF{dX zCC`mpI~Y(obr(S~f6m`mmh8B+$8Y9RV_Q^{!Nb{*#SO^R#r$kGlq5s|H;IU5GmiI~ za$t_B=rg_AL1tx%GMWSu=1*rVhjL>Xvf+Cr#Wc63qrWgiJ*nxX&w|^4=d;B7HUaBL zfUC6{YRtS0?tNP241Yo-k78a_cy{#po&&)IfoY>JQ5WCA-^J%wB@L9hoF4^@??9r5 zJM()6f~Pl|?wO;*BzlV)hwX`@_y!(^INxM>^2kZ{a8bZ?ce8eU@|0#5Qwo`c+}4iR zss^TMUWZ}=2NfufQ~GI@#@nuE_$-JYfRVZk9|j5!1s6ga7i-g&rX4<2J7SAW5Z`}B zdU3;6DwxskOJCQYjl#+uYlmPhpVaX3uZjYl`G5rb^0*1T!ne9=Fpf2tl|!P^XbGa} zn_p(N_;3bgacx8tGt?mkAM%IcWlzB4Ewi^}^cbX0cR1Tnm}w(veJuDOdb$idP>Dmm zrkBGwS?&Yt-BEfEfHXlgl6{UEL#IR+2faU1q`NT#`5 z6iJ3G7e(Zx9|u8y&`69u(GR*H$c)ipINHBqWXr~%g%b%`9C&Tn10OTq9+ci1T?zH( zGn^`R;(11+qs^(=`Tz9U{`K@rfTU9RW*2I%!%>&uOS8D;Ccd0D%d@;0j5t$fp-edliu>@*Mh5gmLzX_HG_N6ut}|Swlzq zz99rU7~6kvL$jw4%W`0B|G^D)utRFV{=29CA4iCogN2hTxgVVd(AROmlWO%}tvemX zf%tK?+4oJjv3e~I^F|MWEWf06D1#I;sWIpE$tCUl+FCV5TJGo6a85X*#;W&>#|mxI zy{2a4y{5)WKal~2juj4_`R%egtw~)?bC|~=9l@=Dx~A7l7HygOaZYu%3*U~2foSjI z+;7^NJ%<4gHAwwgV4Zf(yCxH`q!0=O5{>T`WrAR;?T8AI)11DIM%m}VJ5GemiNdr^U&Pb5Qqej7IwVO~8X z6f}4il*U;E2GklIWIoCom+D}~9&J&8A(5|nb;!0@NR%cYKuFDuh4Q3g3Mm~*A@8W| z6oFrjE}#z93X&$$|Cm9Wr3@30k&H?QM=Pzg5YQBonhcrm^bD~L*CtI3wKq=Xt4vL! zsZs!;T3HBw3Kl^w141m_hbM!mswImHevp7=-5;Jq4C?a}3@Mr#2}M;I%~~+%ML0#4 z!a5j7x8EHUa97&^qY)_>kj`i9Eq=3NG>BN!7y<4 z3?&fZ%o8Rpjkv<;q@Ya^9P>$KFkT^Zf;fjLDAmbrgB}Zw_BM*Gpu|4B3(o;QA13-&Iz&QhIB{)*2+8LvHi@cLar5^bIJGZC zK!(o&M8OP2tmW;_RBqu1D3$qU@IIuH%t6`)Q0$~X=E@-e(-}w>P zxRnqYOWt48=xoi+BD32Yz`sW@w$t8vTv)G@LEN zZYuItM2gQux;UJ7bog82tz?Lu0@r*BZp)Sp^lweDol)?%hR0F52DyTJ!6yA-)d0oO zIp)MeXytd@vFBcUnS}H{?1y?8bgZ8@y4?Uz`C_P)EdbBwN{Zon4%lg~Tu>_%?e5WX zFxoWlzH_GQM(s=<$4K*5BEhukkC*_&J}0Y4uR zwt}&5V~7$a$c&KRPx?Sdu$JUS&FUqQh%GlPp~k7c{W&-|1WUt+mr9;BdA>H-5WXa2 zkX(%PBry~KB4&3Lv7;Yeg+jP-7!;WVk>W}%4s3ZVEEf5GH=Q}uYxrYE>lj2cIp-hs zAuR0%p5vii9}9!QXr6&C%=*5=hU5hU{%{OAy^xu95TAkCcF?%Zln;(%g+i;zuV>U7 zgfkD(@q>qlIs$pSuZ0ex9w2R^f*=Qc5ai%>6~?AMjlKRKycx@*qeYO3g6QG$Y+cy} zG_XRMzBtpv?Sg_87QMMh7Ng($mH&m|$6pNBP3_l_)psVZqdd2R+Atz8cM1b45ROVv zU8u#peRQ6x$)64nE9H0@P{qr8ub0As-$e{HSlf)qp4#@;U)yuB*$cD6P5Vb!>V(q7 zz_@z@6`h*;8NCwL@|ah~K?xH$@obkt?q3n1>DVIDLm%;wTbZ@95XS?8hlGRczN35k zG_+!e`Z%FGn@$b6c)F$7f5!sy&`UNtq*ShbleO_QP3;nPvx-6ypISi5j%xA>UAq{} zOFyz(#M=U{toe7sF`zw>p26o3t_leI#`cvfz?=J6>`{cruRs*zRz&NH(2(@k;1i*c zDwsvBXq+O_+CkPJa>cemke(*QJeonNWNiB(_v9=0VSGeSx{@Ta$kb&iHUU%~ydQL(V?!@DwY`M20Jwp>4V{R(%QR z2}ES75RcPMTvkkh1V2U}6xeEtWkGB{-f|ti9ErFz zcyul(zthVt?JwaCGR*Tutx11}V8rkOnOhFg#Z^YGgg7)Mv`r~X* zNH*_}zD|^^U3ip<09FFC1A5~E9>>mW!Gcv{#{A)w%Zk@W$^IHnK80{1!0FXkEsh(~ z{%ddiMsyqgdx4yW@uA5JSL)8SXXm>>08 z(4MjW3wbQkVS8V!kmqb~sdZ{SW5oFlznP~wKA$-+8kd1C0Cz831WLe7e1dG4(nc9O zTkNM)7Rt|vUkO3tEucU19Vm0@Dfg22d1KYc>!cgwxPuZE$YI4HluKIbSDzb282Qxc zkE@VQ0_6LE-e3Ye-9%PBIp9$eW-22UMA=y9I`YE&^>UyQId#P4qq& z*e;WFsEjPXWn+nPy4MV%nT4v>7$4TDf5UMsSa3Kb(BINesvb|iLRMBapQ?Jvg{0+@ zyV2F4{cbda(3DG441CK}9Up*WBm)~R$AB&aI3y=9Y8~MPrxRRQq6Qq+-uH3z@B6#y z2}-m(LX&38slmA-U?|_lcrMT>kz`CNGnARM7sxxwY39ktX=MOMX|uh;vXmX=&)PHc z5Yc~^dMTSrGs-NlBA+#3i8t3J)_|^TqJ!dCg6H)2uMyLT0`hMbE;b{Nv6tt&ZF)5UwQ&T4j**j@1zXb>5nlOBOA#;J@mA#q*H0}|9a>RNdFPn zxuX7lBdJw`yyo;S_wZ2tH7Ql z-2)4phAA%KU?KZ&W+PYUa1`pfqjj#kpC69mH2T>H{UoGU%&*zHi&Z?^TdOUU8w(oV zdF_#MiApaN7PT!nPRo{GxV*2%F?&BS~>YnzWJYwo`D@>OSi|ro0O81c$SnVkJ2NA9ZRzUAhsbL6CBGk>w28ohl+3 zC{P@ME$%u!*Z~>4=jP?VBgX0Fv=cb00t<5YA-a0xm#pfP`40*idx+KZ=BTpJjaN*O ziLTYiEXEa;w&>VHRsbx;k9ZDxgt{^|#~S=B&C%TZ^;@Uw$GK+Fz1W=!r+-w}_u2R# zMGwQSBYCu6{QSYW9!iG2{;n^BA6n4ivZAA&o~(8B@oPm&%{Qmo+w(8S)@S&n&&E=h zGx4V`SH-mcD>-<|L-JBv&AuYUr=g@Qha*Pc>c(=L7a zMv;sA2tt0JS!-W>4t~r*u%IRecMY#@)%b$`5BJAaM1(|wdAW#GvODWpaq&zRun0t+ zvI%WQ*QF^e6lxW->a;&)-4WGf*MsP6j8Tr#08RnCom@aD`UjQ*Js?ezgUGA*?`BucM%N2eQIr{+Y`6H#}N^&<8z z_g!wD;2L`5Y z)ZJB0S_=em6XfsTovid{Jz>x!9_ojOzegtg*){zfiGp(PpdqAfJNs}{1%kDqf|DHi zBWP+xe>1MpGnvv*_(7K_K{VkP)8&k$H~VfUi$-S+iv);}u*;r%8P3Oj*>m)vLt7(4 zMsFxBA72vMwaNULF7@^^1?tfD)o4>wnRj}{%R947B-x|UQrpO>TCZKj`Y zr5C(Cv64)&&z5Pb!3zZpIB6Xd_1F1ClUytlSgx#F7A$>Hfsv5j)3XZ9*G-P==cxLB3XWIas%tt1Bjq|! z)*^k?r1cso-hcm$S~%>m!-Uv?L_l519@f`2s zW@5)13IU(Bx1{n5xIYZ06o-UyVW)oGn77`x%zVF4SWCuFg7%_W+f2$MFgUQpL*8VI zlXF4u)-c-u{#^EV`-B?nN2Vz7Y_q*&P3eis)=8Wb=CkRuN5b;)&&ZE}O!7jff~yh% zDK$ZJ*PY?;Y!;`Ol4zMT!#)p#z?2m8$><|iihf~E2o|$>uEkM|AvrzLD z-MTP-Q60-?v!t#ss7<8oVD{9e_f${+!&Qc%@?CrMGlqpEZ82? z0lu!c*JV;T$DRaZjzMfM!8Y%p0$4uiKle{+u+#_1H+sQ#DaMY5e@*XTB|t zX?BB1JE6mLCQx#ff~qz#>Mqg&7$viEjk8f)+p?^y(>k!o*Ji8sX{17@(w-kT$)>7% z!Br%k5r|hF!I4#}Qa7b!7r(E_sri2wgcChC%=XJnRu0?Fz+V=!B^PHdvOzw>t1W&B z(d$@?2p5OqrTatjHwt`?5rUb+;y~`_D3GPx7@r}xDX{4m!mq{Y8m-nGqFaiAqiEcS|=gYVmOMmN)5BdbA1r% zt73@qbdM>5b2Pld+|$>HgDTrwH}{OS{76H-d{-(qi@>#1;dUZJ}ko~gbXt?Mwuwv-(?CTc#RT%7F=+2xujeSZ_2QM`_I`|GI z)k1Lh!?lY$XR#NwpaJpe5t*Ga7D@}X^;*#LXMMY9#tmvI+`xhziu-i;IhvoWp+a61 zgWpT9ihW=Y-F0J{l^$v(qcpgXjxe@;a5LStSelF;X4O^y3H;g-U^mzLdD|nNxk6Vy zoI5g()OzX=-27B_Y`$;_XYCv-ekXLl1!i>-4UJYK z$Q)eT39qNjd;uFp{TC+8)gKH)kFoEZ!KseQxRz%;_MyLygO^!3=s<~o24$;5Jnelo zC5}BFm5LVVR0(p}D{cObJ~L0%P1J_7A<+7TxL}VG`L3Inx#hj^iKqo6A^s;5$(4vg zg~Y}D-#JG%X71#~c^aTw*DijG6X{d1cYvtL!%_2wNmwP{iYdHQfl2x%0ZKy9(;~;9 z=m9F;BI|LL$Z9;zY;s4zkvl)of;!6gC4uyK-MJKA)v*?i7LwntANJj}6eS89|C2@% zj#9gTqgqb$n~xRUWK;i0Mzl!W9e<0bjwvWzWQl2@r-4)#AAl~}1NSwq7iaL@2aC~x z%BR@~KpY4_B!i=L8V_hx1;YG0wXe_3pM|-kEbDwv>%{V8Ki~;6$qxt z5T1c>E78{|d;lop92|)Dq0Nm`aNtdd=oGS|OkkcfE3cG;H#>`ofW>u)5Wr1ztASmj zFOdkdND#m%=c`31fxI9ZVcfU4RE2P4DpM~Ck5$0(m;|y-TuPy>m?AlCa0)>y#-;U@_WP`MI|?cWZ`ZI%il1>jM3E7_EL10P($}E#booLBNkDrRiGvm&j+*@@aX7EoQo)y*it}l zCE=nq5CIX8^$%!G?V|pIfe_`8h+#2Lroq2Ak(zK1VTpZ0vMg1&E;2mESq?qx`d@Dk z6^ymt36Zx?{;p0ctO61w+xorbGaR)TtGnG_cgqep|M<=qZKHl}bI0)pEjF!L{=3Le zUYnd{*K1%baB)|930zklZ=XHZBzajEd30ZpQNRzEkJ^r_>)Ad^mtSApw()J#`?Y;! z;Ykj?A7Exo{yrb)AMnC|{(Amn%J$UUFm_hvh#lI(DU5%^EdtG)XR~~tEckd>o_rSS zzMq?dW4(j1;uWR*#2z#3d7JITW(s$$ZIHBp4P`_N9&|93Jj2Lt{aTH=&)|AzU69NT zx&##5wDP!CcZrw+zz@9DS~z?MROW9!D>?+Qd)aE1sHRyPGclmo0^M?*p%znH&YiAi7CQhzN?2*d)q*m>j!ResVF z|Ek&Zwl*<7X?_C!e)y_9pC!KyaKE+>wSbVfvJW&O&OZg^htez-dQM*kFJ;cJfnJXp z$b>wo-W<~YEE$@o!oL9YCPANDN#JN6s-R@n=`vrW) zWvow+Y%fuEwn+P`5j@qSPN3w+g43@L!E*FR>N|;2$bl>(7|{kXWQNH}BV-&6lmuEA zYI#h4gFVL4BS%rv-bvfckq|wt&(cl49N}{ribRFIBPZDb$BXz~f!_WMO-1U)n&GDS zj%m@D6~1j|*Vc>F;dn=2Vn4Yyp9ZLS>G^vgMJ5_^=~2*8 zSW8214bR=<3z1c=OcLZf+ALF7${tIAqPbV(AHu(5MPVA3MVD*OY@SB#p#wxDRyHUx zNd!n0;uX13J2KHph=OYD6qCrD$5BamwyHA@#%0C|4@e;OtAEPglQ_Tbkq~89XPAuh z>~*;Gfz8{-TBhNH=kh_(&&{k@TEJe>OYy-sFLG>HiSkX(D}rCS#=2+D%{VtN3iBBh z<&ZZo;$M-^DXv&j!CtXT@d2>S$s8NW{rpDR8Msz$bk&<7u1O>UQ-+$JrG;MwnfKIi{A!wH2l!+Iw6q2kNt@CYI#od!`bIMo!GEDP35( zCs{cOUgq%vL4xduedogLivuZ*s)Sg$F^m7Ku|fmrscwnk>z;moEZ1x_CT7Tso8 zkyV1toaon7RHB*xk0?Qe*)G&oi9^3$^|!zJ@1K|9hpq1|RU6lpMxW1#7R&p}K;^8~ z+{>1O#jL&i^!^{Xug3izhxj)?w{}R(YdD4oS?^Gw^CwI^GT`<$w*(A8c?b&$*Jk=H zDlnV5`(}trrXChNW*(24cGmH-S3{SDUtK%f$3e&ReXKoRw%TWfSArX_6HGp|ZfIO# ze$vqB)=n-$l5VE~OL@lAU+rAgITv<%9{viqu2;@#+dHjKui2RiuDBOqFK%LNaQ0ab zR$6|H-HZ?pfIxoybioD(uzc+AQF}e1rnjs0+lP4I%k2HKR&dE~d08N&bW$<`NZZbSoxW5J0pKib67SPa5kHL^vyfFQ=n^-CGD?WVtAzYKDY zEJ|JrAc$rZJbvnP79Sar%Djc%teW70J2^W|z==Ix$`Tu4GK_OQ*ziCV<$`~xH&@Memlq4A`cmf&Hdm?zhqo<&r-{CGY>p!#;@zVcGYkBO% zQaoC@Mm$F*LkjABG#8Nrahgb<2qKU~)ybYKoeCLdC{+V#1Qjhi1QkeFVFJgqfS!&_ z#f^-H8Rf@Wn!siYP98t04_U*{823mX;*>9yn1R6PN6DSNQw96QPuXsvA0)Hinl zl(&FPnGi)7z}VV4A;Jf3v)8(SK2j6Nk>_4#xQ7}f{8m}7QlUYH3p!$@oHY6#QPj%a zAemt2wyIh7_WOj#r?y@|x#+JTl_WP3E9MWE*u?sBk*8wr7qAU=rpzf-@Pu)%-My%Q&j%(YySx_QUg#q3OlQ zb-7#Wd#k_upz`3L5*o=sNMefoMZvH)rUwhdbGm`njyktAIaxGrH>`|CrDKQ4YLnzi z;)r#Vr4eG;;O<}cpGMfB)Q9M>_~2Ok7oiCfIkNRpmb5|W5*BWlKJo17$_KD`0B=9> z02`;%a$C5%+1#7>v`Jtrs>jDs?JqfauDnBr6Ol;Bt_ZR=^SI8z&0WIUvT0BzhR-it z5HEu(-Y)e9lI%%^y>=xi^bSmZmQRP5yC~P^z!-e4DQo{An_s4cQrb6qlaApsN8C=o z=O3YE&tmW=!)A_39Sl@|VAVT}13JlhCRx|xP|15E2BZg!217J;=~zyM^Cc45AI?^& zb)cMW- z`?oyN08yq%RBL_1I261n?YOyvdy^3D*XYtfzQH_{u3r;3{-T$Cy*d!kz~$!fFOV|# zM7I1X#<0+kM2Ud}rb424UHxMZA+%E;-_}^N z6u~t6WPx-0XLPVpS;G4T4X)15?L_!w>A&exxly+heELDqIGiq=^QP43@uLCQWAzqc zzS;-eJyiC00)a|f2U!D-Y};298O=!A#uP)ts1G zoR^jP{|iZCTLHo^T11q8*Oe#ck`b{Si7v-1t(zS35j zG2)*a;mjnp74MRiXCN}2~7VaaOjz1i#5x)TtrXM>zxOgz&tZDe;0kIHi+1W8w z<(bNrF;zwHsn&{1sJhK)QRXgqlAm=?MHW7tUZ;M z0~!?IKW8);f{&=$+LB*?|JZgvd1LZ>Xt#Dar&UmRR9yQziEsOaLDEtYv>B=JZc^Sn zJ)jBLWMy5SVQmhYeOh%G{-FYi<0qo@@JM%DArw&rfoPDhYw9CR9q$*PI5IXdPtsOg z>qbCBmmEbKi9q4y>MwSAP{$JUp~y(Vo|s$OfK${5`Bo<759zD9GPx=O*}_c2ywYFX z@0pRr3Tv=Kdu|s}(MRkY?@tjB0>jI()@Tis(62dLkD7!QRSpC)lv%q`?+$|rW)i+) z{-dQ*P+f9UuPyxkx1~-kn4u0ds3zaE;C+Ed$=B?4VCmoewrkh&D}NE$V#9 zyB-r$F#bOL&>fUWDgGVqg@26{5z01E5wTU8p&nYV81BI+da3l0I`3i)`-q3;NQYNpjnPF9caAJ zLYZ;O+Q)32|AGl33iiZ1499|*L2VnVtAfrT5BJ@bxk9%nydpoa2qH=k)=(G(QSt<` zYUT%oj&3;V*a9zxNSazg!5?*HVW9YPUo${zZ=!;N#PJvd3rx#!{-0$-zc+CaS-yjP zD)wK8YGzeHoKO-PB&?|p9rABgI7TdYH$yFig^@Y(bbNq0Y9v<$8_ZBciO~AUXkIUY zOSHD2+ee)T4>x^?IXX-i*PnJ>^Ub#5--$?~B`YvhF+y9D-3Yl?1NPtVJ%>vUFw^IH zHLM*dnjLg`(K1=K2gx5zS|rhpRi~(eDjY+L@1o>}$&x~9EIEXm>;Hh<)~vfu7k;0y^`MRq2w`&z~Hewp~ad`C-t+!&}W&! zNJG@gsSYJgFK|mObP@3ybz;B=(_vAxRjz_%F!3u2`{nj&6-hKPHs!9=vt+~O#ko!@ zvhKL|+kVg0P-2+it6v1^5Z=IcgnQ1)}-XECiu1;1z7&+W6vq3I^ zyAQw0|FLhDHiaYsxS81v;F#2`yv>RKHQ|`Fi1k>A*@#*GIaM7TT>m*)h?$91;h1FX ze>nWJSpQ>*6YKG_i%4>Cb4YM-a*If?i*d4wv2t;Waj>v3i?DNZNb)cX692CuNP_=% zByVnS;c7|z?;Vh=5>5k9q{~?5Xkd)qczH2dWwpvAyUIH+mZ~kVDDW05By*Z(H7kTS zpJntkmkAhYoP(Wxska)X!a0Ub8$siBwGl-<`j#rC@-P(<`@f~Q0*Z%=V}X{(Cnd? z>x^`KdZQhwPDS*BR>aETyDb$ge@PJi!m~r%KoaeqGi+95_v`AB7uvvTwhP&a0JrS6 z^yaWT5NYf9;cm=URJ75#Q z8tZE*K?4B63pH3iyU3SvOJOy_MQh!_8gQJ9GeZ>`?v(uxBx$1KUeqXgOEiJr0G590Mo7G&l)PfipmFaMr^7D|+G_m<8v- z1t1rHF4L8D%bbOq4{3E3l)*f>4sKX@=UuMmg}^L4_M$>e&NTo(GG1ohIb(Nj28%2_ zepffw15zd9Z^rY?%#g7*<6Ev4*wgoP^KyW{c_qN)yc)E!FZy`Q%k38KoYoT@`5C7( zPUm$1r!!7x%*&fBJo~L~CP6X>c*gFG(;26KGfron&N@DOEIj{=p1||m2Xy}K0XlVc z1n#%+@>jY~C+q;wxr)hIr>TxloX(h+M=iX%OHb&$#Q3aJl2pl)0H-tN<>MA!E9+)F z&v>4(JD&!4p6Nps*@Jm`*1{XN^#q=0JkJ*a#%I+z{^mKL(@HkZTX?Hqk6i<}pVcUT zbPKf~3l@I4q2r0OA{H&Yw^=zt^kaUaTQ=#j2Is|!D1JvR`;jVdZGhxWJLzZxE^c(8-WH$y~ZxkZQ-*AZ_i{D zw}Ne8J7BVmI|1`#?6vUukZ$e+y8$JC9+@KJJ_}#m($#*j9~=M&0mElxf{GJh*uuS_ zo0&G_C}75nnq-bx__AA9H55$(jY7wO1|W{qc+$exZ|gn{I}D#uBM$CI4K^IYk#VNN z$Q0?>@q&f#n)Dc_bHx2f=Z|ugJ{}oPlFYkq;ipdBEaO=vHGPdAnm0;T+B2tr3)qD30zYCE(#Xm;lrtp?40Rx+d^ zD-6G{)gdb!AJ^)z6>dGJ)v(pr6|F|B=Inb~jatnI?`SnmTIzmrz{rdO*eD{X7j>X?-#@9QMi6sg}IxK+X03Yl1cArmWPVnx4z zG{UkQ6%GDuSVcdB*yoD+kfwh!v7$`WIH+H)`e~$jmn5ucz|{;wCRQ|2kckzI8u|@e zO+q?T>HNomm4O1N0Yy*(wV)0x01Lq)P!AS^C15FN0F7W7Xadck1uO?Ez)G+RtOjd9 qD`*4lAc77n%|2Cq$+_JI;eQ2ND`S^_YXu$)F*P(W3MC~)Peux&+TL&g diff --git a/doc/developer/blue-app-commands.rst b/doc/developer/blue-app-commands.rst index fb31c02..28ed979 100644 --- a/doc/developer/blue-app-commands.rst +++ b/doc/developer/blue-app-commands.rst @@ -1,3 +1,4 @@ +|_pb| .. Copyright 2017-2019 Cedric Mesnil , Ledger SAS @@ -259,10 +260,7 @@ Others: |_pb| -State Machine -============= -**TBD** Commands overview ================= @@ -1288,57 +1286,6 @@ return |ex|. +--------+-----------------------------------------------------------------+ -Secret Sub -~~~~~~~~~~ - -**Monero** - -sc_sub - -**Description** - - | compute |x1| = |dec|[|spk|](|ex1|) - | compute |x1| = |dec|[|spk|](|ex1|) - | compute |x| = |x1| - |x2| - | compute |ex| = |enc|[|spk|](|x|) - -return |ex|. - -**Command** - -+-----+-----+-----+-----+----------+ -| CLA | INS | P1 | P2 | LC | -+=====+=====+=====+=====+==========+ -| 02 | 3E | 00 | 00 | 41 or 61 | -+-----+-----+-----+-----+----------+ - -**Command data** - -+--------+-----------------------------------------------------------------+ -| Length | Value | -+========+=================================================================+ -| 01 | 00 | -+--------+-----------------------------------------------------------------+ -| 20 | secret key |ex1| | -+--------+-----------------------------------------------------------------+ -| 20 | ephemeral hmac (optional, only during active transaction) | -+--------+-----------------------------------------------------------------+ -| 20 | secret key |ex2| | -+--------+-----------------------------------------------------------------+ -| 20 | ephemeral hmac (optional, only during active transaction) | -+--------+-----------------------------------------------------------------+ - -**Response data** - -+--------+-----------------------------------------------------------------+ -| Length | Value | -+========+=================================================================+ -| 20 | secret key |ex| | -+--------+-----------------------------------------------------------------+ -| 20 | ephemeral hmac (optional, only during active transaction) | -+--------+-----------------------------------------------------------------+ - - Secret Scalar Mult Key ~~~~~~~~~~~~~~~~~~~~~~ @@ -1619,11 +1566,35 @@ to pass the original destination with the |k|, |v|, |AKout|. Unblind |k| and |v| and then verify the commitment |Ctf|. If |Ct| is verified and user validate |Aout|,|Bout| and |v|, continue. +|_pb| + +Transaction State Machine +------------------------- + +During a transaction the following state machine is enforced:: + + OPEN_TX{1} --> STEALTH{1} --> GEN_TXOUT_KEYS{*} --> GEN_COMMITMENT_MASK{*} -- + | | + --(Fake TX only)--------------> BLIND <-- + | + | + --------- VALIDATE{*} <-------- VALIDATE{*} <------- INS_VALIDATE{1} <-- + | mlsag_prehash_finalize mlsag_prehash_update mlsag_prehash_init + | + | + -----> MLSAG{1} ------> MLSAG{*} ------> MLSAG{1} -----> CLOSE_TX + --> mlsag_prepare mlsag_hash mlsag_sign + | | + --------------------------------------------- + + +Note this state machine assume the multi-signature is not supported. +For multi-signature the INS_MLSAG/mlsag_prepare and INS_MLSAG/mlsag_sign may be received several time. + Transaction Commands -------------------- - Open TX ~~~~~~~~ @@ -2367,7 +2338,7 @@ Annexes References ---------- - | [1] ``_ + | [1] ``_ | [2] ``_ | [3] ``_ | [4] ``_ diff --git a/doc/developer/blue-app-monero.template b/doc/developer/blue-app-monero.template index 623244e..940c042 100644 --- a/doc/developer/blue-app-monero.template +++ b/doc/developer/blue-app-monero.template @@ -206,7 +206,7 @@ $endfor$ \centering % \includegraphics[width=0.15\textwidth]{example-image-1x1}\par\vspace{1cm} {\scshape\LARGE Ledger Device for Monero \par} - {\scshape v0.8 \par\vspace{2cm}} + {\scshape v1.5+ \par\vspace{2cm}} \includegraphics{../../images/ledger-monero.png}\par\vspace{2cm} diff --git a/src/monero_api.h b/src/monero_api.h index b82aba2..c79735d 100644 --- a/src/monero_api.h +++ b/src/monero_api.h @@ -16,7 +16,9 @@ #ifndef MONERO_API_H #define MONERO_API_H -int monero_apdu_reset(); +int monero_apdu_reset(void); +int monero_apdu_lock(void); +void monero_lock_and_throw(int sw); void monero_install(unsigned char netId); void monero_init(void); @@ -33,7 +35,6 @@ int monero_apdu_manage_seedwords() ; int monero_apdu_verify_key(void); int monero_apdu_get_chacha8_prekey(void); int monero_apdu_sc_add(void); -int monero_apdu_sc_sub(void); int monero_apdu_scal_mul_key(void); int monero_apdu_scal_mul_base(void); int monero_apdu_generate_keypair(void); @@ -51,7 +52,8 @@ int monero_apdu_get_subaddress_secret_key(void); int monero_apdu_get_tx_proof(void); int monero_apdu_open_tx(void); -void monero_reset_tx(void); +int monero_apdu_open_tx_cont(void); +void monero_reset_tx(int reset_tx_cnt); int monero_apdu_open_subtx(void) ; int monero_apdu_set_signature_mode(void) ; int monero_apdu_stealth(void); @@ -70,10 +72,12 @@ int monero_apdu_mlsag_sign(void); int monero_apdu_close_tx(void); void ui_init(void); -void ui_main_display(unsigned int value); -void monero_ux_user_validation(); +void ui_menu_lock_display(void); +void ui_menu_main_display(unsigned int value); +void ui_menu_info_display(unsigned int value); +void ui_menu_info_display2(unsigned int value, char* line1, char* line2); void ui_export_viewkey_display(unsigned int value); -void ui_menu_any_pubaddr_display(unsigned int value); +void ui_menu_any_pubaddr_display(unsigned int value, unsigned char* pub_view, unsigned char *pub_spend, unsigned char is_subbadress, unsigned char *paymanetID); void ui_menu_pubaddr_display(unsigned int value); @@ -100,7 +104,7 @@ int monero_unblind(unsigned char *v, unsigned char *k, unsigned char *AKout, uns void ui_menu_validation_display(unsigned int value) ; void ui_menu_fee_validation_display(unsigned int value) ; void ui_menu_change_validation_display(unsigned int value) ; - +void ui_menu_opentx_display(unsigned int value); /* ----------------------------------------------------------------------- */ /* --- KEYS & ADDRESS ---- */ /* ----------------------------------------------------------------------- */ @@ -196,6 +200,15 @@ int monero_hash(unsigned int algo, cx_hash_t * hasher, unsigned char* buf, unsi #define monero_sha256_outkeys_final(out) \ monero_hash_final((cx_hash_t *)&G_monero_vstate.sha256_out_keys, (out)?(out):G_monero_vstate.OUTK) + /* + * check 1= 0) { + THROW(SW_WRONG_DATA_RANGE); + } +} +/* ----------------------------------------------------------------------- */ +/* --- --- */ +/* ----------------------------------------------------------------------- */ +void monero_check_scalar_not_null(unsigned char *s) { + if (cx_math_is_zero(s, 32)) { + THROW(SW_WRONG_DATA_RANGE); + } +} /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ diff --git a/src/monero_dispatch.c b/src/monero_dispatch.c index 766136c..c454d5f 100644 --- a/src/monero_dispatch.c +++ b/src/monero_dispatch.c @@ -20,8 +20,30 @@ #include "monero_vars.h" +void update_protocol() { + G_monero_vstate.tx_state_ins = G_monero_vstate.io_ins; + G_monero_vstate.tx_state_p1 = G_monero_vstate.io_p1 ; + G_monero_vstate.tx_state_p2 = G_monero_vstate.io_p2; +} + +void clear_protocol() { + G_monero_vstate.tx_state_ins = 0; + G_monero_vstate.tx_state_p1 = 0; + G_monero_vstate.tx_state_p2 = 0; +} + +int check_potocol() { + /* if locked and pin is veririfed, unlock */ + if ((G_monero_vstate.protocol_barrier == PROTOCOL_LOCKED_UNLOCKABLE) + && (os_global_pin_is_validated() == PIN_VERIFIED)) { + G_monero_vstate.protocol_barrier = PROTOCOL_UNLOCKED; + } + + /* if we are locked, deny all command! */ + if (G_monero_vstate.protocol_barrier != PROTOCOL_UNLOCKED) { + return SW_SECURITY_LOCKED; + } -void check_potocol() { /* the first command enforce the protocol version until application quits */ switch(G_monero_vstate.io_protocol_version) { case 0x00: /* the first one: PCSC epoch */ @@ -36,19 +58,19 @@ void check_potocol() { //FALL THROUGH default: - THROW(SW_CLA_NOT_SUPPORTED); - return ; + return SW_PROTOCOL_NOT_SUPPORTED; } + return SW_OK; } -void check_ins_access() { +int check_ins_access() { if (G_monero_vstate.key_set != 1) { - THROW(SW_CONDITIONS_NOT_SATISFIED); - return; + return SW_KEY_NOT_SET; } switch (G_monero_vstate.io_ins) { + case INS_LOCK_DISPLAY: case INS_RESET: case INS_PUT_KEY: case INS_GET_KEY: @@ -62,7 +84,6 @@ void check_ins_access() { case INS_GEN_KEY_IMAGE: case INS_SECRET_KEY_TO_PUBLIC_KEY: case INS_SECRET_KEY_ADD: - case INS_SECRET_KEY_SUB: case INS_GENERATE_KEYPAIR: case INS_SECRET_SCAL_MUL_KEY: case INS_SECRET_SCAL_MUL_BASE: @@ -75,38 +96,41 @@ void check_ins_access() { case INS_STEALTH: case INS_GET_TX_PROOF: case INS_CLOSE_TX: - return; + return SW_OK; case INS_OPEN_TX: case INS_SET_SIGNATURE_MODE: if (os_global_pin_is_validated() != PIN_VERIFIED) { - break; + return SW_SECURITY_PIN_LOCKED; } - return; + return SW_OK; case INS_GEN_TXOUT_KEYS: case INS_BLIND: case INS_VALIDATE: case INS_MLSAG: case INS_GEN_COMMITMENT_MASK: - if ((os_global_pin_is_validated() != PIN_VERIFIED) || - (G_monero_vstate.tx_in_progress != 1)) { - break; + if (os_global_pin_is_validated() != PIN_VERIFIED) { + return SW_SECURITY_PIN_LOCKED; + } + if (G_monero_vstate.tx_in_progress != 1) { + return SW_COMMAND_NOT_ALLOWED; } - return; + return SW_OK; } - THROW(SW_CONDITIONS_NOT_SATISFIED); - return; - + return SW_INS_NOT_SUPPORTED; } int monero_dispatch() { int sw; - check_potocol(); - check_ins_access(); + if ( ((sw = check_potocol()) != SW_OK) || + ((sw = check_ins_access() != SW_OK)) ) { + monero_io_discard(0); + return sw; + } G_monero_vstate.options = monero_io_fetch_u8(); @@ -115,34 +139,15 @@ int monero_dispatch() { return sw; } + if (G_monero_vstate.io_ins == INS_LOCK_DISPLAY) { + sw = monero_apdu_lock(); + return sw; + } + sw = 0x6F01; switch (G_monero_vstate.io_ins) { - - /* --- START TX --- */ - case INS_OPEN_TX: - sw = monero_apdu_open_tx(); - break; - - case INS_CLOSE_TX: - sw = monero_apdu_close_tx(); - break; - - /* --- SIG MODE --- */ - case INS_SET_SIGNATURE_MODE: - sw = monero_apdu_set_signature_mode(); - break; - - /* --- STEATH PAYMENT --- */ - case INS_STEALTH: - if ((G_monero_vstate.io_p1 != 0) || - (G_monero_vstate.io_p2 != 0)) { - THROW(SW_WRONG_P1P2); - } - sw = monero_apdu_stealth(); - break; - - /* --- KEYS --- */ + /* --- KEYS --- */ case INS_PUT_KEY: sw = monero_apdu_put_key(); break; @@ -184,9 +189,6 @@ int monero_dispatch() { case INS_SECRET_KEY_ADD: sw = monero_apdu_sc_add(); break; - case INS_SECRET_KEY_SUB: - sw = monero_apdu_sc_sub(); - break; case INS_GENERATE_KEYPAIR: sw = monero_apdu_generate_keypair(); break; @@ -211,54 +213,212 @@ int monero_dispatch() { sw = monero_apdu_get_subaddress_secret_key(); break; - /* --- PROOF --- */ + /* --- PARSE --- */ + case INS_UNBLIND: + sw = monero_apdu_unblind(); + break; + /* --- PROOF --- */ case INS_GET_TX_PROOF: sw = monero_apdu_get_tx_proof(); break; + /* ======================================================================= + * Following command are only allowed during transaction and their + * sequence shall be enforced + */ + + /* --- START TX --- */ + case INS_OPEN_TX: + //state machine check + if (G_monero_vstate.tx_state_ins != 0) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + //2. command process + sw = monero_apdu_open_tx(); + update_protocol(); + break; + + case INS_CLOSE_TX: + sw = monero_apdu_close_tx(); + clear_protocol(); + break; + + /* --- SIG MODE --- */ + case INS_SET_SIGNATURE_MODE: + //1. state machine check + if (G_monero_vstate.tx_in_progress != 0) { + //Change sig mode during transacation is not allowed + THROW(SW_COMMAND_NOT_ALLOWED); + } + //2. command process + sw = monero_apdu_set_signature_mode(); + break; + + /* --- STEATH PAYMENT --- */ + case INS_STEALTH: + //1. state machine check + if (G_monero_vstate.tx_in_progress == 1) { + if ((G_monero_vstate.tx_state_ins != INS_OPEN_TX) && + (G_monero_vstate.tx_state_ins != INS_STEALTH)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + if ((G_monero_vstate.io_p1 != 0) || + (G_monero_vstate.io_p2 != 0)) { + THROW(SW_WRONG_P1P2); + } + } + //2. command process + sw = monero_apdu_stealth(); + if (G_monero_vstate.tx_in_progress == 1) { + update_protocol(); + } + break; + + /* --- TX OUT KEYS --- */ case INS_GEN_TXOUT_KEYS: + //1. state machine check + if ((G_monero_vstate.tx_state_ins != INS_OPEN_TX) && + (G_monero_vstate.tx_state_ins != INS_GEN_TXOUT_KEYS) && + (G_monero_vstate.tx_state_ins != INS_STEALTH)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + if ((G_monero_vstate.io_p1 != 0) || + (G_monero_vstate.io_p2 != 0)) { + THROW(SW_WRONG_P1P2); + } + //2. command process sw = monero_apu_generate_txout_keys(); + update_protocol(); break; /*--- COMMITMENT MASK --- */ case INS_GEN_COMMITMENT_MASK: + //1. state machine check + if ((G_monero_vstate.tx_state_ins != INS_GEN_TXOUT_KEYS) && + (G_monero_vstate.tx_state_ins != INS_GEN_COMMITMENT_MASK)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + if ((G_monero_vstate.io_p1 != 0) || + (G_monero_vstate.io_p2 != 0)) { + THROW(SW_WRONG_P1P2); + } + //2. command process sw = monero_apdu_gen_commitment_mask(); + update_protocol(); break; /* --- BLIND --- */ case INS_BLIND: + //1. state machine check + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_FAKE) { + if ((G_monero_vstate.tx_state_ins != INS_GEN_TXOUT_KEYS) && + (G_monero_vstate.tx_state_ins != INS_BLIND)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + } else if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { + if ((G_monero_vstate.tx_state_ins != INS_GEN_COMMITMENT_MASK) && + (G_monero_vstate.tx_state_ins != INS_BLIND)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + } else { + THROW(SW_COMMAND_NOT_ALLOWED); + } + //2. command process + if ((G_monero_vstate.io_p1 != 0) || + (G_monero_vstate.io_p2 != 0)) { + THROW(SW_WRONG_P1P2); + } sw = monero_apdu_blind(); - break; - case INS_UNBLIND: - sw = monero_apdu_unblind(); + update_protocol(); break; /* --- VALIDATE/PREHASH --- */ case INS_VALIDATE: + //1. state machine check + if ((G_monero_vstate.tx_state_ins != INS_BLIND) && + (G_monero_vstate.tx_state_ins != INS_VALIDATE)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + //init PREHASH state machine + if (G_monero_vstate.tx_state_ins == INS_BLIND) { + G_monero_vstate.tx_state_ins = INS_VALIDATE; + G_monero_vstate.tx_state_p1 = 1; + G_monero_vstate.tx_state_p2 = 0; + } + //check new state is allowed + if (G_monero_vstate.tx_state_p1 == G_monero_vstate.io_p1) { + if (G_monero_vstate.tx_state_p2 != G_monero_vstate.io_p2-1) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else if (G_monero_vstate.tx_state_p1 == G_monero_vstate.io_p1-1) { + if (1 != G_monero_vstate.io_p2) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else { + THROW(SW_COMMAND_NOT_ALLOWED); + } + //2. command process if (G_monero_vstate.io_p1 == 1) { sw = monero_apdu_mlsag_prehash_init(); - } else if (G_monero_vstate.io_p1 == 2) { + } + else if (G_monero_vstate.io_p1 == 2) { sw = monero_apdu_mlsag_prehash_update(); - } else if (G_monero_vstate.io_p1 == 3) { + } + else if (G_monero_vstate.io_p1 == 3) { sw = monero_apdu_mlsag_prehash_finalize(); } else { THROW(SW_WRONG_P1P2); } + update_protocol(); break; /* --- MLSAG --- */ case INS_MLSAG: + //1. state machine check + if ((G_monero_vstate.tx_state_ins != INS_VALIDATE) && + (G_monero_vstate.tx_state_ins != INS_MLSAG)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + if (G_monero_vstate.tx_state_ins == INS_VALIDATE) { + if ((G_monero_vstate.tx_state_p1 != 3) || + (G_monero_vstate.io_p1 != 1) || + (G_monero_vstate.io_p2 != 0)) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else { + if (G_monero_vstate.tx_state_p1 == 1) { + if (2 != G_monero_vstate.io_p1) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else if (G_monero_vstate.tx_state_p1 == 2) { + if ((2 != G_monero_vstate.io_p1) && + (3 != G_monero_vstate.io_p1)) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else if (G_monero_vstate.tx_state_p1 == 3) { + if (1 != G_monero_vstate.io_p1) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else { + THROW(SW_COMMAND_NOT_ALLOWED); + } + } + + //2. command process if (G_monero_vstate.io_p1 == 1) { sw = monero_apdu_mlsag_prepare(); - } else if (G_monero_vstate.io_p1 == 2) { + } + else if (G_monero_vstate.io_p1 == 2) { sw = monero_apdu_mlsag_hash(); - } else if (G_monero_vstate.io_p1 == 3) { + } + else if (G_monero_vstate.io_p1 == 3) { sw = monero_apdu_mlsag_sign(); } else { THROW(SW_WRONG_P1P2); } + update_protocol(); break; /* --- KEYS --- */ diff --git a/src/monero_init.c b/src/monero_init.c index b26b51d..22944f7 100644 --- a/src/monero_init.c +++ b/src/monero_init.c @@ -52,6 +52,7 @@ void monero_init() { } G_monero_vstate.protocol = 0xff; + G_monero_vstate.protocol_barrier = PROTOCOL_UNLOCKED; //load key monero_init_private_key(); @@ -85,7 +86,7 @@ void monero_init_private_key() { // m / 44' / 128' / 0' / 0 / 0 path[0] = 0x8000002C; path[1] = 0x80000080; - path[2] = 0x80000000; + path[2] = 0x80000000|N_monero_pstate->account_id; path[3] = 0x00000000; path[4] = 0x00000000; os_perso_derive_node_bip32(CX_CURVE_SECP256K1, path, 5 , seed, chain); @@ -108,7 +109,6 @@ void monero_init_private_key() { THROW(SW_SECURITY_LOAD_KEY); return; } - monero_ecmul_G(G_monero_vstate.A, G_monero_vstate.a); monero_ecmul_G(G_monero_vstate.B, G_monero_vstate.b); @@ -122,13 +122,31 @@ void monero_init_private_key() { /* ----------------------------------------------------------------------- */ /* --- Set up ui/ux --- */ /* ----------------------------------------------------------------------- */ -void monero_init_ux() { - #ifdef UI_NANO_X - monero_base58_public_key(G_monero_vstate.ux_wallet_public_address, G_monero_vstate.A,G_monero_vstate.B, 0, NULL); +void monero_init_ux() { + monero_base58_public_key(G_monero_vstate.ux_address, G_monero_vstate.A,G_monero_vstate.B, 0, NULL); os_memset(G_monero_vstate.ux_wallet_public_short_address, '.', sizeof(G_monero_vstate.ux_wallet_public_short_address)); - os_memmove(G_monero_vstate.ux_wallet_public_short_address, G_monero_vstate.ux_wallet_public_address,5); - os_memmove(G_monero_vstate.ux_wallet_public_short_address+7, G_monero_vstate.ux_wallet_public_address+95-5,5); + + #ifdef HAVE_UX_FLOW + + #ifdef UI_NANO_X + snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), "XMR / %d", N_monero_pstate->account_id); + os_memmove(G_monero_vstate.ux_wallet_public_short_address, G_monero_vstate.ux_address,5); + os_memmove(G_monero_vstate.ux_wallet_public_short_address+7, G_monero_vstate.ux_address+95-5,5); G_monero_vstate.ux_wallet_public_short_address[12] = 0; + #else + snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), " XMR / %d", N_monero_pstate->account_id); + os_memmove(G_monero_vstate.ux_wallet_public_short_address, G_monero_vstate.ux_address,4); + os_memmove(G_monero_vstate.ux_wallet_public_short_address+6, G_monero_vstate.ux_address+95-4,4); + G_monero_vstate.ux_wallet_public_short_address[10] = 0; + #endif + + #else + + snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), "XMR / %d", N_monero_pstate->account_id); + os_memmove(G_monero_vstate.ux_wallet_public_short_address, G_monero_vstate.ux_address,5); + os_memmove(G_monero_vstate.ux_wallet_public_short_address+7, G_monero_vstate.ux_address+95-5,5); + G_monero_vstate.ux_wallet_public_short_address[12] = 0; + #endif } @@ -190,5 +208,23 @@ int monero_apdu_reset() { monero_io_insert_u8(MONERO_VERSION_MAJOR); monero_io_insert_u8(MONERO_VERSION_MINOR); monero_io_insert_u8(MONERO_VERSION_MICRO); - return 0x9000; + return SW_OK; +} + + +/* ----------------------------------------------------------------------- */ +/* --- LOCK --- */ +/* ----------------------------------------------------------------------- */ +int monero_apdu_lock() { + monero_io_discard(0); + monero_lock_and_throw(SW_SECURITY_LOCKED); + return SW_SECURITY_LOCKED; } + +void monero_lock_and_throw(int sw) { + G_monero_vstate.protocol_barrier = PROTOCOL_LOCKED; + snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "Security Err"); + snprintf(G_monero_vstate.ux_info2, sizeof(G_monero_vstate.ux_info2), "%x", sw); + ui_menu_info_display(0); + THROW(sw); +} \ No newline at end of file diff --git a/src/monero_io.c b/src/monero_io.c index 33e6e3a..b7fbe08 100644 --- a/src/monero_io.c +++ b/src/monero_io.c @@ -50,7 +50,6 @@ void monero_io_set_offset(unsigned int offset) { } else { THROW(ERROR_IO_OFFSET); - return ; } } @@ -85,7 +84,6 @@ void monero_io_clear() { void monero_io_hole(unsigned int sz) { if ((G_monero_vstate.io_length + sz) > MONERO_IO_BUFFER_LENGTH) { THROW(ERROR_IO_FULL); - return ; } os_memmove(G_monero_vstate.io_buffer+G_monero_vstate.io_offset+sz, G_monero_vstate.io_buffer+G_monero_vstate.io_offset, @@ -99,17 +97,37 @@ void monero_io_insert(unsigned char const *buff, unsigned int len) { G_monero_vstate.io_offset += len; } -void monero_io_insert_hmac_for(unsigned char* buffer, int len) { - unsigned char hmac[32]; - cx_hmac_sha256(G_monero_vstate.hmac_key, 32, buffer, len, hmac, 32); +void monero_io_insert_hmac_for(unsigned char* buffer, int len, int type) { + //for now, only 32bytes block are allowed + if (len != 32) { + THROW(SW_WRONG_DATA); + } + + unsigned char hmac[32+1+4]; + + os_memmove(hmac,buffer,32); + hmac[32] = type; + if (type == TYPE_ALPHA) { + hmac[33] = (G_monero_vstate.tx_sign_cnt >> 0)&0xFF; + hmac[34] = (G_monero_vstate.tx_sign_cnt >> 8)&0xFF; + hmac[35] = (G_monero_vstate.tx_sign_cnt >> 16)&0xFF; + hmac[36] = (G_monero_vstate.tx_sign_cnt >> 24)&0xFF; + } else { + hmac[33] = 0; + hmac[34] = 0; + hmac[35] = 0; + hmac[36] = 0; + } + cx_hmac_sha256(G_monero_vstate.hmac_key, 32, + hmac, 37, + hmac, 32); monero_io_insert(hmac,32); } -void monero_io_insert_encrypt(unsigned char* buffer, int len) { +void monero_io_insert_encrypt(unsigned char* buffer, int len, int type) { //for now, only 32bytes block are allowed if (len != 32) { THROW(SW_WRONG_DATA); - return ; } monero_io_hole(len); @@ -127,7 +145,7 @@ void monero_io_insert_encrypt(unsigned char* buffer, int len) { #endif G_monero_vstate.io_offset += len; if (G_monero_vstate.tx_in_progress) { - monero_io_insert_hmac_for(G_monero_vstate.io_buffer+G_monero_vstate.io_offset-len, len); + monero_io_insert_hmac_for(G_monero_vstate.io_buffer+G_monero_vstate.io_offset-len, len, type); } } @@ -206,31 +224,47 @@ int monero_io_fetch(unsigned char* buffer, int len) { } +static void monero_io_verify_hmac_for(const unsigned char* buffer, int len, unsigned char *expected_hmac, int type) { + //for now, only 32bytes block allowed + if (len != 32) { + THROW(SW_WRONG_DATA); + } -static void monero_io_verify_hmac_for(const unsigned char* buffer, int len, unsigned char *expected_hmac) { - unsigned char hmac[32]; - + unsigned char hmac[37]; + os_memmove(hmac,buffer,32); + hmac[32] = type; +if (type == TYPE_ALPHA) { + hmac[33] = (G_monero_vstate.tx_sign_cnt >> 0)&0xFF; + hmac[34] = (G_monero_vstate.tx_sign_cnt >> 8)&0xFF; + hmac[35] = (G_monero_vstate.tx_sign_cnt >> 16)&0xFF; + hmac[36] = (G_monero_vstate.tx_sign_cnt >> 24)&0xFF; + } else { + hmac[33] = 0; + hmac[34] = 0; + hmac[35] = 0; + hmac[36] = 0; + } cx_hmac_sha256(G_monero_vstate.hmac_key, 32, - buffer, len, + hmac, 37, hmac, 32); if (os_memcmp(hmac, expected_hmac, 32)) { - THROW(SW_SECURITY_TRUSTED_INPUT); + monero_lock_and_throw(SW_SECURITY_HMAC); } } -int monero_io_fetch_decrypt(unsigned char* buffer, int len) { +int monero_io_fetch_decrypt(unsigned char* buffer, int len, int type) { //for now, only 32bytes block allowed if (len != 32) { - THROW(SW_SECURE_MESSAGING_NOT_SUPPORTED); - return 0; + THROW(SW_WRONG_LENGTH); } if (G_monero_vstate.tx_in_progress) { monero_io_assert_available(len+32); monero_io_verify_hmac_for(G_monero_vstate.io_buffer+G_monero_vstate.io_offset, len, - G_monero_vstate.io_buffer+G_monero_vstate.io_offset + len); + G_monero_vstate.io_buffer+G_monero_vstate.io_offset + len, + type); } else { monero_io_assert_available(len); } @@ -252,6 +286,20 @@ int monero_io_fetch_decrypt(unsigned char* buffer, int len) { if (G_monero_vstate.tx_in_progress) { G_monero_vstate.io_offset += 32; } + if (buffer) { + switch(type) { + case TYPE_SCALAR: + monero_check_scalar_range_1N(buffer); + break; + case TYPE_AMOUNT_KEY: + case TYPE_DERIVATION: + case TYPE_ALPHA: + monero_check_scalar_not_null(buffer); + break; + default: + THROW(SW_SECURITY_INTERNAL); + } + } return len; } @@ -265,7 +313,7 @@ int monero_io_fetch_decrypt_key(unsigned char* buffer) { G_monero_vstate.io_offset += 32; if (G_monero_vstate.tx_in_progress) { monero_io_assert_available(32); - monero_io_verify_hmac_for(C_FAKE_SEC_VIEW_KEY, 32, G_monero_vstate.io_buffer+G_monero_vstate.io_offset); + monero_io_verify_hmac_for(C_FAKE_SEC_VIEW_KEY, 32, G_monero_vstate.io_buffer+G_monero_vstate.io_offset, TYPE_SCALAR); G_monero_vstate.io_offset += 32; } os_memmove(buffer, G_monero_vstate.a,32); @@ -273,17 +321,25 @@ int monero_io_fetch_decrypt_key(unsigned char* buffer) { } //spend? else if (os_memcmp(k, C_FAKE_SEC_SPEND_KEY, 32)==0) { + switch(G_monero_vstate.io_ins) { + case INS_VERIFY_KEY: + case INS_DERIVE_SECRET_KEY: + //case INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY: + break; + default: + THROW(SW_WRONG_DATA); + } G_monero_vstate.io_offset += 32; if (G_monero_vstate.tx_in_progress) { monero_io_assert_available(32); - monero_io_verify_hmac_for(C_FAKE_SEC_SPEND_KEY, 32, G_monero_vstate.io_buffer+G_monero_vstate.io_offset); + monero_io_verify_hmac_for(C_FAKE_SEC_SPEND_KEY, 32, G_monero_vstate.io_buffer+G_monero_vstate.io_offset, TYPE_SCALAR); } os_memmove(buffer, G_monero_vstate.b,32); return 32; } //else else { - return monero_io_fetch_decrypt(buffer, 32); + return monero_io_fetch_decrypt(buffer, 32, TYPE_SCALAR); } } @@ -383,8 +439,7 @@ int monero_io_do(unsigned int io_flags) { else { G_monero_vstate.io_offset = 0; if(G_monero_vstate.io_length > MAX_OUT) { - THROW(SW_FILE_FULL); - return SW_FILE_FULL; + THROW(SW_IO_FULL); } os_memmove(G_io_apdu_buffer, G_monero_vstate.io_buffer+G_monero_vstate.io_offset, G_monero_vstate.io_length); diff --git a/src/monero_key.c b/src/monero_key.c index 54256a4..3097a21 100644 --- a/src/monero_key.c +++ b/src/monero_key.c @@ -66,7 +66,6 @@ static const unsigned int crcTable[256] = { static unsigned long monero_crc32( unsigned long inCrc32, const void *buf, size_t bufLen ) { - unsigned long crc32; unsigned char *byteBuf; size_t i; @@ -82,10 +81,9 @@ static unsigned long monero_crc32( unsigned long inCrc32, const void *buf, void monero_clear_words() { - for (int i = 0; i<25; i++) { - monero_nvm_write((void*)N_monero_pstate->words[i], NULL,WORDS_MAX_LENGTH); - } + monero_nvm_write((void*)N_monero_pstate->words_list, NULL,sizeof(N_monero_pstate->words_list)); } + /** * n : word to write * idx: index of word to copy @@ -93,22 +91,20 @@ void monero_clear_words() { * word_list * len : word_list length */ - static void monero_set_word(unsigned int n, unsigned int idx, unsigned int w_start, unsigned char* word_list, int len) { while (w_start < idx) { len -= 1 + word_list[0]; if (len < 0) { monero_clear_words(); THROW(SW_WRONG_DATA+1); - return; } word_list += 1 + word_list[0]; w_start++; } if ((w_start != idx) || (word_list[0] > (len-1)) || (word_list[0] > 19)) { + monero_clear_words(); THROW(SW_WRONG_DATA+2); - return; } len = word_list[0]; word_list++; @@ -121,6 +117,7 @@ static void monero_set_word(unsigned int n, unsigned int idx, unsigned int w_st int monero_apdu_manage_seedwords() { unsigned int w_start, w_end; unsigned short wc[4]; + switch (G_monero_vstate.io_p1) { //SETUP case 1: @@ -128,7 +125,6 @@ int monero_apdu_manage_seedwords() { w_end = w_start+monero_io_fetch_u32(); if ((w_start >= word_list_length) || (w_end > word_list_length) || (w_start > w_end)) { THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; } for (int i = 0; i<8; i++) { unsigned int val = (seed[i*4+0]<<0) | (seed[i*4+1]<<8) | (seed[i*4+2]<<16) | (seed[i*4+3]<<24); @@ -152,6 +148,20 @@ int monero_apdu_manage_seedwords() { } w_start = monero_crc32(0, G_monero_vstate.io_buffer, G_monero_vstate.io_p2*24)%24; monero_nvm_write((void*)N_monero_pstate->words[24], (void*)N_monero_pstate->words[w_start], WORDS_MAX_LENGTH); + + #ifdef HAVE_UX_FLOW + //transform to list ready to display + unsigned char word[21]; + w_start = 0; + for (int i = 0; i<24; i++) { + w_end = N_monero_pstate->words[i][0]; + os_memmove(word, &N_monero_pstate->words[i][1], w_end); + word[w_end] = (i == 23) ? 0 : ' '; + w_end++; + monero_nvm_write(N_monero_pstate->words_list+w_start, word, w_end); + w_start += w_end; + } + #endif } break; @@ -221,14 +231,8 @@ int monero_apdu_display_address() { G_monero_vstate.disp_addr_mode = DISP_MAIN; } } - monero_base58_public_key(G_monero_vstate.ux_address, - C, D, - (minor|major)?1:0, - (G_monero_vstate.io_p1 == 1)? payment_id : NULL); - - - ui_menu_any_pubaddr_display(0); + ui_menu_any_pubaddr_display(0, C, D, (minor|major)?1:0, (G_monero_vstate.io_p1 == 1)? payment_id : NULL); return 0; } @@ -349,7 +353,6 @@ int monero_apdu_get_key() { default: THROW(SW_WRONG_P1P2); - return SW_WRONG_P1P2; } return SW_OK; } @@ -377,7 +380,6 @@ int monero_apdu_verify_key() { break; default: THROW(SW_WRONG_P1P2); - return SW_WRONG_P1P2; } if (os_memcmp(computed_pub, pub, 32) ==0 ) { verified = 1; @@ -416,31 +418,20 @@ int monero_apdu_sc_add(/*unsigned char *r, unsigned char *s1, unsigned char *s2* unsigned char s2[32]; unsigned char r[32]; //fetch - monero_io_fetch_decrypt(s1,32); - monero_io_fetch_decrypt(s2,32); + monero_io_fetch_decrypt(s1,32, TYPE_SCALAR); + monero_io_fetch_decrypt(s2,32, TYPE_SCALAR); monero_io_discard(0); + if (G_monero_vstate.tx_in_progress) { + if ((os_memcmp(s1, G_monero_vstate.last_derive_secret_key, 32)!=0) || + (os_memcmp(s2, G_monero_vstate.last_get_subaddress_secret_key, 32)!=0)) { + monero_lock_and_throw(SW_WRONG_DATA); + } + } monero_addm(r,s1,s2); - monero_io_insert_encrypt(r,32); - return SW_OK; -} - -/* ----------------------------------------------------------------------- */ -/* --- --- */ -/* ----------------------------------------------------------------------- */ -int monero_apdu_sc_sub(/*unsigned char *r, unsigned char *s1, unsigned char *s2*/) { - unsigned char s1[32]; - unsigned char s2[32]; - unsigned char r[32]; - //fetch - monero_io_fetch_decrypt(s1,32); - monero_io_fetch_decrypt(s2,32); - monero_io_discard(0); - monero_subm(r,s1,s2); - monero_io_insert_encrypt(r,32); + monero_io_insert_encrypt(r,32, TYPE_SCALAR); return SW_OK; } - /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ @@ -465,7 +456,7 @@ int monero_apdu_scal_mul_base(/*const rct::key &sec, rct::key mulkey*/) { unsigned char sec[32]; unsigned char r[32]; //fetch - monero_io_fetch_decrypt(sec,32); + monero_io_fetch_decrypt(sec,32, TYPE_SCALAR); monero_io_discard(0); monero_ecmul_G(r,sec); @@ -483,7 +474,7 @@ int monero_apdu_generate_keypair(/*crypto::public_key &pub, crypto::secret_key & monero_io_discard(0); monero_generate_keypair(pub,sec); monero_io_insert(pub,32); - monero_io_insert_encrypt(sec,32); + monero_io_insert_encrypt(sec,32, TYPE_SCALAR); return SW_OK; } @@ -495,7 +486,7 @@ int monero_apdu_secret_key_to_public_key(/*const crypto::secret_key &sec, crypto unsigned char sec[32]; unsigned char pub[32]; //fetch - monero_io_fetch_decrypt(sec,32); + monero_io_fetch_decrypt(sec,32, TYPE_SCALAR); monero_io_discard(0); //pub monero_ecmul_G(pub,sec); @@ -520,7 +511,7 @@ int monero_apdu_generate_key_derivation(/*const crypto::public_key &pub, const c //Derive and keep monero_generate_key_derivation(drv, pub, sec); - monero_io_insert_encrypt(drv,32); + monero_io_insert_encrypt(drv,32, TYPE_DERIVATION); return SW_OK; } @@ -533,7 +524,7 @@ int monero_apdu_derivation_to_scalar(/*const crypto::key_derivation &derivation, unsigned char res[32]; //fetch - monero_io_fetch_decrypt(derivation,32); + monero_io_fetch_decrypt(derivation,32, TYPE_DERIVATION); output_index = monero_io_fetch_u32(); monero_io_discard(0); @@ -541,7 +532,7 @@ int monero_apdu_derivation_to_scalar(/*const crypto::key_derivation &derivation, monero_derivation_to_scalar(res, derivation, output_index); //pub key - monero_io_insert_encrypt(res,32); + monero_io_insert_encrypt(res,32, TYPE_SCALAR); return SW_OK; } @@ -555,7 +546,7 @@ int monero_apdu_derive_public_key(/*const crypto::key_derivation &derivation, co unsigned char drvpub[32]; //fetch - monero_io_fetch_decrypt(derivation,32); + monero_io_fetch_decrypt(derivation,32, TYPE_DERIVATION); output_index = monero_io_fetch_u32(); monero_io_fetch(pub, 32); monero_io_discard(0); @@ -578,7 +569,7 @@ int monero_apdu_derive_secret_key(/*const crypto::key_derivation &derivation, co unsigned char drvsec[32]; //fetch - monero_io_fetch_decrypt(derivation,32); + monero_io_fetch_decrypt(derivation,32, TYPE_DERIVATION); output_index = monero_io_fetch_u32(); monero_io_fetch_decrypt_key(sec); monero_io_discard(0); @@ -586,8 +577,9 @@ int monero_apdu_derive_secret_key(/*const crypto::key_derivation &derivation, co //pub monero_derive_secret_key(drvsec, derivation, output_index, sec); - //pub key - monero_io_insert_encrypt(drvsec,32); + //sec key + os_memmove(G_monero_vstate.last_derive_secret_key, drvsec, 32); + monero_io_insert_encrypt(drvsec,32, TYPE_SCALAR); return SW_OK; } @@ -601,7 +593,7 @@ int monero_apdu_generate_key_image(/*const crypto::public_key &pub, const crypto //fetch monero_io_fetch(pub,32); - monero_io_fetch_decrypt(sec, 32); + monero_io_fetch_decrypt(sec, 32, TYPE_SCALAR); monero_io_discard(0); //pub @@ -623,7 +615,7 @@ int monero_apdu_derive_subaddress_public_key(/*const crypto::public_key &pub, co //fetch monero_io_fetch(pub,32); - monero_io_fetch_decrypt(derivation, 32); + monero_io_fetch_decrypt(derivation, 32, TYPE_DERIVATION); output_index = monero_io_fetch_u32(); monero_io_discard(0); @@ -687,15 +679,13 @@ int monero_apdu_get_subaddress_secret_key(/*const crypto::secret_key& sec, const monero_io_fetch(index,8); monero_io_discard(0); - //pub monero_get_subaddress_secret_key(sub_sec,sec,index); - //pub key - monero_io_insert_encrypt(sub_sec,32); + os_memmove(G_monero_vstate.last_get_subaddress_secret_key, sub_sec,32); + monero_io_insert_encrypt(sub_sec,32, TYPE_SCALAR); return SW_OK; } - /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ @@ -719,7 +709,6 @@ int monero_apu_generate_txout_keys(/*size_t tx_version, crypto::secret_key tx_se //TMP unsigned char derivation[32]; - tx_version = monero_io_fetch_u32(); monero_io_fetch_decrypt_key(tx_key); txkey_pub = G_monero_vstate.io_buffer+G_monero_vstate.io_offset; monero_io_fetch(NULL,32); @@ -735,10 +724,8 @@ int monero_apu_generate_txout_keys(/*size_t tx_version, crypto::secret_key tx_se monero_io_fetch(NULL,32); } - - //update outkeys hash control - if (G_monero_vstate.sig_mode == TRANSACTION_CREATE_REAL) { + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { if (G_monero_vstate.io_protocol_version == 2) { monero_sha256_outkeys_update(Aout,32); monero_sha256_outkeys_update(Bout,32); @@ -766,7 +753,7 @@ int monero_apu_generate_txout_keys(/*size_t tx_version, crypto::secret_key tx_se //compute amount key AKout (scalar1), version is always greater than 1 monero_derivation_to_scalar(amount_key, derivation, output_index); - if (G_monero_vstate.sig_mode == TRANSACTION_CREATE_REAL) { + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { if (G_monero_vstate.io_protocol_version == 2) { monero_sha256_outkeys_update(amount_key,32); } @@ -777,11 +764,12 @@ int monero_apu_generate_txout_keys(/*size_t tx_version, crypto::secret_key tx_se //send all monero_io_discard(0); - monero_io_insert_encrypt(amount_key,32); + monero_io_insert_encrypt(amount_key,32, TYPE_AMOUNT_KEY); monero_io_insert(out_eph_public_key, 32); if (need_additional_txkeys) { monero_io_insert(additional_txkey_pub, 32); } + G_monero_vstate.tx_output_cnt++; return SW_OK; } diff --git a/src/monero_main.c b/src/monero_main.c index fe28551..15faa20 100644 --- a/src/monero_main.c +++ b/src/monero_main.c @@ -21,15 +21,14 @@ #include "monero_api.h" #include "monero_vars.h" -#include "monero_ux_nanos.h" -//#include "monero_ux_blue.h" - #include "os_io_seproxyhal.h" #include "string.h" #include "glyphs.h" -#ifdef TARGET_NANOX +#ifdef HAVE_UX_FLOW #include "ux.h" +#endif +#ifdef TARGET_NANOX #include "balenos_ble.h" #endif @@ -56,7 +55,7 @@ void monero_main(void) { //THROW(EXCEPTION_IO_RESET); } CATCH_OTHER(e) { - monero_reset_tx(); + monero_reset_tx(1); if (((e&0xF000)==0x9000)||((e&0xFF00)==0x6400)) { sw = e; } else { diff --git a/src/monero_mlsag.c b/src/monero_mlsag.c index 427fd7a..346a5ca 100644 --- a/src/monero_mlsag.c +++ b/src/monero_mlsag.c @@ -17,7 +17,6 @@ #include "cx.h" #include "monero_types.h" #include "monero_api.h" -#include "monero_ux_nanos.h" #include "monero_vars.h" /* ----------------------------------------------------------------------- */ @@ -30,13 +29,17 @@ int monero_apdu_mlsag_prepare() { unsigned char alpha[32]; unsigned char mul[32]; + G_monero_vstate.tx_sign_cnt++; + if (G_monero_vstate.tx_sign_cnt == 0) { + monero_lock_and_throw(SW_SECURITY_MAX_SIGNATURE_REACHED); + } if (G_monero_vstate.io_length>1) { monero_io_fetch(Hi,32); if(G_monero_vstate.options &0x40) { monero_io_fetch(xin,32); } else { - monero_io_fetch_decrypt(xin,32); + monero_io_fetch_decrypt(xin,32, TYPE_SCALAR); } options = 1; } else { @@ -48,7 +51,7 @@ int monero_apdu_mlsag_prepare() { //ai monero_rng_mod_order(alpha); monero_reduce(alpha, alpha); - monero_io_insert_encrypt(alpha, 32); + monero_io_insert_encrypt(alpha, 32, TYPE_ALPHA); //ai.G monero_ecmul_G(mul, alpha); @@ -62,7 +65,6 @@ int monero_apdu_mlsag_prepare() { monero_ecmul_k(mul, Hi, xin); monero_io_insert(mul,32); } - return SW_OK; } @@ -99,17 +101,21 @@ int monero_apdu_mlsag_sign() { unsigned char ss[32]; unsigned char ss2[32]; - if (G_monero_vstate.sig_mode == TRANSACTION_CREATE_FAKE) { + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_FAKE) { monero_io_fetch(xin,32); monero_io_fetch(alpha,32); - } else if (G_monero_vstate.sig_mode == TRANSACTION_CREATE_REAL) { - monero_io_fetch_decrypt(xin,32); - monero_io_fetch_decrypt(alpha,32); + } else if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { + monero_io_fetch_decrypt(xin,32, TYPE_SCALAR); + monero_io_fetch_decrypt(alpha,32, TYPE_ALPHA); } else { - THROW(SW_WRONG_DATA); + monero_lock_and_throw(SW_SECURITY_INTERNAL); } monero_io_discard(1); + //check xin and alpha are not null + if (cx_math_is_zero(xin,32) || cx_math_is_zero(alpha,32)) { + monero_lock_and_throw(SW_SECURITY_RANGE_VALUE); + } monero_reduce(ss, G_monero_vstate.c); monero_reduce(xin,xin); @@ -119,6 +125,6 @@ int monero_apdu_mlsag_sign() { monero_subm(ss2, alpha, ss); monero_io_insert(ss2,32); - monero_io_insert_u32(G_monero_vstate.sig_mode); + monero_io_insert_u32(G_monero_vstate.tx_sig_mode); return SW_OK; } diff --git a/src/monero_open_tx.c b/src/monero_open_tx.c index 36f6e84..06f166a 100644 --- a/src/monero_open_tx.c +++ b/src/monero_open_tx.c @@ -23,7 +23,7 @@ /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -void monero_reset_tx() { +void monero_reset_tx(int reset_tx_cnt) { os_memset(G_monero_vstate.r, 0, 32); os_memset(G_monero_vstate.R, 0, 32); cx_rng(G_monero_vstate.hmac_key, 32); @@ -33,7 +33,9 @@ void monero_reset_tx() { monero_sha256_outkeys_init(); G_monero_vstate.tx_in_progress = 0; G_monero_vstate.tx_output_cnt = 0; - + if (reset_tx_cnt) { + G_monero_vstate.tx_cnt = 0; + } } @@ -51,7 +53,16 @@ int monero_apdu_open_tx() { monero_io_discard(1); - monero_reset_tx(); + monero_reset_tx(0); + G_monero_vstate.tx_cnt++; + ui_menu_opentx_display(0); + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { + return 0; + } + return monero_apdu_open_tx_cont(); +} + +int monero_apdu_open_tx_cont() { G_monero_vstate.tx_in_progress = 1; #ifdef DEBUG_HWDEVICE @@ -64,11 +75,11 @@ int monero_apdu_open_tx() { monero_ecmul_G(G_monero_vstate.R, G_monero_vstate.r); monero_io_insert(G_monero_vstate.R,32); - monero_io_insert_encrypt(G_monero_vstate.r,32); + monero_io_insert_encrypt(G_monero_vstate.r,32, TYPE_SCALAR); monero_io_insert(C_FAKE_SEC_VIEW_KEY,32); - monero_io_insert_hmac_for((void*)C_FAKE_SEC_VIEW_KEY,32); + monero_io_insert_hmac_for((void*)C_FAKE_SEC_VIEW_KEY,32, TYPE_SCALAR); monero_io_insert(C_FAKE_SEC_SPEND_KEY,32); - monero_io_insert_hmac_for((void*)C_FAKE_SEC_SPEND_KEY,32); + monero_io_insert_hmac_for((void*)C_FAKE_SEC_SPEND_KEY,32,TYPE_SCALAR); return SW_OK; } @@ -77,32 +88,27 @@ int monero_apdu_open_tx() { /* ----------------------------------------------------------------------- */ int monero_apdu_close_tx() { monero_io_discard(1); - monero_reset_tx(); - G_monero_vstate.tx_in_progress = 0; + monero_reset_tx(G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL); + ui_menu_main_display(0); return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -/* - * Sub dest address not yet supported: P1 = 2 not supported - */ int monero_abort_tx() { - monero_reset_tx(); + monero_reset_tx(1); + ui_menu_info_display2(0, "TX", "Aborted"); return 0; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -/* - * Sub dest address not yet supported: P1 = 2 not supported - */ int monero_apdu_set_signature_mode() { unsigned int sig_mode; - G_monero_vstate.sig_mode = TRANSACTION_CREATE_FAKE; + G_monero_vstate.tx_sig_mode = TRANSACTION_CREATE_FAKE; sig_mode = monero_io_fetch_u8(); monero_io_discard(0); @@ -111,10 +117,10 @@ int monero_apdu_set_signature_mode() { case TRANSACTION_CREATE_FAKE: break; default: - THROW(SW_WRONG_DATA); + monero_lock_and_throw(SW_WRONG_DATA); } - G_monero_vstate.sig_mode = sig_mode; + G_monero_vstate.tx_sig_mode = sig_mode; - monero_io_insert_u32( G_monero_vstate.sig_mode ); + monero_io_insert_u32( G_monero_vstate.tx_sig_mode ); return SW_OK; } diff --git a/src/monero_prehash.c b/src/monero_prehash.c index e9fcda4..b63e1ce 100644 --- a/src/monero_prehash.c +++ b/src/monero_prehash.c @@ -21,14 +21,13 @@ #include "cx.h" #include "monero_types.h" #include "monero_api.h" -#include "monero_ux_nanos.h" #include "monero_vars.h" /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_mlsag_prehash_init() { - if (G_monero_vstate.sig_mode == TRANSACTION_CREATE_REAL) { + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { if (G_monero_vstate.io_p2 == 1) { monero_sha256_outkeys_final(NULL); monero_sha256_outkeys_init(); @@ -38,7 +37,7 @@ int monero_apdu_mlsag_prehash_init() { } monero_keccak_update_H(G_monero_vstate.io_buffer+G_monero_vstate.io_offset, G_monero_vstate.io_length-G_monero_vstate.io_offset); - if ((G_monero_vstate.sig_mode == TRANSACTION_CREATE_REAL) &&(G_monero_vstate.io_p2==1)) { + if ((G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) &&(G_monero_vstate.io_p2==1)) { // skip type monero_io_fetch_u8(); // fee str @@ -78,7 +77,7 @@ int monero_apdu_mlsag_prehash_update() { } Aout = G_monero_vstate.io_buffer+G_monero_vstate.io_offset; monero_io_fetch(NULL,32); Bout = G_monero_vstate.io_buffer+G_monero_vstate.io_offset; monero_io_fetch(NULL,32); - monero_io_fetch_decrypt(AKout,32); + monero_io_fetch_decrypt(AKout,32, TYPE_AMOUNT_KEY); monero_io_fetch(C, 32); monero_io_fetch(k, 32); monero_io_fetch(v, 32); @@ -93,7 +92,7 @@ int monero_apdu_mlsag_prehash_update() { monero_keccak_update_H(v,32); } - if (G_monero_vstate.sig_mode == TRANSACTION_CREATE_REAL) { + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { if (is_change == 0) { //encode dest adress monero_base58_public_key(&G_monero_vstate.ux_address[0], Aout, Bout, is_subaddress, NULL); @@ -116,7 +115,7 @@ int monero_apdu_mlsag_prehash_update() { os_memmove(aH, kG, 32); } if (os_memcmp(C, aH, 32)) { - THROW(SW_SECURITY_COMMITMENT_CONTROL); + monero_lock_and_throw(SW_SECURITY_COMMITMENT_CONTROL); } //update commitment hash control monero_sha256_commitment_update(C,32); @@ -127,7 +126,7 @@ int monero_apdu_mlsag_prehash_update() { //finalize and check destination hash_control monero_sha256_outkeys_final(k); if (os_memcmp(k, G_monero_vstate.OUTK, 32)) { - THROW(SW_SECURITY_OUTKEYS_CHAIN_CONTROL); + monero_lock_and_throw(SW_SECURITY_OUTKEYS_CHAIN_CONTROL); } } //finalize commitment hash control @@ -174,10 +173,10 @@ int monero_apdu_mlsag_prehash_finalize() { } else { //Finalize and check commitment hash control - if (G_monero_vstate.sig_mode == TRANSACTION_CREATE_REAL) { + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { monero_sha256_commitment_final(H); if (os_memcmp(H,G_monero_vstate.C,32)) { - THROW(SW_SECURITY_COMMITMENT_CHAIN_CONTROL); + monero_lock_and_throw(SW_SECURITY_COMMITMENT_CHAIN_CONTROL); } } //compute last H diff --git a/src/monero_ram.c b/src/monero_ram.c index f9975f9..e9f4ec2 100644 --- a/src/monero_ram.c +++ b/src/monero_ram.c @@ -23,15 +23,15 @@ unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; -#ifdef TARGET_NANOX -/* --- NANO-X config --- */ +#ifdef HAVE_UX_FLOW +/* --- "NANO-X" and "NANO-S flow" config --- */ #include "ux.h" ux_state_t G_ux; bolos_ux_params_t G_ux_params; #else -/* --- NANO-X config --- */ +/* --- "NANO-S legacy" config --- */ ux_state_t ux; diff --git a/src/monero_types.h b/src/monero_types.h index 4af9f79..c1dc9f7 100644 --- a/src/monero_types.h +++ b/src/monero_types.h @@ -74,6 +74,8 @@ struct monero_nv_state_s { #define KEY_MODE_SEED 0x42 unsigned char key_mode; + /* acount id for bip derivation */ + unsigned int account_id; /* spend key */ unsigned char b[32]; @@ -83,8 +85,10 @@ struct monero_nv_state_s { /*words*/ #define WORDS_MAX_LENGTH 20 - char words[26][20]; - + union { + char words[26][WORDS_MAX_LENGTH]; + char words_list[25*WORDS_MAX_LENGTH+25]; + }; } ; typedef struct monero_nv_state_s monero_nv_state_t; @@ -129,11 +133,30 @@ struct monero_v_state_s { /* ------------------------------------------ */ /* --- State Machine --- */ /* ------------------------------------------ */ - - - unsigned int sig_mode; - unsigned int export_view_key; - + unsigned int export_view_key; + unsigned char key_set; + + /* protocol guard */ + #define PROTOCOL_LOCKED 0x42 + #define PROTOCOL_LOCKED_UNLOCKABLE 0x84 + #define PROTOCOL_UNLOCKED 0x24 + unsigned char protocol_barrier; + + /* Tx state machine */ + unsigned char tx_in_progress; + unsigned char tx_cnt; + unsigned char tx_sig_mode; + unsigned char tx_state_ins; + unsigned char tx_state_p1; + unsigned char tx_state_p2; + unsigned char tx_output_cnt; + unsigned int tx_sign_cnt; + + /* sc_add control */ + unsigned char last_derive_secret_key[32]; + unsigned char last_get_subaddress_secret_key[32]; + + /* ------------------------------------------ */ /* --- Crypo --- */ /* ------------------------------------------ */ @@ -146,14 +169,6 @@ struct monero_v_state_s { cx_aes_key_t spk; unsigned char hmac_key[32]; - /* Tx state machine */ - struct { - unsigned char key_set:1; - unsigned int tx_in_progress: 1; - unsigned int tx_state: 4; - }; - unsigned int tx_output_cnt; - /* Tx key */ unsigned char R[32]; unsigned char r[32]; @@ -174,18 +189,17 @@ struct monero_v_state_s { /* ------------------------------------------ */ /* --- UI/UX --- */ /* ------------------------------------------ */ - - #ifdef UI_NANO_X - char ux_wallet_public_address[160]; char ux_wallet_public_short_address[5+2+5+1]; - #endif + char ux_wallet_account_name[14]; union { struct { - /* menu 0: 95-chars + "" + null */ - char ux_menu[132]; + char ux_info1[14]; + char ux_info2[14]; + /* menu */ + char ux_menu[16]; // address to display: 95/106-chars + null - char ux_address[132]; + char ux_address[160]; // xmr to display: max pow(2,64) unit, aka 20-chars + '0' + dot + null char ux_amount[23]; // addr mode @@ -199,11 +213,6 @@ struct monero_v_state_s { struct { unsigned char tmp[340]; }; - #ifdef UI_NANO_X - struct { - char ux_words[520]; - }; - #endif }; }; typedef struct monero_v_state_s monero_v_state_t; @@ -216,6 +225,13 @@ typedef struct monero_v_state_s monero_v_state_t; #define STATE_IDLE 0xC0 +/* --- ... --- */ +#define TYPE_SCALAR 1 +#define TYPE_DERIVATION 2 +#define TYPE_AMOUNT_KEY 3 +#define TYPE_ALPHA 4 + + /* --- ... --- */ #define IO_OFFSET_END (unsigned int)-1 #define IO_OFFSET_MARK (unsigned int)-2 @@ -232,6 +248,7 @@ typedef struct monero_v_state_s monero_v_state_t; #define INS_NONE 0x00 #define INS_RESET 0x02 +#define INS_LOCK_DISPLAY 0x04 #define INS_GET_KEY 0x20 #define INS_DISPLAY_ADDRESS 0x21 @@ -247,7 +264,6 @@ typedef struct monero_v_state_s monero_v_state_t; #define INS_DERIVE_SECRET_KEY 0x38 #define INS_GEN_KEY_IMAGE 0x3A #define INS_SECRET_KEY_ADD 0x3C -#define INS_SECRET_KEY_SUB 0x3E #define INS_GENERATE_KEYPAIR 0x40 #define INS_SECRET_SCAL_MUL_KEY 0x42 #define INS_SECRET_SCAL_MUL_BASE 0x44 @@ -294,53 +310,36 @@ typedef struct monero_v_state_s monero_v_state_t; #define SW_OK 0x9000 -#define SW_ALGORITHM_UNSUPPORTED 0x9484 - -#define SW_BYTES_REMAINING_00 0x6100 - -#define SW_WARNING_STATE_UNCHANGED 0x6200 -#define SW_STATE_TERMINATED 0x6285 - -#define SW_MORE_DATA_AVAILABLE 0x6310 #define SW_WRONG_LENGTH 0x6700 -#define SW_LOGICAL_CHANNEL_NOT_SUPPORTED 0x6881 -#define SW_SECURE_MESSAGING_NOT_SUPPORTED 0x6882 -#define SW_LAST_COMMAND_EXPECTED 0x6883 -#define SW_COMMAND_CHAINING_NOT_SUPPORTED 0x6884 - - -#define SW_SECURITY_LOAD_KEY 0x6900 -#define SW_SECURITY_COMMITMENT_CONTROL 0x6911 -#define SW_SECURITY_AMOUNT_CHAIN_CONTROL 0x6912 -#define SW_SECURITY_COMMITMENT_CHAIN_CONTROL 0x6913 -#define SW_SECURITY_OUTKEYS_CHAIN_CONTROL 0x6914 -#define SW_SECURITY_MAXOUTPUT_REACHED 0x6915 -#define SW_SECURITY_TRUSTED_INPUT 0x6916 - -#define SW_CLIENT_NOT_SUPPORTED 0x6930 - -#define SW_SECURITY_STATUS_NOT_SATISFIED 0x6982 -#define SW_FILE_INVALID 0x6983 -#define SW_PIN_BLOCKED 0x6983 -#define SW_DATA_INVALID 0x6984 -#define SW_CONDITIONS_NOT_SATISFIED 0x6985 -#define SW_COMMAND_NOT_ALLOWED 0x6986 -#define SW_APPLET_SELECT_FAILED 0x6999 - -#define SW_WRONG_DATA 0x6a80 -#define SW_FUNC_NOT_SUPPORTED 0x6a81 -#define SW_FILE_NOT_FOUND 0x6a82 -#define SW_RECORD_NOT_FOUND 0x6a83 -#define SW_FILE_FULL 0x6a84 -#define SW_INCORRECT_P1P2 0x6a86 -#define SW_REFERENCED_DATA_NOT_FOUND 0x6a88 +#define SW_SECURITY_PIN_LOCKED 0x6910 +#define SW_SECURITY_LOAD_KEY 0x6911 +#define SW_SECURITY_COMMITMENT_CONTROL 0x6912 +#define SW_SECURITY_AMOUNT_CHAIN_CONTROL 0x6913 +#define SW_SECURITY_COMMITMENT_CHAIN_CONTROL 0x6914 +#define SW_SECURITY_OUTKEYS_CHAIN_CONTROL 0x6915 +#define SW_SECURITY_MAXOUTPUT_REACHED 0x6916 +#define SW_SECURITY_HMAC 0x6917 +#define SW_SECURITY_RANGE_VALUE 0x6918 +#define SW_SECURITY_INTERNAL 0x6919 +#define SW_SECURITY_MAX_SIGNATURE_REACHED 0x691A +#define SW_SECURITY_LOCKED 0x69EE + + +#define SW_COMMAND_NOT_ALLOWED 0x6980 +#define SW_SUBCOMMAND_NOT_ALLOWED 0x6981 +#define SW_DENY 0x6982 +#define SW_KEY_NOT_SET 0x6983 +#define SW_WRONG_DATA 0x6984 +#define SW_WRONG_DATA_RANGE 0x6985 +#define SW_IO_FULL 0x6986 + +#define SW_CLIENT_NOT_SUPPORTED 0x6A30 #define SW_WRONG_P1P2 0x6b00 -#define SW_CORRECT_LENGTH_00 0x6c00 #define SW_INS_NOT_SUPPORTED 0x6d00 -#define SW_CLA_NOT_SUPPORTED 0x6e00 +#define SW_PROTOCOL_NOT_SUPPORTED 0x6e00 #define SW_UNKNOWN 0x6f00 diff --git a/src/monero_ux_nanos.c b/src/monero_ux_nanos.c index d2896d9..56fce0e 100644 --- a/src/monero_ux_nanos.c +++ b/src/monero_ux_nanos.c @@ -43,71 +43,29 @@ const bagl_element_t* ui_menu_main_preprocessor(const ux_menu_entry_t* entry, ba void ui_menu_settings_display(unsigned int value); -/* ------------------------------- Helpers UX ------------------------------- */ -/* -void ui_info(const char* msg1, const char* msg2, const void *menu_display, unsigned int value) { - os_memset(&G_monero_vstate.ui_dogsays[0], 0, sizeof(ux_menu_entry_t)); - G_monero_vstate.ui_dogsays[0].callback = menu_display; - G_monero_vstate.ui_dogsays[0].userid = value; - G_monero_vstate.ui_dogsays[0].line1 = msg1; - G_monero_vstate.ui_dogsays[0].line2 = msg2; - - os_memset(&G_monero_vstate.ui_dogsays[1],0, sizeof(ux_menu_entry_t)); - UX_MENU_DISPLAY(0, G_monero_vstate.ui_dogsays, NULL); -}; -*/ -/* ----------------------------- FEE VALIDATION ----------------------------- */ -#define ACCEPT 0xACCE -#define REJECT ~ACCEPT - -void ui_menu_amount_validation_action(unsigned int value); - -const ux_menu_entry_t ui_menu_fee_validation[] = { - {NULL, NULL, 1, NULL, " Fee", "?xmr?", 0, 0}, - {NULL, ui_menu_amount_validation_action, REJECT, NULL, "Reject", "Fee", 0, 0}, - {NULL, ui_menu_amount_validation_action, ACCEPT, NULL, "Accept", "Fee", 0, 0}, - UX_MENU_END -}; -const ux_menu_entry_t ui_menu_change_validation[] = { - {NULL, NULL, 1, NULL, " Change", "?xmr?", 0, 0}, - {NULL, ui_menu_amount_validation_action, REJECT, NULL, "Reject", "Change", 0, 0}, - {NULL, ui_menu_amount_validation_action, ACCEPT, NULL, "Accept", "Change", 0, 0}, - UX_MENU_END -}; - -const bagl_element_t* ui_menu_amount_validation_preprocessor(const ux_menu_entry_t* entry, bagl_element_t* element) { - - /* --- Amount --- */ - if ((entry == &ui_menu_fee_validation[0]) || (entry == &ui_menu_change_validation[0])) { - if(element->component.userid==0x22) { - element->text = G_monero_vstate.ux_amount; - } - } - return element; -} - - -void ui_menu_amount_validation_action(unsigned int value) { - unsigned short sw; - if (value == ACCEPT) { - sw = 0x9000; - } else { - sw = SW_SECURITY_STATUS_NOT_SATISFIED; - monero_abort_tx(); - } - monero_io_insert_u16(sw); - monero_io_do(IO_RETURN_AFTER_TX); +/* -------------------------------------- LOCK--------------------------------------- */ + +void ui_menu_pinlock_display() { + struct { + bolos_ux_t ux_id; + // length of parameters in the u union to be copied during the syscall + unsigned int len; + union { + struct { + unsigned int cancellable; + } validate_pin; + } u; + } ux_params; + + os_global_pin_invalidate(); + G_monero_vstate.protocol_barrier = PROTOCOL_LOCKED_UNLOCKABLE; + ux_params.ux_id = BOLOS_UX_VALIDATE_PIN; + ux_params.len = sizeof(ux_params.u.validate_pin); + ux_params.u.validate_pin.cancellable = 0; + os_ux((bolos_ux_params_t*)&ux_params); ui_menu_main_display(0); } -void ui_menu_fee_validation_display(unsigned int value) { - UX_MENU_DISPLAY(0, ui_menu_fee_validation, ui_menu_amount_validation_preprocessor); -} -void ui_menu_change_validation_display(unsigned int value) { - UX_MENU_DISPLAY(0, ui_menu_change_validation, ui_menu_amount_validation_preprocessor); -} - - /* -------------------------------------- 25 WORDS --------------------------------------- */ void ui_menu_words_clear(unsigned int value); void ui_menu_words_back(unsigned int value); @@ -148,13 +106,174 @@ const bagl_element_t* ui_menu_words_preprocessor(const ux_menu_entry_t* entry, b void ui_menu_words_display(unsigned int value) { UX_MENU_DISPLAY(0, ui_menu_words, ui_menu_words_preprocessor); } + void ui_menu_words_clear(unsigned int value) { monero_clear_words(); ui_menu_main_display(0); } + void ui_menu_words_back(unsigned int value) { ui_menu_settings_display(1); } + +/* -------------------------------- INFO UX --------------------------------- */ +unsigned int ui_menu_info_button(unsigned int button_mask, unsigned int button_mask_counter); + +const bagl_element_t ui_menu_info[] = { + // type userid x y w h str rad fill fg bg font_id icon_id + { {BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, + NULL}, + + { {BAGL_LABELINE, 0x01, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, + G_monero_vstate.ux_info1}, + + { {BAGL_LABELINE, 0x02, 0, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, + G_monero_vstate.ux_info2}, +}; + +unsigned int ui_menu_info_button(unsigned int button_mask, unsigned int button_mask_counter) { + switch(button_mask) { + case BUTTON_EVT_RELEASED|BUTTON_RIGHT: + case BUTTON_EVT_RELEASED|BUTTON_LEFT: + if (G_monero_vstate.protocol_barrier == PROTOCOL_LOCKED) { + ui_menu_pinlock_display(); + } else { + ui_menu_main_display(0); + } + break; + + default: + return 0; + } + return 0; +} + +void ui_menu_info_display2(unsigned int value, char* line1, char* line2) { + snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "%s", line1); + snprintf(G_monero_vstate.ux_info2, sizeof(G_monero_vstate.ux_info2), "%s", line2); + UX_DISPLAY(ui_menu_info, NULL); +} + +void ui_menu_info_display(unsigned int value) { + UX_DISPLAY(ui_menu_info, NULL); +} + +/* -------------------------------- OPEN TX UX --------------------------------- */ + +unsigned int ui_menu_opentx_button(unsigned int button_mask, unsigned int button_mask_counter); + +const bagl_element_t ui_menu_opentx[] = { + // type userid x y w h str rad fill fg bg font_id icon_id + { {BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, + NULL}, + + { {BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CROSS }, + NULL}, + + { {BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CHECK }, + NULL}, + + { {BAGL_LABELINE, 0x01, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, + "Process"}, + + { {BAGL_LABELINE, 0x02, 0, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, + "new TX ?"}, + +}; + +unsigned int ui_menu_opentx_button(unsigned int button_mask, unsigned int button_mask_counter) { + unsigned int sw; + unsigned char x[32]; + + monero_io_discard(0); + os_memset(x,0,32); + sw = SW_OK; + + switch(button_mask) { + case BUTTON_EVT_RELEASED|BUTTON_LEFT: // CANCEL + monero_abort_tx(); + sw = SW_DENY; + break; + + case BUTTON_EVT_RELEASED|BUTTON_RIGHT: // OK + sw = monero_apdu_open_tx_cont(); + break; + + default: + return 0; + } + monero_io_insert_u16(sw); + monero_io_do(IO_RETURN_AFTER_TX); + if (sw == SW_OK) { + ui_menu_info_display2(0, "Processing TX", "..."); + } else { + ui_menu_info_display2(0, "Tansaction", "aborted"); + } + return 0; +} + + +void ui_menu_opentx_display(unsigned int value) { + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { + UX_DISPLAY(ui_menu_opentx, (void*)NULL); + } else { + snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "Prepare new"); + snprintf(G_monero_vstate.ux_info2, sizeof(G_monero_vstate.ux_info2), "TX / %d", G_monero_vstate.tx_cnt); + ui_menu_info_display(0); + } +} + +/* ----------------------------- FEE VALIDATION ----------------------------- */ +#define ACCEPT 0xACCE +#define REJECT ~ACCEPT + +void ui_menu_amount_validation_action(unsigned int value); + +const ux_menu_entry_t ui_menu_fee_validation[] = { + {NULL, NULL, 1, NULL, " Fee", "?xmr?", 0, 0}, + {NULL, ui_menu_amount_validation_action, REJECT, NULL, "Reject", "Fee", 0, 0}, + {NULL, ui_menu_amount_validation_action, ACCEPT, NULL, "Accept", "Fee", 0, 0}, + UX_MENU_END +}; +const ux_menu_entry_t ui_menu_change_validation[] = { + {NULL, NULL, 1, NULL, " Change", "?xmr?", 0, 0}, + {NULL, ui_menu_amount_validation_action, REJECT, NULL, "Reject", "Change", 0, 0}, + {NULL, ui_menu_amount_validation_action, ACCEPT, NULL, "Accept", "Change", 0, 0}, + UX_MENU_END +}; + +const bagl_element_t* ui_menu_amount_validation_preprocessor(const ux_menu_entry_t* entry, bagl_element_t* element) { + + /* --- Amount --- */ + if ((entry == &ui_menu_fee_validation[0]) || (entry == &ui_menu_change_validation[0])) { + if(element->component.userid==0x22) { + element->text = G_monero_vstate.ux_amount; + } + } + return element; +} + + +void ui_menu_amount_validation_action(unsigned int value) { + unsigned short sw; + if (value == ACCEPT) { + sw = SW_OK; + } else { + sw = SW_DENY; + monero_abort_tx(); + } + monero_io_insert_u16(sw); + monero_io_do(IO_RETURN_AFTER_TX); + ui_menu_info_display2(0, "Processing TX", "..."); +} + +void ui_menu_fee_validation_display(unsigned int value) { + UX_MENU_DISPLAY(0, ui_menu_fee_validation, ui_menu_amount_validation_preprocessor); +} +void ui_menu_change_validation_display(unsigned int value) { + UX_MENU_DISPLAY(0, ui_menu_change_validation, ui_menu_amount_validation_preprocessor); +} + /* ----------------------------- USER DEST/AMOUNT VALIDATION ----------------------------- */ void ui_menu_validation_action(unsigned int value); @@ -178,14 +297,6 @@ const bagl_element_t* ui_menu_validation_preprocessor(const ux_menu_entry_t* ent element->text = G_monero_vstate.ux_amount; } } - #if 0 - /* --- Fees --- */ - if (entry == &ui_menu_validation[1]) { - if(element->component.userid==0x22) { - element->text = G_monero_vstate.ux_fees; - } - } -#endif /* --- Destination --- */ if (entry == &ui_menu_validation[1]) { @@ -246,14 +357,14 @@ void ui_menu_validation_display(unsigned int value) { void ui_menu_validation_action(unsigned int value) { unsigned short sw; if (value == ACCEPT) { - sw = 0x9000; + sw = SW_OK; } else { - sw = SW_SECURITY_STATUS_NOT_SATISFIED; + sw = SW_DENY; monero_abort_tx(); } monero_io_insert_u16(sw); monero_io_do(IO_RETURN_AFTER_TX); - ui_menu_main_display(0); + ui_menu_info_display2(0, "Processing TX", "..."); } @@ -266,35 +377,19 @@ unsigned int ui_export_viewkey_button(unsigned int button_mask, unsigned int but const bagl_element_t ui_export_viewkey[] = { // type userid x y w h str rad fill fg bg font_id icon_id { {BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, - NULL, - 0, - 0, 0, - NULL, NULL, NULL}, + NULL}, { {BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CROSS }, - NULL, - 0, - 0, 0, - NULL, NULL, NULL }, + NULL}, { {BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CHECK }, - NULL, - 0, - 0, 0, - NULL, NULL, NULL }, + NULL}, { {BAGL_LABELINE, 0x01, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, - G_monero_vstate.ux_menu, - 0, - 0, 0, - NULL, NULL, NULL }, + G_monero_vstate.ux_menu}, { {BAGL_LABELINE, 0x02, 0, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, - G_monero_vstate.ux_menu, - 0, - 0, 0, - NULL, NULL, NULL }, - + G_monero_vstate.ux_menu}, }; void ui_export_viewkey_display(unsigned int value) { @@ -320,7 +415,7 @@ unsigned int ui_export_viewkey_button(unsigned int button_mask, unsigned int but monero_io_discard(0); os_memset(x,0,32); - sw = 0x9000; + sw = SW_OK; switch(button_mask) { case BUTTON_EVT_RELEASED|BUTTON_LEFT: // CANCEL @@ -342,15 +437,59 @@ unsigned int ui_export_viewkey_button(unsigned int button_mask, unsigned int but return 0; } + +/* -------------------------------- ACCOUNT UX --------------------------------- */ + +void ui_menu_account_action(unsigned int value); +const ux_menu_entry_t ui_menu_account[] = { + {NULL, NULL, 0, NULL, "It will reset", "the application!", 0, 0}, + {NULL, ui_menu_main_display, 0, &C_badge_back, "Abort", NULL, 61, 40}, + {NULL, ui_menu_account_action, 0, NULL, "0", NULL, 0, 0}, + {NULL, ui_menu_account_action, 1, NULL, "1", NULL, 0, 0}, + {NULL, ui_menu_account_action, 2, NULL, "2", NULL, 0, 0}, + {NULL, ui_menu_account_action, 3, NULL, "3", NULL, 0, 0}, + {NULL, ui_menu_account_action, 4, NULL, "4", NULL, 0, 0}, + {NULL, ui_menu_account_action, 5, NULL, "5", NULL, 0, 0}, + {NULL, ui_menu_account_action, 6, NULL, "6", NULL, 0, 0}, + {NULL, ui_menu_account_action, 7, NULL, "7", NULL, 0, 0}, + {NULL, ui_menu_account_action, 8, NULL, "8", NULL, 0, 0}, + {NULL, ui_menu_account_action, 9, NULL, "9", NULL, 0, 0}, + UX_MENU_END +}; + +const bagl_element_t* ui_menu_account_preprocessor(const ux_menu_entry_t* entry, bagl_element_t* element) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + for (unsigned int i = 2; i<12; i++) { + if ((entry == &ui_menu_account[i]) && (element->component.userid==0x20) && (N_monero_pstate->account_id == (i-2))) { + G_monero_vstate.ux_menu[0] = '0' + (i-2); + G_monero_vstate.ux_menu[1] = ' '; + G_monero_vstate.ux_menu[2] = '+'; + element->text = G_monero_vstate.ux_menu; + } + } + return element; +} + +void ui_menu_account_action(unsigned int value) { + monero_nvm_write((void*)&N_monero_pstate->account_id, &value, sizeof(unsigned int)); + monero_init(); + ui_menu_main_display(0); +} + +void ui_menu_account_display(unsigned int value) { + UX_MENU_DISPLAY(value, ui_menu_account, ui_menu_account_preprocessor); +} + + /* -------------------------------- NETWORK UX --------------------------------- */ void ui_menu_network_action(unsigned int value); const ux_menu_entry_t ui_menu_network[] = { - {NULL, NULL, 0, NULL, "It will reset", "the application!", 0, 0}, - {NULL, ui_menu_main_display, 0, &C_badge_back, "Abort", NULL, 61, 40}, - {NULL, ui_menu_network_action, TESTNET, NULL, "Test Network ", NULL, 0, 0}, - {NULL, ui_menu_network_action, STAGENET, NULL, "Stage Network", NULL, 0, 0}, + {NULL, NULL, 0, NULL, "It will reset", "the application!", 0, 0}, + {NULL, ui_menu_main_display, 0, &C_badge_back, "Abort", NULL, 61, 40}, + {NULL, ui_menu_network_action, TESTNET, NULL, "Test Network ", NULL, 0, 0}, + {NULL, ui_menu_network_action, STAGENET, NULL, "Stage Network", NULL, 0, 0}, #ifndef MONERO_ALPHA - {NULL, ui_menu_network_action, MAINNET, NULL, "Main Network", NULL, 0, 0}, + {NULL, ui_menu_network_action, MAINNET, NULL, "Main Network", NULL, 0, 0}, #endif UX_MENU_END }; @@ -391,8 +530,8 @@ void ui_menu_network_display(unsigned int value) { const ux_menu_entry_t ui_menu_reset[] = { {NULL, NULL, 0, NULL, "Really Reset ?", NULL, 0, 0}, - {NULL, ui_menu_main_display, 0, &C_badge_back, "No", NULL, 61, 40}, - {NULL, ui_menu_reset_action, 0, NULL, "Yes", NULL, 0, 0}, + {NULL, ui_menu_main_display, 0, &C_badge_back, "No", NULL, 61, 40}, + {NULL, ui_menu_reset_action, 0, NULL, "Yes", NULL, 0, 0}, UX_MENU_END }; @@ -406,14 +545,14 @@ void ui_menu_reset_action(unsigned int value) { /* ------------------------------- SETTINGS UX ------------------------------- */ const ux_menu_entry_t ui_menu_settings[] = { - {NULL, ui_menu_network_display, 0, NULL, "Change Network", NULL, 0, 0}, - {NULL, ui_menu_words_display, 0, NULL, "Show 25 words", NULL, 0, 0}, - {ui_menu_reset, NULL, 0, NULL, "Reset", NULL, 0, 0}, - {NULL, ui_menu_main_display, 2, &C_badge_back, "Back", NULL, 61, 40}, + {NULL, ui_menu_account_display, 0, NULL, "Select Account", NULL, 0, 0}, + {NULL, ui_menu_network_display, 0, NULL, "Select Network", NULL, 0, 0}, + {NULL, ui_menu_words_display, 0, NULL, "Show 25 words", NULL, 0, 0}, + {ui_menu_reset, NULL, 0, NULL, "Reset", NULL, 0, 0}, + {NULL, ui_menu_main_display, 2, &C_badge_back, "Back", NULL, 61, 40}, UX_MENU_END }; - void ui_menu_settings_display(unsigned int value) { UX_MENU_DISPLAY(value, ui_menu_settings, NULL); } @@ -424,12 +563,12 @@ void ui_menu_settings_display(unsigned int value) { #define STR(x) #x #define XSTR(x) STR(x) -const ux_menu_entry_t ui_menu_info[] = { - {NULL, NULL, -1, NULL, "Monero", NULL, 0, 0}, - {NULL, NULL, -1, NULL, "(c) Ledger SAS", NULL, 0, 0}, - {NULL, NULL, -1, NULL, "Spec " XSTR(SPEC_VERSION),NULL, 0, 0}, - {NULL, NULL, -1, NULL, "App " XSTR(MONERO_VERSION), NULL, 0, 0}, - {NULL, ui_menu_main_display, 3, &C_badge_back, "Back", NULL, 61, 40}, +const ux_menu_entry_t ui_menu_about[] = { + {NULL, NULL, -1, NULL, "Monero", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "(c) Ledger SAS", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "Spec " XSTR(SPEC_VERSION), NULL, 0, 0}, + {NULL, NULL, -1, NULL, "App " XSTR(MONERO_VERSION), NULL, 0, 0}, + {NULL, ui_menu_main_display, 3, &C_badge_back, "Back", NULL, 61, 40}, UX_MENU_END }; @@ -582,7 +721,7 @@ const bagl_element_t* ui_menu_pubaddr_preprocessor(const ux_menu_entry_t* entry, void ui_menu_pubaddr_action(unsigned int value) { if (G_monero_vstate.disp_addr_mode) { - monero_io_insert_u16(0x9000); + monero_io_insert_u16(SW_OK); monero_io_do(IO_RETURN_AFTER_TX); } G_monero_vstate.disp_addr_mode = 0; @@ -591,44 +730,32 @@ void ui_menu_pubaddr_action(unsigned int value) { ui_menu_main_display(0); } -void ui_menu_any_pubaddr_display(unsigned int value) { +void ui_menu_any_pubaddr_display(unsigned int value, unsigned char* pub_view, unsigned char *pub_spend, unsigned char is_subbadress, unsigned char *paymanetID){ + monero_base58_public_key(G_monero_vstate.ux_address+strlen(G_monero_vstate.ux_address), pub_view, pub_spend, is_subbadress, paymanetID); UX_MENU_DISPLAY(value, ui_menu_pubaddr, ui_menu_pubaddr_preprocessor); } void ui_menu_pubaddr_display(unsigned int value) { - monero_base58_public_key(G_monero_vstate.ux_address, G_monero_vstate.A, G_monero_vstate.B, 0, NULL); - G_monero_vstate.disp_addr_mode = 0; + monero_base58_public_key(G_monero_vstate.ux_address, G_monero_vstate.A, G_monero_vstate.B, 0, NULL); + G_monero_vstate.disp_addr_mode = 0; G_monero_vstate.disp_addr_M = 0; G_monero_vstate.disp_addr_m = 0; - UX_MENU_DISPLAY(value, ui_menu_pubaddr, ui_menu_pubaddr_preprocessor); + UX_MENU_DISPLAY(value, ui_menu_pubaddr, ui_menu_pubaddr_preprocessor); } /* --------------------------------- MAIN UX --------------------------------- */ const ux_menu_entry_t ui_menu_main[] = { - {NULL, ui_menu_pubaddr_display, 0, NULL, "XMR", "", 0, 0}, - {ui_menu_settings, NULL, 0, NULL, "Settings", NULL, 0, 0}, - {ui_menu_info, NULL, 0, NULL, "About", NULL, 0, 0}, - {NULL, os_sched_exit, 0, &C_icon_dashboard, "Quit app" , NULL, 50, 29}, + {NULL, ui_menu_pubaddr_display, 0, NULL, G_monero_vstate.ux_wallet_account_name, G_monero_vstate.ux_wallet_public_short_address, 0, 0}, + {ui_menu_settings, NULL, 0, NULL, "Settings", NULL, 0, 0}, + {ui_menu_about, NULL, 0, NULL, "About", NULL, 0, 0}, + {NULL, (void*)os_sched_exit, 0, &C_icon_dashboard, "Quit app" , NULL, 50, 29}, UX_MENU_END }; -const bagl_element_t* ui_menu_main_preprocessor(const ux_menu_entry_t* entry, bagl_element_t* element) { - if (entry == &ui_menu_main[0]) { - if(element->component.userid==0x22) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); - monero_base58_public_key(G_monero_vstate.ux_menu, G_monero_vstate.A,G_monero_vstate.B, 0, NULL); - os_memset(G_monero_vstate.ux_menu+5,'.',2); - os_memmove(G_monero_vstate.ux_menu+7, G_monero_vstate.ux_menu+95-5,5); - G_monero_vstate.ux_menu[12] = 0; - element->text = G_monero_vstate.ux_menu; - } - } - return element; -} void ui_menu_main_display(unsigned int value) { - UX_MENU_DISPLAY(value, ui_menu_main, ui_menu_main_preprocessor); + UX_MENU_DISPLAY(value, ui_menu_main, NULL); } void ui_init(void) { diff --git a/src/monero_ux_nanos.h b/src/monero_ux_nanos.h deleted file mode 100644 index 6b6403d..0000000 --- a/src/monero_ux_nanos.h +++ /dev/null @@ -1,22 +0,0 @@ - -/* Copyright 2017 Cedric Mesnil , Ledger SAS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -#ifndef MONERO_UX_NANOS_H -#define MONERO_UX_NANOS_H - - -#endif diff --git a/src/monero_ux_nanox.c b/src/monero_ux_nanox.c deleted file mode 100644 index 40b8f6d..0000000 --- a/src/monero_ux_nanox.c +++ /dev/null @@ -1,714 +0,0 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - -#ifdef UI_NANO_X - -#include "os.h" -#include "cx.h" -#include "monero_types.h" -#include "monero_api.h" -#include "monero_vars.h" - -#include "monero_ux_msg.h" -#include "os_io_seproxyhal.h" -#include "string.h" -#include "glyphs.h" - -/* ----------------------------------------------------------------------- */ -/* --- NanoS UI layout --- */ -/* ----------------------------------------------------------------------- */ - - -#define ACCEPT 0xACCE -#define REJECT ~ACCEPT - - -void ui_menu_main_display(unsigned int value); - -/* -------------------------------------- 25 WORDS --------------------------------------- */ -void ui_menu_words_display(unsigned int value); -void ui_menu_words_clear(unsigned int value); -void ui_menu_words_back(unsigned int value); - - -UX_STEP_NOCB( - ux_menu_words_1_step, - bnnn_paging, - { - .title = "Electrum Seed", - .text = G_monero_vstate.ux_words, - }); - - -UX_STEP_CB( - ux_menu_words_2_step, - bn, - ui_menu_words_clear(0), - { - "CLEAR WORDS", - "(Do not wipe the wallet)" - }); - -UX_STEP_CB( - ux_menu_words_3_step, - pb, - ui_menu_words_back(0), - { - &C_icon_back, - "back" - }); - -UX_FLOW( - ux_flow_words, - &ux_menu_words_1_step, - &ux_menu_words_2_step, - &ux_menu_words_3_step - ); - -void ui_menu_words_clear(unsigned int value) { - monero_clear_words(); - ui_menu_main_display(0); -} - -void ui_menu_words_back(unsigned int value) { - ui_menu_main_display(1); -} - -static int ui_strncpy(char *dest, const char *src, int n) { - int i; - i = 0; - while(i < n) { - if (!src[i]) { - break; - } - dest[i] = src[i]; - i++; - } - return i; -} - -void ui_menu_words_display(unsigned int value) { - int offset = 0; - os_memset(G_monero_vstate.ux_words, 0, sizeof(G_monero_vstate.ux_words)); - for (int i = 0; i < 25; i++) { - offset += ui_strncpy(G_monero_vstate.ux_words+offset, (char*)N_monero_pstate->words[i], 20); - G_monero_vstate.ux_words[offset] = ' '; - offset++; - } - ux_flow_init(0, ux_flow_words, NULL); -} - -void settings_show_25_words(void) { - ui_menu_words_display(0); -} - -/* ----------------------------- FEE VALIDATION ----------------------------- */ - -void ui_menu_amount_validation_action(unsigned int value); - -UX_STEP_NOCB( - ux_menu_validation_fee_1_step, - bn, - { - "Fee", - G_monero_vstate.ux_amount, - }); - -UX_STEP_NOCB( - ux_menu_validation_change_1_step, - bn, - { - "Change", - G_monero_vstate.ux_amount, - }); - -UX_STEP_CB( - ux_menu_validation_cf_2_step, - pb, - ui_menu_amount_validation_action(ACCEPT), - { - &C_icon_validate_14, - "Accept", - }); - -UX_STEP_CB( - ux_menu_validation_cf_3_step, - pb, - ui_menu_amount_validation_action(REJECT), - { - &C_icon_crossmark, - "Reject", - }); - -UX_FLOW(ux_flow_fee, - &ux_menu_validation_fee_1_step, - &ux_menu_validation_cf_2_step, - &ux_menu_validation_cf_3_step - ); - -UX_FLOW(ux_flow_change, - &ux_menu_validation_change_1_step, - &ux_menu_validation_cf_2_step, - &ux_menu_validation_cf_3_step - ); - - -void ui_menu_amount_validation_action(unsigned int value) { - unsigned short sw; - if (value == ACCEPT) { - sw = 0x9000; - } else { - sw = SW_SECURITY_STATUS_NOT_SATISFIED; - monero_abort_tx(); - } - monero_io_insert_u16(sw); - monero_io_do(IO_RETURN_AFTER_TX); - ui_menu_main_display(0); -} - -void ui_menu_fee_validation_display(unsigned int value) { - ux_flow_init(0, ux_flow_fee, NULL); -} - -void ui_menu_change_validation_display(unsigned int value) { - ux_flow_init(0, ux_flow_change, NULL); -} - -/* ----------------------------- USER DEST/AMOUNT VALIDATION ----------------------------- */ -void ui_menu_validation_action(unsigned int value); - -UX_STEP_NOCB( - ux_menu_validation_1_step, - bn, - { - "Amout", - G_monero_vstate.ux_amount - }); - -UX_STEP_NOCB( - ux_menu_validation_2_step, - bnnn_paging, - { - "Destination", - G_monero_vstate.ux_address - }); - -UX_STEP_CB( - ux_menu_validation_3_step, - pb, - ui_menu_validation_action(ACCEPT), - { - &C_icon_validate_14, - "Accept" - }); - -UX_STEP_CB( - ux_menu_validation_4_step, - pb, - ui_menu_validation_action(REJECT), - { - &C_icon_crossmark, - "Reject" - }); - -UX_FLOW(ux_flow_validation, - &ux_menu_validation_1_step, - &ux_menu_validation_2_step, - &ux_menu_validation_3_step, - &ux_menu_validation_4_step - ); - -void ui_menu_validation_display(unsigned int value) { - ux_flow_init(0, ux_flow_validation, NULL); -} - -void ui_menu_validation_action(unsigned int value) { - unsigned short sw; - if (value == ACCEPT) { - sw = 0x9000; - } else { - sw = SW_SECURITY_STATUS_NOT_SATISFIED; - monero_abort_tx(); - } - monero_io_insert_u16(sw); - monero_io_do(IO_RETURN_AFTER_TX); - ui_menu_main_display(0); -} - -/* -------------------------------- EXPORT VIEW KEY UX --------------------------------- */ -unsigned int ui_menu_export_viewkey_action(unsigned int value); - -UX_STEP_NOCB( - ux_menu_export_viewkey_1_step, - nn, - { - "Export", - "View Key" - }); - -UX_STEP_CB( - ux_menu_export_viewkey_2_step, - pb, - ui_menu_export_viewkey_action(ACCEPT), - { - &C_icon_validate_14, - "Accept" - }); - -UX_STEP_CB( - ux_menu_export_viewkey_3_step, - pb, - ui_menu_export_viewkey_action(REJECT), - { - &C_icon_crossmark, - "Reject" - }); - -UX_FLOW(ux_flow_export_viewkey, - &ux_menu_export_viewkey_1_step, - &ux_menu_export_viewkey_2_step, - &ux_menu_export_viewkey_3_step - ); - -void ui_export_viewkey_display(unsigned int value) { - ux_flow_init(0, ux_flow_export_viewkey,NULL); -} - -unsigned int ui_menu_export_viewkey_action(unsigned int value) { - unsigned int sw; - unsigned char x[32]; - - monero_io_discard(0); - os_memset(x,0,32); - sw = 0x9000; - - if (value == ACCEPT) { - monero_io_insert(G_monero_vstate.a, 32); - G_monero_vstate.export_view_key = EXPORT_VIEW_KEY; - } else { - monero_io_insert(x, 32); - G_monero_vstate.export_view_key = 0; - } - monero_io_insert_u16(sw); - monero_io_do(IO_RETURN_AFTER_TX); - ui_menu_main_display(0); - return 0; -} - -/* -------------------------------- NETWORK UX --------------------------------- */ - -const char* const network_submenu_getter_values[] = { - #ifdef MONERO_ALPHA - "Unvailable", - #else - "Main Network", - #endif - "Stage Network", - "Test Network", - "Abort" -}; -const char* const network_submenu_getter_values_selected[] = { - #ifdef MONERO_ALPHA - "Unvailable", - #else - "Main Network +", - #endif - "Stage Network +", - "Test Network +", - "Abort" -}; - - -const char* network_submenu_getter(unsigned int idx) { - if (idx >= ARRAYLEN(network_submenu_getter_values)) { - return NULL; - } - int net; - switch(idx) { - case 0: - #ifdef MONERO_ALPHA - net = -1; - #else - net = MAINNET; - #endif - break; - case 1: - net = STAGENET; - break; - case 2: - net = TESTNET; - break; - default: - net = -1; - break; - } - if (N_monero_pstate->network_id == net) { - return network_submenu_getter_values_selected[idx]; - } else { - return network_submenu_getter_values[idx]; - } -} - -void network_back(void) { - ui_menu_main_display(0); -} - -static void network_set_net(unsigned int network) { - monero_install(network); - monero_init(); -} - -void network_submenu_selector(unsigned int idx) { - switch(idx) { - case 0: - #ifndef MONERO_ALPHA - network_set_net(MAINNET); - #endif - break; - case 1: - network_set_net(STAGENET); - break; - case 2: - network_set_net(TESTNET); - break; - default: - break; - } - ui_menu_main_display(0); -} - - -void ui_menu_network_display(unsigned int value) { - ux_menulist_init(G_ux.stack_count-1, network_submenu_getter, network_submenu_selector); -} - -void settings_change_network(void) { - ui_menu_network_display(0); -} -/* -------------------------------- RESET UX --------------------------------- */ -void ui_menu_reset_display(unsigned int value); -void ui_menu_reset_action(unsigned int value); - -UX_STEP_NOCB( - ux_menu_reset_1_step, - nnn, - { - "", - "Really Reset?", - "" - }); - -UX_STEP_CB( - ux_menu_reset_2_step, - pb, - ui_menu_reset_action(REJECT), - { - &C_icon_crossmark, - "No", - }); - - -UX_STEP_CB( - ux_menu_reset_3_step, - pb, - ui_menu_reset_action(ACCEPT), - { - &C_icon_validate_14, - "Yes", - }); - -UX_FLOW(ux_flow_reset, - &ux_menu_reset_1_step, - &ux_menu_reset_2_step, - &ux_menu_reset_3_step - ); - -void ui_menu_reset_display(unsigned int value) { - ux_flow_init(0, ux_flow_reset, 0); -} - -void settings_reset(void) { - ui_menu_reset_display(0); -} - -void ui_menu_reset_action(unsigned int value) { - if (value == ACCEPT) { - unsigned char magic[4]; - magic[0] = 0; magic[1] = 0; magic[2] = 0; magic[3] = 0; - monero_nvm_write((void*)N_monero_pstate->magic, magic, 4); - monero_init(); - } - ui_menu_main_display(0); -} -/* ------------------------------- SETTINGS UX ------------------------------- */ - -const char* const settings_submenu_getter_values[] = { - "Change Network", - "Show 25 words", - "Reset", - "Back", -}; - -const char* settings_submenu_getter(unsigned int idx) { - if (idx < ARRAYLEN(settings_submenu_getter_values)) { - return settings_submenu_getter_values[idx]; - } - return NULL; -} - -void settings_back(void) { - ui_menu_main_display(0); -} - -void settings_submenu_selector(unsigned int idx) { - switch(idx) { - case 0: - settings_change_network(); - break; - case 1: - settings_show_25_words(); - break; - case 2: - settings_reset(); - break; - default: - settings_back(); - } -} - - - -/* --------------------------------- INFO UX --------------------------------- */ -#define STR(x) #x -#define XSTR(x) STR(x) - -UX_STEP_NOCB( - ux_menu_info_1_step, - bnnn, - { - "Monero", - "(c) Ledger SAS", - "Spec " XSTR(SPEC_VERSION), - "App " XSTR(MONERO_VERSION), - }); - -UX_STEP_CB( - ux_menu_info_2_step, - pb, - ui_menu_main_display(0), - { - &C_icon_back, - "Back", - }); - - - -UX_FLOW(ux_flow_info, - &ux_menu_info_1_step, - &ux_menu_info_2_step - ); - - -void ui_menu_info_display(unsigned int value) { - ux_flow_init(0, ux_flow_info, NULL); -} - -#undef STR -#undef XSTR - - - - - -/* ---------------------------- PUBLIC ADDRESS UX ---------------------------- */ -void ui_menu_pubaddr_action(unsigned int value); - -#define ADDR_TYPE G_monero_vstate.ux_wallet_public_address+108 -#define ADDR_MAJOR G_monero_vstate.ux_wallet_public_address+124 -#define ADDR_MINOR G_monero_vstate.ux_wallet_public_address+140 -#define ADDR_IDSTR G_monero_vstate.ux_wallet_public_address+124 -#define ADDR_ID G_monero_vstate.ux_wallet_public_address+140 - -UX_STEP_NOCB( - ux_menu_pubaddr_01_step, - nn, - { - ADDR_TYPE, - "Address", - }); - - -UX_STEP_NOCB( - ux_menu_pubaddr_02_step, - nn, - { - ADDR_MAJOR, - ADDR_MINOR, - }); - -UX_STEP_NOCB( - ux_menu_pubaddr_1_step, - bnnn_paging, - { - .title = "Address", - .text = G_monero_vstate.ux_wallet_public_address - }); - -UX_STEP_CB( - ux_menu_pubaddr_2_step, - pb, - ui_menu_pubaddr_action(0), - { - &C_icon_back, - "Ok" - }); - -UX_FLOW(ux_flow_pubaddr, - &ux_menu_pubaddr_01_step, - &ux_menu_pubaddr_02_step, - &ux_menu_pubaddr_1_step, - &ux_menu_pubaddr_2_step - ); - -void ui_menu_pubaddr_action(unsigned int value) { - - if (G_monero_vstate.disp_addr_mode) { - monero_io_insert_u16(0x9000); - monero_io_do(IO_RETURN_AFTER_TX); - } - G_monero_vstate.disp_addr_mode = 0; - ui_menu_main_display(0); -} - -/** - * - */ -void ui_menu_any_pubaddr_display(unsigned int value) { - int l = 0; - memset(G_monero_vstate.ux_wallet_public_address,0,sizeof(G_monero_vstate.ux_wallet_public_address)); - - switch (G_monero_vstate.disp_addr_mode) { - case 0: - case DISP_MAIN: - os_memmove(ADDR_TYPE, "Main", 4); - os_memmove(ADDR_MAJOR, "Major: 0", 8); - os_memmove(ADDR_MINOR, "minor: 0", 8); - l = 95; - break; - - case DISP_SUB: - os_memmove(ADDR_TYPE, "Sub", 3); - snprintf(ADDR_MAJOR, 16, "Major: %d", G_monero_vstate.disp_addr_M); - snprintf(ADDR_MINOR, 16, "minor: %d", G_monero_vstate.disp_addr_m); - l = 95; - break; - - case DISP_INTEGRATED: - os_memmove(ADDR_TYPE, "Integrated", 10); - os_memmove(ADDR_IDSTR, "Payment ID", 10); - os_memmove(ADDR_ID, G_monero_vstate.payment_id, 16); - l = 106; - break; - } - - os_memmove(G_monero_vstate.ux_wallet_public_address+strlen(G_monero_vstate.ux_wallet_public_address), - G_monero_vstate.ux_address, - l); - - ux_layout_bnnn_paging_reset(); - ux_flow_init(0, ux_flow_pubaddr, NULL); -} - -void ui_menu_pubaddr_display(unsigned int value) { - memset(G_monero_vstate.ux_address,0,sizeof(G_monero_vstate.ux_address)); - monero_base58_public_key(G_monero_vstate.ux_address, G_monero_vstate.A,G_monero_vstate.B, 0, NULL); - G_monero_vstate.disp_addr_mode = 0; - G_monero_vstate.disp_addr_M = 0; - G_monero_vstate.disp_addr_M = 0; - ui_menu_any_pubaddr_display(value); -} - -#undef ADDR_TYPE -#undef ADDR_MAJOR -#undef ADDR_MINOR -#undef ADDR_IDSTR -#undef ADDR_ID - -/* --------------------------------- MAIN UX --------------------------------- */ - -UX_STEP_CB( - ux_menu_main_1_step, - pbb, - ui_menu_pubaddr_display(0), - { - &C_icon_monero, - "XMR", - G_monero_vstate.ux_wallet_public_short_address - }); - -UX_STEP_CB( - ux_menu_main_2_step, - pb, - ux_menulist_init(G_ux.stack_count-1, settings_submenu_getter, settings_submenu_selector), - { - &C_icon_coggle, - "Settings" - }); - -UX_STEP_CB( - ux_menu_main_3_step, - pb, - ui_menu_info_display(0), - { - &C_icon_certificate, - "About" - }); - -UX_STEP_CB( - ux_menu_main_4_step, - pb, - os_sched_exit(0), - { - &C_icon_dashboard_x, - "Quit app" - }); - - -UX_FLOW(ux_flow_main, - &ux_menu_main_1_step, - &ux_menu_main_2_step, - &ux_menu_main_3_step, - &ux_menu_main_4_step); - -void ui_menu_main_display(unsigned int value) { - // reserve a display stack slot if none yet - if(G_ux.stack_count == 0) { - ux_stack_push(); - } - ux_flow_init(0, ux_flow_main, NULL); -} -/* --- INIT --- */ - -void ui_init(void) { - ui_menu_main_display(0); -} - -void io_seproxyhal_display(const bagl_element_t *element) { - io_seproxyhal_display_default((bagl_element_t *)element); -} - -#endif From f166121d058b5685f4067ac2dba0dfac23700fa2 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 13 Feb 2020 12:14:10 +0800 Subject: [PATCH 05/76] Corrected spelling error in UX file --- src/monero_ux_nanos.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/monero_ux_nanos.c b/src/monero_ux_nanos.c index d3d59c0..3b599ff 100644 --- a/src/monero_ux_nanos.c +++ b/src/monero_ux_nanos.c @@ -63,13 +63,13 @@ void ui_info(const char* msg1, const char* msg2, const void *menu_display, unsig void ui_menu_amount_validation_action(unsigned int value); const ux_menu_entry_t ui_menu_fee_validation[] = { - {NULL, NULL, 1, NULL, " Fee", "?xmr?", 0, 0}, + {NULL, NULL, 1, NULL, " Fee", "?xwp?", 0, 0}, {NULL, ui_menu_amount_validation_action, REJECT, NULL, "Reject", "Fee", 0, 0}, {NULL, ui_menu_amount_validation_action, ACCEPT, NULL, "Accept", "Fee", 0, 0}, UX_MENU_END }; const ux_menu_entry_t ui_menu_change_validation[] = { - {NULL, NULL, 1, NULL, " Change", "?xmr?", 0, 0}, + {NULL, NULL, 1, NULL, " Change", "?xwp?", 0, 0}, {NULL, ui_menu_amount_validation_action, REJECT, NULL, "Reject", "Change", 0, 0}, {NULL, ui_menu_amount_validation_action, ACCEPT, NULL, "Accept", "Change", 0, 0}, UX_MENU_END @@ -159,7 +159,7 @@ void ui_menu_words_back(unsigned int value) { void ui_menu_validation_action(unsigned int value); const ux_menu_entry_t ui_menu_validation[] = { - {NULL, NULL, 1, NULL, " Amount", "?xmr?", 0, 0}, + {NULL, NULL, 1, NULL, " Amount", "?xwp?", 0, 0}, {NULL, NULL, 3, NULL, "Destination", "?dest.1?", 0, 0}, {NULL, NULL, 4, NULL, "?dest.2?", "?dest.2?", 0, 0}, {NULL, NULL, 5, NULL, "?dest.3?", "?dest.3?", 0, 0}, From 63e6831c8062da5c94b96ddca877b397464a582f Mon Sep 17 00:00:00 2001 From: cslashm Date: Fri, 28 Feb 2020 13:26:00 +0100 Subject: [PATCH 06/76] do not ask processing confirmation to avoid timeout will be readd in next version with client support --- Makefile | 2 +- src/monero_open_tx.c | 2 +- src/monero_ux_nano.c | 17 ++++++++++++++++- src/monero_ux_nanos.c | 18 ++++++++++++++++-- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 84fec55..10a5778 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ endif APPVERSION_M=1 APPVERSION_N=5 -APPVERSION_P=0 +APPVERSION_P=1 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) SPECVERSION="1.0" diff --git a/src/monero_open_tx.c b/src/monero_open_tx.c index 06f166a..ae3e653 100644 --- a/src/monero_open_tx.c +++ b/src/monero_open_tx.c @@ -57,7 +57,7 @@ int monero_apdu_open_tx() { G_monero_vstate.tx_cnt++; ui_menu_opentx_display(0); if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { - return 0; + //return 0; } return monero_apdu_open_tx_cont(); } diff --git a/src/monero_ux_nano.c b/src/monero_ux_nano.c index 1d4f43f..54e7405 100644 --- a/src/monero_ux_nano.c +++ b/src/monero_ux_nano.c @@ -218,6 +218,7 @@ unsigned int ui_menu_opentx_action(unsigned int value) { return 0; } +#if 0 void ui_menu_opentx_display(unsigned int value) { if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { ux_flow_init(0, ux_flow_opentx,NULL); @@ -227,7 +228,21 @@ void ui_menu_opentx_display(unsigned int value) { ui_menu_info_display(0); } } - +#else +void ui_menu_opentx_display(unsigned int value) { + uint32_t i; + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { + snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "Processing TX"); + } else { + snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "Preparing TX"); + } + for (i = 0; (i Date: Wed, 1 Apr 2020 15:13:17 +0200 Subject: [PATCH 07/76] Add Timelock verification on device --- Makefile | 7 +- doc/developer/blue-app-commands.rst | 188 +++++++++++++++++++++------- src/monero_api.h | 25 +++- src/monero_crypto.c | 97 +++++++++----- src/monero_dispatch.c | 90 +++++++++++-- src/monero_init.c | 4 +- src/monero_io.c | 12 ++ src/monero_key.c | 4 +- src/monero_mlsag.c | 2 +- src/monero_monero.c | 6 +- src/monero_prefix.c | 69 ++++++++++ src/monero_prehash.c | 19 +-- src/monero_types.h | 7 +- src/monero_ux_nano.c | 20 ++- src/monero_ux_nanos.c | 18 ++- 15 files changed, 454 insertions(+), 114 deletions(-) create mode 100644 src/monero_prefix.c diff --git a/Makefile b/Makefile index 10a5778..3a951a4 100644 --- a/Makefile +++ b/Makefile @@ -36,10 +36,11 @@ ICONNAME = images/icon_monero.gif endif #DEFINES += MONERO_ALPHA +#DEFINES += MONERO_BETA APPVERSION_M=1 -APPVERSION_N=5 -APPVERSION_P=1 +APPVERSION_N=6 +APPVERSION_P=0 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) SPECVERSION="1.0" @@ -160,7 +161,7 @@ CFLAGS += -O3 -Os AS := $(GCCPATH)arm-none-eabi-gcc LD := $(GCCPATH)arm-none-eabi-gcc -#SCRIPT_LD:=script.ld +SCRIPT_LD:=script.ld #LDFLAGS += -O0 -gdwarf-2 -gstrict-dwarf LDFLAGS += -O3 -Os diff --git a/doc/developer/blue-app-commands.rst b/doc/developer/blue-app-commands.rst index 28ed979..0cd798a 100644 --- a/doc/developer/blue-app-commands.rst +++ b/doc/developer/blue-app-commands.rst @@ -371,7 +371,7 @@ Restart the application and check client/application versions compatibility. +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 02 | 00 | 00 | ll | +| 03 | 02 | 00 | 00 | ll | +-----+-----+-----+-----+------+ **Command data** @@ -417,7 +417,7 @@ The application shall: +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 22 | 00 | 00 | e0 | +| 03 | 22 | 00 | 00 | e0 | +-----+-----+-----+-----+------+ **Command data** @@ -459,7 +459,7 @@ Retrieves public base58 encoded public key. +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 20 | 01 | 00 | 01 | +| 03 | 20 | 01 | 00 | 01 | +-----+-----+-----+-----+------+ **Command data** @@ -497,7 +497,7 @@ the client will use the device for scanning the blockchain. +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 20 | 02 | 00 | 01 | +| 03 | 20 | 02 | 00 | 01 | +-----+-----+-----+-----+------+ @@ -541,7 +541,7 @@ if payment ID is provided: +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 21 | xx | 00 | 11 | +| 03 | 21 | xx | 00 | 11 | +-----+-----+-----+-----+------+ if P1 is '00' display non-integradted address. @@ -609,7 +609,7 @@ Verify that the provided private key and public key match. +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 26 | xx | 00 | 41 | +| 03 | 26 | xx | 00 | 41 | +-----+-----+-----+-----+------+ if P1 is '00' the provided public key will be used. @@ -661,7 +661,7 @@ return the full internal state (200 bytes) of Keccak. +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 24 | 00 | 00 | 00 | +| 03 | 24 | 00 | 00 | 00 | +-----+-----+-----+-----+------+ **Command data** @@ -703,7 +703,7 @@ return |eDrv|. +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 02 | 32 | 00 | 00 | 41 or 61 | +| 03 | 32 | 00 | 00 | 41 or 61 | +-----+-----+-----+-----+----------+ **Command data** @@ -753,7 +753,7 @@ return |es|. +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 02 | 34 | 00 | 00 | 25 or 45 | +| 03 | 34 | 00 | 00 | 25 or 45 | +-----+-----+-----+-----+----------+ **Command data** @@ -809,7 +809,7 @@ return |P|'. +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 02 | 36 | 00 | 00 | 25 or 45 | +| 03 | 36 | 00 | 00 | 25 or 45 | +-----+-----+-----+-----+----------+ **Command data** @@ -867,7 +867,7 @@ return |ex|. +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 02 | 38 | 00 | 00 | 65 or 85 | +| 03 | 38 | 00 | 00 | 65 or 85 | +-----+-----+-----+-----+----------+ **Command data** @@ -919,7 +919,7 @@ return |P|' +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 02 | 46 | 00 | 00 | 45 or 65 | +| 03 | 46 | 00 | 00 | 45 or 65 | +-----+-----+-----+-----+----------+ **Command data** @@ -972,7 +972,7 @@ return |D| +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 4a | 00 | 00 | 09 | +| 03 | 4a | 00 | 00 | 09 | +-----+-----+-----+-----+------+ **Command data** @@ -1015,7 +1015,7 @@ return |ed| +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 02 | 4c | 00 | 00 | 39 or 59 | +| 03 | 4c | 00 | 00 | 39 or 59 | +-----+-----+-----+-----+----------+ **Command data** @@ -1069,7 +1069,7 @@ return |C|, |D| +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 48 | 00 | 00 | 09 | +| 03 | 48 | 00 | 00 | 09 | +-----+-----+-----+-----+------+ **Command data** @@ -1116,7 +1116,7 @@ return |Img|. +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 02 | 3a | 00 | 00 | 41 or 61 | +| 03 | 3a | 00 | 00 | 41 or 61 | +-----+-----+-----+-----+----------+ **Command data** @@ -1164,7 +1164,7 @@ return |P|, |ex|. +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 40 | 00 | 00 | 01 | +| 03 | 40 | 00 | 00 | 01 | +-----+-----+-----+-----+------+ **Command data** @@ -1211,7 +1211,7 @@ return |P|. +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 02 | 30 | 00 | 00 | 21 or 41 | +| 03 | 30 | 00 | 00 | 21 or 41 | +-----+-----+-----+-----+----------+ **Command data** @@ -1256,7 +1256,7 @@ return |ex|. +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 02 | 3c | 00 | 00 | 41 or 61 | +| 03 | 3c | 00 | 00 | 41 or 61 | +-----+-----+-----+-----+----------+ **Command data** @@ -1308,7 +1308,7 @@ return |xP| +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 02 | 42 | 00 | 00 | 41 or 61 | +| 03 | 42 | 00 | 00 | 41 or 61 | +-----+-----+-----+-----+----------+ **Command data** @@ -1356,7 +1356,7 @@ return |xG| +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 02 | 44 | 00 | 00 | 21 or 41 | +| 03 | 44 | 00 | 00 | 21 or 41 | +-----+-----+-----+-----+----------+ **Command data** @@ -1406,7 +1406,7 @@ return |PayID| +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 02 | 44 | 00 | 00 | 61 or 81 | +| 03 | 44 | 00 | 00 | 61 or 81 | +-----+-----+-----+-----+----------+ **Command data** @@ -1467,7 +1467,7 @@ return |ek|,|ev| +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 02 | 44 | 00 | 00 | 61 or 81 | +| 03 | 44 | 00 | 00 | 61 or 81 | +-----+-----+-----+-----+----------+ @@ -1573,19 +1573,46 @@ Transaction State Machine During a transaction the following state machine is enforced:: - OPEN_TX{1} --> STEALTH{1} --> GEN_TXOUT_KEYS{*} --> GEN_COMMITMENT_MASK{*} -- - | | - --(Fake TX only)--------------> BLIND <-- - | - | - --------- VALIDATE{*} <-------- VALIDATE{*} <------- INS_VALIDATE{1} <-- - | mlsag_prehash_finalize mlsag_prehash_update mlsag_prehash_init + + OPEN_TX{1} ----------------------------------------------------- + | + ---------------------------------------------------------------- + | + ----> STEALTH{1} ----------------------------------------------- + | + ---------------------------------------------------------------- + | + ----> GEN_TXOUT_KEYS{*} --------------------------------------- + | + ------------------------------------------------------------ --- + | + ----> PREFIX_HASH{1} ---> PREFIX_HASH{*} ---> PREFIX_HASH{1} --- + (ph_init) (ph_update) (ph_finalize) | + | + ---------------------------------------------------------------- + | + ----> GEN_COMMITMENT_MASK{*} ----------------------------------- + only for real TX | + | + ---------------------------------------------------------------- | + ----> BLIND ---------------------------------------------------- + | + ---------------------------------------------------------------- | - -----> MLSAG{1} ------> MLSAG{*} ------> MLSAG{1} -----> CLOSE_TX - --> mlsag_prepare mlsag_hash mlsag_sign - | | - --------------------------------------------- + ----> VALIDATE{1} ---> VALIDATE{*} --- VALIDATE{*} <------------ + mlsag_ph_init mlsag__update mlsag__finalize | + | + --------------------------------------------------------------- + | + ----> MLSAG{1} ------> MLSAG{*} ------> MLSAG{1} --------------- + --> mlsag_prepare mlsag_hash mlsag_sign -- | + | | | + --------------------------------------------------- | + | + ---------------------------------------------------------------- + | + ----> CLOSE_TX Note this state machine assume the multi-signature is not supported. @@ -1626,7 +1653,7 @@ return |r.R| +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 70 | 01 | cnt | 05 | +| 03 | 70 | 01 | cnt | 05 | +-----+-----+-----+-----+------+ **Command data** @@ -1670,7 +1697,7 @@ the transaction and no user confirmation is requested. +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 72 | 00 | 00 | 02 | +| 03 | 72 | 00 | 00 | 02 | +-----+-----+-----+-----+------+ @@ -1694,6 +1721,77 @@ the transaction and no user confirmation is requested. +--------+-----------------------------------------------------------------+ +Hash Prefix +~~~~~~~~~~~ + +Hash prefix init +^^^^^^^^^^^^^^^^ + +**Description** + +ÃŽnit prefix hash and ask user to validate time lock + + +**Command** + ++-----+-----+-----+-----+------+ +| CLA | INS | P1 | P2 | LC | ++=====+=====+=====+=====+======+ +| 03 | 71 | 01 | cnt | 05 | ++-----+-----+-----+-----+------+ + +**Command data** + ++--------+-----------------------------------------------------------------+ +| Length | Value | ++========+=================================================================+ +| 01 | options | ++--------+-----------------------------------------------------------------+ +| varint | TX version | ++--------+-----------------------------------------------------------------+ +| varint | TX timelock | ++--------+-----------------------------------------------------------------+ + +**Response data** + ++--------+-----------------------------------------------------------------+ +| Length | Value | ++========+=================================================================+ ++--------+-----------------------------------------------------------------+ + +Hash prefix update +^^^^^^^^^^^^^^^^^^ + +**Description** + +Update prefix hash with raw data. Options fields tells if there is more data to come or not. + +**Command** + ++-----+-----+-----+-----+------+ +| CLA | INS | P1 | P2 | LC | ++=====+=====+=====+=====+======+ +| 03 | 71 | 02 | cnt | 05 | ++-----+-----+-----+-----+------+ + +**Command data** + ++--------+-----------------------------------------------------------------+ +| Length | Value | ++========+=================================================================+ +| 01 | options | ++--------+-----------------------------------------------------------------+ +| var | raw data to hash | ++--------+-----------------------------------------------------------------+ + +**Response data** + ++--------+-----------------------------------------------------------------+ +| Length | Value | ++========+=================================================================+ ++--------+-----------------------------------------------------------------+ + + Generate Commitment Mask ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1709,7 +1807,7 @@ Return |s| +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 77 | 00 | 00 | 21 | +| 03 | 77 | 00 | 00 | 21 | +-----+-----+-----+-----+------+ **Command data** @@ -1767,7 +1865,7 @@ return |ek|,|ev| +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 78 | 00 | 00 | 81 | +| 03 | 78 | 00 | 00 | 81 | +-----+-----+-----+-----+------+ *specific options* @@ -1851,7 +1949,7 @@ The application returns +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 7B | 01 | cnt | EC | +| 03 | 7B | 01 | cnt | EC | +-----+-----+-----+-----+------+ **Command data** @@ -1931,7 +2029,7 @@ else +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 7C | 01 | cnt | var | | +| 03 | 7C | 01 | cnt | var | | +-----+-----+-----+-----+------+ @@ -2000,7 +2098,7 @@ So for each command received, do: +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 7C | 02 | cnt | E3 | +| 03 | 7C | 02 | cnt | E3 | +-----+-----+-----+-----+------+ @@ -2077,7 +2175,7 @@ Keep |mlsagH| +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 7C | 03 | cnt | 21 | +| 03 | 7C | 03 | cnt | 21 | +-----+-----+-----+-----+------+ @@ -2153,7 +2251,7 @@ return |eai| , |aGi| [|aHi|, |IIi|] +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 84 | 01 | cnt | 61 | +| 03 | 84 | 01 | cnt | 61 | +-----+-----+-----+-----+------+ *specific options* @@ -2245,7 +2343,7 @@ Compute the last matrix ring parameter: +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 84 | 02 | cnt | 21 | +| 03 | 84 | 02 | cnt | 21 | +-----+-----+-----+-----+------+ **Command data** @@ -2296,7 +2394,7 @@ return |ss| +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 02 | 84 | 03 | cnt | 81 | +| 03 | 84 | 03 | cnt | 81 | +-----+-----+-----+-----+------+ diff --git a/src/monero_api.h b/src/monero_api.h index c79735d..a2357fd 100644 --- a/src/monero_api.h +++ b/src/monero_api.h @@ -64,8 +64,12 @@ int monero_apdu_gen_commitment_mask(void); int monero_apdu_mlsag_prehash_init(void); int monero_apdu_mlsag_prehash_update(void); int monero_apdu_mlsag_prehash_finalize(void); + int monero_apu_generate_txout_keys(void); +int monero_apdu_prefix_hash_init(); +int monero_apdu_prefix_hash_update(); + int monero_apdu_mlsag_prepare(void); int monero_apdu_mlsag_hash(void); int monero_apdu_mlsag_sign(void); @@ -99,11 +103,17 @@ int monero_bamount2str(unsigned char *binary, char *str, unsigned int str_len); /** uint64 amount to str */ int monero_amount2str(uint64_t xmr, char *str, unsigned int str_len); +/** uint64 amount to str */ +void monero_uint642str(uint64_t val, char *str, unsigned int str_len); + + int monero_abort_tx() ; int monero_unblind(unsigned char *v, unsigned char *k, unsigned char *AKout, unsigned int short_amount); void ui_menu_validation_display(unsigned int value) ; void ui_menu_fee_validation_display(unsigned int value) ; void ui_menu_change_validation_display(unsigned int value) ; +void ui_menu_timelock_validation_display(unsigned int value); + void ui_menu_opentx_display(unsigned int value); /* ----------------------------------------------------------------------- */ /* --- KEYS & ADDRESS ---- */ @@ -182,9 +192,9 @@ int monero_hash(unsigned int algo, cx_hash_t * hasher, unsigned char* buf, unsi #define monero_keccak_update_H(buf,len) \ monero_hash_update((cx_hash_t *)&G_monero_vstate.keccakH,(buf), (len)) #define monero_keccak_final_H(out) \ - monero_hash_final((cx_hash_t *)&G_monero_vstate.keccakH, (out)?(out):G_monero_vstate.H) + monero_hash_final((cx_hash_t *)&G_monero_vstate.keccakH, (out)) #define monero_keccak_H(buf,len,out) \ - monero_hash(CX_KECCAK, (cx_hash_t *)&G_monero_vstate.keccakH, (buf),(len), (out)?(out):G_monero_vstate.H) + monero_hash(CX_KECCAK, (cx_hash_t *)&G_monero_vstate.keccakH, (buf),(len), (out)) #define monero_sha256_commitment_init() \ monero_hash_init_sha256((cx_hash_t *)&G_monero_vstate.sha256_commitment) @@ -198,7 +208,7 @@ int monero_hash(unsigned int algo, cx_hash_t * hasher, unsigned char* buf, unsi #define monero_sha256_outkeys_update(buf,len) \ monero_hash_update((cx_hash_t *)&G_monero_vstate.sha256_out_keys, (buf), (len)) #define monero_sha256_outkeys_final(out) \ - monero_hash_final((cx_hash_t *)&G_monero_vstate.sha256_out_keys, (out)?(out):G_monero_vstate.OUTK) + monero_hash_final((cx_hash_t *)&G_monero_vstate.sha256_out_keys, (out)) /* * check 10 --- */ /* ----------------------------------------------------------------------- */ -unsigned int monero_encode_varint(unsigned char varint[8], unsigned int out_idx) { +unsigned int monero_encode_varint(unsigned char *varint, unsigned int max_len, uint64_t value) { unsigned int len; len = 0; - while(out_idx >= 0x80) { - varint[len] = (out_idx & 0x7F) | 0x80; - out_idx = out_idx>>7; + while(value >= 0x80) { + if (len == (max_len-1)) { + THROW(SW_WRONG_DATA_RANGE); + } + varint[len] = (value & 0x7F) | 0x80; + value = value>>7; len++; } - varint[len] = out_idx; - len++; - return len; + varint[len] = value; + return len+1; } + +/* ----------------------------------------------------------------------- */ +/* --- assert: max_len>0 --- */ +/* ----------------------------------------------------------------------- */ +unsigned int monero_decode_varint(unsigned char *varint, unsigned int max_len, uint64_t *value) { + uint64_t v; + int len; + v = 0; + len =0; + while((varint[len])&0x80) { + if (len == (max_len-1)) { + THROW(SW_WRONG_DATA_RANGE); + } + v = v + (((varint[len])&0x7f) << (len*7)); + len++; + } + + v = v + (((varint[len])&0x7f) << (len*7)); + *value = v; + return len+1; +} + + /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ @@ -448,7 +476,7 @@ void monero_derivation_to_scalar(unsigned char *scalar, unsigned char *drv_data, unsigned int len_varint; os_memmove(varint, drv_data, 32); - len_varint = monero_encode_varint(varint+32, out_idx); + len_varint = monero_encode_varint(varint+32, 8, out_idx); len_varint += 32; monero_keccak_F(varint,len_varint,varint); monero_reduce(scalar, varint); @@ -810,6 +838,31 @@ void monero_rng_mod_order(unsigned char *r) { monero_reverse32(r,rnd+8); } + +/* ----------------------------------------------------------------------- */ +/* --- --- */ +/* ----------------------------------------------------------------------- */ +/* return 0 if ok, 1 if missing decimal */ +void monero_uint642str(uint64_t val, char *str, unsigned int str_len) { + char stramount[22]; + unsigned int offset,len; + + os_memset(str,0,str_len); + + offset = 22; + while (val) { + offset--; + stramount[offset] = '0' + val % 10; + val = val / 10; + } + len = sizeof(stramount)-offset; + if (len > str_len) { + THROW(SW_WRONG_DATA_RANGE); + } + os_memmove(str, stramount+offset, len); +} + + /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ @@ -886,31 +939,13 @@ int monero_bamount2str(unsigned char *binary, char *str, unsigned int str_len) return monero_amount2str(monero_bamount2uint64(binary), str,str_len); } -/* ----------------------------------------------------------------------- */ -/* --- --- */ -/* ----------------------------------------------------------------------- */ -uint64_t monero_vamount2uint64(unsigned char *binary) { - uint64_t xmr,x; - int shift = 0; - xmr = 0; - while((*binary)&0x80) { - if ( (unsigned int)shift > (8*sizeof(unsigned long long int)-7)) { - return 0; - } - x = *(binary)&0x7f; - xmr = xmr + (x<magic, (void*)C_MAGIC, sizeof(C_MAGIC)) != 0) { - #ifdef MONERO_ALPHA + #if defined(MONERO_ALPHA) || defined(MONERO_BETA) monero_install(STAGENET); #else monero_install(MAINNET); @@ -227,4 +227,4 @@ void monero_lock_and_throw(int sw) { snprintf(G_monero_vstate.ux_info2, sizeof(G_monero_vstate.ux_info2), "%x", sw); ui_menu_info_display(0); THROW(sw); -} \ No newline at end of file +} diff --git a/src/monero_io.c b/src/monero_io.c index b7fbe08..21a523d 100644 --- a/src/monero_io.c +++ b/src/monero_io.c @@ -208,6 +208,9 @@ void monero_io_insert_tlv(unsigned int T, unsigned int L, unsigned char const *V /* ----------------------------------------------------------------------- */ /* FECTH data from received buffer */ /* ----------------------------------------------------------------------- */ +int monero_io_fetch_available() { + return G_monero_vstate.io_length-G_monero_vstate.io_offset; +} void monero_io_assert_available(int sz) { if ((G_monero_vstate.io_length-G_monero_vstate.io_offset) < sz) { THROW(SW_WRONG_LENGTH + (sz&0xFF)); @@ -343,6 +346,15 @@ int monero_io_fetch_decrypt_key(unsigned char* buffer) { } } +uint64_t monero_io_fetch_varint() { + uint64_t v64; + G_monero_vstate.io_offset += + monero_decode_varint(G_monero_vstate.io_buffer+G_monero_vstate.io_offset, + MIN(8, G_monero_vstate.io_length-G_monero_vstate.io_offset), + &v64); + return v64; +} + unsigned int monero_io_fetch_u32() { unsigned int v32; monero_io_assert_available(4); diff --git a/src/monero_key.c b/src/monero_key.c index 3097a21..0ced000 100644 --- a/src/monero_key.c +++ b/src/monero_key.c @@ -726,7 +726,7 @@ int monero_apu_generate_txout_keys(/*size_t tx_version, crypto::secret_key tx_se //update outkeys hash control if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { - if (G_monero_vstate.io_protocol_version == 2) { + if (G_monero_vstate.io_protocol_version >= 2) { monero_sha256_outkeys_update(Aout,32); monero_sha256_outkeys_update(Bout,32); monero_sha256_outkeys_update(&is_change,1); @@ -754,7 +754,7 @@ int monero_apu_generate_txout_keys(/*size_t tx_version, crypto::secret_key tx_se //compute amount key AKout (scalar1), version is always greater than 1 monero_derivation_to_scalar(amount_key, derivation, output_index); if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { - if (G_monero_vstate.io_protocol_version == 2) { + if (G_monero_vstate.io_protocol_version >= 2) { monero_sha256_outkeys_update(amount_key,32); } } diff --git a/src/monero_mlsag.c b/src/monero_mlsag.c index 346a5ca..9506cde 100644 --- a/src/monero_mlsag.c +++ b/src/monero_mlsag.c @@ -76,7 +76,7 @@ int monero_apdu_mlsag_hash() { unsigned char c[32]; if (G_monero_vstate.io_p2 == 1) { monero_keccak_init_H(); - os_memmove(msg, G_monero_vstate.H, 32); + os_memmove(msg, G_monero_vstate.mlsagH, 32); } else { monero_io_fetch(msg, 32); } diff --git a/src/monero_monero.c b/src/monero_monero.c index bc787c6..00c8ff1 100644 --- a/src/monero_monero.c +++ b/src/monero_monero.c @@ -132,7 +132,7 @@ int monero_base58_public_key(char* str_b58, unsigned char *view, unsigned char * break; #endif } - offset = monero_encode_varint(data, prefix); + offset = monero_encode_varint(data, 8, prefix); os_memmove(data+offset,spend,32); os_memmove(data+offset+32,view,32); @@ -141,8 +141,8 @@ int monero_base58_public_key(char* str_b58, unsigned char *view, unsigned char * os_memmove(data+offset, paymanetID, 8); offset += 8; } - monero_keccak_F(data, offset, G_monero_vstate.H); - os_memmove(data+offset, G_monero_vstate.H, 4); + monero_keccak_F(data, offset, G_monero_vstate.mlsagH); + os_memmove(data+offset, G_monero_vstate.mlsagH, 4); offset += 4; unsigned int full_block_count = (offset) / FULL_BLOCK_SIZE; diff --git a/src/monero_prefix.c b/src/monero_prefix.c new file mode 100644 index 0000000..1b0c06f --- /dev/null +++ b/src/monero_prefix.c @@ -0,0 +1,69 @@ +/* Copyright 2017 Cedric Mesnil , Ledger SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* +* Client: rctSigs.cpp.c -> get_pre_mlsag_hash +*/ + +#include "os.h" +#include "cx.h" +#include "monero_types.h" +#include "monero_api.h" +#include "monero_vars.h" + +/* ----------------------------------------------------------------------- */ +/* --- --- */ +/* ----------------------------------------------------------------------- */ +int monero_apdu_prefix_hash_init() { + int timelock; + + monero_keccak_update_H(G_monero_vstate.io_buffer+G_monero_vstate.io_offset, + G_monero_vstate.io_length-G_monero_vstate.io_offset); + + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { + monero_io_fetch_varint(); + timelock = monero_io_fetch_varint(); + if(monero_io_fetch_available() != 0) { + THROW(SW_WRONG_DATA); + } + //ask user + monero_io_discard(1); + if (timelock != 0 ) { + monero_uint642str(timelock, G_monero_vstate.ux_amount, 15); + ui_menu_timelock_validation_display(0); + return 0; + } else { + return SW_OK; + } + } else { + monero_io_discard(1); + return SW_OK; + } +} + +/* ----------------------------------------------------------------------- */ +/* --- --- */ +/* ----------------------------------------------------------------------- */ +int monero_apdu_prefix_hash_update() { + monero_keccak_update_H(G_monero_vstate.io_buffer+G_monero_vstate.io_offset, + G_monero_vstate.io_length-G_monero_vstate.io_offset); + monero_io_discard(0); + if ((G_monero_vstate.options & 0x80) == 0x00) { + monero_keccak_final_H(G_monero_vstate.prefixH); + monero_io_insert(G_monero_vstate.prefixH, 32); + } + + return SW_OK; +} diff --git a/src/monero_prehash.c b/src/monero_prehash.c index b63e1ce..bb81089 100644 --- a/src/monero_prehash.c +++ b/src/monero_prehash.c @@ -29,7 +29,7 @@ int monero_apdu_mlsag_prehash_init() { if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { if (G_monero_vstate.io_p2 == 1) { - monero_sha256_outkeys_final(NULL); + monero_sha256_outkeys_final(G_monero_vstate.OUTK); monero_sha256_outkeys_init(); monero_sha256_commitment_init(); monero_keccak_init_H(); @@ -70,7 +70,7 @@ int monero_apdu_mlsag_prehash_update() { //fetch destination is_subaddress = monero_io_fetch_u8(); - if (G_monero_vstate.io_protocol_version == 2) { + if (G_monero_vstate.io_protocol_version >= 2) { is_change = monero_io_fetch_u8(); } else { is_change = 0; @@ -98,7 +98,7 @@ int monero_apdu_mlsag_prehash_update() { monero_base58_public_key(&G_monero_vstate.ux_address[0], Aout, Bout, is_subaddress, NULL); } //update destination hash control - if (G_monero_vstate.io_protocol_version == 2) { + if (G_monero_vstate.io_protocol_version >= 2) { monero_sha256_outkeys_update(Aout,32); monero_sha256_outkeys_update(Bout,32); monero_sha256_outkeys_update(&is_change,1); @@ -122,7 +122,7 @@ int monero_apdu_mlsag_prehash_update() { if ((G_monero_vstate.options & IN_OPTION_MORE_COMMAND)==0) { - if (G_monero_vstate.io_protocol_version == 2) { + if (G_monero_vstate.io_protocol_version >= 2) { //finalize and check destination hash_control monero_sha256_outkeys_final(k); if (os_memcmp(k, G_monero_vstate.OUTK, 32)) { @@ -186,12 +186,17 @@ int monero_apdu_mlsag_prehash_finalize() { monero_io_fetch(proof,32); monero_io_discard(1); monero_keccak_init_H(); - monero_keccak_update_H(message,32); + if (G_monero_vstate.io_protocol_version == 3) { + if (os_memcmp(message, G_monero_vstate.prefixH, 32) != 0) { + monero_lock_and_throw(SW_SECURITY_PREFIX_HASH); + } + } + monero_keccak_update_H(message, 32); monero_keccak_update_H(H,32); monero_keccak_update_H(proof,32); - monero_keccak_final_H(NULL); + monero_keccak_final_H(G_monero_vstate.mlsagH); #ifdef DEBUG_HWDEVICE - monero_io_insert(G_monero_vstate.H, 32); + monero_io_insert(G_monero_vstate.mlsagH, 32); monero_io_insert(H, 32); #endif } diff --git a/src/monero_types.h b/src/monero_types.h index c1dc9f7..6e8b1dc 100644 --- a/src/monero_types.h +++ b/src/monero_types.h @@ -173,10 +173,11 @@ struct monero_v_state_s { unsigned char R[32]; unsigned char r[32]; - /* mlsag hash */ + /* prefix/mlsag hash */ cx_sha3_t keccakF; cx_sha3_t keccakH; - unsigned char H[32]; + unsigned char prefixH[32]; + unsigned char mlsagH[32]; unsigned char c[32]; /* -- track tx-in/out and commitment -- */ @@ -281,6 +282,7 @@ typedef struct monero_v_state_s monero_v_state_t; #define INS_BLIND 0x78 #define INS_UNBLIND 0x7A #define INS_GEN_TXOUT_KEYS 0x7B +#define INS_PREFIX_HASH 0x7D #define INS_VALIDATE 0x7C #define INS_MLSAG 0x7E #define INS_CLOSE_TX 0x80 @@ -324,6 +326,7 @@ typedef struct monero_v_state_s monero_v_state_t; #define SW_SECURITY_RANGE_VALUE 0x6918 #define SW_SECURITY_INTERNAL 0x6919 #define SW_SECURITY_MAX_SIGNATURE_REACHED 0x691A +#define SW_SECURITY_PREFIX_HASH 0x691B #define SW_SECURITY_LOCKED 0x69EE diff --git a/src/monero_ux_nano.c b/src/monero_ux_nano.c index 54e7405..05ba32f 100644 --- a/src/monero_ux_nano.c +++ b/src/monero_ux_nano.c @@ -244,7 +244,9 @@ void ui_menu_opentx_display(unsigned int value) { } #endif -/* ----------------------------- FEE VALIDATION ----------------------------- */ + + +/* ----------------- FEE/CHANGE/TIMELOCK VALIDATION ----------------- */ void ui_menu_amount_validation_action(unsigned int value); @@ -264,6 +266,14 @@ UX_STEP_NOCB( G_monero_vstate.ux_amount, }); +UX_STEP_NOCB( + ux_menu_validation_timelock_1_step, + bn, + { + "Timelock", + G_monero_vstate.ux_amount, + }); + UX_STEP_CB( ux_menu_validation_cf_2_step, pb, @@ -294,6 +304,11 @@ UX_FLOW(ux_flow_change, &ux_menu_validation_cf_3_step ); +UX_FLOW(ux_flow_timelock, + &ux_menu_validation_timelock_1_step, + &ux_menu_validation_cf_2_step, + &ux_menu_validation_cf_3_step + ); void ui_menu_amount_validation_action(unsigned int value) { unsigned short sw; @@ -316,6 +331,9 @@ void ui_menu_change_validation_display(unsigned int value) { ux_flow_init(0, ux_flow_change, NULL); } +void ui_menu_timelock_validation_display(unsigned int value) { + ux_flow_init(0, ux_flow_timelock, NULL); +} /* ----------------------------- USER DEST/AMOUNT VALIDATION ----------------------------- */ void ui_menu_validation_action(unsigned int value); diff --git a/src/monero_ux_nanos.c b/src/monero_ux_nanos.c index 2fbb2f7..713b391 100644 --- a/src/monero_ux_nanos.c +++ b/src/monero_ux_nanos.c @@ -237,7 +237,8 @@ void ui_menu_opentx_display(unsigned int value) { } #endif -/* ----------------------------- FEE VALIDATION ----------------------------- */ +/* ----------------- FEE/CHANGE/TIMELOCK VALIDATION ----------------- */ + #define ACCEPT 0xACCE #define REJECT ~ACCEPT @@ -255,11 +256,18 @@ const ux_menu_entry_t ui_menu_change_validation[] = { {NULL, ui_menu_amount_validation_action, ACCEPT, NULL, "Accept", "Change", 0, 0}, UX_MENU_END }; - +const ux_menu_entry_t ui_menu_timelock_validation[] = { + {NULL, NULL, 1, NULL, " Timelock", "?...?", 0, 0}, + {NULL, ui_menu_amount_validation_action, REJECT, NULL, "Reject", "Timelock", 0, 0}, + {NULL, ui_menu_amount_validation_action, ACCEPT, NULL, "Accept", "Timelock", 0, 0}, + UX_MENU_END +}; const bagl_element_t* ui_menu_amount_validation_preprocessor(const ux_menu_entry_t* entry, bagl_element_t* element) { /* --- Amount --- */ - if ((entry == &ui_menu_fee_validation[0]) || (entry == &ui_menu_change_validation[0])) { + if ((entry == &ui_menu_fee_validation[0]) || + (entry == &ui_menu_change_validation[0]) || + (entry == &ui_menu_timelock_validation[0])) { if(element->component.userid==0x22) { element->text = G_monero_vstate.ux_amount; } @@ -267,7 +275,6 @@ const bagl_element_t* ui_menu_amount_validation_preprocessor(const ux_menu_entry return element; } - void ui_menu_amount_validation_action(unsigned int value) { unsigned short sw; if (value == ACCEPT) { @@ -287,6 +294,9 @@ void ui_menu_fee_validation_display(unsigned int value) { void ui_menu_change_validation_display(unsigned int value) { UX_MENU_DISPLAY(0, ui_menu_change_validation, ui_menu_amount_validation_preprocessor); } +void ui_menu_timelock_validation_display(unsigned int value) { + UX_MENU_DISPLAY(0, ui_menu_timelock_validation, ui_menu_amount_validation_preprocessor); +} /* ----------------------------- USER DEST/AMOUNT VALIDATION ----------------------------- */ void ui_menu_validation_action(unsigned int value); From 987738926417f7ce2d049580cdb571b12d48b9c9 Mon Sep 17 00:00:00 2001 From: cslashm Date: Tue, 28 Apr 2020 18:10:30 +0200 Subject: [PATCH 08/76] add 1.5.x, 1.6.0 rev entries --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 8d0ee89..d7f484d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,14 @@ Note this is only for testing. For production usage, use the application provide # Revision +## v1.6.0 + + Add Timelock verification on device + +## v1.5.x + + Security Enhancement + ## V1.4.1 Compatibilty check with client version now discards the fourth sub-version number. From 6a3b5f434c47dac430245ea65928a8a7d01dfdbb Mon Sep 17 00:00:00 2001 From: cslashm Date: Tue, 28 Apr 2020 18:42:40 +0200 Subject: [PATCH 09/76] remove local (debug) ld script --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3a951a4..9c0864f 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ CFLAGS += -O3 -Os AS := $(GCCPATH)arm-none-eabi-gcc LD := $(GCCPATH)arm-none-eabi-gcc -SCRIPT_LD:=script.ld +#SCRIPT_LD:=script.ld #LDFLAGS += -O0 -gdwarf-2 -gstrict-dwarf LDFLAGS += -O3 -Os From 12f0c6085d24d674e2f598fc39a7a1c584222a58 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sun, 3 May 2020 19:48:18 +0800 Subject: [PATCH 10/76] Edited makefile --- Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 10a5778..7f83fe9 100644 --- a/Makefile +++ b/Makefile @@ -23,9 +23,9 @@ $(error Environment variable BOLOS_SDK is not set) endif include $(BOLOS_SDK)/Makefile.defines -#Monero /44'/128' -APP_LOAD_PARAMS= --path "2147483692/2147483776" --curve secp256k1 $(COMMON_LOAD_PARAMS) --appFlags 0x240 -APPNAME = "Monero" +#Swap /44'/10343' +APP_LOAD_PARAMS= --path "2147483692/2147493991" --curve secp256k1 $(COMMON_LOAD_PARAMS) --appFlags 0x240 +APPNAME = "Swap" ifeq ($(TARGET_NAME),TARGET_BLUE) ICONNAME = images/icon_monero_blue.gif @@ -63,8 +63,8 @@ endif #DEFINES += IOCRYPT ## Debug options -#DEFINES += DEBUG_HWDEVICE -#DEFINES += IODUMMYCRYPT +DEFINES += DEBUG_HWDEVICE +DEFINES += IODUMMYCRYPT #DEFINES += IONOCRYPT ################ From 4c612d11ce10831f1430bcb9bd213d999c53fd5d Mon Sep 17 00:00:00 2001 From: Ryan Date: Sun, 3 May 2020 19:55:49 +0800 Subject: [PATCH 11/76] Ported 1.5.1 to swap --- src/monero_init.c | 6 +++--- src/monero_types.h | 18 +++++++++--------- src/monero_ux_nano.c | 6 +++--- src/monero_ux_nanos.c | 8 ++++---- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/monero_init.c b/src/monero_init.c index 22944f7..566ee72 100644 --- a/src/monero_init.c +++ b/src/monero_init.c @@ -83,9 +83,9 @@ void monero_init_private_key() { //generate account keys // m / purpose' / coin_type' / account' / change / address_index - // m / 44' / 128' / 0' / 0 / 0 + // m / 44' / 10343' / 0' / 0 / 0 path[0] = 0x8000002C; - path[1] = 0x80000080; + path[1] = 0x80002867; path[2] = 0x80000000|N_monero_pstate->account_id; path[3] = 0x00000000; path[4] = 0x00000000; @@ -175,7 +175,7 @@ void monero_install(unsigned char netId) { /* ----------------------------------------------------------------------- */ #define MONERO_SUPPORTED_CLIENT_SIZE 1 const char * const monero_supported_client[MONERO_SUPPORTED_CLIENT_SIZE] = { - "0.15.0.", + "3.", }; int monero_apdu_reset() { diff --git a/src/monero_types.h b/src/monero_types.h index c1dc9f7..3adbc6c 100644 --- a/src/monero_types.h +++ b/src/monero_types.h @@ -41,17 +41,17 @@ #define MONERO_EXT_CHALLENGE_LENTH 254 /* --- ... --- */ -#define MAINNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 18 -#define MAINNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX 19 -#define MAINNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX 42 +#define MAINNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 10343 +#define MAINNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX 13671 +#define MAINNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX 11368 -#define STAGENET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 24 -#define STAGENET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX 25 -#define STAGENET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX 36 +#define STAGENET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 10343 +#define STAGENET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX 13671 +#define STAGENET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX 11368 -#define TESTNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 53 -#define TESTNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX 54 -#define TESTNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX 63 +#define TESTNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 23325 +#define TESTNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX 20894 +#define TESTNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX 25628 enum network_type { #ifndef MONERO_ALPHA diff --git a/src/monero_ux_nano.c b/src/monero_ux_nano.c index 54e7405..5b6835d 100644 --- a/src/monero_ux_nano.c +++ b/src/monero_ux_nano.c @@ -323,7 +323,7 @@ UX_STEP_NOCB( ux_menu_validation_1_step, bn, { - "Amout", + "Amount", G_monero_vstate.ux_amount }); @@ -696,7 +696,7 @@ UX_STEP_NOCB( ux_menu_about_1_step, bnnn, { - "Monero", + "Swap", "(c) Ledger SAS", "Spec " XSTR(SPEC_VERSION), "App " XSTR(MONERO_VERSION), @@ -706,7 +706,7 @@ UX_STEP_NOCB( ux_menu_about_1a_step, bn, { - "Monero", + "Swap", "(c) Ledger SAS", }); diff --git a/src/monero_ux_nanos.c b/src/monero_ux_nanos.c index 2fbb2f7..303cb3b 100644 --- a/src/monero_ux_nanos.c +++ b/src/monero_ux_nanos.c @@ -244,13 +244,13 @@ void ui_menu_opentx_display(unsigned int value) { void ui_menu_amount_validation_action(unsigned int value); const ux_menu_entry_t ui_menu_fee_validation[] = { - {NULL, NULL, 1, NULL, " Fee", "?xmr?", 0, 0}, + {NULL, NULL, 1, NULL, " Fee", "?xwp?", 0, 0}, {NULL, ui_menu_amount_validation_action, REJECT, NULL, "Reject", "Fee", 0, 0}, {NULL, ui_menu_amount_validation_action, ACCEPT, NULL, "Accept", "Fee", 0, 0}, UX_MENU_END }; const ux_menu_entry_t ui_menu_change_validation[] = { - {NULL, NULL, 1, NULL, " Change", "?xmr?", 0, 0}, + {NULL, NULL, 1, NULL, " Change", "?xwp?", 0, 0}, {NULL, ui_menu_amount_validation_action, REJECT, NULL, "Reject", "Change", 0, 0}, {NULL, ui_menu_amount_validation_action, ACCEPT, NULL, "Accept", "Change", 0, 0}, UX_MENU_END @@ -292,7 +292,7 @@ void ui_menu_change_validation_display(unsigned int value) { void ui_menu_validation_action(unsigned int value); const ux_menu_entry_t ui_menu_validation[] = { - {NULL, NULL, 1, NULL, " Amount", "?xmr?", 0, 0}, + {NULL, NULL, 1, NULL, " Amount", "?xwp?", 0, 0}, {NULL, NULL, 3, NULL, "Destination", "?dest.1?", 0, 0}, {NULL, NULL, 4, NULL, "?dest.2?", "?dest.2?", 0, 0}, {NULL, NULL, 5, NULL, "?dest.3?", "?dest.3?", 0, 0}, @@ -578,7 +578,7 @@ void ui_menu_settings_display(unsigned int value) { #define XSTR(x) STR(x) const ux_menu_entry_t ui_menu_about[] = { - {NULL, NULL, -1, NULL, "Monero", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "Swap", NULL, 0, 0}, {NULL, NULL, -1, NULL, "(c) Ledger SAS", NULL, 0, 0}, {NULL, NULL, -1, NULL, "Spec " XSTR(SPEC_VERSION), NULL, 0, 0}, {NULL, NULL, -1, NULL, "App " XSTR(MONERO_VERSION), NULL, 0, 0}, From 66455976d3f757894c290170bc532997325fdbf7 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sun, 3 May 2020 20:10:23 +0800 Subject: [PATCH 12/76] Fully ported swap to 1.5.1 --- src/monero_init.c | 6 +++--- src/monero_key.c | 2 +- src/monero_monero.c | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/monero_init.c b/src/monero_init.c index 566ee72..1d7e344 100644 --- a/src/monero_init.c +++ b/src/monero_init.c @@ -129,12 +129,12 @@ void monero_init_ux() { #ifdef HAVE_UX_FLOW #ifdef UI_NANO_X - snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), "XMR / %d", N_monero_pstate->account_id); + snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), "XWP / %d", N_monero_pstate->account_id); os_memmove(G_monero_vstate.ux_wallet_public_short_address, G_monero_vstate.ux_address,5); os_memmove(G_monero_vstate.ux_wallet_public_short_address+7, G_monero_vstate.ux_address+95-5,5); G_monero_vstate.ux_wallet_public_short_address[12] = 0; #else - snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), " XMR / %d", N_monero_pstate->account_id); + snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), " XWP / %d", N_monero_pstate->account_id); os_memmove(G_monero_vstate.ux_wallet_public_short_address, G_monero_vstate.ux_address,4); os_memmove(G_monero_vstate.ux_wallet_public_short_address+6, G_monero_vstate.ux_address+95-4,4); G_monero_vstate.ux_wallet_public_short_address[10] = 0; @@ -142,7 +142,7 @@ void monero_init_ux() { #else - snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), "XMR / %d", N_monero_pstate->account_id); + snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), "XWP / %d", N_monero_pstate->account_id); os_memmove(G_monero_vstate.ux_wallet_public_short_address, G_monero_vstate.ux_address,5); os_memmove(G_monero_vstate.ux_wallet_public_short_address+7, G_monero_vstate.ux_address+95-5,5); G_monero_vstate.ux_wallet_public_short_address[12] = 0; diff --git a/src/monero_key.c b/src/monero_key.c index 3097a21..40de887 100644 --- a/src/monero_key.c +++ b/src/monero_key.c @@ -330,7 +330,7 @@ int monero_apdu_get_key() { // m/44'/128'/0'/0/0 path[0] = 0x8000002C; - path[1] = 0x80000080; + path[1] = 0x80002867; path[2] = 0x80000000; path[3] = 0x00000000; path[4] = 0x00000000; diff --git a/src/monero_monero.c b/src/monero_monero.c index bc787c6..f4f98d2 100644 --- a/src/monero_monero.c +++ b/src/monero_monero.c @@ -19,14 +19,14 @@ #ifndef MONERO_ALPHA const unsigned char C_MAINNET_NETWORK_ID[] = { - 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x10 + 0xF2 ,0x39, 0x23, 0x70 , 0x61, 0x04 , 0x41, 0x60, 0x17, 0x32, 0x00, 0x81, 0x16, 0xA1, 0xA1, 0x10 }; #endif const unsigned char C_TESTNET_NETWORK_ID[] = { - 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x11 + 0xF3 ,0x39, 0x23, 0x70 , 0x61, 0x04 , 0x41, 0x60, 0x17, 0x32, 0x00, 0x81, 0x16, 0xA1, 0xA1, 0x10 }; const unsigned char C_STAGENET_NETWORK_ID[] = { - 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x12 + 0xF4 ,0x39, 0x23, 0x70 , 0x61, 0x04 , 0x41, 0x60, 0x17, 0x32, 0x00, 0x81, 0x16, 0xA1, 0xA1, 0x10 }; From abac560bc80c03202d8d37bfd0d2fa9f62815af9 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sun, 3 May 2020 20:20:03 +0800 Subject: [PATCH 13/76] Merge v1.5.1 feature branch with dev --- Makefile | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Makefile b/Makefile index 09b1cb0..7f83fe9 100644 --- a/Makefile +++ b/Makefile @@ -24,11 +24,7 @@ endif include $(BOLOS_SDK)/Makefile.defines #Swap /44'/10343' -<<<<<<< HEAD -APP_LOAD_PARAMS= --path "2147483692/2147493991" --curve secp256k1 $(COMMON_LOAD_PARAMS) --appFlags 0x40 -======= APP_LOAD_PARAMS= --path "2147483692/2147493991" --curve secp256k1 $(COMMON_LOAD_PARAMS) --appFlags 0x240 ->>>>>>> v1.5.1 APPNAME = "Swap" ifeq ($(TARGET_NAME),TARGET_BLUE) @@ -67,11 +63,7 @@ endif #DEFINES += IOCRYPT ## Debug options -<<<<<<< HEAD -#DEFINES += DEBUG_HWDEVICE -======= DEFINES += DEBUG_HWDEVICE ->>>>>>> v1.5.1 DEFINES += IODUMMYCRYPT #DEFINES += IONOCRYPT From 452c7d99ad9780a35726438eca426a851dffb4ab Mon Sep 17 00:00:00 2001 From: Nicolas Iooss Date: Wed, 6 May 2020 17:38:55 +0200 Subject: [PATCH 14/76] Do not call cx_hash with CX_NO_REINIT Currently `monero_hash_final()` calls `cx_hash(CX_LAST)` and `monero_hash()` calls `cx_hash(CX_LAST|CX_NO_REINIT)`. This is quite confusing. As option `CX_NO_REINIT` is not used by `cx_hash` anyway, drop it. --- src/monero_crypto.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monero_crypto.c b/src/monero_crypto.c index b96314e..80fd9c0 100644 --- a/src/monero_crypto.c +++ b/src/monero_crypto.c @@ -167,7 +167,7 @@ int monero_hash(unsigned int algo, cx_hash_t * hasher, unsigned char* buf, unsig } else { cx_keccak_init((cx_sha3_t *)hasher, 256); } - return cx_hash(hasher, CX_LAST|CX_NO_REINIT, buf, len, out, 32); + return cx_hash(hasher, CX_LAST, buf, len, out, 32); } /* ----------------------------------------------------------------------- */ From 28d87ea67600608e1a092d5e3351161671098bf7 Mon Sep 17 00:00:00 2001 From: Nicolas Iooss Date: Wed, 6 May 2020 18:36:52 +0200 Subject: [PATCH 15/76] Fix many errors in documentation The documentation of the application interface contained many misspellings, formatting issues, incorrect APDU instructions, etc. --- doc/developer/blue-app-commands.rst | 72 ++++++++++++++--------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/doc/developer/blue-app-commands.rst b/doc/developer/blue-app-commands.rst index 0cd798a..fde67f2 100644 --- a/doc/developer/blue-app-commands.rst +++ b/doc/developer/blue-app-commands.rst @@ -487,7 +487,7 @@ Get Private View Keys **Description** -Retrieves the private view key in order to accelarate the blockchain scan. +Retrieves the private view key in order to accelerate the blockchain scan. The device should ask the user to accept or reject this export. If rejected the client will use the device for scanning the blockchain. @@ -526,7 +526,7 @@ Display Address **Description** -Display requested main address ,sub address or integrated adrdess. +Display requested main address, sub address or integrated address. | compute |x| = |dec|[|spk|](|ex|) @@ -544,7 +544,7 @@ if payment ID is provided: | 03 | 21 | xx | 00 | 11 | +-----+-----+-----+-----+------+ -if P1 is '00' display non-integradted address. +if P1 is '00' display non-integrated address. if P1 is '01' display integrated address. @@ -614,10 +614,10 @@ Verify that the provided private key and public key match. if P1 is '00' the provided public key will be used. -if P1 is '01' the public view is key will be used and the provided public key will +if P1 is '01' the public view is key will be used and the provided private key will be 'ignored' -if P is '02' the public spend is key will be used and the provided public key will +if P1 is '02' the public spend is key will be used and the provided private key will be 'ignored' Any other value will be rejected. @@ -690,7 +690,7 @@ crypto::generate_key_derivation. **Description** -Compute the secret key derivation and returned it encrypted. +Compute the secret key derivation and return it encrypted. | compute |x| = |dec|[|spk|](|ex|) | compute |Drv| = |keyDrv|(|x|,|P|) @@ -972,7 +972,7 @@ return |D| +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 03 | 4a | 00 | 00 | 09 | +| 03 | 4A | 00 | 00 | 09 | +-----+-----+-----+-----+------+ **Command data** @@ -1015,7 +1015,7 @@ return |ed| +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 03 | 4c | 00 | 00 | 39 or 59 | +| 03 | 4C | 00 | 00 | 39 or 59 | +-----+-----+-----+-----+----------+ **Command data** @@ -1116,7 +1116,7 @@ return |Img|. +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 03 | 3a | 00 | 00 | 41 or 61 | +| 03 | 3A | 00 | 00 | 41 or 61 | +-----+-----+-----+-----+----------+ **Command data** @@ -1256,7 +1256,7 @@ return |ex|. +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 03 | 3c | 00 | 00 | 41 or 61 | +| 03 | 3C | 00 | 00 | 41 or 61 | +-----+-----+-----+-----+----------+ **Command data** @@ -1406,7 +1406,7 @@ return |PayID| +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 03 | 44 | 00 | 00 | 61 or 81 | +| 03 | 76 | 00 | 00 | 61 or 81 | +-----+-----+-----+-----+----------+ **Command data** @@ -1456,7 +1456,7 @@ If blind V1: | compute |ev| = |v|-|s| If blind V2: - | compute |k| = |Hs|("commitment_mask" \| |Akout|) % order + | compute |k| = |Hs|("commitment_mask" \| |Akout|) % |order| | compute |s| = |Hs|("amount" \| |Akout|) | compute |v|[0:7] = |ev|[0:7]^|s|[0:7] @@ -1467,10 +1467,9 @@ return |ek|,|ev| +-----+-----+-----+-----+----------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+==========+ -| 03 | 44 | 00 | 00 | 61 or 81 | +| 03 | 7A | 00 | 00 | 61 or 81 | +-----+-----+-----+-----+----------+ - *specific options* +---------------+----------------------------------------------------------+ @@ -1671,7 +1670,7 @@ return |r.R| +--------+-----------------------------------------------------------------+ | Length | Value | +========+=================================================================+ -| 20 | public transcation key |R| | +| 20 | public transaction key |R| | +--------+-----------------------------------------------------------------+ | 20 | encrypted private transaction key |er| | +--------+-----------------------------------------------------------------+ @@ -1697,7 +1696,7 @@ the transaction and no user confirmation is requested. +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 03 | 72 | 00 | 00 | 02 | +| 03 | 72 | 01 | 00 | 02 | +-----+-----+-----+-----+------+ @@ -1708,7 +1707,7 @@ the transaction and no user confirmation is requested. +========+=================================================================+ | 01 | options | +--------+-----------------------------------------------------------------+ -| 01 | '1' aka 'fake' or '2' aka real' | +| 01 | '1' aka 'real' or '2' aka 'fake' | +--------+-----------------------------------------------------------------+ @@ -1729,15 +1728,14 @@ Hash prefix init **Description** -ÃŽnit prefix hash and ask user to validate time lock - +Init prefix hash and ask user to validate time lock **Command** +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 03 | 71 | 01 | cnt | 05 | +| 03 | 7D | 01 | cnt | 05 | +-----+-----+-----+-----+------+ **Command data** @@ -1757,6 +1755,7 @@ Hash prefix init +--------+-----------------------------------------------------------------+ | Length | Value | +========+=================================================================+ +| | | +--------+-----------------------------------------------------------------+ Hash prefix update @@ -1771,7 +1770,7 @@ Update prefix hash with raw data. Options fields tells if there is more data to +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 03 | 71 | 02 | cnt | 05 | +| 03 | 7D | 02 | cnt | 05 | +-----+-----+-----+-----+------+ **Command data** @@ -1789,6 +1788,7 @@ Update prefix hash with raw data. Options fields tells if there is more data to +--------+-----------------------------------------------------------------+ | Length | Value | +========+=================================================================+ +| | | +--------+-----------------------------------------------------------------+ @@ -1916,7 +1916,7 @@ Generate TX output keys .. |sub| replace:: :math:`\mathit{is\_subaddress}` .. |chgaddr| replace:: :math:`\mathit{is\_change\_address}` -Compute addtional key |P| if needed, amount key blinding and ephemeral destination key. +Compute additional key |P| if needed, amount key blinding and ephemeral destination key. | if |nak| : | if |sub| : @@ -2052,6 +2052,8 @@ if ``cnt>1`` : +--------+-----------------------------------------------------------------+ | Length | Value | +========+=================================================================+ +| 01 | options | ++--------+-----------------------------------------------------------------+ | 20 | pseudoOut | +--------+-----------------------------------------------------------------+ @@ -2142,7 +2144,7 @@ So for each command received, do: | ``-------00`` | Blind V1 | +---------------+----------------------------------------------------------+ -Note: Whatever the mask scheme is, |v| is always transmited as 32 bytes. +Note: Whatever the mask scheme is, |v| is always transmitted as 32 bytes. Finalize MLSAG-prehash @@ -2159,7 +2161,6 @@ Finally the application receives the last part of data: | if last command: | finalize |ctH|' | check |ctH| == |ctH|' - | update |mlsagH|: | |s| = finalize |mlsagH| | compute |mlsagH| = |Hs| (:math:`message` \| |s| \| :math:`proof`) | @@ -2167,7 +2168,6 @@ Finally the application receives the last part of data: | update |ctH|': |Hupd|(|Ct|) | update |mlsagH|: |Hupd|(|Ct|) - Keep |mlsagH| **Command** @@ -2187,7 +2187,6 @@ not last: | Length | Value | +========+=================================================================+ | 01 | options | - +--------+-----------------------------------------------------------------+ | 20 | one serialized commitment : | | | | @@ -2251,7 +2250,7 @@ return |eai| , |aGi| [|aHi|, |IIi|] +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 03 | 84 | 01 | cnt | 61 | +| 03 | 7E | 01 | cnt | 61 | +-----+-----+-----+-----+------+ *specific options* @@ -2336,14 +2335,14 @@ Compute the last matrix ring parameter: | update |mlsagH|: |Hs|(inputs) | | if last command: - | c = finalize |mlsagH| % order + | c = finalize |mlsagH| % |order| **Command** +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 03 | 84 | 02 | cnt | 21 | +| 03 | 7E | 02 | cnt | 21 | +-----+-----+-----+-----+------+ **Command data** @@ -2394,10 +2393,9 @@ return |ss| +-----+-----+-----+-----+------+ | CLA | INS | P1 | P2 | LC | +=====+=====+=====+=====+======+ -| 03 | 84 | 03 | cnt | 81 | +| 03 | 7E | 03 | cnt | 81 | +-----+-----+-----+-----+------+ - **Command data** +--------+-----------------------------------------------------------------+ @@ -2447,7 +2445,7 @@ References Helper functions ---------------- -**|keyDrv|** +**|keyDrv|** (|keyDrv|) | *input* : :math:`r , P` | *output*: :math:`\mathfrak{D}` @@ -2458,7 +2456,7 @@ Helper functions | -**|Hs|** +**|Hs|** (|Hs|) | *input*: :math:`raw` | *output*: :math:`s` @@ -2467,23 +2465,23 @@ Helper functions | |s| = |H|(:math:`raw`) | -**|Hps|** +**|Hps|** (|Hps|) | *input*: :math:`D, idx` | *output*: :math:`s` | | :math:`data` = :math:`point2bytes(D) | varint(idx)` - | |s| = |H|(:math:`data`) % order + | |s| = |H|(:math:`data`) % |order| | -**|Hp|** +**|Hp|** (|Hp|) | *input*: :math:`P` | *output*: :math:`Q` | | :math:`data` = :math:`point2bytes(P)` - | |s| = |H|(:math:`data`) % order + | |s| = |H|(:math:`data`) % |order| | :math:`Q` = :math:`ge\_from\_fe(s)` From 90a35b97e3413b68bb131e568b475ee80917a77f Mon Sep 17 00:00:00 2001 From: Nicolas Iooss Date: Thu, 7 May 2020 18:17:37 +0200 Subject: [PATCH 16/76] Document why monero_apdu_sc_add filters its parameters The code is quite hard to read, and seems like a blacklist at first glance (it forbids to add two scalars). In fact the code filters the parameters to *only* allow adding two secret scalars! Add a comment to make this clearer. --- src/monero_key.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/monero_key.c b/src/monero_key.c index 0ced000..2c5bf5a 100644 --- a/src/monero_key.c +++ b/src/monero_key.c @@ -422,6 +422,11 @@ int monero_apdu_sc_add(/*unsigned char *r, unsigned char *s1, unsigned char *s2* monero_io_fetch_decrypt(s2,32, TYPE_SCALAR); monero_io_discard(0); if (G_monero_vstate.tx_in_progress) { + // During a transaction, only "last_derive_secret_key+last_get_subaddress_secret_key" + // is allowed, in order to match the call at + // https://github.com/monero-project/monero/blob/v0.15.0.5/src/cryptonote_basic/cryptonote_format_utils.cpp#L331 + // + // hwdev.sc_secret_add(scalar_step2, scalar_step1,subaddr_sk); if ((os_memcmp(s1, G_monero_vstate.last_derive_secret_key, 32)!=0) || (os_memcmp(s2, G_monero_vstate.last_get_subaddress_secret_key, 32)!=0)) { monero_lock_and_throw(SW_WRONG_DATA); From 41afb4a93866a8cb0a1b58cbd127000b869667e7 Mon Sep 17 00:00:00 2001 From: Philippe Dumonet Date: Sun, 10 May 2020 14:18:50 +0200 Subject: [PATCH 17/76] Fix issue #49 The encode_block function would not encode \x00 bytes at the beginning of the block. --- src/monero_monero.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/monero_monero.c b/src/monero_monero.c index 00c8ff1..2f8b681 100644 --- a/src/monero_monero.c +++ b/src/monero_monero.c @@ -86,12 +86,11 @@ static uint64_t uint_8be_to_64(const unsigned char* data, size_t size) { static void encode_block(const unsigned char* block, unsigned int size, char* res) { uint64_t num = uint_8be_to_64(block, size); - int i = encoded_block_sizes[size] - 1; - while (0 < num) { + int i = encoded_block_sizes[size]; + while (i--) { uint64_t remainder = num % alphabet_size; num /= alphabet_size; res[i] = alphabet[remainder]; - --i; } } From 5d0e509ea71a9c21ea2ddfaa173f914fd7e16290 Mon Sep 17 00:00:00 2001 From: Nicolas Iooss Date: Wed, 13 May 2020 14:38:36 +0200 Subject: [PATCH 18/76] Fix transition check for INS_PREFIX_HASH Protocol v3 introduced INS_PREFIX_HASH in order to verify the timelock on devices. This works by sending the following sequence of APDU: - INS=INS_PREFIX_HASH, P1=1, content={varint version, varint timelock} - INS=INS_PREFIX_HASH, P1=2, P2=1, content=1st chunk of data - INS=INS_PREFIX_HASH, P1=2, P2=2, content=2nd chunk of data - INS=INS_PREFIX_HASH, P1=2, P2=3, content=3rd chunk of data - ... (cf. function device_ledger::get_transaction_prefix_hash() from https://github.com/monero-project/monero/blob/77a008f71454ce4dd4bce033bc0319a49e2cec51/src/device/device_ledger.cpp#L1418 ) Currently, the first 2 chunks of data are accepted but not the 3rd one: the device then returns SW=0x6981 = SW_SUBCOMMAND_NOT_ALLOWED. This is because the dispatcher incorrectly compares P1-1 with the last P2, instead of P2-1. Fix this. --- src/monero_dispatch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monero_dispatch.c b/src/monero_dispatch.c index 3b0a82d..25fb745 100644 --- a/src/monero_dispatch.c +++ b/src/monero_dispatch.c @@ -330,7 +330,7 @@ int monero_dispatch() { } } else if (G_monero_vstate.tx_state_p1 == 2) { if ((G_monero_vstate.io_p1 != 2)|| - (G_monero_vstate.io_p1-1 != G_monero_vstate.tx_state_p2)) { + (G_monero_vstate.io_p2-1 != G_monero_vstate.tx_state_p2)) { THROW(SW_SUBCOMMAND_NOT_ALLOWED); } } else { From cf8aeeeb316d60b371291bf926d203e19eabcc83 Mon Sep 17 00:00:00 2001 From: Nicolas Iooss Date: Wed, 13 May 2020 15:38:36 +0200 Subject: [PATCH 19/76] Forbid transition from INS_BLIND to INS_VALIDATE(P1=2) directly When signing a Monero transaction, the state machine of the APDU is expected to behave like this: - ... - INS=INS_BLIND (possibly repeated) - INS=INS_VALIDATE, P1=1, P2=1 => call monero_apdu_mlsag_prehash_init() that updates some hashes and asks the user to validate the fee - INS=INS_VALIDATE, P1=1, P2=2 - INS=INS_VALIDATE, P1=1, P2=3 - ... - INS=INS_VALIDATE, P1=2, P2=1 - INS=INS_VALIDATE, P1=2, P2=2 - ... Because of the way the transition from `INS_BLIND` is verified, it is currently possible to skip `INS=INS_VALIDATE, P1=1` by sending `INS=INS_VALIDATE, P1=2, P2=1` direcly. This makes the transaction signing fail later, because some hash states did not get reset properly, so this would not have any impact from a security perspective. Nevertheless, removing this unexpected transition makes working on the state machine easier. --- src/monero_dispatch.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/monero_dispatch.c b/src/monero_dispatch.c index 3b0a82d..db7eb3e 100644 --- a/src/monero_dispatch.c +++ b/src/monero_dispatch.c @@ -418,6 +418,10 @@ int monero_dispatch() { G_monero_vstate.tx_state_ins = INS_VALIDATE; G_monero_vstate.tx_state_p1 = 1; G_monero_vstate.tx_state_p2 = 0; + if ((G_monero_vstate.io_p1 != 1) || + (G_monero_vstate.io_p2 != 1)) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } } //check new state is allowed if (G_monero_vstate.tx_state_p1 == G_monero_vstate.io_p1) { From 64c9c71635328691625865db43ced9510d5f8f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Wed, 27 May 2020 19:23:15 +0200 Subject: [PATCH 20/76] Remove compatibility with protocol v2 --- src/monero_dispatch.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/monero_dispatch.c b/src/monero_dispatch.c index a74853d..fc19df7 100644 --- a/src/monero_dispatch.c +++ b/src/monero_dispatch.c @@ -47,7 +47,6 @@ int check_potocol() { /* the first command enforce the protocol version until application quits */ switch(G_monero_vstate.io_protocol_version) { case 0x00: /* the first one: PCSC epoch */ - case 0x02: /* protocol V2 */ case 0x03: /* protocol V3 */ if (G_monero_vstate.protocol == 0xff) { G_monero_vstate.protocol = G_monero_vstate.io_protocol_version; @@ -305,9 +304,6 @@ int monero_dispatch() { /* --- PREFIX HASH --- */ case INS_PREFIX_HASH: //1. state machine check - if (G_monero_vstate.protocol < 3) { - THROW(SW_COMMAND_NOT_ALLOWED); - } if ((G_monero_vstate.tx_state_ins != INS_GEN_TXOUT_KEYS) && (G_monero_vstate.tx_state_ins != INS_PREFIX_HASH)) { THROW(SW_COMMAND_NOT_ALLOWED); @@ -351,12 +347,6 @@ int monero_dispatch() { /*--- COMMITMENT MASK --- */ case INS_GEN_COMMITMENT_MASK: //1. state machine check - if (G_monero_vstate.protocol == 2) { - if ((G_monero_vstate.tx_state_ins != INS_GEN_TXOUT_KEYS) && - (G_monero_vstate.tx_state_ins != INS_GEN_COMMITMENT_MASK)) { - THROW(SW_COMMAND_NOT_ALLOWED); - } - } if (G_monero_vstate.protocol == 3) { if ((G_monero_vstate.tx_state_ins != INS_PREFIX_HASH) && (G_monero_vstate.tx_state_ins != INS_GEN_COMMITMENT_MASK)) { @@ -377,12 +367,6 @@ int monero_dispatch() { case INS_BLIND: //1. state machine check if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_FAKE) { - if (G_monero_vstate.protocol == 2) { - if ((G_monero_vstate.tx_state_ins != INS_GEN_TXOUT_KEYS) && - (G_monero_vstate.tx_state_ins != INS_BLIND)) { - THROW(SW_COMMAND_NOT_ALLOWED); - } - } if (G_monero_vstate.protocol == 3) { if ((G_monero_vstate.tx_state_ins != INS_PREFIX_HASH) && (G_monero_vstate.tx_state_ins != INS_BLIND)) { From 5d1887116b2ab3130af98b049af095f4cf504fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 28 May 2020 10:52:01 +0200 Subject: [PATCH 21/76] Update version of monero supported client --- src/monero_init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monero_init.c b/src/monero_init.c index 3d1d538..39c8a01 100644 --- a/src/monero_init.c +++ b/src/monero_init.c @@ -175,7 +175,7 @@ void monero_install(unsigned char netId) { /* ----------------------------------------------------------------------- */ #define MONERO_SUPPORTED_CLIENT_SIZE 1 const char * const monero_supported_client[MONERO_SUPPORTED_CLIENT_SIZE] = { - "0.15.0.", + "0.16.0.", }; int monero_apdu_reset() { From 7019c5a794f67edc643f9b931c66e66109ffcf10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 2 Jul 2020 10:42:40 +0200 Subject: [PATCH 22/76] Add code style with clang-format --- .clang-format | 12 + src/monero_api.h | 196 +++---- src/monero_blind.c | 65 +-- src/monero_crypto.c | 541 ++++++++++--------- src/monero_dispatch.c | 864 +++++++++++++++--------------- src/monero_init.c | 310 +++++------ src/monero_io.c | 641 +++++++++++------------ src/monero_key.c | 1126 ++++++++++++++++++++------------------- src/monero_main.c | 342 ++++++------ src/monero_mlsag.c | 48 +- src/monero_monero.c | 95 ++-- src/monero_open_tx.c | 49 +- src/monero_prefix.c | 22 +- src/monero_prehash.c | 103 ++-- src/monero_proof.c | 35 +- src/monero_ram.c | 4 +- src/monero_stealth.c | 33 +- src/monero_types.h | 467 ++++++++--------- src/monero_ux_msg.c | 7 +- src/monero_ux_msg.h | 13 +- src/monero_ux_nano.c | 1164 ++++++++++++++++------------------------- src/monero_ux_nanos.c | 1056 ++++++++++++++++++------------------- src/monero_vars.h | 7 +- 23 files changed, 3421 insertions(+), 3779 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a9d6f7a --- /dev/null +++ b/.clang-format @@ -0,0 +1,12 @@ +--- +BasedOnStyle: Google +IndentWidth: 4 +--- +Language: Cpp +ColumnLimit: 100 +PointerAlignment: Right +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: true +AllowAllParametersOfDeclarationOnNextLine: false +SortIncludes: false +--- diff --git a/src/monero_api.h b/src/monero_api.h index a2357fd..63e602c 100644 --- a/src/monero_api.h +++ b/src/monero_api.h @@ -14,7 +14,7 @@ */ #ifndef MONERO_API_H -#define MONERO_API_H +#define MONERO_API_H int monero_apdu_reset(void); int monero_apdu_lock(void); @@ -31,7 +31,7 @@ int monero_dispatch(void); int monero_apdu_put_key(void); int monero_apdu_get_key(void); int monero_apdu_display_address(void); -int monero_apdu_manage_seedwords() ; +int monero_apdu_manage_seedwords(); int monero_apdu_verify_key(void); int monero_apdu_get_chacha8_prekey(void); int monero_apdu_sc_add(void); @@ -54,8 +54,8 @@ int monero_apdu_get_tx_proof(void); int monero_apdu_open_tx(void); int monero_apdu_open_tx_cont(void); void monero_reset_tx(int reset_tx_cnt); -int monero_apdu_open_subtx(void) ; -int monero_apdu_set_signature_mode(void) ; +int monero_apdu_open_subtx(void); +int monero_apdu_set_signature_mode(void); int monero_apdu_stealth(void); int monero_apdu_blind(void); int monero_apdu_unblind(void); @@ -79,39 +79,41 @@ void ui_init(void); void ui_menu_lock_display(void); void ui_menu_main_display(unsigned int value); void ui_menu_info_display(unsigned int value); -void ui_menu_info_display2(unsigned int value, char* line1, char* line2); +void ui_menu_info_display2(unsigned int value, char *line1, char *line2); void ui_export_viewkey_display(unsigned int value); -void ui_menu_any_pubaddr_display(unsigned int value, unsigned char* pub_view, unsigned char *pub_spend, unsigned char is_subbadress, unsigned char *paymanetID); +void ui_menu_any_pubaddr_display(unsigned int value, unsigned char *pub_view, + unsigned char *pub_spend, unsigned char is_subbadress, + unsigned char *paymanetID); void ui_menu_pubaddr_display(unsigned int value); - /* ----------------------------------------------------------------------- */ /* --- MISC ---- */ /* ----------------------------------------------------------------------- */ -#define OFFSETOF(type, field) ((unsigned int)&(((type*)NULL)->field)) +#define OFFSETOF(type, field) ((unsigned int)&(((type *)NULL)->field)) -int monero_base58_public_key( char* str_b58, unsigned char *view, unsigned char *spend, unsigned char is_subbadress, unsigned char *paymanetID); +int monero_base58_public_key(char *str_b58, unsigned char *view, unsigned char *spend, + unsigned char is_subbadress, unsigned char *paymanetID); /** unsigned varint amount to uint64 */ uint64_t monero_vamount2uint64(unsigned char *binary); /** binary little endian unsigned int amount to uint64 */ uint64_t monero_bamount2uint64(unsigned char *binary); /** unsigned varint amount to str */ -int monero_vamount2str(unsigned char *binary, char *str, unsigned int str_len); +int monero_vamount2str(unsigned char *binary, char *str, unsigned int str_len); /** binary little endian unsigned int amount to str */ -int monero_bamount2str(unsigned char *binary, char *str, unsigned int str_len); +int monero_bamount2str(unsigned char *binary, char *str, unsigned int str_len); /** uint64 amount to str */ -int monero_amount2str(uint64_t xmr, char *str, unsigned int str_len); +int monero_amount2str(uint64_t xmr, char *str, unsigned int str_len); /** uint64 amount to str */ -void monero_uint642str(uint64_t val, char *str, unsigned int str_len); - - -int monero_abort_tx() ; -int monero_unblind(unsigned char *v, unsigned char *k, unsigned char *AKout, unsigned int short_amount); -void ui_menu_validation_display(unsigned int value) ; -void ui_menu_fee_validation_display(unsigned int value) ; -void ui_menu_change_validation_display(unsigned int value) ; +void monero_uint642str(uint64_t val, char *str, unsigned int str_len); + +int monero_abort_tx(); +int monero_unblind(unsigned char *v, unsigned char *k, unsigned char *AKout, + unsigned int short_amount); +void ui_menu_validation_display(unsigned int value); +void ui_menu_fee_validation_display(unsigned int value); +void ui_menu_change_validation_display(unsigned int value); void ui_menu_timelock_validation_display(unsigned int value); void ui_menu_opentx_display(unsigned int value); @@ -135,8 +137,10 @@ void monero_generate_keypair(unsigned char *ec_pub, unsigned char *ec_priv); * P [in] point in 02 y or 04 x y format * k [in] 32 bytes scalar */ -void monero_generate_key_derivation(unsigned char *drv_data, unsigned char *P, unsigned char *scalar); -void monero_derivation_to_scalar(unsigned char *scalar, unsigned char *drv_data, unsigned int out_idx); +void monero_generate_key_derivation(unsigned char *drv_data, unsigned char *P, + unsigned char *scalar); +void monero_derivation_to_scalar(unsigned char *scalar, unsigned char *drv_data, + unsigned int out_idx); /* * compute x = Hps(drv_data,out_idx) + ec_pv * @@ -144,7 +148,8 @@ void monero_derivation_to_scalar(unsigned char *scalar, unsigned char *drv_data, * drv_data [in] 32 bytes derivation data (point) * ec_pv [in] 32 bytes private key */ -void monero_derive_secret_key(unsigned char *x, unsigned char *drv_data, unsigned int out_idx, unsigned char *ec_priv); +void monero_derive_secret_key(unsigned char *x, unsigned char *drv_data, unsigned int out_idx, + unsigned char *ec_priv); /* * compute x = Hps(drv_data,out_idx)*G + ec_pub * @@ -152,12 +157,14 @@ void monero_derive_secret_key(unsigned char *x, unsigned char *drv_data, unsigne * drv_data [in] 32 bytes derivation data (point) * ec_pub [in] 32 bytes public key */ -void monero_derive_public_key(unsigned char *x, unsigned char* drv_data, unsigned int out_idx, unsigned char *ec_pub); +void monero_derive_public_key(unsigned char *x, unsigned char *drv_data, unsigned int out_idx, + unsigned char *ec_pub); void monero_secret_key_to_public_key(unsigned char *ec_pub, unsigned char *ec_priv); -void monero_generate_key_image(unsigned char *img, unsigned char *P, unsigned char* x); +void monero_generate_key_image(unsigned char *img, unsigned char *P, unsigned char *x); -void monero_derive_subaddress_public_key(unsigned char *x, unsigned char *pub, unsigned char* drv_data, unsigned int index); -void monero_get_subaddress_spend_public_key(unsigned char *x,unsigned char *index); +void monero_derive_subaddress_public_key(unsigned char *x, unsigned char *pub, + unsigned char *drv_data, unsigned int index); +void monero_get_subaddress_spend_public_key(unsigned char *x, unsigned char *index); void monero_get_subaddress(unsigned char *C, unsigned char *D, unsigned char *index); void monero_get_subaddress_secret_key(unsigned char *sub_s, unsigned char *s, unsigned char *index); @@ -167,57 +174,54 @@ void monero_clear_words(); /* ----------------------------------------------------------------------- */ extern const unsigned char C_ED25519_ORDER[]; - void monero_aes_derive(cx_aes_key_t *sk, unsigned char *seed32, unsigned char *a, unsigned char *b); void monero_aes_generate(cx_aes_key_t *sk); /* Compute Monero-Hash of data*/ -void monero_hash_init_keccak(cx_hash_t * hasher); -void monero_hash_init_sha256(cx_hash_t * hasher); -void monero_hash_update(cx_hash_t * hasher, unsigned char* buf, unsigned int len) ; -int monero_hash_final(cx_hash_t * hasher, unsigned char* out); -int monero_hash(unsigned int algo, cx_hash_t * hasher, unsigned char* buf, unsigned int len, unsigned char* out); - -#define monero_keccak_init_F() \ - monero_hash_init_keccak((cx_hash_t *)&G_monero_vstate.keccakF) -#define monero_keccak_update_F(buf,len) \ - monero_hash_update((cx_hash_t *)&G_monero_vstate.keccakF,(buf), (len)) -#define monero_keccak_final_F(out) \ - monero_hash_final((cx_hash_t *)&G_monero_vstate.keccakF, (out)) -#define monero_keccak_F(buf,len,out) \ - monero_hash(CX_KECCAK, (cx_hash_t *)&G_monero_vstate.keccakF, (buf),(len), (out)) - -#define monero_keccak_init_H() \ - monero_hash_init_keccak((cx_hash_t *)&G_monero_vstate.keccakH) -#define monero_keccak_update_H(buf,len) \ - monero_hash_update((cx_hash_t *)&G_monero_vstate.keccakH,(buf), (len)) -#define monero_keccak_final_H(out) \ - monero_hash_final((cx_hash_t *)&G_monero_vstate.keccakH, (out)) -#define monero_keccak_H(buf,len,out) \ - monero_hash(CX_KECCAK, (cx_hash_t *)&G_monero_vstate.keccakH, (buf),(len), (out)) +void monero_hash_init_keccak(cx_hash_t *hasher); +void monero_hash_init_sha256(cx_hash_t *hasher); +void monero_hash_update(cx_hash_t *hasher, unsigned char *buf, unsigned int len); +int monero_hash_final(cx_hash_t *hasher, unsigned char *out); +int monero_hash(unsigned int algo, cx_hash_t *hasher, unsigned char *buf, unsigned int len, + unsigned char *out); + +#define monero_keccak_init_F() monero_hash_init_keccak((cx_hash_t *)&G_monero_vstate.keccakF) +#define monero_keccak_update_F(buf, len) \ + monero_hash_update((cx_hash_t *)&G_monero_vstate.keccakF, (buf), (len)) +#define monero_keccak_final_F(out) monero_hash_final((cx_hash_t *)&G_monero_vstate.keccakF, (out)) +#define monero_keccak_F(buf, len, out) \ + monero_hash(CX_KECCAK, (cx_hash_t *)&G_monero_vstate.keccakF, (buf), (len), (out)) + +#define monero_keccak_init_H() monero_hash_init_keccak((cx_hash_t *)&G_monero_vstate.keccakH) +#define monero_keccak_update_H(buf, len) \ + monero_hash_update((cx_hash_t *)&G_monero_vstate.keccakH, (buf), (len)) +#define monero_keccak_final_H(out) monero_hash_final((cx_hash_t *)&G_monero_vstate.keccakH, (out)) +#define monero_keccak_H(buf, len, out) \ + monero_hash(CX_KECCAK, (cx_hash_t *)&G_monero_vstate.keccakH, (buf), (len), (out)) #define monero_sha256_commitment_init() \ monero_hash_init_sha256((cx_hash_t *)&G_monero_vstate.sha256_commitment) -#define monero_sha256_commitment_update(buf,len) \ - monero_hash_update((cx_hash_t *)&G_monero_vstate.sha256_commitment,(buf), (len)) -#define monero_sha256_commitment_final(out) \ - monero_hash_final((cx_hash_t *)&G_monero_vstate.sha256_commitment, (out)?(out):G_monero_vstate.C) +#define monero_sha256_commitment_update(buf, len) \ + monero_hash_update((cx_hash_t *)&G_monero_vstate.sha256_commitment, (buf), (len)) +#define monero_sha256_commitment_final(out) \ + monero_hash_final((cx_hash_t *)&G_monero_vstate.sha256_commitment, \ + (out) ? (out) : G_monero_vstate.C) #define monero_sha256_outkeys_init() \ monero_hash_init_sha256((cx_hash_t *)&G_monero_vstate.sha256_out_keys) -#define monero_sha256_outkeys_update(buf,len) \ +#define monero_sha256_outkeys_update(buf, len) \ monero_hash_update((cx_hash_t *)&G_monero_vstate.sha256_out_keys, (buf), (len)) #define monero_sha256_outkeys_final(out) \ monero_hash_final((cx_hash_t *)&G_monero_vstate.sha256_out_keys, (out)) - /* - * check 1= 0x80) { - if (len == (max_len-1)) { + while (value >= 0x80) { + if (len == (max_len - 1)) { THROW(SW_WRONG_DATA_RANGE); } varint[len] = (value & 0x7F) | 0x80; - value = value>>7; + value = value >> 7; len++; } varint[len] = value; - return len+1; + return len + 1; } - /* ----------------------------------------------------------------------- */ /* --- assert: max_len>0 --- */ /* ----------------------------------------------------------------------- */ @@ -104,66 +102,62 @@ unsigned int monero_decode_varint(unsigned char *varint, unsigned int max_len, u uint64_t v; int len; v = 0; - len =0; - while((varint[len])&0x80) { - if (len == (max_len-1)) { + len = 0; + while ((varint[len]) & 0x80) { + if (len == (max_len - 1)) { THROW(SW_WRONG_DATA_RANGE); } - v = v + (((varint[len])&0x7f) << (len*7)); + v = v + (((varint[len]) & 0x7f) << (len * 7)); len++; } - - v = v + (((varint[len])&0x7f) << (len*7)); + + v = v + (((varint[len]) & 0x7f) << (len * 7)); *value = v; - return len+1; + return len + 1; } - /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ void monero_reverse32(unsigned char *rscal, unsigned char *scal) { unsigned char x; unsigned int i; - for (i = 0; i<16; i++) { - x = scal[i]; - rscal[i] = scal [31-i]; - rscal[31-i] = x; + for (i = 0; i < 16; i++) { + x = scal[i]; + rscal[i] = scal[31 - i]; + rscal[31 - i] = x; } } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -void monero_hash_init_sha256(cx_hash_t * hasher) { - cx_sha256_init((cx_sha256_t *)hasher); -} +void monero_hash_init_sha256(cx_hash_t *hasher) { cx_sha256_init((cx_sha256_t *)hasher); } -void monero_hash_init_keccak(cx_hash_t * hasher) { - cx_keccak_init((cx_sha3_t *)hasher, 256); -} +void monero_hash_init_keccak(cx_hash_t *hasher) { cx_keccak_init((cx_sha3_t *)hasher, 256); } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -void monero_hash_update(cx_hash_t * hasher, unsigned char* buf, unsigned int len) { +void monero_hash_update(cx_hash_t *hasher, unsigned char *buf, unsigned int len) { cx_hash(hasher, 0, buf, len, NULL, 0); } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -int monero_hash_final(cx_hash_t * hasher, unsigned char* out) { +int monero_hash_final(cx_hash_t *hasher, unsigned char *out) { return cx_hash(hasher, CX_LAST, NULL, 0, out, 32); } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -int monero_hash(unsigned int algo, cx_hash_t * hasher, unsigned char* buf, unsigned int len, unsigned char* out) { +int monero_hash(unsigned int algo, cx_hash_t *hasher, unsigned char *buf, unsigned int len, + unsigned char *out) { hasher->algo = algo; if (algo == CX_SHA256) { - cx_sha256_init((cx_sha256_t *)hasher); + cx_sha256_init((cx_sha256_t *)hasher); } else { cx_keccak_init((cx_sha3_t *)hasher, 256); } @@ -229,45 +223,43 @@ const unsigned char C_fe_ma2[] = { * 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffc8db3de3c9 */ 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xdb, 0x3d, 0xe3, 0xc9 -}; + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xdb, 0x3d, 0xe3, 0xc9}; const unsigned char C_fe_ma[] = { /* -A * 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff892e7 */ 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x92, 0xe7 -}; + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x92, 0xe7}; const unsigned char C_fe_fffb1[] = { /* sqrt(-2 * A * (A + 2)) * 0x7e71fbefdad61b1720a9c53741fb19e3d19404a8b92a738d22a76975321c41ee */ 0x7e, 0x71, 0xfb, 0xef, 0xda, 0xd6, 0x1b, 0x17, 0x20, 0xa9, 0xc5, 0x37, 0x41, 0xfb, 0x19, 0xe3, - 0xd1, 0x94, 0x04, 0xa8, 0xb9, 0x2a, 0x73, 0x8d, 0x22, 0xa7, 0x69, 0x75, 0x32, 0x1c, 0x41, 0xee -}; + 0xd1, 0x94, 0x04, 0xa8, 0xb9, 0x2a, 0x73, 0x8d, 0x22, 0xa7, 0x69, 0x75, 0x32, 0x1c, 0x41, 0xee}; const unsigned char C_fe_fffb2[] = { /* sqrt(2 * A * (A + 2)) * 0x4d061e0a045a2cf691d451b7c0165fbe51de03460456f7dfd2de6483607c9ae0 */ 0x4d, 0x06, 0x1e, 0x0a, 0x04, 0x5a, 0x2c, 0xf6, 0x91, 0xd4, 0x51, 0xb7, 0xc0, 0x16, 0x5f, 0xbe, - 0x51, 0xde, 0x03, 0x46, 0x04, 0x56, 0xf7, 0xdf, 0xd2, 0xde, 0x64, 0x83, 0x60, 0x7c, 0x9a, 0xe0 -}; + 0x51, 0xde, 0x03, 0x46, 0x04, 0x56, 0xf7, 0xdf, 0xd2, 0xde, 0x64, 0x83, 0x60, 0x7c, 0x9a, 0xe0}; const unsigned char C_fe_fffb3[] = { /* sqrt(-sqrt(-1) * A * (A + 2)) * 674a110d14c208efb89546403f0da2ed4024ff4ea5964229581b7d8717302c66 */ - 0x67, 0x4a, 0x11, 0x0d, 0x14, 0xc2, 0x08, 0xef, 0xb8, 0x95, 0x46, 0x40, 0x3f, 0x0d, 0xa2, 0xed, - 0x40, 0x24, 0xff, 0x4e, 0xa5, 0x96, 0x42, 0x29, 0x58, 0x1b, 0x7d, 0x87, 0x17, 0x30, 0x2c, 0x66 + 0x67, 0x4a, 0x11, 0x0d, 0x14, 0xc2, 0x08, 0xef, 0xb8, 0x95, 0x46, + 0x40, 0x3f, 0x0d, 0xa2, 0xed, 0x40, 0x24, 0xff, 0x4e, 0xa5, 0x96, + 0x42, 0x29, 0x58, 0x1b, 0x7d, 0x87, 0x17, 0x30, 0x2c, 0x66 }; const unsigned char C_fe_fffb4[] = { /* sqrt(sqrt(-1) * A * (A + 2)) * 1a43f3031067dbf926c0f4887ef7432eee46fc08a13f4a49853d1903b6b39186 */ - 0x1a, 0x43, 0xf3, 0x03, 0x10, 0x67, 0xdb, 0xf9, 0x26, 0xc0, 0xf4, 0x88, 0x7e, 0xf7, 0x43, 0x2e, - 0xee, 0x46, 0xfc, 0x08, 0xa1, 0x3f, 0x4a, 0x49, 0x85, 0x3d, 0x19, 0x03, 0xb6, 0xb3, 0x91, 0x86 + 0x1a, 0x43, 0xf3, 0x03, 0x10, 0x67, 0xdb, 0xf9, 0x26, 0xc0, 0xf4, + 0x88, 0x7e, 0xf7, 0x43, 0x2e, 0xee, 0x46, 0xfc, 0x08, 0xa1, 0x3f, + 0x4a, 0x49, 0x85, 0x3d, 0x19, 0x03, 0xb6, 0xb3, 0x91, 0x86 }; const unsigned char C_fe_sqrtm1[] = { @@ -275,17 +267,14 @@ const unsigned char C_fe_sqrtm1[] = { * 0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0 */ 0x2b, 0x83, 0x24, 0x80, 0x4f, 0xc1, 0xdf, 0x0b, 0x2b, 0x4d, 0x00, 0x99, 0x3d, 0xfb, 0xd7, 0xa7, - 0x2f, 0x43, 0x18, 0x06, 0xad, 0x2f, 0xe4, 0x78, 0xc4, 0xee, 0x1b, 0x27, 0x4a, 0x0e, 0xa0, 0xb0 -}; + 0x2f, 0x43, 0x18, 0x06, 0xad, 0x2f, 0xe4, 0x78, 0xc4, 0xee, 0x1b, 0x27, 0x4a, 0x0e, 0xa0, 0xb0}; const unsigned char C_fe_qm5div8[] = { 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd -}; + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd}; -static void monero_ge_fromfe_frombytes(unsigned char *ge , unsigned char *bytes) { - - #define MOD (unsigned char *)C_ED25519_FIELD,32 - #define fe_isnegative(f) (f[31]&1) +static void monero_ge_fromfe_frombytes(unsigned char *ge, unsigned char *bytes) { +#define MOD (unsigned char *)C_ED25519_FIELD, 32 +#define fe_isnegative(f) (f[31] & 1) #if 0 unsigned char u[32], v[32], w[32], x[32], y[32], z[32]; unsigned char rX[32], rY[32], rZ[32]; @@ -298,20 +287,20 @@ static void monero_ge_fromfe_frombytes(unsigned char *ge , unsigned char *bytes) } uv; - #define uv7 uv._uv7 - #define v3 uv._v3 +#define uv7 uv._uv7 +#define v3 uv._v3 - #define Pxy uv._Pxy +#define Pxy uv._Pxy #else - #define u (G_monero_vstate.io_buffer+0*32) - #define v (G_monero_vstate.io_buffer+1*32) - #define w (G_monero_vstate.io_buffer+2*32) - #define x (G_monero_vstate.io_buffer+3*32) - #define y (G_monero_vstate.io_buffer+4*32) - #define z (G_monero_vstate.io_buffer+5*32) - #define rX (G_monero_vstate.io_buffer+6*32) - #define rY (G_monero_vstate.io_buffer+7*32) - #define rZ (G_monero_vstate.io_buffer+8*32) +#define u (G_monero_vstate.io_buffer + 0 * 32) +#define v (G_monero_vstate.io_buffer + 1 * 32) +#define w (G_monero_vstate.io_buffer + 2 * 32) +#define x (G_monero_vstate.io_buffer + 3 * 32) +#define y (G_monero_vstate.io_buffer + 4 * 32) +#define z (G_monero_vstate.io_buffer + 5 * 32) +#define rX (G_monero_vstate.io_buffer + 6 * 32) +#define rY (G_monero_vstate.io_buffer + 7 * 32) +#define rZ (G_monero_vstate.io_buffer + 8 * 32) //#define uv7 (G_monero_vstate.io_buffer+9*32) //#define v3 (G_monero_vstate.io_buffer+10*32) @@ -319,117 +308,117 @@ static void monero_ge_fromfe_frombytes(unsigned char *ge , unsigned char *bytes) unsigned char _Pxy[65]; struct { unsigned char _uv7[32]; - unsigned char _v3[32]; + unsigned char _v3[32]; }; - } uv; - #define uv7 uv._uv7 - #define v3 uv._v3 +#define uv7 uv._uv7 +#define v3 uv._v3 - #define Pxy uv._Pxy +#define Pxy uv._Pxy -#if MONERO_IO_BUFFER_LENGTH < (9*32) -#error MONERO_IO_BUFFER_LENGTH is too small +#if MONERO_IO_BUFFER_LENGTH < (9 * 32) +#error MONERO_IO_BUFFER_LENGTH is too small #endif #endif unsigned char sign; - //cx works in BE - monero_reverse32(u,bytes); + // cx works in BE + monero_reverse32(u, bytes); cx_math_modm(u, 32, (unsigned char *)C_ED25519_FIELD, 32); - //go on - cx_math_multm(v, u, u, MOD); /* 2 * u^2 */ - cx_math_addm (v, v, v, MOD); - - os_memset (w, 0, 32); w[31] = 1; /* w = 1 */ - cx_math_addm (w, v, w,MOD ); /* w = 2 * u^2 + 1 */ - cx_math_multm(x, w, w, MOD); /* w^2 */ - cx_math_multm(y, (unsigned char *)C_fe_ma2, v, MOD); /* -2 * A^2 * u^2 */ - cx_math_addm (x, x, y, MOD); /* x = w^2 - 2 * A^2 * u^2 */ - - //inline fe_divpowm1(r->X, w, x); // (w / x)^(m + 1) => fe_divpowm1(r,u,v) - #define _u w - #define _v x - cx_math_multm(v3, _v, _v, MOD); - cx_math_multm(v3, v3, _v, MOD); /* v3 = v^3 */ - cx_math_multm(uv7, v3, v3, MOD); + // go on + cx_math_multm(v, u, u, MOD); /* 2 * u^2 */ + cx_math_addm(v, v, v, MOD); + + os_memset(w, 0, 32); + w[31] = 1; /* w = 1 */ + cx_math_addm(w, v, w, MOD); /* w = 2 * u^2 + 1 */ + cx_math_multm(x, w, w, MOD); /* w^2 */ + cx_math_multm(y, (unsigned char *)C_fe_ma2, v, MOD); /* -2 * A^2 * u^2 */ + cx_math_addm(x, x, y, MOD); /* x = w^2 - 2 * A^2 * u^2 */ + +// inline fe_divpowm1(r->X, w, x); // (w / x)^(m + 1) => fe_divpowm1(r,u,v) +#define _u w +#define _v x + cx_math_multm(v3, _v, _v, MOD); + cx_math_multm(v3, v3, _v, MOD); /* v3 = v^3 */ + cx_math_multm(uv7, v3, v3, MOD); cx_math_multm(uv7, uv7, _v, MOD); - cx_math_multm(uv7, uv7, _u, MOD); /* uv7 = uv^7 */ - cx_math_powm (uv7, uv7, (unsigned char *)C_fe_qm5div8, 32, MOD); /* (uv^7)^((q-5)/8)*/ + cx_math_multm(uv7, uv7, _u, MOD); /* uv7 = uv^7 */ + cx_math_powm(uv7, uv7, (unsigned char *)C_fe_qm5div8, 32, MOD); /* (uv^7)^((q-5)/8)*/ cx_math_multm(uv7, uv7, v3, MOD); - cx_math_multm(rX, uv7, w, MOD); /* u^(m+1)v^(-(m+1)) */ - #undef _u - #undef _v + cx_math_multm(rX, uv7, w, MOD); /* u^(m+1)v^(-(m+1)) */ +#undef _u +#undef _v - cx_math_multm(y, rX,rX, MOD); + cx_math_multm(y, rX, rX, MOD); cx_math_multm(x, y, x, MOD); cx_math_subm(y, w, x, MOD); os_memmove(z, C_fe_ma, 32); - if (!cx_math_is_zero(y,32)) { - cx_math_addm(y, w, x, MOD); - if (!cx_math_is_zero(y,32)) { - goto negative; - } else { - cx_math_multm(rX, rX, (unsigned char *)C_fe_fffb1, MOD); - } - } else { - cx_math_multm(rX, rX, (unsigned char *)C_fe_fffb2, MOD); - } - cx_math_multm(rX, rX, u, MOD); // u * sqrt(2 * A * (A + 2) * w / x) - cx_math_multm(z, z, v, MOD); // -2 * A * u^2 - sign = 0; - - goto setsign; - - negative: - cx_math_multm(x, x, (unsigned char *)C_fe_sqrtm1, MOD); - cx_math_subm(y, w, x, MOD); - if (!cx_math_is_zero(y,32)) { - cx_math_addm(y, w, x, MOD); - cx_math_multm(rX, rX, (unsigned char *)C_fe_fffb3, MOD); - } else { - cx_math_multm(rX, rX, (unsigned char *)C_fe_fffb4, MOD); - } - // r->X = sqrt(A * (A + 2) * w / x) - // z = -A - sign = 1; - - setsign: - if (fe_isnegative(rX) != sign) { - //fe_neg(r->X, r->X); - cx_math_sub(rX, (unsigned char *)C_ED25519_FIELD, rX, 32); - } - cx_math_addm(rZ, z, w, MOD); - cx_math_subm(rY, z, w, MOD); - cx_math_multm(rX, rX, rZ, MOD); - - //back to monero y-affine - cx_math_invprimem(u, rZ, MOD); - Pxy[0] = 0x04; - cx_math_multm(&Pxy[1], rX, u, MOD); - cx_math_multm(&Pxy[1+32], rY, u, MOD); - cx_edward_compress_point(CX_CURVE_Ed25519, Pxy, sizeof(Pxy)); - os_memmove(ge, &Pxy[1], 32); - - #undef u - #undef v - #undef w - #undef x - #undef y - #undef z - #undef rX - #undef rY - #undef rZ - - #undef uv7 - #undef v3 - - #undef Pxy + if (!cx_math_is_zero(y, 32)) { + cx_math_addm(y, w, x, MOD); + if (!cx_math_is_zero(y, 32)) { + goto negative; + } else { + cx_math_multm(rX, rX, (unsigned char *)C_fe_fffb1, MOD); + } + } else { + cx_math_multm(rX, rX, (unsigned char *)C_fe_fffb2, MOD); + } + cx_math_multm(rX, rX, u, MOD); // u * sqrt(2 * A * (A + 2) * w / x) + cx_math_multm(z, z, v, MOD); // -2 * A * u^2 + sign = 0; + + goto setsign; + +negative: + cx_math_multm(x, x, (unsigned char *)C_fe_sqrtm1, MOD); + cx_math_subm(y, w, x, MOD); + if (!cx_math_is_zero(y, 32)) { + cx_math_addm(y, w, x, MOD); + cx_math_multm(rX, rX, (unsigned char *)C_fe_fffb3, MOD); + } else { + cx_math_multm(rX, rX, (unsigned char *)C_fe_fffb4, MOD); + } + // r->X = sqrt(A * (A + 2) * w / x) + // z = -A + sign = 1; + +setsign: + if (fe_isnegative(rX) != sign) { + // fe_neg(r->X, r->X); + cx_math_sub(rX, (unsigned char *)C_ED25519_FIELD, rX, 32); + } + cx_math_addm(rZ, z, w, MOD); + cx_math_subm(rY, z, w, MOD); + cx_math_multm(rX, rX, rZ, MOD); + + // back to monero y-affine + cx_math_invprimem(u, rZ, MOD); + Pxy[0] = 0x04; + cx_math_multm(&Pxy[1], rX, u, MOD); + cx_math_multm(&Pxy[1 + 32], rY, u, MOD); + cx_edward_compress_point(CX_CURVE_Ed25519, Pxy, sizeof(Pxy)); + os_memmove(ge, &Pxy[1], 32); + +#undef u +#undef v +#undef w +#undef x +#undef y +#undef z +#undef rX +#undef rY +#undef rZ + +#undef uv7 +#undef v3 + +#undef Pxy } /* ======================================================================= */ @@ -440,7 +429,7 @@ static void monero_ge_fromfe_frombytes(unsigned char *ge , unsigned char *bytes) /* --- --- */ /* ----------------------------------------------------------------------- */ void monero_hash_to_scalar(unsigned char *scalar, unsigned char *raw, unsigned int raw_len) { - monero_keccak_F(raw,raw_len,scalar); + monero_keccak_F(raw, raw_len, scalar); monero_reduce(scalar, scalar); } @@ -464,50 +453,52 @@ void monero_generate_keypair(unsigned char *ec_pub, unsigned char *ec_priv) { /* ----------------------------------------------------------------------- */ /* --- ok --- */ /* ----------------------------------------------------------------------- */ -void monero_generate_key_derivation(unsigned char *drv_data, unsigned char *P, unsigned char *scalar) { - monero_ecmul_8k(drv_data,P,scalar); +void monero_generate_key_derivation(unsigned char *drv_data, unsigned char *P, + unsigned char *scalar) { + monero_ecmul_8k(drv_data, P, scalar); } /* ----------------------------------------------------------------------- */ /* --- ok --- */ /* ----------------------------------------------------------------------- */ -void monero_derivation_to_scalar(unsigned char *scalar, unsigned char *drv_data, unsigned int out_idx) { - unsigned char varint[32+8]; +void monero_derivation_to_scalar(unsigned char *scalar, unsigned char *drv_data, + unsigned int out_idx) { + unsigned char varint[32 + 8]; unsigned int len_varint; os_memmove(varint, drv_data, 32); - len_varint = monero_encode_varint(varint+32, 8, out_idx); + len_varint = monero_encode_varint(varint + 32, 8, out_idx); len_varint += 32; - monero_keccak_F(varint,len_varint,varint); + monero_keccak_F(varint, len_varint, varint); monero_reduce(scalar, varint); } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -void monero_derive_secret_key(unsigned char *x, - unsigned char *drv_data, unsigned int out_idx, unsigned char *ec_priv) { +void monero_derive_secret_key(unsigned char *x, unsigned char *drv_data, unsigned int out_idx, + unsigned char *ec_priv) { unsigned char tmp[32]; - //derivation to scalar - monero_derivation_to_scalar(tmp,drv_data,out_idx); + // derivation to scalar + monero_derivation_to_scalar(tmp, drv_data, out_idx); - //generate + // generate monero_addm(x, tmp, ec_priv); } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -void monero_derive_public_key(unsigned char *x, - unsigned char* drv_data, unsigned int out_idx, unsigned char *ec_pub) { +void monero_derive_public_key(unsigned char *x, unsigned char *drv_data, unsigned int out_idx, + unsigned char *ec_pub) { unsigned char tmp[32]; - //derivation to scalar - monero_derivation_to_scalar(tmp,drv_data,out_idx); - //generate - monero_ecmul_G(tmp,tmp); - monero_ecadd(x,tmp,ec_pub); + // derivation to scalar + monero_derivation_to_scalar(tmp, drv_data, out_idx); + // generate + monero_ecmul_G(tmp, tmp); + monero_ecadd(x, tmp, ec_pub); } /* ----------------------------------------------------------------------- */ @@ -520,10 +511,10 @@ void monero_secret_key_to_public_key(unsigned char *ec_pub, unsigned char *ec_pr /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -void monero_generate_key_image(unsigned char *img, unsigned char *P, unsigned char* x) { +void monero_generate_key_image(unsigned char *img, unsigned char *P, unsigned char *x) { unsigned char I[32]; - monero_hash_to_ec(I,P); - monero_ecmul_k(img, I,x); + monero_hash_to_ec(I, P); + monero_ecmul_k(img, I, x); } /* ======================================================================= */ @@ -533,49 +524,50 @@ void monero_generate_key_image(unsigned char *img, unsigned char *P, unsigned ch /* ----------------------------------------------------------------------- */ /* --- ok --- */ /* ----------------------------------------------------------------------- */ -void monero_derive_subaddress_public_key(unsigned char *x, - unsigned char *pub, unsigned char* drv_data, unsigned int index) { - unsigned char scalarG[32]; +void monero_derive_subaddress_public_key(unsigned char *x, unsigned char *pub, + unsigned char *drv_data, unsigned int index) { + unsigned char scalarG[32]; - monero_derivation_to_scalar(scalarG , drv_data, index); - monero_ecmul_G(scalarG, scalarG); - monero_ecsub(x, pub, scalarG); + monero_derivation_to_scalar(scalarG, drv_data, index); + monero_ecmul_G(scalarG, scalarG); + monero_ecsub(x, pub, scalarG); } /* ----------------------------------------------------------------------- */ /* --- ok --- */ /* ----------------------------------------------------------------------- */ -void monero_get_subaddress_spend_public_key(unsigned char *x,unsigned char *index) { +void monero_get_subaddress_spend_public_key(unsigned char *x, unsigned char *index) { // m = Hs(a || index_major || index_minor) monero_get_subaddress_secret_key(x, G_monero_vstate.a, index); // M = m*G - monero_secret_key_to_public_key(x,x); + monero_secret_key_to_public_key(x, x); // D = B + M - monero_ecadd(x,x,G_monero_vstate.B); - } + monero_ecadd(x, x, G_monero_vstate.B); +} /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ void monero_get_subaddress(unsigned char *C, unsigned char *D, unsigned char *index) { - //retrieve D + // retrieve D monero_get_subaddress_spend_public_key(D, index); // C = a*D - monero_ecmul_k(C,D,G_monero_vstate.a); + monero_ecmul_k(C, D, G_monero_vstate.a); } /* ----------------------------------------------------------------------- */ /* --- ok --- */ /* ----------------------------------------------------------------------- */ -static const char C_sub_address_prefix[] = {'S','u','b','A','d','d','r', 0}; +static const char C_sub_address_prefix[] = {'S', 'u', 'b', 'A', 'd', 'd', 'r', 0}; -void monero_get_subaddress_secret_key(unsigned char *sub_s, unsigned char *s, unsigned char *index) { - unsigned char in[sizeof(C_sub_address_prefix)+32+8]; +void monero_get_subaddress_secret_key(unsigned char *sub_s, unsigned char *s, + unsigned char *index) { + unsigned char in[sizeof(C_sub_address_prefix) + 32 + 8]; - os_memmove(in, C_sub_address_prefix, sizeof(C_sub_address_prefix)), - os_memmove(in+sizeof(C_sub_address_prefix), s, 32); - os_memmove(in+sizeof(C_sub_address_prefix)+32, index, 8); - //hash_to_scalar with more that 32bytes: + os_memmove(in, C_sub_address_prefix, sizeof(C_sub_address_prefix)), + os_memmove(in + sizeof(C_sub_address_prefix), s, 32); + os_memmove(in + sizeof(C_sub_address_prefix) + 32, index, 8); + // hash_to_scalar with more that 32bytes: monero_keccak_F(in, sizeof(in), sub_s); monero_reduce(sub_s, sub_s); } @@ -583,15 +575,14 @@ void monero_get_subaddress_secret_key(unsigned char *sub_s, unsigned char *s, un /* ======================================================================= */ /* MATH */ /* ======================================================================= */ - + /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -void monero_check_scalar_range_1N(unsigned char *s) { +void monero_check_scalar_range_1N(unsigned char *s) { unsigned char x[32]; - monero_reverse32(x,s); - if (cx_math_is_zero(x,32) || - cx_math_cmp(x, C_ED25519_ORDER, 32) >= 0) { + monero_reverse32(x, s); + if (cx_math_is_zero(x, 32) || cx_math_cmp(x, C_ED25519_ORDER, 32) >= 0) { THROW(SW_WRONG_DATA_RANGE); } } @@ -607,7 +598,7 @@ void monero_check_scalar_not_null(unsigned char *s) { /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -void monero_ecmul_G(unsigned char *W, unsigned char *scalar32) { +void monero_ecmul_G(unsigned char *W, unsigned char *scalar32) { unsigned char Pxy[65]; unsigned char s[32]; monero_reverse32(s, scalar32); @@ -620,7 +611,7 @@ void monero_ecmul_G(unsigned char *W, unsigned char *scalar32) { /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -void monero_ecmul_H(unsigned char *W, unsigned char *scalar32) { +void monero_ecmul_H(unsigned char *W, unsigned char *scalar32) { unsigned char Pxy[65]; unsigned char s[32]; @@ -701,7 +692,6 @@ void monero_ecadd(unsigned char *W, unsigned char *P, unsigned char *Q) { os_memmove(W, &Pxy[1], 32); } - /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ @@ -717,7 +707,7 @@ void monero_ecsub(unsigned char *W, unsigned char *P, unsigned char *Q) { os_memmove(&Qxy[1], Q, 32); cx_edward_decompress_point(CX_CURVE_Ed25519, Qxy, sizeof(Qxy)); - cx_math_sub(Qxy+1, (unsigned char *)C_ED25519_FIELD, Qxy+1, 32); + cx_math_sub(Qxy + 1, (unsigned char *)C_ED25519_FIELD, Qxy + 1, 32); cx_ecfp_add_point(CX_CURVE_Ed25519, Pxy, Pxy, Qxy, sizeof(Pxy)); cx_edward_compress_point(CX_CURVE_Ed25519, Pxy, sizeof(Pxy)); @@ -739,10 +729,10 @@ void monero_ecsub(unsigned char *W, unsigned char *P, unsigned char *Q) { } */ void monero_ecdhHash(unsigned char *x, unsigned char *k) { - unsigned char data[38]; - os_memmove(data, "amount", 6); - os_memmove(data + 6, k, 32); - monero_keccak_F(data, 38, x); + unsigned char data[38]; + os_memmove(data, "amount", 6); + os_memmove(data + 6, k, 32); + monero_keccak_F(data, 38, x); } /* ----------------------------------------------------------------------- */ @@ -759,11 +749,11 @@ void monero_ecdhHash(unsigned char *x, unsigned char *k) { return scalar; } */ -void monero_genCommitmentMask(unsigned char *c, unsigned char *sk) { +void monero_genCommitmentMask(unsigned char *c, unsigned char *sk) { unsigned char data[15 + 32]; os_memmove(data, "commitment_mask", 15); os_memmove(data + 15, sk, 32); - monero_hash_to_scalar(c, data, 15+32); + monero_hash_to_scalar(c, data, 15 + 32); } /* ----------------------------------------------------------------------- */ @@ -773,10 +763,10 @@ void monero_addm(unsigned char *r, unsigned char *a, unsigned char *b) { unsigned char ra[32]; unsigned char rb[32]; - monero_reverse32(ra,a); - monero_reverse32(rb,b); - cx_math_addm(r, ra, rb, (unsigned char *)C_ED25519_ORDER, 32); - monero_reverse32(r,r); + monero_reverse32(ra, a); + monero_reverse32(rb, b); + cx_math_addm(r, ra, rb, (unsigned char *)C_ED25519_ORDER, 32); + monero_reverse32(r, r); } /* ----------------------------------------------------------------------- */ @@ -786,10 +776,10 @@ void monero_subm(unsigned char *r, unsigned char *a, unsigned char *b) { unsigned char ra[32]; unsigned char rb[32]; - monero_reverse32(ra,a); - monero_reverse32(rb,b); - cx_math_subm(r, ra, rb, (unsigned char *)C_ED25519_ORDER, 32); - monero_reverse32(r,r); + monero_reverse32(ra, a); + monero_reverse32(rb, b); + cx_math_subm(r, ra, rb, (unsigned char *)C_ED25519_ORDER, 32); + monero_reverse32(r, r); } /* ----------------------------------------------------------------------- */ /* --- --- */ @@ -798,10 +788,10 @@ void monero_multm(unsigned char *r, unsigned char *a, unsigned char *b) { unsigned char ra[32]; unsigned char rb[32]; - monero_reverse32(ra,a); - monero_reverse32(rb,b); - cx_math_multm(r, ra, rb, (unsigned char *)C_ED25519_ORDER, 32); - monero_reverse32(r,r); + monero_reverse32(ra, a); + monero_reverse32(rb, b); + cx_math_multm(r, ra, rb, (unsigned char *)C_ED25519_ORDER, 32); + monero_reverse32(r, r); } /* ----------------------------------------------------------------------- */ @@ -811,11 +801,11 @@ void monero_multm_8(unsigned char *r, unsigned char *a) { unsigned char ra[32]; unsigned char rb[32]; - monero_reverse32(ra,a); - os_memset(rb,0,32); + monero_reverse32(ra, a); + os_memset(rb, 0, 32); rb[31] = 8; - cx_math_multm(r, ra, rb, (unsigned char *)C_ED25519_ORDER, 32); - monero_reverse32(r,r); + cx_math_multm(r, ra, rb, (unsigned char *)C_ED25519_ORDER, 32); + monero_reverse32(r, r); } /* ----------------------------------------------------------------------- */ @@ -823,31 +813,30 @@ void monero_multm_8(unsigned char *r, unsigned char *a) { /* ----------------------------------------------------------------------- */ void monero_reduce(unsigned char *r, unsigned char *a) { unsigned char ra[32]; - monero_reverse32(ra,a); + monero_reverse32(ra, a); cx_math_modm(ra, 32, (unsigned char *)C_ED25519_ORDER, 32); - monero_reverse32(r,ra); + monero_reverse32(r, ra); } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ void monero_rng_mod_order(unsigned char *r) { - unsigned char rnd[32+8]; - cx_rng(rnd,32+8); - cx_math_modm(rnd, 32+8, (unsigned char *)C_ED25519_ORDER, 32); - monero_reverse32(r,rnd+8); + unsigned char rnd[32 + 8]; + cx_rng(rnd, 32 + 8); + cx_math_modm(rnd, 32 + 8, (unsigned char *)C_ED25519_ORDER, 32); + monero_reverse32(r, rnd + 8); } - /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ /* return 0 if ok, 1 if missing decimal */ -void monero_uint642str(uint64_t val, char *str, unsigned int str_len) { +void monero_uint642str(uint64_t val, char *str, unsigned int str_len) { char stramount[22]; - unsigned int offset,len; + unsigned int offset, len; - os_memset(str,0,str_len); + os_memset(str, 0, str_len); offset = 22; while (val) { @@ -855,34 +844,33 @@ void monero_uint642str(uint64_t val, char *str, unsigned int str_len) { stramount[offset] = '0' + val % 10; val = val / 10; } - len = sizeof(stramount)-offset; + len = sizeof(stramount) - offset; if (len > str_len) { THROW(SW_WRONG_DATA_RANGE); } - os_memmove(str, stramount+offset, len); + os_memmove(str, stramount + offset, len); } - /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ /* return 0 if ok, 1 if missing decimal */ -int monero_amount2str(uint64_t xmr, char *str, unsigned int str_len) { - //max uint64 is 18446744073709551616, aka 20 char, plus dot +int monero_amount2str(uint64_t xmr, char *str, unsigned int str_len) { + // max uint64 is 18446744073709551616, aka 20 char, plus dot char stramount[22]; - unsigned int offset,len,ov; + unsigned int offset, len, ov; - os_memset(str,0,str_len); + os_memset(str, 0, str_len); - os_memset(stramount,'0',sizeof(stramount)); + os_memset(stramount, '0', sizeof(stramount)); stramount[21] = 0; - //special case + // special case if (xmr == 0) { str[0] = '0'; return 1; } - //uint64 units to str + // uint64 units to str // offset: 0 | 1-20 | 21 // ---------------------- // value: 0 | xmrunits | 0 @@ -896,26 +884,26 @@ int monero_amount2str(uint64_t xmr, char *str, unsigned int str_len) { // offset: 0-7 | 8 | 9-20 |21 // ---------------------- // value: xmr | . | units| 0 - os_memmove(stramount, stramount+1, 8); + os_memmove(stramount, stramount + 1, 8); stramount[8] = '.'; offset = 0; - while((stramount[offset]=='0') && (stramount[offset] != '.')) { + while ((stramount[offset] == '0') && (stramount[offset] != '.')) { offset++; } if (stramount[offset] == '.') { offset--; } len = 20; - while((stramount[len]=='0') && (stramount[len] != '.')) { + while ((stramount[len] == '0') && (stramount[len] != '.')) { len--; } - len = len-offset+1; + len = len - offset + 1; ov = 0; - if (len>(str_len-1)) { - len = str_len-1; + if (len > (str_len - 1)) { + len = str_len - 1; ov = 1; } - os_memmove(str, stramount+offset, len); + os_memmove(str, stramount + offset, len); return ov; } @@ -926,8 +914,8 @@ uint64_t monero_bamount2uint64(unsigned char *binary) { uint64_t xmr; int i; xmr = 0; - for (i=7; i>=0; i--) { - xmr = xmr*256 + binary[i]; + for (i = 7; i >= 0; i--) { + xmr = xmr * 256 + binary[i]; } return xmr; } @@ -935,17 +923,16 @@ uint64_t monero_bamount2uint64(unsigned char *binary) { /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -int monero_bamount2str(unsigned char *binary, char *str, unsigned int str_len) { - return monero_amount2str(monero_bamount2uint64(binary), str,str_len); +int monero_bamount2str(unsigned char *binary, char *str, unsigned int str_len) { + return monero_amount2str(monero_bamount2uint64(binary), str, str_len); } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -int monero_vamount2str(unsigned char *binary, char *str, unsigned int str_len) { - //return monero_amount2str(monero_vamount2uint64(binary), str,str_len); +int monero_vamount2str(unsigned char *binary, char *str, unsigned int str_len) { + // return monero_amount2str(monero_vamount2uint64(binary), str,str_len); uint64_t amount; monero_decode_varint(binary, 8, &amount); - return monero_amount2str(amount, str,str_len); + return monero_amount2str(amount, str, str_len); } - diff --git a/src/monero_dispatch.c b/src/monero_dispatch.c index fc19df7..08dbc5c 100644 --- a/src/monero_dispatch.c +++ b/src/monero_dispatch.c @@ -19,473 +19,455 @@ #include "monero_api.h" #include "monero_vars.h" - void update_protocol() { - G_monero_vstate.tx_state_ins = G_monero_vstate.io_ins; - G_monero_vstate.tx_state_p1 = G_monero_vstate.io_p1 ; - G_monero_vstate.tx_state_p2 = G_monero_vstate.io_p2; + G_monero_vstate.tx_state_ins = G_monero_vstate.io_ins; + G_monero_vstate.tx_state_p1 = G_monero_vstate.io_p1; + G_monero_vstate.tx_state_p2 = G_monero_vstate.io_p2; } void clear_protocol() { - G_monero_vstate.tx_state_ins = 0; - G_monero_vstate.tx_state_p1 = 0; - G_monero_vstate.tx_state_p2 = 0; + G_monero_vstate.tx_state_ins = 0; + G_monero_vstate.tx_state_p1 = 0; + G_monero_vstate.tx_state_p2 = 0; } -int check_potocol() { - /* if locked and pin is veririfed, unlock */ - if ((G_monero_vstate.protocol_barrier == PROTOCOL_LOCKED_UNLOCKABLE) - && (os_global_pin_is_validated() == PIN_VERIFIED)) { - G_monero_vstate.protocol_barrier = PROTOCOL_UNLOCKED; - } - - /* if we are locked, deny all command! */ - if (G_monero_vstate.protocol_barrier != PROTOCOL_UNLOCKED) { - return SW_SECURITY_LOCKED; - } - - /* the first command enforce the protocol version until application quits */ - switch(G_monero_vstate.io_protocol_version) { - case 0x00: /* the first one: PCSC epoch */ - case 0x03: /* protocol V3 */ - if (G_monero_vstate.protocol == 0xff) { - G_monero_vstate.protocol = G_monero_vstate.io_protocol_version; +int check_potocol() { + /* if locked and pin is veririfed, unlock */ + if ((G_monero_vstate.protocol_barrier == PROTOCOL_LOCKED_UNLOCKABLE) && + (os_global_pin_is_validated() == PIN_VERIFIED)) { + G_monero_vstate.protocol_barrier = PROTOCOL_UNLOCKED; } - if (G_monero_vstate.protocol == G_monero_vstate.io_protocol_version) { - break; + + /* if we are locked, deny all command! */ + if (G_monero_vstate.protocol_barrier != PROTOCOL_UNLOCKED) { + return SW_SECURITY_LOCKED; } - //unknown protocol or hot protocol switch is not allowed - //FALL THROUGH - default: - return SW_PROTOCOL_NOT_SUPPORTED; - } - return SW_OK; + /* the first command enforce the protocol version until application quits */ + switch (G_monero_vstate.io_protocol_version) { + case 0x00: /* the first one: PCSC epoch */ + case 0x03: /* protocol V3 */ + if (G_monero_vstate.protocol == 0xff) { + G_monero_vstate.protocol = G_monero_vstate.io_protocol_version; + } + if (G_monero_vstate.protocol == G_monero_vstate.io_protocol_version) { + break; + } + // unknown protocol or hot protocol switch is not allowed + // FALL THROUGH + + default: + return SW_PROTOCOL_NOT_SUPPORTED; + } + return SW_OK; } int check_ins_access() { - - if (G_monero_vstate.key_set != 1) { - return SW_KEY_NOT_SET; - } - - switch (G_monero_vstate.io_ins) { - case INS_LOCK_DISPLAY: - case INS_RESET: - case INS_PUT_KEY: - case INS_GET_KEY: - case INS_DISPLAY_ADDRESS: - case INS_VERIFY_KEY: - case INS_GET_CHACHA8_PREKEY: - case INS_GEN_KEY_DERIVATION: - case INS_DERIVATION_TO_SCALAR: - case INS_DERIVE_PUBLIC_KEY: - case INS_DERIVE_SECRET_KEY: - case INS_GEN_KEY_IMAGE: - case INS_SECRET_KEY_TO_PUBLIC_KEY: - case INS_SECRET_KEY_ADD: - case INS_GENERATE_KEYPAIR: - case INS_SECRET_SCAL_MUL_KEY: - case INS_SECRET_SCAL_MUL_BASE: - case INS_DERIVE_SUBADDRESS_PUBLIC_KEY: - case INS_GET_SUBADDRESS: - case INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY: - case INS_GET_SUBADDRESS_SECRET_KEY: - case INS_MANAGE_SEEDWORDS: - case INS_UNBLIND: - case INS_STEALTH: - case INS_GET_TX_PROOF: - case INS_CLOSE_TX: - return SW_OK; - - case INS_OPEN_TX: - case INS_SET_SIGNATURE_MODE: - if (os_global_pin_is_validated() != PIN_VERIFIED) { - return SW_SECURITY_PIN_LOCKED; + if (G_monero_vstate.key_set != 1) { + return SW_KEY_NOT_SET; } - return SW_OK; - case INS_GEN_TXOUT_KEYS: - case INS_PREFIX_HASH: - case INS_BLIND: - case INS_VALIDATE: - case INS_MLSAG: - case INS_GEN_COMMITMENT_MASK: - if (os_global_pin_is_validated() != PIN_VERIFIED) { - return SW_SECURITY_PIN_LOCKED; + switch (G_monero_vstate.io_ins) { + case INS_LOCK_DISPLAY: + case INS_RESET: + case INS_PUT_KEY: + case INS_GET_KEY: + case INS_DISPLAY_ADDRESS: + case INS_VERIFY_KEY: + case INS_GET_CHACHA8_PREKEY: + case INS_GEN_KEY_DERIVATION: + case INS_DERIVATION_TO_SCALAR: + case INS_DERIVE_PUBLIC_KEY: + case INS_DERIVE_SECRET_KEY: + case INS_GEN_KEY_IMAGE: + case INS_SECRET_KEY_TO_PUBLIC_KEY: + case INS_SECRET_KEY_ADD: + case INS_GENERATE_KEYPAIR: + case INS_SECRET_SCAL_MUL_KEY: + case INS_SECRET_SCAL_MUL_BASE: + case INS_DERIVE_SUBADDRESS_PUBLIC_KEY: + case INS_GET_SUBADDRESS: + case INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY: + case INS_GET_SUBADDRESS_SECRET_KEY: + case INS_MANAGE_SEEDWORDS: + case INS_UNBLIND: + case INS_STEALTH: + case INS_GET_TX_PROOF: + case INS_CLOSE_TX: + return SW_OK; + + case INS_OPEN_TX: + case INS_SET_SIGNATURE_MODE: + if (os_global_pin_is_validated() != PIN_VERIFIED) { + return SW_SECURITY_PIN_LOCKED; + } + return SW_OK; + + case INS_GEN_TXOUT_KEYS: + case INS_PREFIX_HASH: + case INS_BLIND: + case INS_VALIDATE: + case INS_MLSAG: + case INS_GEN_COMMITMENT_MASK: + if (os_global_pin_is_validated() != PIN_VERIFIED) { + return SW_SECURITY_PIN_LOCKED; + } + if (G_monero_vstate.tx_in_progress != 1) { + return SW_COMMAND_NOT_ALLOWED; + } + return SW_OK; } - if (G_monero_vstate.tx_in_progress != 1) { - return SW_COMMAND_NOT_ALLOWED; - } - return SW_OK; - } - return SW_INS_NOT_SUPPORTED; + return SW_INS_NOT_SUPPORTED; } int monero_dispatch() { + int sw; - int sw; - - if ( ((sw = check_potocol()) != SW_OK) || - ((sw = check_ins_access() != SW_OK)) ) { - monero_io_discard(0); - return sw; - } - - G_monero_vstate.options = monero_io_fetch_u8(); - - if (G_monero_vstate.io_ins == INS_RESET) { - sw = monero_apdu_reset(); - return sw; - } - - if (G_monero_vstate.io_ins == INS_LOCK_DISPLAY) { - sw = monero_apdu_lock(); - return sw; - } - - sw = 0x6F01; - - switch (G_monero_vstate.io_ins) { - /* --- KEYS --- */ - case INS_PUT_KEY: - sw = monero_apdu_put_key(); - break; - case INS_GET_KEY: - sw = monero_apdu_get_key(); - break; - case INS_DISPLAY_ADDRESS: - sw = monero_apdu_display_address(); - break; - case INS_MANAGE_SEEDWORDS: - sw = monero_apdu_manage_seedwords(); - break; - - /* --- PROVISIONING--- */ - case INS_VERIFY_KEY: - sw = monero_apdu_verify_key(); - break; - case INS_GET_CHACHA8_PREKEY: - sw = monero_apdu_get_chacha8_prekey(); - break; - case INS_SECRET_KEY_TO_PUBLIC_KEY: - sw = monero_apdu_secret_key_to_public_key(); - break; - case INS_GEN_KEY_DERIVATION: - sw = monero_apdu_generate_key_derivation(); - break; - case INS_DERIVATION_TO_SCALAR: - sw = monero_apdu_derivation_to_scalar(); - break; - case INS_DERIVE_PUBLIC_KEY: - sw = monero_apdu_derive_public_key(); - break; - case INS_DERIVE_SECRET_KEY: - sw = monero_apdu_derive_secret_key(); - break; - case INS_GEN_KEY_IMAGE: - sw = monero_apdu_generate_key_image(); - break; - case INS_SECRET_KEY_ADD: - sw = monero_apdu_sc_add(); - break; - case INS_GENERATE_KEYPAIR: - sw = monero_apdu_generate_keypair(); - break; - case INS_SECRET_SCAL_MUL_KEY: - sw = monero_apdu_scal_mul_key(); - break; - case INS_SECRET_SCAL_MUL_BASE: - sw = monero_apdu_scal_mul_base(); - break; - - /* --- ADRESSES --- */ - case INS_DERIVE_SUBADDRESS_PUBLIC_KEY: - sw = monero_apdu_derive_subaddress_public_key(); - break; - case INS_GET_SUBADDRESS: - sw = monero_apdu_get_subaddress(); - break; - case INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY: - sw = monero_apdu_get_subaddress_spend_public_key(); - break; - case INS_GET_SUBADDRESS_SECRET_KEY: - sw = monero_apdu_get_subaddress_secret_key(); - break; - - /* --- PARSE --- */ - case INS_UNBLIND: - sw = monero_apdu_unblind(); - break; - - /* --- PROOF --- */ - case INS_GET_TX_PROOF: - sw = monero_apdu_get_tx_proof(); - break; - - /* ======================================================================= - * Following command are only allowed during transaction and their - * sequence shall be enforced - */ - - /* --- START TX --- */ - case INS_OPEN_TX: - //state machine check - if (G_monero_vstate.tx_state_ins != 0) { - THROW(SW_COMMAND_NOT_ALLOWED); - } - //2. command process - sw = monero_apdu_open_tx(); - update_protocol(); - break; - - case INS_CLOSE_TX: - sw = monero_apdu_close_tx(); - clear_protocol(); - break; - - /* --- SIG MODE --- */ - case INS_SET_SIGNATURE_MODE: - //1. state machine check - if (G_monero_vstate.tx_in_progress != 0) { - //Change sig mode during transacation is not allowed - THROW(SW_COMMAND_NOT_ALLOWED); - } - //2. command process - sw = monero_apdu_set_signature_mode(); - break; - - /* --- STEATH PAYMENT --- */ - case INS_STEALTH: - //1. state machine check - if (G_monero_vstate.tx_in_progress == 1) { - if ((G_monero_vstate.tx_state_ins != INS_OPEN_TX) && - (G_monero_vstate.tx_state_ins != INS_STEALTH)) { - THROW(SW_COMMAND_NOT_ALLOWED); - } - if ((G_monero_vstate.io_p1 != 0) || - (G_monero_vstate.io_p2 != 0)) { - THROW(SW_WRONG_P1P2); - } - } - //2. command process - sw = monero_apdu_stealth(); - if (G_monero_vstate.tx_in_progress == 1) { - update_protocol(); + if (((sw = check_potocol()) != SW_OK) || ((sw = check_ins_access() != SW_OK))) { + monero_io_discard(0); + return sw; } - break; + G_monero_vstate.options = monero_io_fetch_u8(); - /* --- TX OUT KEYS --- */ - case INS_GEN_TXOUT_KEYS: - //1. state machine check - if ((G_monero_vstate.tx_state_ins != INS_OPEN_TX) && - (G_monero_vstate.tx_state_ins != INS_GEN_TXOUT_KEYS) && - (G_monero_vstate.tx_state_ins != INS_STEALTH)) { - THROW(SW_COMMAND_NOT_ALLOWED); - } - if (G_monero_vstate.protocol == 3) { - if ((G_monero_vstate.tx_state_ins != INS_OPEN_TX) && - (G_monero_vstate.tx_state_ins != INS_GEN_TXOUT_KEYS) && - (G_monero_vstate.tx_state_ins != INS_STEALTH)) { - THROW(SW_COMMAND_NOT_ALLOWED); - } - } - if ((G_monero_vstate.io_p1 != 0) || - (G_monero_vstate.io_p2 != 0)) { - THROW(SW_WRONG_P1P2); - } - //2. command process - sw = monero_apu_generate_txout_keys(); - update_protocol(); - break; - - /* --- PREFIX HASH --- */ - case INS_PREFIX_HASH: - //1. state machine check - if ((G_monero_vstate.tx_state_ins != INS_GEN_TXOUT_KEYS) && - (G_monero_vstate.tx_state_ins != INS_PREFIX_HASH)) { - THROW(SW_COMMAND_NOT_ALLOWED); - } - //init prefixhash state machine - if (G_monero_vstate.tx_state_ins == INS_GEN_TXOUT_KEYS) { - G_monero_vstate.tx_state_ins = INS_PREFIX_HASH; - G_monero_vstate.tx_state_p1 = 0; - G_monero_vstate.tx_state_p2 = 0; - } - //check new state is allowed - if (G_monero_vstate.tx_state_p1 == 0) { - if (1 != G_monero_vstate.io_p1) { - THROW(SW_SUBCOMMAND_NOT_ALLOWED); - } - } else if (G_monero_vstate.tx_state_p1 == 1) { - if ((G_monero_vstate.io_p1 != 2) || - (G_monero_vstate.io_p2 != 1)) { - THROW(SW_SUBCOMMAND_NOT_ALLOWED); - } - } else if (G_monero_vstate.tx_state_p1 == 2) { - if ((G_monero_vstate.io_p1 != 2)|| - (G_monero_vstate.io_p2-1 != G_monero_vstate.tx_state_p2)) { - THROW(SW_SUBCOMMAND_NOT_ALLOWED); - } - } else { - THROW(SW_COMMAND_NOT_ALLOWED); - } - //2. command process - if (G_monero_vstate.io_p1 == 1) { - sw = monero_apdu_prefix_hash_init(); - } - else if (G_monero_vstate.io_p1 == 2) { - sw = monero_apdu_prefix_hash_update(); - } else { - THROW(SW_WRONG_P1P2); - } - update_protocol(); - break; - - /*--- COMMITMENT MASK --- */ - case INS_GEN_COMMITMENT_MASK: - //1. state machine check - if (G_monero_vstate.protocol == 3) { - if ((G_monero_vstate.tx_state_ins != INS_PREFIX_HASH) && - (G_monero_vstate.tx_state_ins != INS_GEN_COMMITMENT_MASK)) { - THROW(SW_COMMAND_NOT_ALLOWED); - } + if (G_monero_vstate.io_ins == INS_RESET) { + sw = monero_apdu_reset(); + return sw; } - if ((G_monero_vstate.io_p1 != 0) || - (G_monero_vstate.io_p2 != 0)) { - THROW(SW_WRONG_P1P2); - } - //2. command process - sw = monero_apdu_gen_commitment_mask(); - update_protocol(); - break; - - /* --- BLIND --- */ - case INS_BLIND: - //1. state machine check - if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_FAKE) { - if (G_monero_vstate.protocol == 3) { - if ((G_monero_vstate.tx_state_ins != INS_PREFIX_HASH) && - (G_monero_vstate.tx_state_ins != INS_BLIND)) { - THROW(SW_COMMAND_NOT_ALLOWED); - } - } - } else if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { - if ((G_monero_vstate.tx_state_ins != INS_GEN_COMMITMENT_MASK) && - (G_monero_vstate.tx_state_ins != INS_BLIND)) { - THROW(SW_COMMAND_NOT_ALLOWED); - } - } else { - THROW(SW_COMMAND_NOT_ALLOWED); - } - //2. command process - if ((G_monero_vstate.io_p1 != 0) || - (G_monero_vstate.io_p2 != 0)) { - THROW(SW_WRONG_P1P2); - } - sw = monero_apdu_blind(); - update_protocol(); - break; - - /* --- VALIDATE/PREHASH --- */ - case INS_VALIDATE: - //1. state machine check - if ((G_monero_vstate.tx_state_ins != INS_BLIND) && - (G_monero_vstate.tx_state_ins != INS_VALIDATE)) { - THROW(SW_COMMAND_NOT_ALLOWED); - } - //init PREHASH state machine - if (G_monero_vstate.tx_state_ins == INS_BLIND) { - G_monero_vstate.tx_state_ins = INS_VALIDATE; - G_monero_vstate.tx_state_p1 = 1; - G_monero_vstate.tx_state_p2 = 0; - if ((G_monero_vstate.io_p1 != 1) || - (G_monero_vstate.io_p2 != 1)) { - THROW(SW_SUBCOMMAND_NOT_ALLOWED); - } - } - //check new state is allowed - if (G_monero_vstate.tx_state_p1 == G_monero_vstate.io_p1) { - if (G_monero_vstate.tx_state_p2 != G_monero_vstate.io_p2-1) { - THROW(SW_SUBCOMMAND_NOT_ALLOWED); - } - } else if (G_monero_vstate.tx_state_p1 == G_monero_vstate.io_p1-1) { - if (1 != G_monero_vstate.io_p2) { - THROW(SW_SUBCOMMAND_NOT_ALLOWED); - } - } else { - THROW(SW_COMMAND_NOT_ALLOWED); - } - //2. command process - if (G_monero_vstate.io_p1 == 1) { - sw = monero_apdu_mlsag_prehash_init(); - } - else if (G_monero_vstate.io_p1 == 2) { - sw = monero_apdu_mlsag_prehash_update(); - } - else if (G_monero_vstate.io_p1 == 3) { - sw = monero_apdu_mlsag_prehash_finalize(); - } else { - THROW(SW_WRONG_P1P2); - } - update_protocol(); - break; - - /* --- MLSAG --- */ - case INS_MLSAG: - //1. state machine check - if ((G_monero_vstate.tx_state_ins != INS_VALIDATE) && - (G_monero_vstate.tx_state_ins != INS_MLSAG)) { - THROW(SW_COMMAND_NOT_ALLOWED); - } - if (G_monero_vstate.tx_state_ins == INS_VALIDATE) { - if ((G_monero_vstate.tx_state_p1 != 3) || - (G_monero_vstate.io_p1 != 1) || - (G_monero_vstate.io_p2 != 0)) { - THROW(SW_SUBCOMMAND_NOT_ALLOWED); - } - } else { - if (G_monero_vstate.tx_state_p1 == 1) { - if (2 != G_monero_vstate.io_p1) { - THROW(SW_SUBCOMMAND_NOT_ALLOWED); - } - } else if (G_monero_vstate.tx_state_p1 == 2) { - if ((2 != G_monero_vstate.io_p1) && - (3 != G_monero_vstate.io_p1)) { - THROW(SW_SUBCOMMAND_NOT_ALLOWED); - } - } else if (G_monero_vstate.tx_state_p1 == 3) { - if (1 != G_monero_vstate.io_p1) { - THROW(SW_SUBCOMMAND_NOT_ALLOWED); - } - } else { - THROW(SW_COMMAND_NOT_ALLOWED); - } + if (G_monero_vstate.io_ins == INS_LOCK_DISPLAY) { + sw = monero_apdu_lock(); + return sw; } - //2. command process - if (G_monero_vstate.io_p1 == 1) { - sw = monero_apdu_mlsag_prepare(); - } - else if (G_monero_vstate.io_p1 == 2) { - sw = monero_apdu_mlsag_hash(); - } - else if (G_monero_vstate.io_p1 == 3) { - sw = monero_apdu_mlsag_sign(); - } else { - THROW(SW_WRONG_P1P2); + sw = 0x6F01; + + switch (G_monero_vstate.io_ins) { + /* --- KEYS --- */ + case INS_PUT_KEY: + sw = monero_apdu_put_key(); + break; + case INS_GET_KEY: + sw = monero_apdu_get_key(); + break; + case INS_DISPLAY_ADDRESS: + sw = monero_apdu_display_address(); + break; + case INS_MANAGE_SEEDWORDS: + sw = monero_apdu_manage_seedwords(); + break; + + /* --- PROVISIONING--- */ + case INS_VERIFY_KEY: + sw = monero_apdu_verify_key(); + break; + case INS_GET_CHACHA8_PREKEY: + sw = monero_apdu_get_chacha8_prekey(); + break; + case INS_SECRET_KEY_TO_PUBLIC_KEY: + sw = monero_apdu_secret_key_to_public_key(); + break; + case INS_GEN_KEY_DERIVATION: + sw = monero_apdu_generate_key_derivation(); + break; + case INS_DERIVATION_TO_SCALAR: + sw = monero_apdu_derivation_to_scalar(); + break; + case INS_DERIVE_PUBLIC_KEY: + sw = monero_apdu_derive_public_key(); + break; + case INS_DERIVE_SECRET_KEY: + sw = monero_apdu_derive_secret_key(); + break; + case INS_GEN_KEY_IMAGE: + sw = monero_apdu_generate_key_image(); + break; + case INS_SECRET_KEY_ADD: + sw = monero_apdu_sc_add(); + break; + case INS_GENERATE_KEYPAIR: + sw = monero_apdu_generate_keypair(); + break; + case INS_SECRET_SCAL_MUL_KEY: + sw = monero_apdu_scal_mul_key(); + break; + case INS_SECRET_SCAL_MUL_BASE: + sw = monero_apdu_scal_mul_base(); + break; + + /* --- ADRESSES --- */ + case INS_DERIVE_SUBADDRESS_PUBLIC_KEY: + sw = monero_apdu_derive_subaddress_public_key(); + break; + case INS_GET_SUBADDRESS: + sw = monero_apdu_get_subaddress(); + break; + case INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY: + sw = monero_apdu_get_subaddress_spend_public_key(); + break; + case INS_GET_SUBADDRESS_SECRET_KEY: + sw = monero_apdu_get_subaddress_secret_key(); + break; + + /* --- PARSE --- */ + case INS_UNBLIND: + sw = monero_apdu_unblind(); + break; + + /* --- PROOF --- */ + case INS_GET_TX_PROOF: + sw = monero_apdu_get_tx_proof(); + break; + + /* ======================================================================= + * Following command are only allowed during transaction and their + * sequence shall be enforced + */ + + /* --- START TX --- */ + case INS_OPEN_TX: + // state machine check + if (G_monero_vstate.tx_state_ins != 0) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + // 2. command process + sw = monero_apdu_open_tx(); + update_protocol(); + break; + + case INS_CLOSE_TX: + sw = monero_apdu_close_tx(); + clear_protocol(); + break; + + /* --- SIG MODE --- */ + case INS_SET_SIGNATURE_MODE: + // 1. state machine check + if (G_monero_vstate.tx_in_progress != 0) { + // Change sig mode during transacation is not allowed + THROW(SW_COMMAND_NOT_ALLOWED); + } + // 2. command process + sw = monero_apdu_set_signature_mode(); + break; + + /* --- STEATH PAYMENT --- */ + case INS_STEALTH: + // 1. state machine check + if (G_monero_vstate.tx_in_progress == 1) { + if ((G_monero_vstate.tx_state_ins != INS_OPEN_TX) && + (G_monero_vstate.tx_state_ins != INS_STEALTH)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + if ((G_monero_vstate.io_p1 != 0) || (G_monero_vstate.io_p2 != 0)) { + THROW(SW_WRONG_P1P2); + } + } + // 2. command process + sw = monero_apdu_stealth(); + if (G_monero_vstate.tx_in_progress == 1) { + update_protocol(); + } + break; + + /* --- TX OUT KEYS --- */ + case INS_GEN_TXOUT_KEYS: + // 1. state machine check + if ((G_monero_vstate.tx_state_ins != INS_OPEN_TX) && + (G_monero_vstate.tx_state_ins != INS_GEN_TXOUT_KEYS) && + (G_monero_vstate.tx_state_ins != INS_STEALTH)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + if (G_monero_vstate.protocol == 3) { + if ((G_monero_vstate.tx_state_ins != INS_OPEN_TX) && + (G_monero_vstate.tx_state_ins != INS_GEN_TXOUT_KEYS) && + (G_monero_vstate.tx_state_ins != INS_STEALTH)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + } + if ((G_monero_vstate.io_p1 != 0) || (G_monero_vstate.io_p2 != 0)) { + THROW(SW_WRONG_P1P2); + } + // 2. command process + sw = monero_apu_generate_txout_keys(); + update_protocol(); + break; + + /* --- PREFIX HASH --- */ + case INS_PREFIX_HASH: + // 1. state machine check + if ((G_monero_vstate.tx_state_ins != INS_GEN_TXOUT_KEYS) && + (G_monero_vstate.tx_state_ins != INS_PREFIX_HASH)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + // init prefixhash state machine + if (G_monero_vstate.tx_state_ins == INS_GEN_TXOUT_KEYS) { + G_monero_vstate.tx_state_ins = INS_PREFIX_HASH; + G_monero_vstate.tx_state_p1 = 0; + G_monero_vstate.tx_state_p2 = 0; + } + // check new state is allowed + if (G_monero_vstate.tx_state_p1 == 0) { + if (1 != G_monero_vstate.io_p1) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else if (G_monero_vstate.tx_state_p1 == 1) { + if ((G_monero_vstate.io_p1 != 2) || (G_monero_vstate.io_p2 != 1)) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else if (G_monero_vstate.tx_state_p1 == 2) { + if ((G_monero_vstate.io_p1 != 2) || + (G_monero_vstate.io_p2 - 1 != G_monero_vstate.tx_state_p2)) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else { + THROW(SW_COMMAND_NOT_ALLOWED); + } + // 2. command process + if (G_monero_vstate.io_p1 == 1) { + sw = monero_apdu_prefix_hash_init(); + } else if (G_monero_vstate.io_p1 == 2) { + sw = monero_apdu_prefix_hash_update(); + } else { + THROW(SW_WRONG_P1P2); + } + update_protocol(); + break; + + /*--- COMMITMENT MASK --- */ + case INS_GEN_COMMITMENT_MASK: + // 1. state machine check + if (G_monero_vstate.protocol == 3) { + if ((G_monero_vstate.tx_state_ins != INS_PREFIX_HASH) && + (G_monero_vstate.tx_state_ins != INS_GEN_COMMITMENT_MASK)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + } + + if ((G_monero_vstate.io_p1 != 0) || (G_monero_vstate.io_p2 != 0)) { + THROW(SW_WRONG_P1P2); + } + // 2. command process + sw = monero_apdu_gen_commitment_mask(); + update_protocol(); + break; + + /* --- BLIND --- */ + case INS_BLIND: + // 1. state machine check + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_FAKE) { + if (G_monero_vstate.protocol == 3) { + if ((G_monero_vstate.tx_state_ins != INS_PREFIX_HASH) && + (G_monero_vstate.tx_state_ins != INS_BLIND)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + } + } else if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { + if ((G_monero_vstate.tx_state_ins != INS_GEN_COMMITMENT_MASK) && + (G_monero_vstate.tx_state_ins != INS_BLIND)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + } else { + THROW(SW_COMMAND_NOT_ALLOWED); + } + // 2. command process + if ((G_monero_vstate.io_p1 != 0) || (G_monero_vstate.io_p2 != 0)) { + THROW(SW_WRONG_P1P2); + } + sw = monero_apdu_blind(); + update_protocol(); + break; + + /* --- VALIDATE/PREHASH --- */ + case INS_VALIDATE: + // 1. state machine check + if ((G_monero_vstate.tx_state_ins != INS_BLIND) && + (G_monero_vstate.tx_state_ins != INS_VALIDATE)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + // init PREHASH state machine + if (G_monero_vstate.tx_state_ins == INS_BLIND) { + G_monero_vstate.tx_state_ins = INS_VALIDATE; + G_monero_vstate.tx_state_p1 = 1; + G_monero_vstate.tx_state_p2 = 0; + if ((G_monero_vstate.io_p1 != 1) || (G_monero_vstate.io_p2 != 1)) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } + // check new state is allowed + if (G_monero_vstate.tx_state_p1 == G_monero_vstate.io_p1) { + if (G_monero_vstate.tx_state_p2 != G_monero_vstate.io_p2 - 1) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else if (G_monero_vstate.tx_state_p1 == G_monero_vstate.io_p1 - 1) { + if (1 != G_monero_vstate.io_p2) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else { + THROW(SW_COMMAND_NOT_ALLOWED); + } + // 2. command process + if (G_monero_vstate.io_p1 == 1) { + sw = monero_apdu_mlsag_prehash_init(); + } else if (G_monero_vstate.io_p1 == 2) { + sw = monero_apdu_mlsag_prehash_update(); + } else if (G_monero_vstate.io_p1 == 3) { + sw = monero_apdu_mlsag_prehash_finalize(); + } else { + THROW(SW_WRONG_P1P2); + } + update_protocol(); + break; + + /* --- MLSAG --- */ + case INS_MLSAG: + // 1. state machine check + if ((G_monero_vstate.tx_state_ins != INS_VALIDATE) && + (G_monero_vstate.tx_state_ins != INS_MLSAG)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + if (G_monero_vstate.tx_state_ins == INS_VALIDATE) { + if ((G_monero_vstate.tx_state_p1 != 3) || (G_monero_vstate.io_p1 != 1) || + (G_monero_vstate.io_p2 != 0)) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else { + if (G_monero_vstate.tx_state_p1 == 1) { + if (2 != G_monero_vstate.io_p1) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else if (G_monero_vstate.tx_state_p1 == 2) { + if ((2 != G_monero_vstate.io_p1) && (3 != G_monero_vstate.io_p1)) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else if (G_monero_vstate.tx_state_p1 == 3) { + if (1 != G_monero_vstate.io_p1) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else { + THROW(SW_COMMAND_NOT_ALLOWED); + } + } + + // 2. command process + if (G_monero_vstate.io_p1 == 1) { + sw = monero_apdu_mlsag_prepare(); + } else if (G_monero_vstate.io_p1 == 2) { + sw = monero_apdu_mlsag_hash(); + } else if (G_monero_vstate.io_p1 == 3) { + sw = monero_apdu_mlsag_sign(); + } else { + THROW(SW_WRONG_P1P2); + } + update_protocol(); + break; + + /* --- KEYS --- */ + + default: + THROW(SW_INS_NOT_SUPPORTED); + break; } - update_protocol(); - break; - - /* --- KEYS --- */ - - default: - THROW(SW_INS_NOT_SUPPORTED); - break; - } - return sw; + return sw; } diff --git a/src/monero_init.c b/src/monero_init.c index 39c8a01..f4ede52 100644 --- a/src/monero_init.c +++ b/src/monero_init.c @@ -19,212 +19,212 @@ #include "monero_api.h" #include "monero_vars.h" - /* ----------------------*/ /* -- A Kind of Magic -- */ /* ----------------------*/ -const unsigned char C_MAGIC[8] = { - 'M','O','N','E','R','O','H','W' -}; +const unsigned char C_MAGIC[8] = {'M', 'O', 'N', 'E', 'R', 'O', 'H', 'W'}; const unsigned char C_FAKE_SEC_VIEW_KEY[32] = { - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 -}; + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; const unsigned char C_FAKE_SEC_SPEND_KEY[32] = { - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF -}; + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; /* ----------------------------------------------------------------------- */ /* --- Boot --- */ /* ----------------------------------------------------------------------- */ void monero_init() { - os_memset(&G_monero_vstate, 0, sizeof(monero_v_state_t)); - - //first init ? - if (os_memcmp((void*)N_monero_pstate->magic, (void*)C_MAGIC, sizeof(C_MAGIC)) != 0) { - #if defined(MONERO_ALPHA) || defined(MONERO_BETA) - monero_install(STAGENET); - #else - monero_install(MAINNET); - #endif - } - - G_monero_vstate.protocol = 0xff; - G_monero_vstate.protocol_barrier = PROTOCOL_UNLOCKED; - - //load key - monero_init_private_key(); - //ux conf - monero_init_ux(); - // Let's go! - G_monero_vstate.state = STATE_IDLE; -} + os_memset(&G_monero_vstate, 0, sizeof(monero_v_state_t)); + + // first init ? + if (os_memcmp((void*)N_monero_pstate->magic, (void*)C_MAGIC, sizeof(C_MAGIC)) != 0) { +#if defined(MONERO_ALPHA) || defined(MONERO_BETA) + monero_install(STAGENET); +#else + monero_install(MAINNET); +#endif + } + G_monero_vstate.protocol = 0xff; + G_monero_vstate.protocol_barrier = PROTOCOL_UNLOCKED; + + // load key + monero_init_private_key(); + // ux conf + monero_init_ux(); + // Let's go! + G_monero_vstate.state = STATE_IDLE; +} /* ----------------------------------------------------------------------- */ /* --- init private keys --- */ /* ----------------------------------------------------------------------- */ void monero_wipe_private_key() { - os_memset(G_monero_vstate.a, 0, 32); - os_memset(G_monero_vstate.b, 0, 32); - os_memset(G_monero_vstate.A, 0, 32); - os_memset(G_monero_vstate.B, 0, 32); - os_memset(&G_monero_vstate.spk, 0, sizeof(G_monero_vstate.spk)); - G_monero_vstate.key_set = 0; + os_memset(G_monero_vstate.a, 0, 32); + os_memset(G_monero_vstate.b, 0, 32); + os_memset(G_monero_vstate.A, 0, 32); + os_memset(G_monero_vstate.B, 0, 32); + os_memset(&G_monero_vstate.spk, 0, sizeof(G_monero_vstate.spk)); + G_monero_vstate.key_set = 0; } void monero_init_private_key() { - unsigned int path[5]; - unsigned char seed[32]; - unsigned char chain[32]; - - //generate account keys - - // m / purpose' / coin_type' / account' / change / address_index - // m / 44' / 128' / 0' / 0 / 0 - path[0] = 0x8000002C; - path[1] = 0x80000080; - path[2] = 0x80000000|N_monero_pstate->account_id; - path[3] = 0x00000000; - path[4] = 0x00000000; - os_perso_derive_node_bip32(CX_CURVE_SECP256K1, path, 5 , seed, chain); - - switch(N_monero_pstate->key_mode) { - case KEY_MODE_SEED: - - monero_keccak_F(seed,32,G_monero_vstate.b); - monero_reduce(G_monero_vstate.b,G_monero_vstate.b); - monero_keccak_F(G_monero_vstate.b,32,G_monero_vstate.a); - monero_reduce(G_monero_vstate.a,G_monero_vstate.a); - break; - - case KEY_MODE_EXTERNAL: - os_memmove(G_monero_vstate.a, (void*)N_monero_pstate->a, 32); - os_memmove(G_monero_vstate.b, (void*)N_monero_pstate->b, 32); - break; - - default : - THROW(SW_SECURITY_LOAD_KEY); - return; - } - monero_ecmul_G(G_monero_vstate.A, G_monero_vstate.a); - monero_ecmul_G(G_monero_vstate.B, G_monero_vstate.b); - - //generate key protection - monero_aes_derive(&G_monero_vstate.spk,chain,G_monero_vstate.a,G_monero_vstate.b); - - - G_monero_vstate.key_set = 1; + unsigned int path[5]; + unsigned char seed[32]; + unsigned char chain[32]; + + // generate account keys + + // m / purpose' / coin_type' / account' / change / address_index + // m / 44' / 128' / 0' / 0 / 0 + path[0] = 0x8000002C; + path[1] = 0x80000080; + path[2] = 0x80000000 | N_monero_pstate->account_id; + path[3] = 0x00000000; + path[4] = 0x00000000; + os_perso_derive_node_bip32(CX_CURVE_SECP256K1, path, 5, seed, chain); + + switch (N_monero_pstate->key_mode) { + case KEY_MODE_SEED: + + monero_keccak_F(seed, 32, G_monero_vstate.b); + monero_reduce(G_monero_vstate.b, G_monero_vstate.b); + monero_keccak_F(G_monero_vstate.b, 32, G_monero_vstate.a); + monero_reduce(G_monero_vstate.a, G_monero_vstate.a); + break; + + case KEY_MODE_EXTERNAL: + os_memmove(G_monero_vstate.a, (void*)N_monero_pstate->a, 32); + os_memmove(G_monero_vstate.b, (void*)N_monero_pstate->b, 32); + break; + + default: + THROW(SW_SECURITY_LOAD_KEY); + return; + } + monero_ecmul_G(G_monero_vstate.A, G_monero_vstate.a); + monero_ecmul_G(G_monero_vstate.B, G_monero_vstate.b); + + // generate key protection + monero_aes_derive(&G_monero_vstate.spk, chain, G_monero_vstate.a, G_monero_vstate.b); + + G_monero_vstate.key_set = 1; } /* ----------------------------------------------------------------------- */ /* --- Set up ui/ux --- */ /* ----------------------------------------------------------------------- */ -void monero_init_ux() { - monero_base58_public_key(G_monero_vstate.ux_address, G_monero_vstate.A,G_monero_vstate.B, 0, NULL); - os_memset(G_monero_vstate.ux_wallet_public_short_address, '.', sizeof(G_monero_vstate.ux_wallet_public_short_address)); - - #ifdef HAVE_UX_FLOW - - #ifdef UI_NANO_X - snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), "XMR / %d", N_monero_pstate->account_id); - os_memmove(G_monero_vstate.ux_wallet_public_short_address, G_monero_vstate.ux_address,5); - os_memmove(G_monero_vstate.ux_wallet_public_short_address+7, G_monero_vstate.ux_address+95-5,5); - G_monero_vstate.ux_wallet_public_short_address[12] = 0; - #else - snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), " XMR / %d", N_monero_pstate->account_id); - os_memmove(G_monero_vstate.ux_wallet_public_short_address, G_monero_vstate.ux_address,4); - os_memmove(G_monero_vstate.ux_wallet_public_short_address+6, G_monero_vstate.ux_address+95-4,4); - G_monero_vstate.ux_wallet_public_short_address[10] = 0; - #endif - - #else - - snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), "XMR / %d", N_monero_pstate->account_id); - os_memmove(G_monero_vstate.ux_wallet_public_short_address, G_monero_vstate.ux_address,5); - os_memmove(G_monero_vstate.ux_wallet_public_short_address+7, G_monero_vstate.ux_address+95-5,5); - G_monero_vstate.ux_wallet_public_short_address[12] = 0; - - #endif +void monero_init_ux() { + monero_base58_public_key(G_monero_vstate.ux_address, G_monero_vstate.A, G_monero_vstate.B, 0, + NULL); + os_memset(G_monero_vstate.ux_wallet_public_short_address, '.', + sizeof(G_monero_vstate.ux_wallet_public_short_address)); + +#ifdef HAVE_UX_FLOW + +#ifdef UI_NANO_X + snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), + "XMR / %d", N_monero_pstate->account_id); + os_memmove(G_monero_vstate.ux_wallet_public_short_address, G_monero_vstate.ux_address, 5); + os_memmove(G_monero_vstate.ux_wallet_public_short_address + 7, + G_monero_vstate.ux_address + 95 - 5, 5); + G_monero_vstate.ux_wallet_public_short_address[12] = 0; +#else + snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), + " XMR / %d", N_monero_pstate->account_id); + os_memmove(G_monero_vstate.ux_wallet_public_short_address, G_monero_vstate.ux_address, 4); + os_memmove(G_monero_vstate.ux_wallet_public_short_address + 6, + G_monero_vstate.ux_address + 95 - 4, 4); + G_monero_vstate.ux_wallet_public_short_address[10] = 0; +#endif + +#else + + snprintf(G_monero_vstate.ux_wallet_account_name, sizeof(G_monero_vstate.ux_wallet_account_name), + "XMR / %d", N_monero_pstate->account_id); + os_memmove(G_monero_vstate.ux_wallet_public_short_address, G_monero_vstate.ux_address, 5); + os_memmove(G_monero_vstate.ux_wallet_public_short_address + 7, + G_monero_vstate.ux_address + 95 - 5, 5); + G_monero_vstate.ux_wallet_public_short_address[12] = 0; + +#endif } /* ----------------------------------------------------------------------- */ /* --- Install/ReInstall Monero app --- */ /* ----------------------------------------------------------------------- */ void monero_install(unsigned char netId) { - unsigned char c; + unsigned char c; - //full reset data - monero_nvm_write((void*)N_monero_pstate, NULL, sizeof(monero_nv_state_t)); + // full reset data + monero_nvm_write((void*)N_monero_pstate, NULL, sizeof(monero_nv_state_t)); - //set mode key - c = KEY_MODE_SEED; - nvm_write((void*)&N_monero_pstate->key_mode, &c, 1); + // set mode key + c = KEY_MODE_SEED; + nvm_write((void*)&N_monero_pstate->key_mode, &c, 1); - //set net id - monero_nvm_write((void*)&N_monero_pstate->network_id, &netId, 1); + // set net id + monero_nvm_write((void*)&N_monero_pstate->network_id, &netId, 1); - //write magic - monero_nvm_write((void*)N_monero_pstate->magic, (void*)C_MAGIC, sizeof(C_MAGIC)); + // write magic + monero_nvm_write((void*)N_monero_pstate->magic, (void*)C_MAGIC, sizeof(C_MAGIC)); } /* ----------------------------------------------------------------------- */ /* --- Reset --- */ /* ----------------------------------------------------------------------- */ #define MONERO_SUPPORTED_CLIENT_SIZE 1 -const char * const monero_supported_client[MONERO_SUPPORTED_CLIENT_SIZE] = { - "0.16.0.", +const char* const monero_supported_client[MONERO_SUPPORTED_CLIENT_SIZE] = { + "0.16.0.", }; int monero_apdu_reset() { - - unsigned int client_version_len; - char client_version[16]; - client_version_len = G_monero_vstate.io_length - G_monero_vstate.io_offset; - if (client_version_len > 14) { - THROW(SW_CLIENT_NOT_SUPPORTED+1); - } - monero_io_fetch((unsigned char*)&client_version[0], client_version_len); - client_version[client_version_len] = '.'; - client_version_len++; - client_version[client_version_len] = 0; - unsigned int i = 0; - while(i < MONERO_SUPPORTED_CLIENT_SIZE) { - unsigned int monero_supported_client_len = strlen((char*)PIC(monero_supported_client[i])); - if ((monero_supported_client_len <= client_version_len) && - (os_memcmp((char*)PIC(monero_supported_client[i]), client_version, monero_supported_client_len)==0) ) { - break; + unsigned int client_version_len; + char client_version[16]; + client_version_len = G_monero_vstate.io_length - G_monero_vstate.io_offset; + if (client_version_len > 14) { + THROW(SW_CLIENT_NOT_SUPPORTED + 1); + } + monero_io_fetch((unsigned char*)&client_version[0], client_version_len); + client_version[client_version_len] = '.'; + client_version_len++; + client_version[client_version_len] = 0; + unsigned int i = 0; + while (i < MONERO_SUPPORTED_CLIENT_SIZE) { + unsigned int monero_supported_client_len = strlen((char*)PIC(monero_supported_client[i])); + if ((monero_supported_client_len <= client_version_len) && + (os_memcmp((char*)PIC(monero_supported_client[i]), client_version, + monero_supported_client_len) == 0)) { + break; + } + i++; + } + if (i == MONERO_SUPPORTED_CLIENT_SIZE) { + THROW(SW_CLIENT_NOT_SUPPORTED); } - i++; - } - if (i == MONERO_SUPPORTED_CLIENT_SIZE) { - THROW(SW_CLIENT_NOT_SUPPORTED); - } - - monero_io_discard(0); - monero_init(); - monero_io_insert_u8(MONERO_VERSION_MAJOR); - monero_io_insert_u8(MONERO_VERSION_MINOR); - monero_io_insert_u8(MONERO_VERSION_MICRO); - return SW_OK; -} + monero_io_discard(0); + monero_init(); + monero_io_insert_u8(MONERO_VERSION_MAJOR); + monero_io_insert_u8(MONERO_VERSION_MINOR); + monero_io_insert_u8(MONERO_VERSION_MICRO); + return SW_OK; +} /* ----------------------------------------------------------------------- */ /* --- LOCK --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_lock() { - monero_io_discard(0); - monero_lock_and_throw(SW_SECURITY_LOCKED); - return SW_SECURITY_LOCKED; + monero_io_discard(0); + monero_lock_and_throw(SW_SECURITY_LOCKED); + return SW_SECURITY_LOCKED; } -void monero_lock_and_throw(int sw) { - G_monero_vstate.protocol_barrier = PROTOCOL_LOCKED; - snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "Security Err"); - snprintf(G_monero_vstate.ux_info2, sizeof(G_monero_vstate.ux_info2), "%x", sw); - ui_menu_info_display(0); - THROW(sw); +void monero_lock_and_throw(int sw) { + G_monero_vstate.protocol_barrier = PROTOCOL_LOCKED; + snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "Security Err"); + snprintf(G_monero_vstate.ux_info2, sizeof(G_monero_vstate.ux_info2), "%x", sw); + ui_menu_info_display(0); + THROW(sw); } diff --git a/src/monero_io.c b/src/monero_io.c index 21a523d..36ddd41 100644 --- a/src/monero_io.c +++ b/src/monero_io.c @@ -20,419 +20,397 @@ #include "monero_vars.h" #if defined(IODUMMYCRYPT) - #warning IODUMMYCRYPT activated +#warning IODUMMYCRYPT activated #endif #if defined(IONOCRYPT) - #warning IONOCRYPT activated +#warning IONOCRYPT activated #endif - /* * io_buff: contains current message part * io_off: offset in current message part * io_length: length of current message part */ - - /* ----------------------------------------------------------------------- */ /* MISC */ /* ----------------------------------------------------------------------- */ void monero_io_set_offset(unsigned int offset) { - if (offset == IO_OFFSET_END) { - G_monero_vstate.io_offset = G_monero_vstate.io_length; - } - else if (offset == IO_OFFSET_MARK) { - G_monero_vstate.io_offset = G_monero_vstate.io_mark; - } - else if (offset < G_monero_vstate.io_length) { - G_monero_vstate.io_offset = offset; - } - else { - THROW(ERROR_IO_OFFSET); - } -} - - -void monero_io_mark() { - G_monero_vstate.io_mark = G_monero_vstate.io_offset; + if (offset == IO_OFFSET_END) { + G_monero_vstate.io_offset = G_monero_vstate.io_length; + } else if (offset == IO_OFFSET_MARK) { + G_monero_vstate.io_offset = G_monero_vstate.io_mark; + } else if (offset < G_monero_vstate.io_length) { + G_monero_vstate.io_offset = offset; + } else { + THROW(ERROR_IO_OFFSET); + } } +void monero_io_mark() { G_monero_vstate.io_mark = G_monero_vstate.io_offset; } void monero_io_inserted(unsigned int len) { - G_monero_vstate.io_offset += len; - G_monero_vstate.io_length += len; + G_monero_vstate.io_offset += len; + G_monero_vstate.io_length += len; } void monero_io_discard(int clear) { - G_monero_vstate.io_length = 0; - G_monero_vstate.io_offset = 0; - G_monero_vstate.io_mark = 0; - if (clear) { - monero_io_clear(); - } + G_monero_vstate.io_length = 0; + G_monero_vstate.io_offset = 0; + G_monero_vstate.io_mark = 0; + if (clear) { + monero_io_clear(); + } } -void monero_io_clear() { - os_memset(G_monero_vstate.io_buffer, 0 , MONERO_IO_BUFFER_LENGTH); -} +void monero_io_clear() { os_memset(G_monero_vstate.io_buffer, 0, MONERO_IO_BUFFER_LENGTH); } /* ----------------------------------------------------------------------- */ /* INSERT data to be sent */ /* ----------------------------------------------------------------------- */ void monero_io_hole(unsigned int sz) { - if ((G_monero_vstate.io_length + sz) > MONERO_IO_BUFFER_LENGTH) { - THROW(ERROR_IO_FULL); - } - os_memmove(G_monero_vstate.io_buffer+G_monero_vstate.io_offset+sz, - G_monero_vstate.io_buffer+G_monero_vstate.io_offset, - G_monero_vstate.io_length-G_monero_vstate.io_offset); - G_monero_vstate.io_length += sz; + if ((G_monero_vstate.io_length + sz) > MONERO_IO_BUFFER_LENGTH) { + THROW(ERROR_IO_FULL); + } + os_memmove(G_monero_vstate.io_buffer + G_monero_vstate.io_offset + sz, + G_monero_vstate.io_buffer + G_monero_vstate.io_offset, + G_monero_vstate.io_length - G_monero_vstate.io_offset); + G_monero_vstate.io_length += sz; } -void monero_io_insert(unsigned char const *buff, unsigned int len) { - monero_io_hole(len); - os_memmove(G_monero_vstate.io_buffer+G_monero_vstate.io_offset, buff, len); - G_monero_vstate.io_offset += len; +void monero_io_insert(unsigned char const* buff, unsigned int len) { + monero_io_hole(len); + os_memmove(G_monero_vstate.io_buffer + G_monero_vstate.io_offset, buff, len); + G_monero_vstate.io_offset += len; } void monero_io_insert_hmac_for(unsigned char* buffer, int len, int type) { - //for now, only 32bytes block are allowed - if (len != 32) { - THROW(SW_WRONG_DATA); - } - - unsigned char hmac[32+1+4]; - - os_memmove(hmac,buffer,32); - hmac[32] = type; - if (type == TYPE_ALPHA) { - hmac[33] = (G_monero_vstate.tx_sign_cnt >> 0)&0xFF; - hmac[34] = (G_monero_vstate.tx_sign_cnt >> 8)&0xFF; - hmac[35] = (G_monero_vstate.tx_sign_cnt >> 16)&0xFF; - hmac[36] = (G_monero_vstate.tx_sign_cnt >> 24)&0xFF; - } else { - hmac[33] = 0; - hmac[34] = 0; - hmac[35] = 0; - hmac[36] = 0; - } - cx_hmac_sha256(G_monero_vstate.hmac_key, 32, - hmac, 37, - hmac, 32); - monero_io_insert(hmac,32); + // for now, only 32bytes block are allowed + if (len != 32) { + THROW(SW_WRONG_DATA); + } + + unsigned char hmac[32 + 1 + 4]; + + os_memmove(hmac, buffer, 32); + hmac[32] = type; + if (type == TYPE_ALPHA) { + hmac[33] = (G_monero_vstate.tx_sign_cnt >> 0) & 0xFF; + hmac[34] = (G_monero_vstate.tx_sign_cnt >> 8) & 0xFF; + hmac[35] = (G_monero_vstate.tx_sign_cnt >> 16) & 0xFF; + hmac[36] = (G_monero_vstate.tx_sign_cnt >> 24) & 0xFF; + } else { + hmac[33] = 0; + hmac[34] = 0; + hmac[35] = 0; + hmac[36] = 0; + } + cx_hmac_sha256(G_monero_vstate.hmac_key, 32, hmac, 37, hmac, 32); + monero_io_insert(hmac, 32); } void monero_io_insert_encrypt(unsigned char* buffer, int len, int type) { - //for now, only 32bytes block are allowed - if (len != 32) { - THROW(SW_WRONG_DATA); - } + // for now, only 32bytes block are allowed + if (len != 32) { + THROW(SW_WRONG_DATA); + } - monero_io_hole(len); + monero_io_hole(len); #if defined(IODUMMYCRYPT) - for (int i = 0; i>24; - G_monero_vstate.io_buffer[G_monero_vstate.io_offset+1] = v32>>16; - G_monero_vstate.io_buffer[G_monero_vstate.io_offset+2] = v32>>8; - G_monero_vstate.io_buffer[G_monero_vstate.io_offset+3] = v32>>0; - G_monero_vstate.io_offset += 4; +void monero_io_insert_u32(unsigned int v32) { + monero_io_hole(4); + G_monero_vstate.io_buffer[G_monero_vstate.io_offset + 0] = v32 >> 24; + G_monero_vstate.io_buffer[G_monero_vstate.io_offset + 1] = v32 >> 16; + G_monero_vstate.io_buffer[G_monero_vstate.io_offset + 2] = v32 >> 8; + G_monero_vstate.io_buffer[G_monero_vstate.io_offset + 3] = v32 >> 0; + G_monero_vstate.io_offset += 4; } -void monero_io_insert_u24(unsigned int v24) { - monero_io_hole(3); - G_monero_vstate.io_buffer[G_monero_vstate.io_offset+0] = v24>>16; - G_monero_vstate.io_buffer[G_monero_vstate.io_offset+1] = v24>>8; - G_monero_vstate.io_buffer[G_monero_vstate.io_offset+2] = v24>>0; - G_monero_vstate.io_offset += 3; +void monero_io_insert_u24(unsigned int v24) { + monero_io_hole(3); + G_monero_vstate.io_buffer[G_monero_vstate.io_offset + 0] = v24 >> 16; + G_monero_vstate.io_buffer[G_monero_vstate.io_offset + 1] = v24 >> 8; + G_monero_vstate.io_buffer[G_monero_vstate.io_offset + 2] = v24 >> 0; + G_monero_vstate.io_offset += 3; } -void monero_io_insert_u16(unsigned int v16) { - monero_io_hole(2); - G_monero_vstate.io_buffer[G_monero_vstate.io_offset+0] = v16>>8; - G_monero_vstate.io_buffer[G_monero_vstate.io_offset+1] = v16>>0; - G_monero_vstate.io_offset += 2; +void monero_io_insert_u16(unsigned int v16) { + monero_io_hole(2); + G_monero_vstate.io_buffer[G_monero_vstate.io_offset + 0] = v16 >> 8; + G_monero_vstate.io_buffer[G_monero_vstate.io_offset + 1] = v16 >> 0; + G_monero_vstate.io_offset += 2; } void monero_io_insert_u8(unsigned int v8) { - monero_io_hole(1); - G_monero_vstate.io_buffer[G_monero_vstate.io_offset+0] = v8; - G_monero_vstate.io_offset += 1; + monero_io_hole(1); + G_monero_vstate.io_buffer[G_monero_vstate.io_offset + 0] = v8; + G_monero_vstate.io_offset += 1; } void monero_io_insert_t(unsigned int T) { - if (T &0xFF00) { - monero_io_insert_u16(T); - } else { - monero_io_insert_u8(T); - } + if (T & 0xFF00) { + monero_io_insert_u16(T); + } else { + monero_io_insert_u8(T); + } } void monero_io_insert_tl(unsigned int T, unsigned int L) { - monero_io_insert_t(T); - if (L < 128) { - monero_io_insert_u8(L); - } else if (L < 256) { - monero_io_insert_u16(0x8100|L); - } else { - monero_io_insert_u8(0x82); - monero_io_insert_u16(L); - } + monero_io_insert_t(T); + if (L < 128) { + monero_io_insert_u8(L); + } else if (L < 256) { + monero_io_insert_u16(0x8100 | L); + } else { + monero_io_insert_u8(0x82); + monero_io_insert_u16(L); + } } -void monero_io_insert_tlv(unsigned int T, unsigned int L, unsigned char const *V) { - monero_io_insert_tl(T,L); - monero_io_insert(V, L); +void monero_io_insert_tlv(unsigned int T, unsigned int L, unsigned char const* V) { + monero_io_insert_tl(T, L); + monero_io_insert(V, L); } /* ----------------------------------------------------------------------- */ /* FECTH data from received buffer */ /* ----------------------------------------------------------------------- */ -int monero_io_fetch_available() { - return G_monero_vstate.io_length-G_monero_vstate.io_offset; -} +int monero_io_fetch_available() { return G_monero_vstate.io_length - G_monero_vstate.io_offset; } void monero_io_assert_available(int sz) { - if ((G_monero_vstate.io_length-G_monero_vstate.io_offset) < sz) { - THROW(SW_WRONG_LENGTH + (sz&0xFF)); - } + if ((G_monero_vstate.io_length - G_monero_vstate.io_offset) < sz) { + THROW(SW_WRONG_LENGTH + (sz & 0xFF)); + } } int monero_io_fetch(unsigned char* buffer, int len) { - monero_io_assert_available(len); - if (buffer) { - os_memmove(buffer, G_monero_vstate.io_buffer+G_monero_vstate.io_offset, len); - } - G_monero_vstate.io_offset += len; - return len; + monero_io_assert_available(len); + if (buffer) { + os_memmove(buffer, G_monero_vstate.io_buffer + G_monero_vstate.io_offset, len); + } + G_monero_vstate.io_offset += len; + return len; } +static void monero_io_verify_hmac_for(const unsigned char* buffer, int len, + unsigned char* expected_hmac, int type) { + // for now, only 32bytes block allowed + if (len != 32) { + THROW(SW_WRONG_DATA); + } -static void monero_io_verify_hmac_for(const unsigned char* buffer, int len, unsigned char *expected_hmac, int type) { - //for now, only 32bytes block allowed - if (len != 32) { - THROW(SW_WRONG_DATA); - } - - unsigned char hmac[37]; - os_memmove(hmac,buffer,32); - hmac[32] = type; -if (type == TYPE_ALPHA) { - hmac[33] = (G_monero_vstate.tx_sign_cnt >> 0)&0xFF; - hmac[34] = (G_monero_vstate.tx_sign_cnt >> 8)&0xFF; - hmac[35] = (G_monero_vstate.tx_sign_cnt >> 16)&0xFF; - hmac[36] = (G_monero_vstate.tx_sign_cnt >> 24)&0xFF; - } else { - hmac[33] = 0; - hmac[34] = 0; - hmac[35] = 0; - hmac[36] = 0; - } - cx_hmac_sha256(G_monero_vstate.hmac_key, 32, - hmac, 37, - hmac, 32); - if (os_memcmp(hmac, expected_hmac, 32)) { - monero_lock_and_throw(SW_SECURITY_HMAC); + unsigned char hmac[37]; + os_memmove(hmac, buffer, 32); + hmac[32] = type; + if (type == TYPE_ALPHA) { + hmac[33] = (G_monero_vstate.tx_sign_cnt >> 0) & 0xFF; + hmac[34] = (G_monero_vstate.tx_sign_cnt >> 8) & 0xFF; + hmac[35] = (G_monero_vstate.tx_sign_cnt >> 16) & 0xFF; + hmac[36] = (G_monero_vstate.tx_sign_cnt >> 24) & 0xFF; + } else { + hmac[33] = 0; + hmac[34] = 0; + hmac[35] = 0; + hmac[36] = 0; + } + cx_hmac_sha256(G_monero_vstate.hmac_key, 32, hmac, 37, hmac, 32); + if (os_memcmp(hmac, expected_hmac, 32)) { + monero_lock_and_throw(SW_SECURITY_HMAC); } } int monero_io_fetch_decrypt(unsigned char* buffer, int len, int type) { + // for now, only 32bytes block allowed + if (len != 32) { + THROW(SW_WRONG_LENGTH); + } + if (G_monero_vstate.tx_in_progress) { + monero_io_assert_available(len + 32); + monero_io_verify_hmac_for(G_monero_vstate.io_buffer + G_monero_vstate.io_offset, len, + G_monero_vstate.io_buffer + G_monero_vstate.io_offset + len, + type); + } else { + monero_io_assert_available(len); + } - //for now, only 32bytes block allowed - if (len != 32) { - THROW(SW_WRONG_LENGTH); - } - - if (G_monero_vstate.tx_in_progress) { - monero_io_assert_available(len+32); - monero_io_verify_hmac_for(G_monero_vstate.io_buffer+G_monero_vstate.io_offset, len, - G_monero_vstate.io_buffer+G_monero_vstate.io_offset + len, - type); - } else { - monero_io_assert_available(len); - } - - if (buffer) { + if (buffer) { #if defined(IODUMMYCRYPT) - for (int i = 0; i MAX_OUT) { - THROW(SW_IO_FULL); + // if IO_ASYNCH_REPLY has been set, + // monero_io_exchange will return when IO_RETURN_AFTER_TX will set in ui + if (io_flags & IO_ASYNCH_REPLY) { + monero_io_exchange(CHANNEL_APDU | IO_ASYNCH_REPLY, 0); } - os_memmove(G_io_apdu_buffer, G_monero_vstate.io_buffer+G_monero_vstate.io_offset, G_monero_vstate.io_length); - - if (io_flags & IO_RETURN_AFTER_TX) { - monero_io_exchange(CHANNEL_APDU |IO_RETURN_AFTER_TX, G_monero_vstate.io_length); - return 0; - } else { - monero_io_exchange(CHANNEL_APDU, G_monero_vstate.io_length); + // else send data now + else { + G_monero_vstate.io_offset = 0; + if (G_monero_vstate.io_length > MAX_OUT) { + THROW(SW_IO_FULL); + } + os_memmove(G_io_apdu_buffer, G_monero_vstate.io_buffer + G_monero_vstate.io_offset, + G_monero_vstate.io_length); + + if (io_flags & IO_RETURN_AFTER_TX) { + monero_io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, G_monero_vstate.io_length); + return 0; + } else { + monero_io_exchange(CHANNEL_APDU, G_monero_vstate.io_length); + } } - } - - //--- set up received data --- - G_monero_vstate.io_offset = 0; - G_monero_vstate.io_length = 0; - G_monero_vstate.io_protocol_version = G_io_apdu_buffer[0]; - G_monero_vstate.io_ins = G_io_apdu_buffer[1]; - G_monero_vstate.io_p1 = G_io_apdu_buffer[2]; - G_monero_vstate.io_p2 = G_io_apdu_buffer[3]; - G_monero_vstate.io_lc = 0; - G_monero_vstate.io_le = 0; - G_monero_vstate.io_lc = G_io_apdu_buffer[4]; - os_memmove(G_monero_vstate.io_buffer, G_io_apdu_buffer+5, G_monero_vstate.io_lc); - G_monero_vstate.io_length = G_monero_vstate.io_lc; - - return 0; -} - + //--- set up received data --- + G_monero_vstate.io_offset = 0; + G_monero_vstate.io_length = 0; + G_monero_vstate.io_protocol_version = G_io_apdu_buffer[0]; + G_monero_vstate.io_ins = G_io_apdu_buffer[1]; + G_monero_vstate.io_p1 = G_io_apdu_buffer[2]; + G_monero_vstate.io_p2 = G_io_apdu_buffer[3]; + G_monero_vstate.io_lc = 0; + G_monero_vstate.io_le = 0; + G_monero_vstate.io_lc = G_io_apdu_buffer[4]; + os_memmove(G_monero_vstate.io_buffer, G_io_apdu_buffer + 5, G_monero_vstate.io_lc); + G_monero_vstate.io_length = G_monero_vstate.io_lc; + + return 0; +} diff --git a/src/monero_key.c b/src/monero_key.c index 2c5bf5a..1ee2034 100644 --- a/src/monero_key.c +++ b/src/monero_key.c @@ -19,69 +19,60 @@ #include "monero_api.h" #include "monero_vars.h" - - /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ static const unsigned int crcTable[256] = { - 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535, - 0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD, - 0xE7B82D07,0x90BF1D91,0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D, - 0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC, - 0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,0x3B6E20C8,0x4C69105E,0xD56041E4, - 0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C, - 0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,0x26D930AC, - 0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F, - 0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB, - 0xB6662D3D,0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F, - 0x9FBFE4A5,0xE8B8D433,0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB, - 0x086D3D2D,0x91646C97,0xE6635C01,0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E, - 0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,0x8BBEB8EA, - 0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,0x4DB26158,0x3AB551CE, - 0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,0x4369E96A, - 0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9, - 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409, - 0xCE61E49F,0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81, - 0xB7BD5C3B,0xC0BA6CAD,0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739, - 0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8, - 0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,0xF00F9344,0x8708A3D2,0x1E01F268, - 0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0, - 0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,0xD6D6A3E8, - 0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B, - 0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF, - 0x4669BE79,0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703, - 0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7, - 0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A, - 0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE, - 0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,0x86D3D2D4,0xF1D4E242, - 0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6, - 0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, - 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D, - 0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5, - 0x47B2CF7F,0x30B5FFE9,0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605, - 0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94, - 0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D }; - -static unsigned long monero_crc32( unsigned long inCrc32, const void *buf, - size_t bufLen ) -{ + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D}; + +static unsigned long monero_crc32(unsigned long inCrc32, const void *buf, size_t bufLen) { unsigned long crc32; unsigned char *byteBuf; size_t i; /** accumulate crc32 for buffer **/ crc32 = inCrc32 ^ 0xFFFFFFFF; - byteBuf = (unsigned char*) buf; - for (i=0; i < bufLen; i++) { - crc32 = (crc32 >> 8) ^ crcTable[ (crc32 ^ byteBuf[i]) & 0xFF ]; + byteBuf = (unsigned char *)buf; + for (i = 0; i < bufLen; i++) { + crc32 = (crc32 >> 8) ^ crcTable[(crc32 ^ byteBuf[i]) & 0xFF]; } - return( crc32 ^ 0xFFFFFFFF ); + return (crc32 ^ 0xFFFFFFFF); } - void monero_clear_words() { - monero_nvm_write((void*)N_monero_pstate->words_list, NULL,sizeof(N_monero_pstate->words_list)); + monero_nvm_write((void *)N_monero_pstate->words_list, NULL, + sizeof(N_monero_pstate->words_list)); } /** @@ -91,89 +82,96 @@ void monero_clear_words() { * word_list * len : word_list length */ -static void monero_set_word(unsigned int n, unsigned int idx, unsigned int w_start, unsigned char* word_list, int len) { - while (w_start < idx) { - len -= 1 + word_list[0]; - if (len < 0) { - monero_clear_words(); - THROW(SW_WRONG_DATA+1); +static void monero_set_word(unsigned int n, unsigned int idx, unsigned int w_start, + unsigned char *word_list, int len) { + while (w_start < idx) { + len -= 1 + word_list[0]; + if (len < 0) { + monero_clear_words(); + THROW(SW_WRONG_DATA + 1); + } + word_list += 1 + word_list[0]; + w_start++; + } + + if ((w_start != idx) || (word_list[0] > (len - 1)) || (word_list[0] > 19)) { + monero_clear_words(); + THROW(SW_WRONG_DATA + 2); } - word_list += 1 + word_list[0]; - w_start++; - } - - if ((w_start != idx) || (word_list[0] > (len-1)) || (word_list[0] > 19)) { - monero_clear_words(); - THROW(SW_WRONG_DATA+2); - } - len = word_list[0]; - word_list++; - monero_nvm_write((void*)N_monero_pstate->words[n], word_list, len); + len = word_list[0]; + word_list++; + monero_nvm_write((void *)N_monero_pstate->words[n], word_list, len); } #define word_list_length 1626 -#define seed G_monero_vstate.b +#define seed G_monero_vstate.b int monero_apdu_manage_seedwords() { - unsigned int w_start, w_end; - unsigned short wc[4]; - - switch (G_monero_vstate.io_p1) { - //SETUP - case 1: - w_start = monero_io_fetch_u32(); - w_end = w_start+monero_io_fetch_u32(); - if ((w_start >= word_list_length) || (w_end > word_list_length) || (w_start > w_end)) { - THROW(SW_WRONG_DATA); - } - for (int i = 0; i<8; i++) { - unsigned int val = (seed[i*4+0]<<0) | (seed[i*4+1]<<8) | (seed[i*4+2]<<16) | (seed[i*4+3]<<24); - wc[0] = val % word_list_length; - wc[1] = ((val / word_list_length) + wc[0]) % word_list_length; - wc[2] = (((val / word_list_length) / word_list_length) + wc[1]) % word_list_length; - - for (int wi = 0; wi < 3; wi++) { - if ((wc[wi] >= w_start) && (wc[wi] < w_end)) { - monero_set_word(i*3+wi, wc[wi], w_start, G_monero_vstate.io_buffer+G_monero_vstate.io_offset, MONERO_IO_BUFFER_LENGTH-G_monero_vstate.io_offset); - } - } - } - - monero_io_discard(1); - if (G_monero_vstate.io_p2) { - for (int i = 0; i<24; i++) { - for (int j = 0; jwords[i][j]; - } - } - w_start = monero_crc32(0, G_monero_vstate.io_buffer, G_monero_vstate.io_p2*24)%24; - monero_nvm_write((void*)N_monero_pstate->words[24], (void*)N_monero_pstate->words[w_start], WORDS_MAX_LENGTH); - - #ifdef HAVE_UX_FLOW - //transform to list ready to display - unsigned char word[21]; - w_start = 0; - for (int i = 0; i<24; i++) { - w_end = N_monero_pstate->words[i][0]; - os_memmove(word, &N_monero_pstate->words[i][1], w_end); - word[w_end] = (i == 23) ? 0 : ' '; - w_end++; - monero_nvm_write(N_monero_pstate->words_list+w_start, word, w_end); - w_start += w_end; - } - #endif + unsigned int w_start, w_end; + unsigned short wc[4]; + + switch (G_monero_vstate.io_p1) { + // SETUP + case 1: + w_start = monero_io_fetch_u32(); + w_end = w_start + monero_io_fetch_u32(); + if ((w_start >= word_list_length) || (w_end > word_list_length) || (w_start > w_end)) { + THROW(SW_WRONG_DATA); + } + for (int i = 0; i < 8; i++) { + unsigned int val = (seed[i * 4 + 0] << 0) | (seed[i * 4 + 1] << 8) | + (seed[i * 4 + 2] << 16) | (seed[i * 4 + 3] << 24); + wc[0] = val % word_list_length; + wc[1] = ((val / word_list_length) + wc[0]) % word_list_length; + wc[2] = (((val / word_list_length) / word_list_length) + wc[1]) % word_list_length; + + for (int wi = 0; wi < 3; wi++) { + if ((wc[wi] >= w_start) && (wc[wi] < w_end)) { + monero_set_word(i * 3 + wi, wc[wi], w_start, + G_monero_vstate.io_buffer + G_monero_vstate.io_offset, + MONERO_IO_BUFFER_LENGTH - G_monero_vstate.io_offset); + } + } + } + + monero_io_discard(1); + if (G_monero_vstate.io_p2) { + for (int i = 0; i < 24; i++) { + for (int j = 0; j < G_monero_vstate.io_p2; j++) { + G_monero_vstate.io_buffer[i * G_monero_vstate.io_p2 + j] = + N_monero_pstate->words[i][j]; + } + } + w_start = + monero_crc32(0, G_monero_vstate.io_buffer, G_monero_vstate.io_p2 * 24) % 24; + monero_nvm_write((void *)N_monero_pstate->words[24], + (void *)N_monero_pstate->words[w_start], WORDS_MAX_LENGTH); + +#ifdef HAVE_UX_FLOW + // transform to list ready to display + unsigned char word[21]; + w_start = 0; + for (int i = 0; i < 24; i++) { + w_end = N_monero_pstate->words[i][0]; + os_memmove(word, &N_monero_pstate->words[i][1], w_end); + word[w_end] = (i == 23) ? 0 : ' '; + w_end++; + monero_nvm_write(N_monero_pstate->words_list + w_start, word, w_end); + w_start += w_end; + } +#endif + } + + break; + + // CLEAR + case 2: + monero_io_discard(0); + monero_clear_words(); + break; } - break; - - //CLEAR - case 2: - monero_io_discard(0); - monero_clear_words(); - break; - } - - return SW_OK; + return SW_OK; } #undef seed #undef word_list_length @@ -181,232 +179,223 @@ int monero_apdu_manage_seedwords() { /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -static void monero_payment_id_to_str(const unsigned char *payment_id, char* str) { - for (int i = 0; i<8; i++) { - if (payment_id[i] <= 0xF) { - snprintf(str+i*2,3, "0%x",payment_id[i]); - } else { - snprintf(str+i*2,3, "%x",payment_id[i]); +static void monero_payment_id_to_str(const unsigned char *payment_id, char *str) { + for (int i = 0; i < 8; i++) { + if (payment_id[i] <= 0xF) { + snprintf(str + i * 2, 3, "0%x", payment_id[i]); + } else { + snprintf(str + i * 2, 3, "%x", payment_id[i]); + } } - } } -int monero_apdu_display_address() { - unsigned int major; - unsigned int minor; - unsigned char index[8]; - unsigned char payment_id[8]; - unsigned char C[32]; - unsigned char D[32]; - - //fetch - monero_io_fetch(index, 8); - monero_io_fetch(payment_id, 8); - monero_io_discard(0); - - major = (index[0]<<0)|(index[1]<<8)|(index[2]<<16)|(index[3]<<24); - minor = (index[4]<<0)|(index[5]<<8)|(index[6]<<16)|(index[7]<<24); - if ((minor|major) && (G_monero_vstate.io_p1 == 1)) { - THROW(SW_WRONG_DATA); - } - - //retrieve pub keys - if (minor|major) { - monero_get_subaddress(C, D, index); - } else { - os_memmove(C, G_monero_vstate.A, 32); - os_memmove(D, G_monero_vstate.B, 32); - } - - //prepare UI - if (minor|major) { - G_monero_vstate.disp_addr_M = major; - G_monero_vstate.disp_addr_m = minor; - G_monero_vstate.disp_addr_mode = DISP_SUB; - } else { - if (G_monero_vstate.io_p1 == 1) { - monero_payment_id_to_str(payment_id, G_monero_vstate.payment_id); - G_monero_vstate.disp_addr_mode = DISP_INTEGRATED; +int monero_apdu_display_address() { + unsigned int major; + unsigned int minor; + unsigned char index[8]; + unsigned char payment_id[8]; + unsigned char C[32]; + unsigned char D[32]; + + // fetch + monero_io_fetch(index, 8); + monero_io_fetch(payment_id, 8); + monero_io_discard(0); + + major = (index[0] << 0) | (index[1] << 8) | (index[2] << 16) | (index[3] << 24); + minor = (index[4] << 0) | (index[5] << 8) | (index[6] << 16) | (index[7] << 24); + if ((minor | major) && (G_monero_vstate.io_p1 == 1)) { + THROW(SW_WRONG_DATA); + } + + // retrieve pub keys + if (minor | major) { + monero_get_subaddress(C, D, index); } else { - G_monero_vstate.disp_addr_mode = DISP_MAIN; + os_memmove(C, G_monero_vstate.A, 32); + os_memmove(D, G_monero_vstate.B, 32); } - } - ui_menu_any_pubaddr_display(0, C, D, (minor|major)?1:0, (G_monero_vstate.io_p1 == 1)? payment_id : NULL); - return 0; + // prepare UI + if (minor | major) { + G_monero_vstate.disp_addr_M = major; + G_monero_vstate.disp_addr_m = minor; + G_monero_vstate.disp_addr_mode = DISP_SUB; + } else { + if (G_monero_vstate.io_p1 == 1) { + monero_payment_id_to_str(payment_id, G_monero_vstate.payment_id); + G_monero_vstate.disp_addr_mode = DISP_INTEGRATED; + } else { + G_monero_vstate.disp_addr_mode = DISP_MAIN; + } + } + + ui_menu_any_pubaddr_display(0, C, D, (minor | major) ? 1 : 0, + (G_monero_vstate.io_p1 == 1) ? payment_id : NULL); + return 0; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -int is_fake_view_key(unsigned char *s) { - return os_memcmp(s,C_FAKE_SEC_VIEW_KEY, 32) == 0; -} +int is_fake_view_key(unsigned char *s) { return os_memcmp(s, C_FAKE_SEC_VIEW_KEY, 32) == 0; } -int is_fake_spend_key(unsigned char *s) { - return os_memcmp(s,C_FAKE_SEC_SPEND_KEY, 32) == 0; -} +int is_fake_spend_key(unsigned char *s) { return os_memcmp(s, C_FAKE_SEC_SPEND_KEY, 32) == 0; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_put_key() { + unsigned char raw[32]; + unsigned char pub[32]; + unsigned char sec[32]; + if (G_monero_vstate.io_length != (32 * 2 + 32 * 2 + 95)) { + THROW(SW_WRONG_LENGTH); + return SW_WRONG_LENGTH; + } - unsigned char raw[32]; - unsigned char pub[32]; - unsigned char sec[32]; - - if (G_monero_vstate.io_length != (32*2 + 32*2 + 95)) { - THROW(SW_WRONG_LENGTH); - return SW_WRONG_LENGTH; - } - - //view key - monero_io_fetch(sec, 32); - monero_io_fetch(pub, 32); - monero_ecmul_G(raw,sec); - if (os_memcmp(pub, raw, 32)) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } - nvm_write((void*)N_monero_pstate->a, sec, 32); - - //spend key - monero_io_fetch(sec, 32); - monero_io_fetch(pub, 32); - monero_ecmul_G(raw,sec); - if (os_memcmp(pub, raw, 32)) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } - nvm_write((void*)N_monero_pstate->b, sec, 32); - + // view key + monero_io_fetch(sec, 32); + monero_io_fetch(pub, 32); + monero_ecmul_G(raw, sec); + if (os_memcmp(pub, raw, 32)) { + THROW(SW_WRONG_DATA); + return SW_WRONG_DATA; + } + nvm_write((void *)N_monero_pstate->a, sec, 32); + + // spend key + monero_io_fetch(sec, 32); + monero_io_fetch(pub, 32); + monero_ecmul_G(raw, sec); + if (os_memcmp(pub, raw, 32)) { + THROW(SW_WRONG_DATA); + return SW_WRONG_DATA; + } + nvm_write((void *)N_monero_pstate->b, sec, 32); - //change mode - unsigned char key_mode = KEY_MODE_EXTERNAL; - nvm_write((void*)&N_monero_pstate->key_mode, &key_mode, 1); + // change mode + unsigned char key_mode = KEY_MODE_EXTERNAL; + nvm_write((void *)&N_monero_pstate->key_mode, &key_mode, 1); - monero_io_discard(1); + monero_io_discard(1); - return SW_OK; + return SW_OK; } - /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_get_key() { + monero_io_discard(1); + switch (G_monero_vstate.io_p1) { + // get pub + case 1: + // view key + monero_io_insert(G_monero_vstate.A, 32); + // spend key + monero_io_insert(G_monero_vstate.B, 32); + // public base address + monero_base58_public_key((char *)G_monero_vstate.io_buffer + G_monero_vstate.io_offset, + G_monero_vstate.A, G_monero_vstate.B, 0, NULL); + monero_io_inserted(95); + break; + + // get private + case 2: + // view key + if (G_monero_vstate.export_view_key == EXPORT_VIEW_KEY) { + monero_io_insert(G_monero_vstate.a, 32); + } else { + ui_export_viewkey_display(0); + return 0; + } + break; + +#if DEBUG_HWDEVICE + // get info + case 3: { + unsigned int path[5]; + unsigned char seed[32]; + + // m/44'/128'/0'/0/0 + path[0] = 0x8000002C; + path[1] = 0x80000080; + path[2] = 0x80000000; + path[3] = 0x00000000; + path[4] = 0x00000000; + + os_perso_derive_node_bip32(CX_CURVE_SECP256K1, path, 5, seed, G_monero_vstate.a); + monero_io_insert(seed, 32); + + monero_io_insert(G_monero_vstate.b, 32); + monero_io_insert(G_monero_vstate.a, 32); + + break; + } - monero_io_discard(1); - switch (G_monero_vstate.io_p1) { - //get pub - case 1: - //view key - monero_io_insert(G_monero_vstate.A, 32); - //spend key - monero_io_insert(G_monero_vstate.B, 32); - //public base address - monero_base58_public_key((char*)G_monero_vstate.io_buffer+G_monero_vstate.io_offset, G_monero_vstate.A, G_monero_vstate.B, 0, NULL); - monero_io_inserted(95); - break; - - //get private - case 2: - //view key - if (G_monero_vstate.export_view_key == EXPORT_VIEW_KEY) { - monero_io_insert(G_monero_vstate.a, 32); - } else { - ui_export_viewkey_display(0); - return 0; - } - break; - - #if DEBUG_HWDEVICE - //get info - case 3:{ - unsigned int path[5]; - unsigned char seed[32]; - - // m/44'/128'/0'/0/0 - path[0] = 0x8000002C; - path[1] = 0x80000080; - path[2] = 0x80000000; - path[3] = 0x00000000; - path[4] = 0x00000000; - - os_perso_derive_node_bip32(CX_CURVE_SECP256K1, path, 5 , seed, G_monero_vstate.a); - monero_io_insert(seed, 32); - - monero_io_insert(G_monero_vstate.b, 32); - monero_io_insert(G_monero_vstate.a, 32); + // get info + case 4: + monero_io_insert(G_monero_vstate.a, 32); + monero_io_insert(G_monero_vstate.b, 32); + break; +#endif - break; + default: + THROW(SW_WRONG_P1P2); } - - //get info - case 4 : - monero_io_insert(G_monero_vstate.a, 32); - monero_io_insert(G_monero_vstate.b, 32); - break; - #endif - - default: - THROW(SW_WRONG_P1P2); - } - return SW_OK; + return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_verify_key() { - unsigned char pub[32]; - unsigned char priv[32]; - unsigned char computed_pub[32]; - unsigned int verified = 0; - - monero_io_fetch_decrypt_key(priv); - monero_io_fetch(pub, 32); - switch (G_monero_vstate.io_p1) { - case 0: - monero_secret_key_to_public_key(computed_pub, priv); - break; - case 1: - os_memmove(computed_pub, G_monero_vstate.A, 32); - break; - case 2: - os_memmove(computed_pub, G_monero_vstate.B, 32); - break; - default: - THROW(SW_WRONG_P1P2); - } - if (os_memcmp(computed_pub, pub, 32) ==0 ) { - verified = 1; - } - - monero_io_discard(1); - monero_io_insert_u32(verified); - return SW_OK; -} - + unsigned char pub[32]; + unsigned char priv[32]; + unsigned char computed_pub[32]; + unsigned int verified = 0; + + monero_io_fetch_decrypt_key(priv); + monero_io_fetch(pub, 32); + switch (G_monero_vstate.io_p1) { + case 0: + monero_secret_key_to_public_key(computed_pub, priv); + break; + case 1: + os_memmove(computed_pub, G_monero_vstate.A, 32); + break; + case 2: + os_memmove(computed_pub, G_monero_vstate.B, 32); + break; + default: + THROW(SW_WRONG_P1P2); + } + if (os_memcmp(computed_pub, pub, 32) == 0) { + verified = 1; + } + monero_io_discard(1); + monero_io_insert_u32(verified); + return SW_OK; +} /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ #define CHACHA8_KEY_TAIL 0x8c int monero_apdu_get_chacha8_prekey(/*char *prekey*/) { - unsigned char abt[65]; - unsigned char pre[32]; - - monero_io_discard(0); - os_memmove(abt, G_monero_vstate.a, 32); - os_memmove(abt+32, G_monero_vstate.b, 32); - abt[64] = CHACHA8_KEY_TAIL; - monero_keccak_F(abt, 65, pre); - monero_io_insert((unsigned char*)G_monero_vstate.keccakF.acc, 200); - return SW_OK; + unsigned char abt[65]; + unsigned char pre[32]; + + monero_io_discard(0); + os_memmove(abt, G_monero_vstate.a, 32); + os_memmove(abt + 32, G_monero_vstate.b, 32); + abt[64] = CHACHA8_KEY_TAIL; + monero_keccak_F(abt, 65, pre); + monero_io_insert((unsigned char *)G_monero_vstate.keccakF.acc, 200); + return SW_OK; } #undef CHACHA8_KEY_TAIL @@ -414,281 +403,285 @@ int monero_apdu_get_chacha8_prekey(/*char *prekey*/) { /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_sc_add(/*unsigned char *r, unsigned char *s1, unsigned char *s2*/) { - unsigned char s1[32]; - unsigned char s2[32]; - unsigned char r[32]; - //fetch - monero_io_fetch_decrypt(s1,32, TYPE_SCALAR); - monero_io_fetch_decrypt(s2,32, TYPE_SCALAR); - monero_io_discard(0); - if (G_monero_vstate.tx_in_progress) { - // During a transaction, only "last_derive_secret_key+last_get_subaddress_secret_key" - // is allowed, in order to match the call at - // https://github.com/monero-project/monero/blob/v0.15.0.5/src/cryptonote_basic/cryptonote_format_utils.cpp#L331 - // - // hwdev.sc_secret_add(scalar_step2, scalar_step1,subaddr_sk); - if ((os_memcmp(s1, G_monero_vstate.last_derive_secret_key, 32)!=0) || - (os_memcmp(s2, G_monero_vstate.last_get_subaddress_secret_key, 32)!=0)) { - monero_lock_and_throw(SW_WRONG_DATA); + unsigned char s1[32]; + unsigned char s2[32]; + unsigned char r[32]; + // fetch + monero_io_fetch_decrypt(s1, 32, TYPE_SCALAR); + monero_io_fetch_decrypt(s2, 32, TYPE_SCALAR); + monero_io_discard(0); + if (G_monero_vstate.tx_in_progress) { + // During a transaction, only "last_derive_secret_key+last_get_subaddress_secret_key" + // is allowed, in order to match the call at + // https://github.com/monero-project/monero/blob/v0.15.0.5/src/cryptonote_basic/cryptonote_format_utils.cpp#L331 + // + // hwdev.sc_secret_add(scalar_step2, scalar_step1,subaddr_sk); + if ((os_memcmp(s1, G_monero_vstate.last_derive_secret_key, 32) != 0) || + (os_memcmp(s2, G_monero_vstate.last_get_subaddress_secret_key, 32) != 0)) { + monero_lock_and_throw(SW_WRONG_DATA); + } } - } - monero_addm(r,s1,s2); - monero_io_insert_encrypt(r,32, TYPE_SCALAR); - return SW_OK; + monero_addm(r, s1, s2); + monero_io_insert_encrypt(r, 32, TYPE_SCALAR); + return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_scal_mul_key(/*const rct::key &pub, const rct::key &sec, rct::key mulkey*/) { - unsigned char pub[32]; - unsigned char sec[32]; - unsigned char r[32]; - //fetch - monero_io_fetch(pub,32); - monero_io_fetch_decrypt_key(sec); - monero_io_discard(0); - - monero_ecmul_k(r,pub,sec); - monero_io_insert(r, 32); - return SW_OK; + unsigned char pub[32]; + unsigned char sec[32]; + unsigned char r[32]; + // fetch + monero_io_fetch(pub, 32); + monero_io_fetch_decrypt_key(sec); + monero_io_discard(0); + + monero_ecmul_k(r, pub, sec); + monero_io_insert(r, 32); + return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_scal_mul_base(/*const rct::key &sec, rct::key mulkey*/) { - unsigned char sec[32]; - unsigned char r[32]; - //fetch - monero_io_fetch_decrypt(sec,32, TYPE_SCALAR); - monero_io_discard(0); - - monero_ecmul_G(r,sec); - monero_io_insert(r, 32); - return SW_OK; + unsigned char sec[32]; + unsigned char r[32]; + // fetch + monero_io_fetch_decrypt(sec, 32, TYPE_SCALAR); + monero_io_discard(0); + + monero_ecmul_G(r, sec); + monero_io_insert(r, 32); + return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_generate_keypair(/*crypto::public_key &pub, crypto::secret_key &sec*/) { - unsigned char sec[32]; - unsigned char pub[32]; - - monero_io_discard(0); - monero_generate_keypair(pub,sec); - monero_io_insert(pub,32); - monero_io_insert_encrypt(sec,32, TYPE_SCALAR); - return SW_OK; -} + unsigned char sec[32]; + unsigned char pub[32]; + monero_io_discard(0); + monero_generate_keypair(pub, sec); + monero_io_insert(pub, 32); + monero_io_insert_encrypt(sec, 32, TYPE_SCALAR); + return SW_OK; +} /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -int monero_apdu_secret_key_to_public_key(/*const crypto::secret_key &sec, crypto::public_key &pub*/) { - unsigned char sec[32]; - unsigned char pub[32]; - //fetch - monero_io_fetch_decrypt(sec,32, TYPE_SCALAR); - monero_io_discard(0); - //pub - monero_ecmul_G(pub,sec); - //pub key - monero_io_insert(pub,32); - return SW_OK; +int monero_apdu_secret_key_to_public_key( + /*const crypto::secret_key &sec, crypto::public_key &pub*/) { + unsigned char sec[32]; + unsigned char pub[32]; + // fetch + monero_io_fetch_decrypt(sec, 32, TYPE_SCALAR); + monero_io_discard(0); + // pub + monero_ecmul_G(pub, sec); + // pub key + monero_io_insert(pub, 32); + return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_generate_key_derivation(/*const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_derivation &derivation*/) { - unsigned char pub[32]; - unsigned char sec[32]; - unsigned char drv[32]; - //fetch - monero_io_fetch(pub,32); - monero_io_fetch_decrypt_key(sec); + unsigned char pub[32]; + unsigned char sec[32]; + unsigned char drv[32]; + // fetch + monero_io_fetch(pub, 32); + monero_io_fetch_decrypt_key(sec); - monero_io_discard(0); + monero_io_discard(0); - //Derive and keep - monero_generate_key_derivation(drv, pub, sec); + // Derive and keep + monero_generate_key_derivation(drv, pub, sec); - monero_io_insert_encrypt(drv,32, TYPE_DERIVATION); - return SW_OK; + monero_io_insert_encrypt(drv, 32, TYPE_DERIVATION); + return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -int monero_apdu_derivation_to_scalar(/*const crypto::key_derivation &derivation, const size_t output_index, ec_scalar &res*/) { - unsigned char derivation[32]; - unsigned int output_index; - unsigned char res[32]; +int monero_apdu_derivation_to_scalar( + /*const crypto::key_derivation &derivation, const size_t output_index, ec_scalar &res*/) { + unsigned char derivation[32]; + unsigned int output_index; + unsigned char res[32]; - //fetch - monero_io_fetch_decrypt(derivation,32, TYPE_DERIVATION); - output_index = monero_io_fetch_u32(); - monero_io_discard(0); + // fetch + monero_io_fetch_decrypt(derivation, 32, TYPE_DERIVATION); + output_index = monero_io_fetch_u32(); + monero_io_discard(0); - //pub - monero_derivation_to_scalar(res, derivation, output_index); + // pub + monero_derivation_to_scalar(res, derivation, output_index); - //pub key - monero_io_insert_encrypt(res,32, TYPE_SCALAR); - return SW_OK; + // pub key + monero_io_insert_encrypt(res, 32, TYPE_SCALAR); + return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_derive_public_key(/*const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::public_key &pub, public_key &derived_pub*/) { - unsigned char derivation[32]; - unsigned int output_index; - unsigned char pub[32]; - unsigned char drvpub[32]; - - //fetch - monero_io_fetch_decrypt(derivation,32, TYPE_DERIVATION); - output_index = monero_io_fetch_u32(); - monero_io_fetch(pub, 32); - monero_io_discard(0); - - //pub - monero_derive_public_key(drvpub, derivation, output_index, pub); - - //pub key - monero_io_insert(drvpub,32); - return SW_OK; + unsigned char derivation[32]; + unsigned int output_index; + unsigned char pub[32]; + unsigned char drvpub[32]; + + // fetch + monero_io_fetch_decrypt(derivation, 32, TYPE_DERIVATION); + output_index = monero_io_fetch_u32(); + monero_io_fetch(pub, 32); + monero_io_discard(0); + + // pub + monero_derive_public_key(drvpub, derivation, output_index, pub); + + // pub key + monero_io_insert(drvpub, 32); + return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_derive_secret_key(/*const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::secret_key &sec, secret_key &derived_sec*/){ - unsigned char derivation[32]; - unsigned int output_index; - unsigned char sec[32]; - unsigned char drvsec[32]; - - //fetch - monero_io_fetch_decrypt(derivation,32, TYPE_DERIVATION); - output_index = monero_io_fetch_u32(); - monero_io_fetch_decrypt_key(sec); - monero_io_discard(0); - - //pub - monero_derive_secret_key(drvsec, derivation, output_index, sec); - - //sec key - os_memmove(G_monero_vstate.last_derive_secret_key, drvsec, 32); - monero_io_insert_encrypt(drvsec,32, TYPE_SCALAR); - return SW_OK; + unsigned char derivation[32]; + unsigned int output_index; + unsigned char sec[32]; + unsigned char drvsec[32]; + + // fetch + monero_io_fetch_decrypt(derivation, 32, TYPE_DERIVATION); + output_index = monero_io_fetch_u32(); + monero_io_fetch_decrypt_key(sec); + monero_io_discard(0); + + // pub + monero_derive_secret_key(drvsec, derivation, output_index, sec); + + // sec key + os_memmove(G_monero_vstate.last_derive_secret_key, drvsec, 32); + monero_io_insert_encrypt(drvsec, 32, TYPE_SCALAR); + return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -int monero_apdu_generate_key_image(/*const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image*/){ - unsigned char pub[32]; - unsigned char sec[32]; - unsigned char image[32]; +int monero_apdu_generate_key_image( + /*const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image*/) { + unsigned char pub[32]; + unsigned char sec[32]; + unsigned char image[32]; - //fetch - monero_io_fetch(pub,32); - monero_io_fetch_decrypt(sec, 32, TYPE_SCALAR); - monero_io_discard(0); + // fetch + monero_io_fetch(pub, 32); + monero_io_fetch_decrypt(sec, 32, TYPE_SCALAR); + monero_io_discard(0); - //pub - monero_generate_key_image(image, pub, sec); + // pub + monero_generate_key_image(image, pub, sec); - //pub key - monero_io_insert(image,32); - return SW_OK; + // pub key + monero_io_insert(image, 32); + return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_derive_subaddress_public_key(/*const crypto::public_key &pub, const crypto::key_derivation &derivation, const std::size_t output_index, public_key &derived_pub*/) { - unsigned char pub[32]; - unsigned char derivation[32]; - unsigned int output_index; - unsigned char sub_pub[32]; - - //fetch - monero_io_fetch(pub,32); - monero_io_fetch_decrypt(derivation, 32, TYPE_DERIVATION); - output_index = monero_io_fetch_u32(); - monero_io_discard(0); - - //pub - monero_derive_subaddress_public_key(sub_pub, pub, derivation, output_index); - //pub key - monero_io_insert(sub_pub,32); - return SW_OK; + unsigned char pub[32]; + unsigned char derivation[32]; + unsigned int output_index; + unsigned char sub_pub[32]; + + // fetch + monero_io_fetch(pub, 32); + monero_io_fetch_decrypt(derivation, 32, TYPE_DERIVATION); + output_index = monero_io_fetch_u32(); + monero_io_discard(0); + + // pub + monero_derive_subaddress_public_key(sub_pub, pub, derivation, output_index); + // pub key + monero_io_insert(sub_pub, 32); + return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -int monero_apdu_get_subaddress(/*const cryptonote::subaddress_index& index, cryptonote::account_public_address &address*/) { - unsigned char index[8]; - unsigned char C[32]; - unsigned char D[32]; +int monero_apdu_get_subaddress( + /*const cryptonote::subaddress_index& index, cryptonote::account_public_address &address*/) { + unsigned char index[8]; + unsigned char C[32]; + unsigned char D[32]; - //fetch - monero_io_fetch(index,8); - monero_io_discard(0); + // fetch + monero_io_fetch(index, 8); + monero_io_discard(0); - //pub - monero_get_subaddress(C,D,index); + // pub + monero_get_subaddress(C, D, index); - //pub key - monero_io_insert(C,32); - monero_io_insert(D,32); - return SW_OK; + // pub key + monero_io_insert(C, 32); + monero_io_insert(D, 32); + return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -int monero_apdu_get_subaddress_spend_public_key(/*const cryptonote::subaddress_index& index, crypto::public_key D*/) { - unsigned char index[8]; - unsigned char D[32]; +int monero_apdu_get_subaddress_spend_public_key( + /*const cryptonote::subaddress_index& index, crypto::public_key D*/) { + unsigned char index[8]; + unsigned char D[32]; - //fetch - monero_io_fetch(index,8); - monero_io_discard(1); + // fetch + monero_io_fetch(index, 8); + monero_io_discard(1); - //pub - monero_get_subaddress_spend_public_key(D, index); + // pub + monero_get_subaddress_spend_public_key(D, index); - //pub key - monero_io_insert(D,32); + // pub key + monero_io_insert(D, 32); - return SW_OK; + return SW_OK; } /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_get_subaddress_secret_key(/*const crypto::secret_key& sec, const cryptonote::subaddress_index& index, crypto::secret_key &sub_sec*/) { - unsigned char sec[32]; - unsigned char index[8]; - unsigned char sub_sec[32]; + unsigned char sec[32]; + unsigned char index[8]; + unsigned char sub_sec[32]; - monero_io_fetch_decrypt_key(sec); - monero_io_fetch(index,8); - monero_io_discard(0); + monero_io_fetch_decrypt_key(sec); + monero_io_fetch(index, 8); + monero_io_discard(0); - monero_get_subaddress_secret_key(sub_sec,sec,index); + monero_get_subaddress_secret_key(sub_sec, sec, index); - os_memmove(G_monero_vstate.last_get_subaddress_secret_key, sub_sec,32); - monero_io_insert_encrypt(sub_sec,32, TYPE_SCALAR); - return SW_OK; + os_memmove(G_monero_vstate.last_get_subaddress_secret_key, sub_sec, 32); + monero_io_insert_encrypt(sub_sec, 32, TYPE_SCALAR); + return SW_OK; } /* ----------------------------------------------------------------------- */ @@ -696,86 +689,89 @@ int monero_apdu_get_subaddress_secret_key(/*const crypto::secret_key& sec, const /* ----------------------------------------------------------------------- */ int monero_apu_generate_txout_keys(/*size_t tx_version, crypto::secret_key tx_sec, crypto::public_key Aout, crypto::public_key Bout, size_t output_index, bool is_change, bool is_subaddress, bool need_additional_key*/) { - //IN - unsigned int tx_version; - unsigned char tx_key[32]; - unsigned char *txkey_pub; - unsigned char *Aout; - unsigned char *Bout; - unsigned int output_index; - unsigned char is_change; - unsigned char is_subaddress; - unsigned char need_additional_txkeys; - unsigned char additional_txkey_sec[32]; - //OUT - unsigned char additional_txkey_pub[32]; - #define amount_key tx_key - #define out_eph_public_key additional_txkey_sec - //TMP - unsigned char derivation[32]; - - tx_version = monero_io_fetch_u32(); - monero_io_fetch_decrypt_key(tx_key); - txkey_pub = G_monero_vstate.io_buffer+G_monero_vstate.io_offset; monero_io_fetch(NULL,32); - Aout = G_monero_vstate.io_buffer+G_monero_vstate.io_offset; monero_io_fetch(NULL,32); - Bout = G_monero_vstate.io_buffer+G_monero_vstate.io_offset; monero_io_fetch(NULL,32); - output_index = monero_io_fetch_u32(); - is_change = monero_io_fetch_u8(); - is_subaddress = monero_io_fetch_u8(); - need_additional_txkeys = monero_io_fetch_u8(); - if (need_additional_txkeys) { - monero_io_fetch_decrypt_key(additional_txkey_sec); - } else { - monero_io_fetch(NULL,32); - } - - //update outkeys hash control - if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { - if (G_monero_vstate.io_protocol_version >= 2) { - monero_sha256_outkeys_update(Aout,32); - monero_sha256_outkeys_update(Bout,32); - monero_sha256_outkeys_update(&is_change,1); + // IN + unsigned int tx_version; + unsigned char tx_key[32]; + unsigned char *txkey_pub; + unsigned char *Aout; + unsigned char *Bout; + unsigned int output_index; + unsigned char is_change; + unsigned char is_subaddress; + unsigned char need_additional_txkeys; + unsigned char additional_txkey_sec[32]; + // OUT + unsigned char additional_txkey_pub[32]; +#define amount_key tx_key +#define out_eph_public_key additional_txkey_sec + // TMP + unsigned char derivation[32]; + + tx_version = monero_io_fetch_u32(); + monero_io_fetch_decrypt_key(tx_key); + txkey_pub = G_monero_vstate.io_buffer + G_monero_vstate.io_offset; + monero_io_fetch(NULL, 32); + Aout = G_monero_vstate.io_buffer + G_monero_vstate.io_offset; + monero_io_fetch(NULL, 32); + Bout = G_monero_vstate.io_buffer + G_monero_vstate.io_offset; + monero_io_fetch(NULL, 32); + output_index = monero_io_fetch_u32(); + is_change = monero_io_fetch_u8(); + is_subaddress = monero_io_fetch_u8(); + need_additional_txkeys = monero_io_fetch_u8(); + if (need_additional_txkeys) { + monero_io_fetch_decrypt_key(additional_txkey_sec); + } else { + monero_io_fetch(NULL, 32); + } + + // update outkeys hash control + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { + if (G_monero_vstate.io_protocol_version >= 2) { + monero_sha256_outkeys_update(Aout, 32); + monero_sha256_outkeys_update(Bout, 32); + monero_sha256_outkeys_update(&is_change, 1); + } } - } - // make additional tx pubkey if necessary - if (need_additional_txkeys) { - if (is_subaddress) { - monero_ecmul_k(additional_txkey_pub, Bout, additional_txkey_sec); + // make additional tx pubkey if necessary + if (need_additional_txkeys) { + if (is_subaddress) { + monero_ecmul_k(additional_txkey_pub, Bout, additional_txkey_sec); + } else { + monero_ecmul_G(additional_txkey_pub, additional_txkey_sec); + } } else { - monero_ecmul_G(additional_txkey_pub, additional_txkey_sec); + os_memset(additional_txkey_pub, 0, 32); } - } else { - os_memset(additional_txkey_pub, 0, 32); - } - - //derivation - if (is_change) { - monero_generate_key_derivation(derivation, txkey_pub, G_monero_vstate.a); - } else { - monero_generate_key_derivation(derivation, Aout, (is_subaddress && need_additional_txkeys) ? additional_txkey_sec : tx_key); - } - - //compute amount key AKout (scalar1), version is always greater than 1 - monero_derivation_to_scalar(amount_key, derivation, output_index); - if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { - if (G_monero_vstate.io_protocol_version >= 2) { - monero_sha256_outkeys_update(amount_key,32); - } - } - - //compute ephemeral output key - monero_derive_public_key(out_eph_public_key, derivation, output_index, Bout); - - //send all - monero_io_discard(0); - monero_io_insert_encrypt(amount_key,32, TYPE_AMOUNT_KEY); - monero_io_insert(out_eph_public_key, 32); - if (need_additional_txkeys) { - monero_io_insert(additional_txkey_pub, 32); - } - G_monero_vstate.tx_output_cnt++; - return SW_OK; -} + // derivation + if (is_change) { + monero_generate_key_derivation(derivation, txkey_pub, G_monero_vstate.a); + } else { + monero_generate_key_derivation( + derivation, Aout, + (is_subaddress && need_additional_txkeys) ? additional_txkey_sec : tx_key); + } + // compute amount key AKout (scalar1), version is always greater than 1 + monero_derivation_to_scalar(amount_key, derivation, output_index); + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { + if (G_monero_vstate.io_protocol_version >= 2) { + monero_sha256_outkeys_update(amount_key, 32); + } + } + + // compute ephemeral output key + monero_derive_public_key(out_eph_public_key, derivation, output_index, Bout); + + // send all + monero_io_discard(0); + monero_io_insert_encrypt(amount_key, 32, TYPE_AMOUNT_KEY); + monero_io_insert(out_eph_public_key, 32); + if (need_additional_txkeys) { + monero_io_insert(additional_txkey_pub, 32); + } + G_monero_vstate.tx_output_cnt++; + return SW_OK; +} diff --git a/src/monero_main.c b/src/monero_main.c index 15faa20..ea1d993 100644 --- a/src/monero_main.c +++ b/src/monero_main.c @@ -26,10 +26,10 @@ #include "glyphs.h" #ifdef HAVE_UX_FLOW - #include "ux.h" +#include "ux.h" #endif #ifdef TARGET_NANOX - #include "balenos_ble.h" +#include "balenos_ble.h" #endif /* ----------------------------------------------------------------------- */ @@ -37,203 +37,191 @@ /* ----------------------------------------------------------------------- */ void monero_main(void) { - unsigned int io_flags, cont; - io_flags = 0; - cont = 1; - while(cont) { - - volatile unsigned short sw = 0; - BEGIN_TRY { - TRY { - monero_io_do(io_flags); - sw = monero_dispatch(); - } - CATCH(EXCEPTION_IO_RESET){ - sw = 0; - cont = 0; - monero_io_discard(1); - //THROW(EXCEPTION_IO_RESET); - } - CATCH_OTHER(e) { - monero_reset_tx(1); - if (((e&0xF000)==0x9000)||((e&0xFF00)==0x6400)) { - sw = e; - } else { - monero_io_discard(1); - if ( (e & 0xFFFF0000) || - ((e&0xF000)!=0x6000) ) { - monero_io_insert_u32(e); - sw = 0x6f42; - } else { - sw = e; - } - } - } - FINALLY { - if (sw) { - monero_io_insert_u16(sw); - io_flags = 0; - } else { - io_flags = IO_ASYNCH_REPLY; + unsigned int io_flags, cont; + io_flags = 0; + cont = 1; + while (cont) { + volatile unsigned short sw = 0; + BEGIN_TRY { + TRY { + monero_io_do(io_flags); + sw = monero_dispatch(); + } + CATCH(EXCEPTION_IO_RESET) { + sw = 0; + cont = 0; + monero_io_discard(1); + // THROW(EXCEPTION_IO_RESET); + } + CATCH_OTHER(e) { + monero_reset_tx(1); + if (((e & 0xF000) == 0x9000) || ((e & 0xFF00) == 0x6400)) { + sw = e; + } else { + monero_io_discard(1); + if ((e & 0xFFFF0000) || ((e & 0xF000) != 0x6000)) { + monero_io_insert_u32(e); + sw = 0x6f42; + } else { + sw = e; + } + } + } + FINALLY { + if (sw) { + monero_io_insert_u16(sw); + io_flags = 0; + } else { + io_flags = IO_ASYNCH_REPLY; + } + } } - } + END_TRY; } - END_TRY; - } } unsigned char io_event(unsigned char channel) { - unsigned int s_before ; - unsigned int s_after ; - - s_before = os_global_pin_is_validated(); - - // nothing done with the event, throw an error on the transport layer if - // needed - // can't have more than one tag in the reply, not supported yet. - switch (G_io_seproxyhal_spi_buffer[0]) { - case SEPROXYHAL_TAG_FINGER_EVENT: - UX_FINGER_EVENT(G_io_seproxyhal_spi_buffer); - break; - // power off if long push, else pass to the application callback if any - case SEPROXYHAL_TAG_BUTTON_PUSH_EVENT: // for Nano S - UX_BUTTON_PUSH_EVENT(G_io_seproxyhal_spi_buffer); - break; - - - // other events are propagated to the UX just in case - default: - UX_DEFAULT_EVENT(); - break; - - case SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT: - UX_DISPLAYED_EVENT({}); - break; - case SEPROXYHAL_TAG_TICKER_EVENT: - UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, - { - // only allow display when not locked of overlayed by an OS UX. - if (UX_ALLOWED ) { - UX_REDISPLAY(); - } - }); - break; - } - - // close the event if not done previously (by a display or whatever) - if (!io_seproxyhal_spi_is_status_sent()) { - io_seproxyhal_general_status(); - } - - s_after = os_global_pin_is_validated(); - - if (s_before!=s_after) { - if (s_after == PIN_VERIFIED) { - monero_init_private_key(); - } else { - ;//do nothing, allowing TX parsing in lock mode - //monero_wipe_private_key(); + unsigned int s_before; + unsigned int s_after; + + s_before = os_global_pin_is_validated(); + + // nothing done with the event, throw an error on the transport layer if + // needed + // can't have more than one tag in the reply, not supported yet. + switch (G_io_seproxyhal_spi_buffer[0]) { + case SEPROXYHAL_TAG_FINGER_EVENT: + UX_FINGER_EVENT(G_io_seproxyhal_spi_buffer); + break; + // power off if long push, else pass to the application callback if any + case SEPROXYHAL_TAG_BUTTON_PUSH_EVENT: // for Nano S + UX_BUTTON_PUSH_EVENT(G_io_seproxyhal_spi_buffer); + break; + + // other events are propagated to the UX just in case + default: + UX_DEFAULT_EVENT(); + break; + + case SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT: + UX_DISPLAYED_EVENT({}); + break; + case SEPROXYHAL_TAG_TICKER_EVENT: + UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, { + // only allow display when not locked of overlayed by an OS UX. + if (UX_ALLOWED) { + UX_REDISPLAY(); + } + }); + break; } - } - // command has been processed, DO NOT reset the current APDU transport - return 1; + // close the event if not done previously (by a display or whatever) + if (!io_seproxyhal_spi_is_status_sent()) { + io_seproxyhal_general_status(); + } + + s_after = os_global_pin_is_validated(); + + if (s_before != s_after) { + if (s_after == PIN_VERIFIED) { + monero_init_private_key(); + } else { + ; // do nothing, allowing TX parsing in lock mode + // monero_wipe_private_key(); + } + } + + // command has been processed, DO NOT reset the current APDU transport + return 1; } unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) { - switch (channel & ~(IO_FLAGS)) { - case CHANNEL_KEYBOARD: - break; - - // multiplexed io exchange over a SPI channel and TLV encapsulated protocol - case CHANNEL_SPI: - if (tx_len) { - io_seproxyhal_spi_send(G_io_apdu_buffer, tx_len); - - if (channel & IO_RESET_AFTER_REPLIED) { - reset(); - } - return 0; // nothing received from the master so far (it's a tx - // transaction) - } else { - return io_seproxyhal_spi_recv(G_io_apdu_buffer, - sizeof(G_io_apdu_buffer), 0); + switch (channel & ~(IO_FLAGS)) { + case CHANNEL_KEYBOARD: + break; + + // multiplexed io exchange over a SPI channel and TLV encapsulated protocol + case CHANNEL_SPI: + if (tx_len) { + io_seproxyhal_spi_send(G_io_apdu_buffer, tx_len); + + if (channel & IO_RESET_AFTER_REPLIED) { + reset(); + } + return 0; // nothing received from the master so far (it's a tx + // transaction) + } else { + return io_seproxyhal_spi_recv(G_io_apdu_buffer, sizeof(G_io_apdu_buffer), 0); + } + + default: + THROW(INVALID_PARAMETER); + return 0; } - - default: - THROW(INVALID_PARAMETER); return 0; - } - return 0; } void app_exit(void) { - BEGIN_TRY_L(exit) { - TRY_L(exit) { - os_sched_exit(-1); + BEGIN_TRY_L(exit) { + TRY_L(exit) { os_sched_exit(-1); } + FINALLY_L(exit) {} } - FINALLY_L(exit) { - } - } - END_TRY_L(exit); + END_TRY_L(exit); } /* -------------------------------------------------------------- */ __attribute__((section(".boot"))) int main(void) { - // exit critical section - __asm volatile("cpsie i"); - unsigned int cont = 1; - - // ensure exception will work as planned - os_boot(); - while(cont) { - UX_INIT(); - - BEGIN_TRY { - TRY { - - //start communication with MCU - io_seproxyhal_init(); - - USB_power(0); - USB_power(1); - - #ifdef HAVE_USB_CLASS_CCID - io_usb_ccid_set_card_inserted(1); - #endif - - #ifdef TARGET_NANOX - G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0); - BLE_power(0, NULL); - BLE_power(1, "Nano X - Monero"); - #endif - - monero_init(); - - //set up initial screen - ui_init(); - - //start the application - //the first exchange will: - // - display the initial screen - // - send the ATR - // - receive the first command - monero_main(); - } - CATCH(EXCEPTION_IO_RESET) { - // reset IO and UX - ; - } - CATCH_OTHER(e) { - cont = 0; - } - FINALLY { - } + // exit critical section + __asm volatile("cpsie i"); + unsigned int cont = 1; + + // ensure exception will work as planned + os_boot(); + while (cont) { + UX_INIT(); + + BEGIN_TRY { + TRY { + // start communication with MCU + io_seproxyhal_init(); + + USB_power(0); + USB_power(1); + +#ifdef HAVE_USB_CLASS_CCID + io_usb_ccid_set_card_inserted(1); +#endif + +#ifdef TARGET_NANOX + G_io_app.plane_mode = os_setting_get(OS_SETTING_PLANEMODE, NULL, 0); + BLE_power(0, NULL); + BLE_power(1, "Nano X - Monero"); +#endif + + monero_init(); + + // set up initial screen + ui_init(); + + // start the application + // the first exchange will: + // - display the initial screen + // - send the ATR + // - receive the first command + monero_main(); + } + CATCH(EXCEPTION_IO_RESET) { + // reset IO and UX + ; + } + CATCH_OTHER(e) { cont = 0; } + FINALLY {} + } + END_TRY; } - END_TRY; - } - app_exit(); + app_exit(); } #endif diff --git a/src/monero_mlsag.c b/src/monero_mlsag.c index 9506cde..bf1a225 100644 --- a/src/monero_mlsag.c +++ b/src/monero_mlsag.c @@ -34,36 +34,36 @@ int monero_apdu_mlsag_prepare() { monero_lock_and_throw(SW_SECURITY_MAX_SIGNATURE_REACHED); } - if (G_monero_vstate.io_length>1) { - monero_io_fetch(Hi,32); - if(G_monero_vstate.options &0x40) { - monero_io_fetch(xin,32); + if (G_monero_vstate.io_length > 1) { + monero_io_fetch(Hi, 32); + if (G_monero_vstate.options & 0x40) { + monero_io_fetch(xin, 32); } else { - monero_io_fetch_decrypt(xin,32, TYPE_SCALAR); + monero_io_fetch_decrypt(xin, 32, TYPE_SCALAR); } options = 1; - } else { + } else { options = 0; } monero_io_discard(1); - //ai + // ai monero_rng_mod_order(alpha); monero_reduce(alpha, alpha); monero_io_insert_encrypt(alpha, 32, TYPE_ALPHA); - //ai.G + // ai.G monero_ecmul_G(mul, alpha); - monero_io_insert(mul,32); + monero_io_insert(mul, 32); if (options) { - //ai.Hi + // ai.Hi monero_ecmul_k(mul, Hi, alpha); - monero_io_insert(mul,32); - //IIi = xin.Hi + monero_io_insert(mul, 32); + // IIi = xin.Hi monero_ecmul_k(mul, Hi, xin); - monero_io_insert(mul,32); + monero_io_insert(mul, 32); } return SW_OK; } @@ -83,10 +83,10 @@ int monero_apdu_mlsag_hash() { monero_io_discard(1); monero_keccak_update_H(msg, 32); - if ((G_monero_vstate.options&0x80) == 0 ) { + if ((G_monero_vstate.options & 0x80) == 0) { monero_keccak_final_H(c); - monero_reduce(c,c); - monero_io_insert(c,32); + monero_reduce(c, c); + monero_io_insert(c, 32); os_memmove(G_monero_vstate.c, c, 32); } return SW_OK; @@ -102,29 +102,29 @@ int monero_apdu_mlsag_sign() { unsigned char ss2[32]; if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_FAKE) { - monero_io_fetch(xin,32); - monero_io_fetch(alpha,32); + monero_io_fetch(xin, 32); + monero_io_fetch(alpha, 32); } else if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { - monero_io_fetch_decrypt(xin,32, TYPE_SCALAR); - monero_io_fetch_decrypt(alpha,32, TYPE_ALPHA); + monero_io_fetch_decrypt(xin, 32, TYPE_SCALAR); + monero_io_fetch_decrypt(alpha, 32, TYPE_ALPHA); } else { monero_lock_and_throw(SW_SECURITY_INTERNAL); } monero_io_discard(1); - //check xin and alpha are not null - if (cx_math_is_zero(xin,32) || cx_math_is_zero(alpha,32)) { + // check xin and alpha are not null + if (cx_math_is_zero(xin, 32) || cx_math_is_zero(alpha, 32)) { monero_lock_and_throw(SW_SECURITY_RANGE_VALUE); } monero_reduce(ss, G_monero_vstate.c); - monero_reduce(xin,xin); + monero_reduce(xin, xin); monero_multm(ss, ss, xin); monero_reduce(alpha, alpha); monero_subm(ss2, alpha, ss); - monero_io_insert(ss2,32); + monero_io_insert(ss2, 32); monero_io_insert_u32(G_monero_vstate.tx_sig_mode); return SW_OK; } diff --git a/src/monero_monero.c b/src/monero_monero.c index 2f8b681..a2fcfe5 100644 --- a/src/monero_monero.c +++ b/src/monero_monero.c @@ -18,17 +18,13 @@ #include "monero_vars.h" #ifndef MONERO_ALPHA -const unsigned char C_MAINNET_NETWORK_ID[] = { - 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x10 -}; +const unsigned char C_MAINNET_NETWORK_ID[] = {0x12, 0x30, 0xF1, 0x71, 0x61, 0x04, 0x41, 0x61, + 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x10}; #endif -const unsigned char C_TESTNET_NETWORK_ID[] = { - 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x11 -}; -const unsigned char C_STAGENET_NETWORK_ID[] = { - 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x12 -}; - +const unsigned char C_TESTNET_NETWORK_ID[] = {0x12, 0x30, 0xF1, 0x71, 0x61, 0x04, 0x41, 0x61, + 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x11}; +const unsigned char C_STAGENET_NETWORK_ID[] = {0x12, 0x30, 0xF1, 0x71, 0x61, 0x04, 0x41, 0x61, + 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x12}; // Copyright (c) 2014-2017, The Monero Project // @@ -58,33 +54,46 @@ const unsigned char C_STAGENET_NETWORK_ID[] = { // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -const char alphabet[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; -#define alphabet_size (sizeof(alphabet) - 1) -const unsigned int encoded_block_sizes[] = {0, 2, 3, 5, 6, 7, 9, 10, 11}; -#define FULL_BLOCK_SIZE 8 //(sizeof(encoded_block_sizes) / sizeof(encoded_block_sizes[0]) - 1) -#define FULL_ENCODED_BLOCK_SIZE 11 //encoded_block_sizes[full_block_size]; -#define ADDR_CHECKSUM_SIZE 4 - +const char alphabet[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; +#define alphabet_size (sizeof(alphabet) - 1) +const unsigned int encoded_block_sizes[] = {0, 2, 3, 5, 6, 7, 9, 10, 11}; +#define FULL_BLOCK_SIZE 8 //(sizeof(encoded_block_sizes) / sizeof(encoded_block_sizes[0]) - 1) +#define FULL_ENCODED_BLOCK_SIZE 11 // encoded_block_sizes[full_block_size]; +#define ADDR_CHECKSUM_SIZE 4 static uint64_t uint_8be_to_64(const unsigned char* data, size_t size) { uint64_t res = 0; switch (9 - size) { - case 1: res |= *data++; - case 2: res <<= 8; res |= *data++; - case 3: res <<= 8; res |= *data++; - case 4: res <<= 8; res |= *data++; - case 5: res <<= 8; res |= *data++; - case 6: res <<= 8; res |= *data++; - case 7: res <<= 8; res |= *data++; - case 8: res <<= 8; res |= *data; - break; + case 1: + res |= *data++; + case 2: + res <<= 8; + res |= *data++; + case 3: + res <<= 8; + res |= *data++; + case 4: + res <<= 8; + res |= *data++; + case 5: + res <<= 8; + res |= *data++; + case 6: + res <<= 8; + res |= *data++; + case 7: + res <<= 8; + res |= *data++; + case 8: + res <<= 8; + res |= *data; + break; } return res; } -static void encode_block(const unsigned char* block, unsigned int size, char* res) { +static void encode_block(const unsigned char* block, unsigned int size, char* res) { uint64_t num = uint_8be_to_64(block, size); int i = encoded_block_sizes[size]; while (i--) { @@ -94,13 +103,14 @@ static void encode_block(const unsigned char* block, unsigned int size, char* } } -int monero_base58_public_key(char* str_b58, unsigned char *view, unsigned char *spend, unsigned char is_subbadress, unsigned char *paymanetID) { - unsigned char data[72+8]; +int monero_base58_public_key(char* str_b58, unsigned char* view, unsigned char* spend, + unsigned char is_subbadress, unsigned char* paymanetID) { + unsigned char data[72 + 8]; unsigned int offset; unsigned int prefix; - //data[0] = N_monero_pstate->network_id; - switch(N_monero_pstate->network_id) { + // data[0] = N_monero_pstate->network_id; + switch (N_monero_pstate->network_id) { case TESTNET: if (paymanetID) { prefix = TESTNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; @@ -119,7 +129,7 @@ int monero_base58_public_key(char* str_b58, unsigned char *view, unsigned char * prefix = STAGENET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; } break; - #ifndef MONERO_ALPHA +#ifndef MONERO_ALPHA case MAINNET: if (paymanetID) { prefix = MAINNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; @@ -129,31 +139,32 @@ int monero_base58_public_key(char* str_b58, unsigned char *view, unsigned char * prefix = MAINNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; } break; - #endif +#endif } offset = monero_encode_varint(data, 8, prefix); - os_memmove(data+offset,spend,32); - os_memmove(data+offset+32,view,32); + os_memmove(data + offset, spend, 32); + os_memmove(data + offset + 32, view, 32); offset += 64; if (paymanetID) { - os_memmove(data+offset, paymanetID, 8); + os_memmove(data + offset, paymanetID, 8); offset += 8; } monero_keccak_F(data, offset, G_monero_vstate.mlsagH); - os_memmove(data+offset, G_monero_vstate.mlsagH, 4); + os_memmove(data + offset, G_monero_vstate.mlsagH, 4); offset += 4; unsigned int full_block_count = (offset) / FULL_BLOCK_SIZE; - unsigned int last_block_size = (offset) % FULL_BLOCK_SIZE; + unsigned int last_block_size = (offset) % FULL_BLOCK_SIZE; for (size_t i = 0; i < full_block_count; ++i) { - encode_block(data + i * FULL_BLOCK_SIZE, FULL_BLOCK_SIZE, &str_b58[i * FULL_ENCODED_BLOCK_SIZE]); + encode_block(data + i * FULL_BLOCK_SIZE, FULL_BLOCK_SIZE, + &str_b58[i * FULL_ENCODED_BLOCK_SIZE]); } if (0 < last_block_size) { - encode_block(data + full_block_count * FULL_BLOCK_SIZE, last_block_size, &str_b58[full_block_count * FULL_ENCODED_BLOCK_SIZE]); + encode_block(data + full_block_count * FULL_BLOCK_SIZE, last_block_size, + &str_b58[full_block_count * FULL_ENCODED_BLOCK_SIZE]); } return 0; } - diff --git a/src/monero_open_tx.c b/src/monero_open_tx.c index ae3e653..2967dda 100644 --- a/src/monero_open_tx.c +++ b/src/monero_open_tx.c @@ -19,7 +19,6 @@ #include "monero_api.h" #include "monero_vars.h" - /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ @@ -34,10 +33,9 @@ void monero_reset_tx(int reset_tx_cnt) { G_monero_vstate.tx_in_progress = 0; G_monero_vstate.tx_output_cnt = 0; if (reset_tx_cnt) { - G_monero_vstate.tx_cnt = 0; + G_monero_vstate.tx_cnt = 0; } - } - +} /* ----------------------------------------------------------------------- */ /* --- --- */ @@ -46,7 +44,6 @@ void monero_reset_tx(int reset_tx_cnt) { * HD wallet not yet supported : account is assumed to be zero */ int monero_apdu_open_tx() { - unsigned int account; account = monero_io_fetch_u32(); @@ -57,7 +54,7 @@ int monero_apdu_open_tx() { G_monero_vstate.tx_cnt++; ui_menu_opentx_display(0); if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { - //return 0; + // return 0; } return monero_apdu_open_tx_cont(); } @@ -65,21 +62,21 @@ int monero_apdu_open_tx() { int monero_apdu_open_tx_cont() { G_monero_vstate.tx_in_progress = 1; - #ifdef DEBUG_HWDEVICE +#ifdef DEBUG_HWDEVICE os_memset(G_monero_vstate.hmac_key, 0xab, 32); - #else +#else cx_rng(G_monero_vstate.hmac_key, 32); - #endif +#endif monero_rng_mod_order(G_monero_vstate.r); monero_ecmul_G(G_monero_vstate.R, G_monero_vstate.r); - monero_io_insert(G_monero_vstate.R,32); - monero_io_insert_encrypt(G_monero_vstate.r,32, TYPE_SCALAR); - monero_io_insert(C_FAKE_SEC_VIEW_KEY,32); - monero_io_insert_hmac_for((void*)C_FAKE_SEC_VIEW_KEY,32, TYPE_SCALAR); - monero_io_insert(C_FAKE_SEC_SPEND_KEY,32); - monero_io_insert_hmac_for((void*)C_FAKE_SEC_SPEND_KEY,32,TYPE_SCALAR); + monero_io_insert(G_monero_vstate.R, 32); + monero_io_insert_encrypt(G_monero_vstate.r, 32, TYPE_SCALAR); + monero_io_insert(C_FAKE_SEC_VIEW_KEY, 32); + monero_io_insert_hmac_for((void*)C_FAKE_SEC_VIEW_KEY, 32, TYPE_SCALAR); + monero_io_insert(C_FAKE_SEC_SPEND_KEY, 32); + monero_io_insert_hmac_for((void*)C_FAKE_SEC_SPEND_KEY, 32, TYPE_SCALAR); return SW_OK; } @@ -87,10 +84,10 @@ int monero_apdu_open_tx_cont() { /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_close_tx() { - monero_io_discard(1); - monero_reset_tx(G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL); - ui_menu_main_display(0); - return SW_OK; + monero_io_discard(1); + monero_reset_tx(G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL); + ui_menu_main_display(0); + return SW_OK; } /* ----------------------------------------------------------------------- */ @@ -112,15 +109,15 @@ int monero_apdu_set_signature_mode() { sig_mode = monero_io_fetch_u8(); monero_io_discard(0); - switch(sig_mode) { - case TRANSACTION_CREATE_REAL: - case TRANSACTION_CREATE_FAKE: - break; - default: - monero_lock_and_throw(SW_WRONG_DATA); + switch (sig_mode) { + case TRANSACTION_CREATE_REAL: + case TRANSACTION_CREATE_FAKE: + break; + default: + monero_lock_and_throw(SW_WRONG_DATA); } G_monero_vstate.tx_sig_mode = sig_mode; - monero_io_insert_u32( G_monero_vstate.tx_sig_mode ); + monero_io_insert_u32(G_monero_vstate.tx_sig_mode); return SW_OK; } diff --git a/src/monero_prefix.c b/src/monero_prefix.c index 1b0c06f..7ec0945 100644 --- a/src/monero_prefix.c +++ b/src/monero_prefix.c @@ -14,8 +14,8 @@ */ /* -* Client: rctSigs.cpp.c -> get_pre_mlsag_hash -*/ + * Client: rctSigs.cpp.c -> get_pre_mlsag_hash + */ #include "os.h" #include "cx.h" @@ -29,18 +29,18 @@ int monero_apdu_prefix_hash_init() { int timelock; - monero_keccak_update_H(G_monero_vstate.io_buffer+G_monero_vstate.io_offset, - G_monero_vstate.io_length-G_monero_vstate.io_offset); + monero_keccak_update_H(G_monero_vstate.io_buffer + G_monero_vstate.io_offset, + G_monero_vstate.io_length - G_monero_vstate.io_offset); if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { monero_io_fetch_varint(); timelock = monero_io_fetch_varint(); - if(monero_io_fetch_available() != 0) { - THROW(SW_WRONG_DATA); + if (monero_io_fetch_available() != 0) { + THROW(SW_WRONG_DATA); } - //ask user + // ask user monero_io_discard(1); - if (timelock != 0 ) { + if (timelock != 0) { monero_uint642str(timelock, G_monero_vstate.ux_amount, 15); ui_menu_timelock_validation_display(0); return 0; @@ -57,13 +57,13 @@ int monero_apdu_prefix_hash_init() { /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_prefix_hash_update() { - monero_keccak_update_H(G_monero_vstate.io_buffer+G_monero_vstate.io_offset, - G_monero_vstate.io_length-G_monero_vstate.io_offset); + monero_keccak_update_H(G_monero_vstate.io_buffer + G_monero_vstate.io_offset, + G_monero_vstate.io_length - G_monero_vstate.io_offset); monero_io_discard(0); if ((G_monero_vstate.options & 0x80) == 0x00) { monero_keccak_final_H(G_monero_vstate.prefixH); monero_io_insert(G_monero_vstate.prefixH, 32); } - + return SW_OK; } diff --git a/src/monero_prehash.c b/src/monero_prehash.c index bb81089..717a812 100644 --- a/src/monero_prehash.c +++ b/src/monero_prehash.c @@ -14,8 +14,8 @@ */ /* -* Client: rctSigs.cpp.c -> get_pre_mlsag_hash -*/ + * Client: rctSigs.cpp.c -> get_pre_mlsag_hash + */ #include "os.h" #include "cx.h" @@ -35,14 +35,15 @@ int monero_apdu_mlsag_prehash_init() { monero_keccak_init_H(); } } - monero_keccak_update_H(G_monero_vstate.io_buffer+G_monero_vstate.io_offset, - G_monero_vstate.io_length-G_monero_vstate.io_offset); - if ((G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) &&(G_monero_vstate.io_p2==1)) { + monero_keccak_update_H(G_monero_vstate.io_buffer + G_monero_vstate.io_offset, + G_monero_vstate.io_length - G_monero_vstate.io_offset); + if ((G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) && (G_monero_vstate.io_p2 == 1)) { // skip type monero_io_fetch_u8(); // fee str - monero_vamount2str(G_monero_vstate.io_buffer+G_monero_vstate.io_offset, G_monero_vstate.ux_amount, 15); - //ask user + monero_vamount2str(G_monero_vstate.io_buffer + G_monero_vstate.io_offset, + G_monero_vstate.ux_amount, 15); + // ask user monero_io_discard(1); ui_menu_fee_validation_display(0); return 0; @@ -65,48 +66,51 @@ int monero_apdu_mlsag_prehash_update() { unsigned char v[32]; unsigned char k[32]; - #define aH AKout +#define aH AKout unsigned char kG[32]; - //fetch destination + // fetch destination is_subaddress = monero_io_fetch_u8(); if (G_monero_vstate.io_protocol_version >= 2) { - is_change = monero_io_fetch_u8(); + is_change = monero_io_fetch_u8(); } else { is_change = 0; } - Aout = G_monero_vstate.io_buffer+G_monero_vstate.io_offset; monero_io_fetch(NULL,32); - Bout = G_monero_vstate.io_buffer+G_monero_vstate.io_offset; monero_io_fetch(NULL,32); - monero_io_fetch_decrypt(AKout,32, TYPE_AMOUNT_KEY); + Aout = G_monero_vstate.io_buffer + G_monero_vstate.io_offset; + monero_io_fetch(NULL, 32); + Bout = G_monero_vstate.io_buffer + G_monero_vstate.io_offset; + monero_io_fetch(NULL, 32); + monero_io_fetch_decrypt(AKout, 32, TYPE_AMOUNT_KEY); monero_io_fetch(C, 32); monero_io_fetch(k, 32); monero_io_fetch(v, 32); monero_io_discard(0); - //update MLSAG prehash - if ((G_monero_vstate.options&0x03) == 0x02) { - monero_keccak_update_H(v,8); + // update MLSAG prehash + if ((G_monero_vstate.options & 0x03) == 0x02) { + monero_keccak_update_H(v, 8); } else { - monero_keccak_update_H(k,32); - monero_keccak_update_H(v,32); + monero_keccak_update_H(k, 32); + monero_keccak_update_H(v, 32); } if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { if (is_change == 0) { - //encode dest adress - monero_base58_public_key(&G_monero_vstate.ux_address[0], Aout, Bout, is_subaddress, NULL); + // encode dest adress + monero_base58_public_key(&G_monero_vstate.ux_address[0], Aout, Bout, is_subaddress, + NULL); } - //update destination hash control + // update destination hash control if (G_monero_vstate.io_protocol_version >= 2) { - monero_sha256_outkeys_update(Aout,32); - monero_sha256_outkeys_update(Bout,32); - monero_sha256_outkeys_update(&is_change,1); - monero_sha256_outkeys_update(AKout,32); + monero_sha256_outkeys_update(Aout, 32); + monero_sha256_outkeys_update(Bout, 32); + monero_sha256_outkeys_update(&is_change, 1); + monero_sha256_outkeys_update(AKout, 32); } - //check C = aH+kG - monero_unblind(v,k, AKout, G_monero_vstate.options&0x03); + // check C = aH+kG + monero_unblind(v, k, AKout, G_monero_vstate.options & 0x03); monero_ecmul_G(kG, k); if (!cx_math_is_zero(v, 32)) { monero_ecmul_H(aH, v); @@ -117,31 +121,30 @@ int monero_apdu_mlsag_prehash_update() { if (os_memcmp(C, aH, 32)) { monero_lock_and_throw(SW_SECURITY_COMMITMENT_CONTROL); } - //update commitment hash control - monero_sha256_commitment_update(C,32); - + // update commitment hash control + monero_sha256_commitment_update(C, 32); - if ((G_monero_vstate.options & IN_OPTION_MORE_COMMAND)==0) { + if ((G_monero_vstate.options & IN_OPTION_MORE_COMMAND) == 0) { if (G_monero_vstate.io_protocol_version >= 2) { - //finalize and check destination hash_control + // finalize and check destination hash_control monero_sha256_outkeys_final(k); if (os_memcmp(k, G_monero_vstate.OUTK, 32)) { monero_lock_and_throw(SW_SECURITY_OUTKEYS_CHAIN_CONTROL); } } - //finalize commitment hash control + // finalize commitment hash control monero_sha256_commitment_final(NULL); monero_sha256_commitment_init(); } - //ask user + // ask user uint64_t amount; amount = monero_bamount2uint64(v); if (amount) { monero_amount2str(amount, G_monero_vstate.ux_amount, 15); if (!is_change) { ui_menu_validation_display(0); - } else { + } else { ui_menu_change_validation_display(0); } return 0; @@ -149,10 +152,9 @@ int monero_apdu_mlsag_prehash_update() { } return SW_OK; - #undef aH +#undef aH } - /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ @@ -162,28 +164,28 @@ int monero_apdu_mlsag_prehash_finalize() { unsigned char H[32]; if (G_monero_vstate.options & IN_OPTION_MORE_COMMAND) { - //accumulate - monero_io_fetch(H,32); + // accumulate + monero_io_fetch(H, 32); monero_io_discard(1); - monero_keccak_update_H(H,32); - monero_sha256_commitment_update(H,32); + monero_keccak_update_H(H, 32); + monero_sha256_commitment_update(H, 32); #ifdef DEBUG_HWDEVICE monero_io_insert(H, 32); #endif } else { - //Finalize and check commitment hash control + // Finalize and check commitment hash control if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { monero_sha256_commitment_final(H); - if (os_memcmp(H,G_monero_vstate.C,32)) { + if (os_memcmp(H, G_monero_vstate.C, 32)) { monero_lock_and_throw(SW_SECURITY_COMMITMENT_CHAIN_CONTROL); } } - //compute last H + // compute last H monero_keccak_final_H(H); - //compute last prehash - monero_io_fetch(message,32); - monero_io_fetch(proof,32); + // compute last prehash + monero_io_fetch(message, 32); + monero_io_fetch(proof, 32); monero_io_discard(1); monero_keccak_init_H(); if (G_monero_vstate.io_protocol_version == 3) { @@ -191,9 +193,9 @@ int monero_apdu_mlsag_prehash_finalize() { monero_lock_and_throw(SW_SECURITY_PREFIX_HASH); } } - monero_keccak_update_H(message, 32); - monero_keccak_update_H(H,32); - monero_keccak_update_H(proof,32); + monero_keccak_update_H(message, 32); + monero_keccak_update_H(H, 32); + monero_keccak_update_H(proof, 32); monero_keccak_final_H(G_monero_vstate.mlsagH); #ifdef DEBUG_HWDEVICE monero_io_insert(G_monero_vstate.mlsagH, 32); @@ -203,4 +205,3 @@ int monero_apdu_mlsag_prehash_finalize() { return SW_OK; } - diff --git a/src/monero_proof.c b/src/monero_proof.c index dc9cfd8..1ab1b21 100644 --- a/src/monero_proof.c +++ b/src/monero_proof.c @@ -42,32 +42,37 @@ int monero_apdu_get_tx_proof() { unsigned char XY[32]; unsigned char sig_c[32]; unsigned char sig_r[32]; - #define k (G_monero_vstate.tmp+256) +#define k (G_monero_vstate.tmp + 256) - msg = G_monero_vstate.io_buffer+G_monero_vstate.io_offset; monero_io_fetch(NULL,32); - R = G_monero_vstate.io_buffer+G_monero_vstate.io_offset; monero_io_fetch(NULL,32); - A = G_monero_vstate.io_buffer+G_monero_vstate.io_offset; monero_io_fetch(NULL,32); - B = G_monero_vstate.io_buffer+G_monero_vstate.io_offset; monero_io_fetch(NULL,32); - D = G_monero_vstate.io_buffer+G_monero_vstate.io_offset; monero_io_fetch(NULL,32); + msg = G_monero_vstate.io_buffer + G_monero_vstate.io_offset; + monero_io_fetch(NULL, 32); + R = G_monero_vstate.io_buffer + G_monero_vstate.io_offset; + monero_io_fetch(NULL, 32); + A = G_monero_vstate.io_buffer + G_monero_vstate.io_offset; + monero_io_fetch(NULL, 32); + B = G_monero_vstate.io_buffer + G_monero_vstate.io_offset; + monero_io_fetch(NULL, 32); + D = G_monero_vstate.io_buffer + G_monero_vstate.io_offset; + monero_io_fetch(NULL, 32); monero_io_fetch_decrypt_key(r); monero_io_discard(0); monero_rng_mod_order(k); - os_memmove(G_monero_vstate.tmp+32*0, msg, 32); - os_memmove(G_monero_vstate.tmp+32*1, D, 32); + os_memmove(G_monero_vstate.tmp + 32 * 0, msg, 32); + os_memmove(G_monero_vstate.tmp + 32 * 1, D, 32); - if(G_monero_vstate.options&1) { - monero_ecmul_k(XY,B,k); + if (G_monero_vstate.options & 1) { + monero_ecmul_k(XY, B, k); } else { - monero_ecmul_G(XY,k); + monero_ecmul_G(XY, k); } - os_memmove(G_monero_vstate.tmp+32*2, XY, 32); + os_memmove(G_monero_vstate.tmp + 32 * 2, XY, 32); - monero_ecmul_k(XY,A,k); - os_memmove(G_monero_vstate.tmp+32*3, XY, 32); + monero_ecmul_k(XY, A, k); + os_memmove(G_monero_vstate.tmp + 32 * 3, XY, 32); - monero_hash_to_scalar(sig_c, &G_monero_vstate.tmp[0],32*4); + monero_hash_to_scalar(sig_c, &G_monero_vstate.tmp[0], 32 * 4); monero_multm(XY, sig_c, r); monero_subm(sig_r, k, XY); diff --git a/src/monero_ram.c b/src/monero_ram.c index e9f4ec2..a69d26a 100644 --- a/src/monero_ram.c +++ b/src/monero_ram.c @@ -37,6 +37,4 @@ ux_state_t ux; #endif - -monero_v_state_t G_monero_vstate; - +monero_v_state_t G_monero_vstate; diff --git a/src/monero_stealth.c b/src/monero_stealth.c index d87f29d..609b8e1 100644 --- a/src/monero_stealth.c +++ b/src/monero_stealth.c @@ -19,39 +19,38 @@ #include "monero_api.h" #include "monero_vars.h" - /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_stealth() { - int i ; + int i; unsigned char pub[32]; unsigned char sec[32]; unsigned char drv[33]; unsigned char payID[8]; - - //fetch pub - monero_io_fetch(pub,32); - //fetch sec + + // fetch pub + monero_io_fetch(pub, 32); + // fetch sec monero_io_fetch_decrypt_key(sec); - //fetch paymentID - monero_io_fetch(payID,8); + // fetch paymentID + monero_io_fetch(payID, 8); monero_io_discard(0); - //Compute Dout + // Compute Dout monero_generate_key_derivation(drv, pub, sec); - - //compute mask + + // compute mask drv[32] = ENCRYPTED_PAYMENT_ID_TAIL; - monero_keccak_F(drv,33,sec); - - //stealth! - for (i=0; i<8; i++) { + monero_keccak_F(drv, 33, sec); + + // stealth! + for (i = 0; i < 8; i++) { payID[i] = payID[i] ^ sec[i]; } - - monero_io_insert(payID,8); + + monero_io_insert(payID, 8); return SW_OK; } \ No newline at end of file diff --git a/src/monero_types.h b/src/monero_types.h index 6e8b1dc..af6745c 100644 --- a/src/monero_types.h +++ b/src/monero_types.h @@ -20,7 +20,7 @@ #if CX_APILEVEL == 8 #define PIN_VERIFIED (!0) -#elif CX_APILEVEL == 9 || CX_APILEVEL == 10 +#elif CX_APILEVEL == 9 || CX_APILEVEL == 10 #define PIN_VERIFIED BOLOS_UX_OK #else @@ -30,201 +30,188 @@ /* cannot send more that F0 bytes in CCID, why? do not know for now * So set up length to F0 minus 2 bytes for SW */ -#define MONERO_APDU_LENGTH 0xFE - +#define MONERO_APDU_LENGTH 0xFE /* big private DO */ -#define MONERO_EXT_PRIVATE_DO_LENGTH 512 +#define MONERO_EXT_PRIVATE_DO_LENGTH 512 /* will be fixed..1024 is not enougth */ -#define MONERO_EXT_CARD_HOLDER_CERT_LENTH 2560 +#define MONERO_EXT_CARD_HOLDER_CERT_LENTH 2560 /* random choice */ -#define MONERO_EXT_CHALLENGE_LENTH 254 +#define MONERO_EXT_CHALLENGE_LENTH 254 /* --- ... --- */ -#define MAINNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 18 -#define MAINNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX 19 -#define MAINNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX 42 +#define MAINNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 18 +#define MAINNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX 19 +#define MAINNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX 42 #define STAGENET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 24 #define STAGENET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX 25 #define STAGENET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX 36 -#define TESTNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 53 -#define TESTNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX 54 -#define TESTNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX 63 +#define TESTNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 53 +#define TESTNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX 54 +#define TESTNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX 63 enum network_type { - #ifndef MONERO_ALPHA - MAINNET = 0, - #endif - TESTNET = 1, - STAGENET = 2, - FAKECHAIN = 3 +#ifndef MONERO_ALPHA + MAINNET = 0, +#endif + TESTNET = 1, + STAGENET = 2, + FAKECHAIN = 3 }; struct monero_nv_state_s { - /* magic */ - unsigned char magic[8]; - - /* network */ - unsigned char network_id; - - /* key mode */ - #define KEY_MODE_EXTERNAL 0x21 - #define KEY_MODE_SEED 0x42 - unsigned char key_mode; - - /* acount id for bip derivation */ - unsigned int account_id; - - /* spend key */ - unsigned char b[32]; - /* view key */ - unsigned char a[32]; - - - /*words*/ - #define WORDS_MAX_LENGTH 20 - union { - char words[26][WORDS_MAX_LENGTH]; - char words_list[25*WORDS_MAX_LENGTH+25]; - }; -} ; + /* magic */ + unsigned char magic[8]; + + /* network */ + unsigned char network_id; + +/* key mode */ +#define KEY_MODE_EXTERNAL 0x21 +#define KEY_MODE_SEED 0x42 + unsigned char key_mode; + + /* acount id for bip derivation */ + unsigned int account_id; + + /* spend key */ + unsigned char b[32]; + /* view key */ + unsigned char a[32]; + +/*words*/ +#define WORDS_MAX_LENGTH 20 + union { + char words[26][WORDS_MAX_LENGTH]; + char words_list[25 * WORDS_MAX_LENGTH + 25]; + }; +}; typedef struct monero_nv_state_s monero_nv_state_t; #define MONERO_IO_BUFFER_LENGTH (300) -enum device_mode { - NONE, - TRANSACTION_CREATE_REAL, - TRANSACTION_CREATE_FAKE, - TRANSACTION_PARSE -}; +enum device_mode { NONE, TRANSACTION_CREATE_REAL, TRANSACTION_CREATE_FAKE, TRANSACTION_PARSE }; -#define EXPORT_VIEW_KEY 0xC001BEEF +#define EXPORT_VIEW_KEY 0xC001BEEF -#define DISP_MAIN 0x51 -#define DISP_SUB 0x52 -#define DISP_INTEGRATED 0x53 +#define DISP_MAIN 0x51 +#define DISP_SUB 0x52 +#define DISP_INTEGRATED 0x53 struct monero_v_state_s { - unsigned char state; - unsigned char protocol; - - /* ------------------------------------------ */ - /* --- IO --- */ - /* ------------------------------------------ */ - - /* io state*/ - unsigned char io_protocol_version; - unsigned char io_ins; - unsigned char io_p1; - unsigned char io_p2; - unsigned char io_lc; - unsigned char io_le; - unsigned short io_length; - unsigned short io_offset; - unsigned short io_mark; - unsigned char io_buffer[MONERO_IO_BUFFER_LENGTH]; - - - unsigned int options; - - /* ------------------------------------------ */ - /* --- State Machine --- */ - /* ------------------------------------------ */ - unsigned int export_view_key; - unsigned char key_set; - - /* protocol guard */ - #define PROTOCOL_LOCKED 0x42 - #define PROTOCOL_LOCKED_UNLOCKABLE 0x84 - #define PROTOCOL_UNLOCKED 0x24 - unsigned char protocol_barrier; - - /* Tx state machine */ - unsigned char tx_in_progress; - unsigned char tx_cnt; - unsigned char tx_sig_mode; - unsigned char tx_state_ins; - unsigned char tx_state_p1; - unsigned char tx_state_p2; - unsigned char tx_output_cnt; - unsigned int tx_sign_cnt; - - /* sc_add control */ - unsigned char last_derive_secret_key[32]; - unsigned char last_get_subaddress_secret_key[32]; - - - /* ------------------------------------------ */ - /* --- Crypo --- */ - /* ------------------------------------------ */ - unsigned char b[32]; - unsigned char a[32]; - unsigned char A[32]; - unsigned char B[32]; - - /* SPK */ - cx_aes_key_t spk; - unsigned char hmac_key[32]; - - /* Tx key */ - unsigned char R[32]; - unsigned char r[32]; - - /* prefix/mlsag hash */ - cx_sha3_t keccakF; - cx_sha3_t keccakH; - unsigned char prefixH[32]; - unsigned char mlsagH[32]; - unsigned char c[32]; - - /* -- track tx-in/out and commitment -- */ - cx_sha256_t sha256_out_keys; - unsigned char OUTK[32]; - - cx_sha256_t sha256_commitment; - unsigned char C[32]; - - /* ------------------------------------------ */ - /* --- UI/UX --- */ - /* ------------------------------------------ */ - char ux_wallet_public_short_address[5+2+5+1]; - char ux_wallet_account_name[14]; - - union { - struct { - char ux_info1[14]; - char ux_info2[14]; - /* menu */ - char ux_menu[16]; - // address to display: 95/106-chars + null - char ux_address[160]; - // xmr to display: max pow(2,64) unit, aka 20-chars + '0' + dot + null - char ux_amount[23]; - // addr mode - unsigned char disp_addr_mode; - //M.m address - unsigned int disp_addr_M; - unsigned int disp_addr_m; - //payment id - char payment_id[16]; - }; - struct { - unsigned char tmp[340]; + unsigned char state; + unsigned char protocol; + + /* ------------------------------------------ */ + /* --- IO --- */ + /* ------------------------------------------ */ + + /* io state*/ + unsigned char io_protocol_version; + unsigned char io_ins; + unsigned char io_p1; + unsigned char io_p2; + unsigned char io_lc; + unsigned char io_le; + unsigned short io_length; + unsigned short io_offset; + unsigned short io_mark; + unsigned char io_buffer[MONERO_IO_BUFFER_LENGTH]; + + unsigned int options; + + /* ------------------------------------------ */ + /* --- State Machine --- */ + /* ------------------------------------------ */ + unsigned int export_view_key; + unsigned char key_set; + +/* protocol guard */ +#define PROTOCOL_LOCKED 0x42 +#define PROTOCOL_LOCKED_UNLOCKABLE 0x84 +#define PROTOCOL_UNLOCKED 0x24 + unsigned char protocol_barrier; + + /* Tx state machine */ + unsigned char tx_in_progress; + unsigned char tx_cnt; + unsigned char tx_sig_mode; + unsigned char tx_state_ins; + unsigned char tx_state_p1; + unsigned char tx_state_p2; + unsigned char tx_output_cnt; + unsigned int tx_sign_cnt; + + /* sc_add control */ + unsigned char last_derive_secret_key[32]; + unsigned char last_get_subaddress_secret_key[32]; + + /* ------------------------------------------ */ + /* --- Crypo --- */ + /* ------------------------------------------ */ + unsigned char b[32]; + unsigned char a[32]; + unsigned char A[32]; + unsigned char B[32]; + + /* SPK */ + cx_aes_key_t spk; + unsigned char hmac_key[32]; + + /* Tx key */ + unsigned char R[32]; + unsigned char r[32]; + + /* prefix/mlsag hash */ + cx_sha3_t keccakF; + cx_sha3_t keccakH; + unsigned char prefixH[32]; + unsigned char mlsagH[32]; + unsigned char c[32]; + + /* -- track tx-in/out and commitment -- */ + cx_sha256_t sha256_out_keys; + unsigned char OUTK[32]; + + cx_sha256_t sha256_commitment; + unsigned char C[32]; + + /* ------------------------------------------ */ + /* --- UI/UX --- */ + /* ------------------------------------------ */ + char ux_wallet_public_short_address[5 + 2 + 5 + 1]; + char ux_wallet_account_name[14]; + + union { + struct { + char ux_info1[14]; + char ux_info2[14]; + /* menu */ + char ux_menu[16]; + // address to display: 95/106-chars + null + char ux_address[160]; + // xmr to display: max pow(2,64) unit, aka 20-chars + '0' + dot + null + char ux_amount[23]; + // addr mode + unsigned char disp_addr_mode; + // M.m address + unsigned int disp_addr_M; + unsigned int disp_addr_m; + // payment id + char payment_id[16]; + }; + struct { + unsigned char tmp[340]; + }; }; - }; }; -typedef struct monero_v_state_s monero_v_state_t; +typedef struct monero_v_state_s monero_v_state_t; +#define SIZEOF_TX_VSTATE (sizeof(monero_v_state_t) - OFFSETOF(monero_v_state_t, state)) - - -#define SIZEOF_TX_VSTATE (sizeof(monero_v_state_t) - OFFSETOF(monero_v_state_t, state)) - - -#define STATE_IDLE 0xC0 +#define STATE_IDLE 0xC0 /* --- ... --- */ #define TYPE_SCALAR 1 @@ -232,88 +219,84 @@ typedef struct monero_v_state_s monero_v_state_t; #define TYPE_AMOUNT_KEY 3 #define TYPE_ALPHA 4 - /* --- ... --- */ -#define IO_OFFSET_END (unsigned int)-1 -#define IO_OFFSET_MARK (unsigned int)-2 +#define IO_OFFSET_END (unsigned int)-1 +#define IO_OFFSET_MARK (unsigned int)-2 -#define ENCRYPTED_PAYMENT_ID_TAIL 0x8d +#define ENCRYPTED_PAYMENT_ID_TAIL 0x8d /* --- Errors --- */ -#define ERROR(x) ((x)<<16) +#define ERROR(x) ((x) << 16) -#define ERROR_IO_OFFSET ERROR(1) -#define ERROR_IO_FULL ERROR(2) +#define ERROR_IO_OFFSET ERROR(1) +#define ERROR_IO_FULL ERROR(2) /* --- INS --- */ -#define INS_NONE 0x00 -#define INS_RESET 0x02 -#define INS_LOCK_DISPLAY 0x04 - -#define INS_GET_KEY 0x20 -#define INS_DISPLAY_ADDRESS 0x21 -#define INS_PUT_KEY 0x22 -#define INS_GET_CHACHA8_PREKEY 0x24 -#define INS_VERIFY_KEY 0x26 -#define INS_MANAGE_SEEDWORDS 0x28 - -#define INS_SECRET_KEY_TO_PUBLIC_KEY 0x30 -#define INS_GEN_KEY_DERIVATION 0x32 -#define INS_DERIVATION_TO_SCALAR 0x34 -#define INS_DERIVE_PUBLIC_KEY 0x36 -#define INS_DERIVE_SECRET_KEY 0x38 -#define INS_GEN_KEY_IMAGE 0x3A -#define INS_SECRET_KEY_ADD 0x3C -#define INS_GENERATE_KEYPAIR 0x40 -#define INS_SECRET_SCAL_MUL_KEY 0x42 -#define INS_SECRET_SCAL_MUL_BASE 0x44 +#define INS_NONE 0x00 +#define INS_RESET 0x02 +#define INS_LOCK_DISPLAY 0x04 + +#define INS_GET_KEY 0x20 +#define INS_DISPLAY_ADDRESS 0x21 +#define INS_PUT_KEY 0x22 +#define INS_GET_CHACHA8_PREKEY 0x24 +#define INS_VERIFY_KEY 0x26 +#define INS_MANAGE_SEEDWORDS 0x28 + +#define INS_SECRET_KEY_TO_PUBLIC_KEY 0x30 +#define INS_GEN_KEY_DERIVATION 0x32 +#define INS_DERIVATION_TO_SCALAR 0x34 +#define INS_DERIVE_PUBLIC_KEY 0x36 +#define INS_DERIVE_SECRET_KEY 0x38 +#define INS_GEN_KEY_IMAGE 0x3A +#define INS_SECRET_KEY_ADD 0x3C +#define INS_GENERATE_KEYPAIR 0x40 +#define INS_SECRET_SCAL_MUL_KEY 0x42 +#define INS_SECRET_SCAL_MUL_BASE 0x44 #define INS_DERIVE_SUBADDRESS_PUBLIC_KEY 0x46 #define INS_GET_SUBADDRESS 0x48 #define INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY 0x4A #define INS_GET_SUBADDRESS_SECRET_KEY 0x4C -#define INS_OPEN_TX 0x70 -#define INS_SET_SIGNATURE_MODE 0x72 -#define INS_GET_ADDITIONAL_KEY 0x74 -#define INS_STEALTH 0x76 -#define INS_GEN_COMMITMENT_MASK 0x77 -#define INS_BLIND 0x78 -#define INS_UNBLIND 0x7A -#define INS_GEN_TXOUT_KEYS 0x7B -#define INS_PREFIX_HASH 0x7D -#define INS_VALIDATE 0x7C -#define INS_MLSAG 0x7E -#define INS_CLOSE_TX 0x80 - -#define INS_GET_TX_PROOF 0xA0 -#define INS_GEN_SIGNATURE 0xA2 -#define INS_GEN_RING_SIGNATURE 0xA4 - - - -#define INS_GET_RESPONSE 0xc0 +#define INS_OPEN_TX 0x70 +#define INS_SET_SIGNATURE_MODE 0x72 +#define INS_GET_ADDITIONAL_KEY 0x74 +#define INS_STEALTH 0x76 +#define INS_GEN_COMMITMENT_MASK 0x77 +#define INS_BLIND 0x78 +#define INS_UNBLIND 0x7A +#define INS_GEN_TXOUT_KEYS 0x7B +#define INS_PREFIX_HASH 0x7D +#define INS_VALIDATE 0x7C +#define INS_MLSAG 0x7E +#define INS_CLOSE_TX 0x80 + +#define INS_GET_TX_PROOF 0xA0 +#define INS_GEN_SIGNATURE 0xA2 +#define INS_GEN_RING_SIGNATURE 0xA4 + +#define INS_GET_RESPONSE 0xc0 /* --- OPTIONS --- */ -#define IN_OPTION_MASK 0x000000FF -#define OUT_OPTION_MASK 0x0000FF00 +#define IN_OPTION_MASK 0x000000FF +#define OUT_OPTION_MASK 0x0000FF00 -#define IN_OPTION_MORE_COMMAND 0x00000080 +#define IN_OPTION_MORE_COMMAND 0x00000080 /* --- IO constants --- */ -#define OFFSET_CLA 0 -#define OFFSET_INS 1 -#define OFFSET_P1 2 -#define OFFSET_P2 3 -#define OFFSET_P3 4 -#define OFFSET_CDATA 5 -#define OFFSET_EXT_CDATA 7 +#define OFFSET_CLA 0 +#define OFFSET_INS 1 +#define OFFSET_P1 2 +#define OFFSET_P2 3 +#define OFFSET_P3 4 +#define OFFSET_CDATA 5 +#define OFFSET_EXT_CDATA 7 +#define SW_OK 0x9000 -#define SW_OK 0x9000 - -#define SW_WRONG_LENGTH 0x6700 +#define SW_WRONG_LENGTH 0x6700 #define SW_SECURITY_PIN_LOCKED 0x6910 #define SW_SECURITY_LOAD_KEY 0x6911 @@ -329,22 +312,20 @@ typedef struct monero_v_state_s monero_v_state_t; #define SW_SECURITY_PREFIX_HASH 0x691B #define SW_SECURITY_LOCKED 0x69EE +#define SW_COMMAND_NOT_ALLOWED 0x6980 +#define SW_SUBCOMMAND_NOT_ALLOWED 0x6981 +#define SW_DENY 0x6982 +#define SW_KEY_NOT_SET 0x6983 +#define SW_WRONG_DATA 0x6984 +#define SW_WRONG_DATA_RANGE 0x6985 +#define SW_IO_FULL 0x6986 -#define SW_COMMAND_NOT_ALLOWED 0x6980 -#define SW_SUBCOMMAND_NOT_ALLOWED 0x6981 -#define SW_DENY 0x6982 -#define SW_KEY_NOT_SET 0x6983 -#define SW_WRONG_DATA 0x6984 -#define SW_WRONG_DATA_RANGE 0x6985 -#define SW_IO_FULL 0x6986 - -#define SW_CLIENT_NOT_SUPPORTED 0x6A30 - -#define SW_WRONG_P1P2 0x6b00 -#define SW_INS_NOT_SUPPORTED 0x6d00 -#define SW_PROTOCOL_NOT_SUPPORTED 0x6e00 +#define SW_CLIENT_NOT_SUPPORTED 0x6A30 -#define SW_UNKNOWN 0x6f00 +#define SW_WRONG_P1P2 0x6b00 +#define SW_INS_NOT_SUPPORTED 0x6d00 +#define SW_PROTOCOL_NOT_SUPPORTED 0x6e00 +#define SW_UNKNOWN 0x6f00 #endif diff --git a/src/monero_ux_msg.c b/src/monero_ux_msg.c index acc5c0e..826a48b 100644 --- a/src/monero_ux_msg.c +++ b/src/monero_ux_msg.c @@ -13,8 +13,5 @@ * limitations under the License. */ - -const char * const C_OK = "OK"; -const char * const C_NOK = "NOK"; - - +const char* const C_OK = "OK"; +const char* const C_NOK = "NOK"; diff --git a/src/monero_ux_msg.h b/src/monero_ux_msg.h index d6cdc92..ac4a757 100644 --- a/src/monero_ux_msg.h +++ b/src/monero_ux_msg.h @@ -16,15 +16,12 @@ #ifndef MONERO_UX_MSG_H #define MONERO_UX_MSG_H -extern const char * const C_OK; -extern const char * const C_NOK; +extern const char* const C_OK; +extern const char* const C_NOK; +#define PICSTR(x) ((char*)PIC(x)) -#define PICSTR(x) ((char*)PIC(x)) - - -#define OK PICSTR(C_OK) -#define NOK PICSTR(C_NOK) - +#define OK PICSTR(C_OK) +#define NOK PICSTR(C_NOK) #endif diff --git a/src/monero_ux_nano.c b/src/monero_ux_nano.c index 05ba32f..84702a9 100644 --- a/src/monero_ux_nano.c +++ b/src/monero_ux_nano.c @@ -13,8 +13,6 @@ * limitations under the License. */ - - #if defined(UI_NANO_X) || defined(UI_NANO_SX) #include "os.h" @@ -32,190 +30,126 @@ /* --- NanoX UI layout --- */ /* ----------------------------------------------------------------------- */ - -#define ACCEPT 0xACCE -#define REJECT ~ACCEPT - +#define ACCEPT 0xACCE +#define REJECT ~ACCEPT void ui_menu_main_display(unsigned int value); - /* -------------------------------------- LOCK--------------------------------------- */ void ui_menu_pinlock_display() { - struct { - bolos_ux_t ux_id; - // length of parameters in the u union to be copied during the syscall - unsigned int len; - union { - struct { - unsigned int cancellable; - } validate_pin; - } u; - - } ux_params; - - os_global_pin_invalidate(); - G_monero_vstate.protocol_barrier = PROTOCOL_LOCKED_UNLOCKABLE; - ux_params.ux_id = BOLOS_UX_VALIDATE_PIN; - ux_params.len = sizeof(ux_params.u.validate_pin); - ux_params.u.validate_pin.cancellable = 0; - os_ux((bolos_ux_params_t*)&ux_params); - ui_menu_main_display(0); + struct { + bolos_ux_t ux_id; + // length of parameters in the u union to be copied during the syscall + unsigned int len; + union { + struct { + unsigned int cancellable; + } validate_pin; + } u; + + } ux_params; + + os_global_pin_invalidate(); + G_monero_vstate.protocol_barrier = PROTOCOL_LOCKED_UNLOCKABLE; + ux_params.ux_id = BOLOS_UX_VALIDATE_PIN; + ux_params.len = sizeof(ux_params.u.validate_pin); + ux_params.u.validate_pin.cancellable = 0; + os_ux((bolos_ux_params_t*)&ux_params); + ui_menu_main_display(0); } - /* -------------------------------------- 25 WORDS --------------------------------------- */ void ui_menu_words_display(unsigned int value); void ui_menu_words_clear(unsigned int value); void ui_menu_words_back(unsigned int value); - -UX_STEP_NOCB( - ux_menu_words_1_step, -#ifdef UI_NANO_X - bnnn_paging, +UX_STEP_NOCB(ux_menu_words_1_step, +#ifdef UI_NANO_X + bnnn_paging, #else - bn_paging, + bn_paging, #endif - { - .title = "Electrum Seed", - .text = "NOTSET", - }); - - -UX_STEP_CB( - ux_menu_words_2_step, - bn, - ui_menu_words_clear(0), - { - "CLEAR WORDS", - "(Do not wipe the wallet)" - }); - -UX_STEP_CB( - ux_menu_words_3_step, - pb, - ui_menu_words_back(0), - { - &C_icon_back, - "back" - }); - -UX_FLOW( - ux_flow_words, - &ux_menu_words_1_step, - &ux_menu_words_2_step, - &ux_menu_words_3_step - ); + { + .title = "Electrum Seed", + .text = "NOTSET", + }); + +UX_STEP_CB(ux_menu_words_2_step, bn, ui_menu_words_clear(0), + {"CLEAR WORDS", "(Do not wipe the wallet)"}); + +UX_STEP_CB(ux_menu_words_3_step, pb, ui_menu_words_back(0), {&C_icon_back, "back"}); + +UX_FLOW(ux_flow_words, &ux_menu_words_1_step, &ux_menu_words_2_step, &ux_menu_words_3_step); void ui_menu_words_clear(unsigned int value) { - monero_clear_words(); - ui_menu_main_display(0); + monero_clear_words(); + ui_menu_main_display(0); } -void ui_menu_words_back(unsigned int value) { - ui_menu_main_display(1); -} +void ui_menu_words_back(unsigned int value) { ui_menu_main_display(1); } -void ui_menu_words_display(unsigned int value) { - ux_flow_init(0, ux_flow_words, NULL); -} +void ui_menu_words_display(unsigned int value) { ux_flow_init(0, ux_flow_words, NULL); } -void settings_show_25_words(void) { - ui_menu_words_display(0); -} +void settings_show_25_words(void) { ui_menu_words_display(0); } /* -------------------------------- INFO UX --------------------------------- */ unsigned int ui_menu_info_action(unsigned int value); -UX_STEP_CB( - ux_menu_info_1_step, - nn, - ui_menu_info_action(0), - { - G_monero_vstate.ux_info1, - G_monero_vstate.ux_info2, - }); +UX_STEP_CB(ux_menu_info_1_step, nn, ui_menu_info_action(0), + { + G_monero_vstate.ux_info1, + G_monero_vstate.ux_info2, + }); -UX_FLOW( - ux_flow_info, - &ux_menu_info_1_step - ); +UX_FLOW(ux_flow_info, &ux_menu_info_1_step); unsigned int ui_menu_info_action(unsigned int value) { - if (G_monero_vstate.protocol_barrier == PROTOCOL_LOCKED) { - ui_menu_pinlock_display(); - } else { - ui_menu_main_display(0); - } - return 0; + if (G_monero_vstate.protocol_barrier == PROTOCOL_LOCKED) { + ui_menu_pinlock_display(); + } else { + ui_menu_main_display(0); + } + return 0; } void ui_menu_info_display2(unsigned int value, char* line1, char* line2) { - snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "%s", line1); - snprintf(G_monero_vstate.ux_info2, sizeof(G_monero_vstate.ux_info2), "%s", line2); - ux_flow_init(0, ux_flow_info,NULL); + snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "%s", line1); + snprintf(G_monero_vstate.ux_info2, sizeof(G_monero_vstate.ux_info2), "%s", line2); + ux_flow_init(0, ux_flow_info, NULL); } -void ui_menu_info_display(unsigned int value) { - ux_flow_init(0, ux_flow_info,NULL); -} +void ui_menu_info_display(unsigned int value) { ux_flow_init(0, ux_flow_info, NULL); } /* -------------------------------- OPEN TX UX --------------------------------- */ unsigned int ui_menu_opentx_action(unsigned int value); -UX_STEP_NOCB( - ux_menu_opentx_1_step, - nn, - { - "Process", - "new TX ?" - }); - -UX_STEP_CB( - ux_menu_opentx_2_step, - pb, - ui_menu_opentx_action(ACCEPT), - { - &C_icon_validate_14, - "Yes" - }); - -UX_STEP_CB( - ux_menu_opentx_3_step, - pb, - ui_menu_opentx_action(REJECT), - { - &C_icon_crossmark, - "No!" - }); - -UX_FLOW(ux_flow_opentx, - &ux_menu_opentx_1_step, - &ux_menu_opentx_2_step, - &ux_menu_opentx_3_step - ); +UX_STEP_NOCB(ux_menu_opentx_1_step, nn, {"Process", "new TX ?"}); + +UX_STEP_CB(ux_menu_opentx_2_step, pb, ui_menu_opentx_action(ACCEPT), {&C_icon_validate_14, "Yes"}); + +UX_STEP_CB(ux_menu_opentx_3_step, pb, ui_menu_opentx_action(REJECT), {&C_icon_crossmark, "No!"}); +UX_FLOW(ux_flow_opentx, &ux_menu_opentx_1_step, &ux_menu_opentx_2_step, &ux_menu_opentx_3_step); unsigned int ui_menu_opentx_action(unsigned int value) { - unsigned int sw; - unsigned char x[32]; + unsigned int sw; + unsigned char x[32]; - monero_io_discard(0); - os_memset(x,0,32); - sw = SW_OK; + monero_io_discard(0); + os_memset(x, 0, 32); + sw = SW_OK; - if (value == ACCEPT) { - sw = monero_apdu_open_tx_cont(); - ui_menu_info_display2(0, "Processing TX", "..."); - } else { - monero_abort_tx(0); - sw = SW_DENY; - ui_menu_info_display2(0, "Tansaction", "aborted"); - } - monero_io_insert_u16(sw); - monero_io_do(IO_RETURN_AFTER_TX); - return 0; + if (value == ACCEPT) { + sw = monero_apdu_open_tx_cont(); + ui_menu_info_display2(0, "Processing TX", "..."); + } else { + monero_abort_tx(0); + sw = SW_DENY; + ui_menu_info_display2(0, "Tansaction", "aborted"); + } + monero_io_insert_u16(sw); + monero_io_do(IO_RETURN_AFTER_TX); + return 0; } #if 0 @@ -228,647 +162,473 @@ void ui_menu_opentx_display(unsigned int value) { ui_menu_info_display(0); } } -#else +#else void ui_menu_opentx_display(unsigned int value) { - uint32_t i; - if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { - snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "Processing TX"); - } else { - snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "Preparing TX"); - } - for (i = 0; (i= ARRAYLEN(account_submenu_getter_values)) { - return NULL; - } - if (N_monero_pstate->account_id == idx) { - return account_submenu_getter_values_selected[idx]; - } else { - return account_submenu_getter_values[idx]; - } + if (idx >= ARRAYLEN(account_submenu_getter_values)) { + return NULL; + } + if (N_monero_pstate->account_id == idx) { + return account_submenu_getter_values_selected[idx]; + } else { + return account_submenu_getter_values[idx]; + } } -void account_back(void) { - ui_menu_main_display(0); -} +void account_back(void) { ui_menu_main_display(0); } void account_submenu_selector(unsigned int idx) { - if (idx<=9) { - monero_nvm_write((void*)&N_monero_pstate->account_id, &idx, sizeof(unsigned int)); - monero_init(); - } - ui_menu_main_display(0); + if (idx <= 9) { + monero_nvm_write((void*)&N_monero_pstate->account_id, &idx, sizeof(unsigned int)); + monero_init(); + } + ui_menu_main_display(0); } void ui_menu_account_display(unsigned int value) { - ux_menulist_init(G_ux.stack_count-1, account_submenu_getter, account_submenu_selector); + ux_menulist_init(G_ux.stack_count - 1, account_submenu_getter, account_submenu_selector); } -void settings_change_account(void) { - ui_menu_account_display(0); -} +void settings_change_account(void) { ui_menu_account_display(0); } /* -------------------------------- NETWORK UX --------------------------------- */ const char* const network_submenu_getter_values[] = { - #ifdef MONERO_ALPHA - "Unvailable", - #else - "Main Network", - #endif - "Stage Network", - "Test Network", - "Abort" -}; +#ifdef MONERO_ALPHA + "Unvailable", +#else + "Main Network", +#endif + "Stage Network", "Test Network", "Abort"}; const char* const network_submenu_getter_values_selected[] = { - #ifdef MONERO_ALPHA - "Unvailable", - #else - "Main Network +", - #endif - "Stage Network +", - "Test Network +", - "Abort" -}; - +#ifdef MONERO_ALPHA + "Unvailable", +#else + "Main Network +", +#endif + "Stage Network +", "Test Network +", "Abort"}; const char* network_submenu_getter(unsigned int idx) { - if (idx >= ARRAYLEN(network_submenu_getter_values)) { - return NULL; - } - int net; - switch(idx) { - case 0: - #ifdef MONERO_ALPHA - net = -1; - #else - net = MAINNET; - #endif - break; - case 1: - net = STAGENET; - break; - case 2: - net = TESTNET; - break; - default: - net = -1; - break; - } - if (N_monero_pstate->network_id == net) { - return network_submenu_getter_values_selected[idx]; - } else { - return network_submenu_getter_values[idx]; - } + if (idx >= ARRAYLEN(network_submenu_getter_values)) { + return NULL; + } + int net; + switch (idx) { + case 0: +#ifdef MONERO_ALPHA + net = -1; +#else + net = MAINNET; +#endif + break; + case 1: + net = STAGENET; + break; + case 2: + net = TESTNET; + break; + default: + net = -1; + break; + } + if (N_monero_pstate->network_id == net) { + return network_submenu_getter_values_selected[idx]; + } else { + return network_submenu_getter_values[idx]; + } } -void network_back(void) { - ui_menu_main_display(0); -} +void network_back(void) { ui_menu_main_display(0); } static void network_set_net(unsigned int network) { - monero_install(network); - monero_init(); + monero_install(network); + monero_init(); } void network_submenu_selector(unsigned int idx) { - switch(idx) { - case 0: - #ifndef MONERO_ALPHA - network_set_net(MAINNET); - #endif - break; - case 1: - network_set_net(STAGENET); - break; - case 2: - network_set_net(TESTNET); - break; - default: - break; - } - ui_menu_main_display(0); + switch (idx) { + case 0: +#ifndef MONERO_ALPHA + network_set_net(MAINNET); +#endif + break; + case 1: + network_set_net(STAGENET); + break; + case 2: + network_set_net(TESTNET); + break; + default: + break; + } + ui_menu_main_display(0); } - void ui_menu_network_display(unsigned int value) { - ux_menulist_init(G_ux.stack_count-1, network_submenu_getter, network_submenu_selector); + ux_menulist_init(G_ux.stack_count - 1, network_submenu_getter, network_submenu_selector); } -void settings_change_network(void) { - ui_menu_network_display(0); -} +void settings_change_network(void) { ui_menu_network_display(0); } /* -------------------------------- RESET UX --------------------------------- */ void ui_menu_reset_display(unsigned int value); void ui_menu_reset_action(unsigned int value); -UX_STEP_NOCB( - ux_menu_reset_1_step, - nn, - { - "", - "Really Reset?", - }); - -UX_STEP_CB( - ux_menu_reset_2_step, - pb, - ui_menu_reset_action(REJECT), - { - &C_icon_crossmark, - "No", - }); - - -UX_STEP_CB( - ux_menu_reset_3_step, - pb, - ui_menu_reset_action(ACCEPT), - { - &C_icon_validate_14, - "Yes", - }); - -UX_FLOW(ux_flow_reset, - &ux_menu_reset_1_step, - &ux_menu_reset_2_step, - &ux_menu_reset_3_step - ); - -void ui_menu_reset_display(unsigned int value) { - ux_flow_init(0, ux_flow_reset, 0); -} +UX_STEP_NOCB(ux_menu_reset_1_step, nn, + { + "", + "Really Reset?", + }); -void settings_reset(void) { - ui_menu_reset_display(0); -} +UX_STEP_CB(ux_menu_reset_2_step, pb, ui_menu_reset_action(REJECT), + { + &C_icon_crossmark, + "No", + }); + +UX_STEP_CB(ux_menu_reset_3_step, pb, ui_menu_reset_action(ACCEPT), + { + &C_icon_validate_14, + "Yes", + }); + +UX_FLOW(ux_flow_reset, &ux_menu_reset_1_step, &ux_menu_reset_2_step, &ux_menu_reset_3_step); + +void ui_menu_reset_display(unsigned int value) { ux_flow_init(0, ux_flow_reset, 0); } + +void settings_reset(void) { ui_menu_reset_display(0); } void ui_menu_reset_action(unsigned int value) { - if (value == ACCEPT) { - unsigned char magic[4]; - magic[0] = 0; magic[1] = 0; magic[2] = 0; magic[3] = 0; - monero_nvm_write((void*)N_monero_pstate->magic, magic, 4); - monero_init(); - } - ui_menu_main_display(0); + if (value == ACCEPT) { + unsigned char magic[4]; + magic[0] = 0; + magic[1] = 0; + magic[2] = 0; + magic[3] = 0; + monero_nvm_write((void*)N_monero_pstate->magic, magic, 4); + monero_init(); + } + ui_menu_main_display(0); } /* ------------------------------- SETTINGS UX ------------------------------- */ const char* const settings_submenu_getter_values[] = { - "Select Account", - "Select Network", - "Show 25 words", - "Reset", - "Back", + "Select Account", "Select Network", "Show 25 words", "Reset", "Back", }; const char* settings_submenu_getter(unsigned int idx) { - if (idx < ARRAYLEN(settings_submenu_getter_values)) { - return settings_submenu_getter_values[idx]; - } - return NULL; + if (idx < ARRAYLEN(settings_submenu_getter_values)) { + return settings_submenu_getter_values[idx]; + } + return NULL; } -void settings_back(void) { - ui_menu_main_display(0); -} +void settings_back(void) { ui_menu_main_display(0); } void settings_submenu_selector(unsigned int idx) { - switch(idx) { - case 0: - settings_change_account() ; - break; - case 1: - settings_change_network(); - break; - case 2: - settings_show_25_words(); - break; - case 3: - settings_reset(); - break; - default: - settings_back(); - } + switch (idx) { + case 0: + settings_change_account(); + break; + case 1: + settings_change_network(); + break; + case 2: + settings_show_25_words(); + break; + case 3: + settings_reset(); + break; + default: + settings_back(); + } } - - /* --------------------------------- ABOUT UX --------------------------------- */ #define STR(x) #x #define XSTR(x) STR(x) #ifdef UI_NANO_X -UX_STEP_NOCB( - ux_menu_about_1_step, - bnnn, - { - "Monero", - "(c) Ledger SAS", - "Spec " XSTR(SPEC_VERSION), - "App " XSTR(MONERO_VERSION), - }); -#else -UX_STEP_NOCB( - ux_menu_about_1a_step, - bn, - { - "Monero", - "(c) Ledger SAS", - }); - -UX_STEP_NOCB( - ux_menu_about_1b_step, - nn, - { - "Spec " XSTR(SPEC_VERSION), - "App " XSTR(MONERO_VERSION), - }); +UX_STEP_NOCB(ux_menu_about_1_step, bnnn, + { + "Monero", + "(c) Ledger SAS", + "Spec " XSTR(SPEC_VERSION), + "App " XSTR(MONERO_VERSION), + }); +#else +UX_STEP_NOCB(ux_menu_about_1a_step, bn, + { + "Monero", + "(c) Ledger SAS", + }); + +UX_STEP_NOCB(ux_menu_about_1b_step, nn, + { + "Spec " XSTR(SPEC_VERSION), + "App " XSTR(MONERO_VERSION), + }); #endif - -UX_STEP_CB( - ux_menu_about_2_step, - pb, - ui_menu_main_display(0), - { - &C_icon_back, - "Back", - }); - - +UX_STEP_CB(ux_menu_about_2_step, pb, ui_menu_main_display(0), + { + &C_icon_back, + "Back", + }); UX_FLOW(ux_flow_about, - #ifdef UI_NANO_X - &ux_menu_about_1_step, - #else - &ux_menu_about_1a_step, - &ux_menu_about_1b_step, - #endif - &ux_menu_about_2_step - ); - - -void ui_menu_about_display(unsigned int value) { - ux_flow_init(0, ux_flow_about, NULL); -} +#ifdef UI_NANO_X + &ux_menu_about_1_step, +#else + &ux_menu_about_1a_step, &ux_menu_about_1b_step, +#endif + &ux_menu_about_2_step); + +void ui_menu_about_display(unsigned int value) { ux_flow_init(0, ux_flow_about, NULL); } #undef STR #undef XSTR +/* ---------------------------- PUBLIC ADDRESS UX ---------------------------- */ +void ui_menu_pubaddr_action(unsigned int value); +#define ADDR_TYPE G_monero_vstate.ux_address + 108 +#define ADDR_MAJOR G_monero_vstate.ux_address + 124 +#define ADDR_MINOR G_monero_vstate.ux_address + 140 +#define ADDR_IDSTR G_monero_vstate.ux_address + 124 +#define ADDR_ID G_monero_vstate.ux_address + 140 +UX_STEP_NOCB(ux_menu_pubaddr_01_step, nn, + { + ADDR_TYPE, + "Address", + }); +UX_STEP_NOCB(ux_menu_pubaddr_02_step, nn, + { + ADDR_MAJOR, + ADDR_MINOR, + }); -/* ---------------------------- PUBLIC ADDRESS UX ---------------------------- */ -void ui_menu_pubaddr_action(unsigned int value); +UX_STEP_NOCB(ux_menu_pubaddr_1_step, bnnn_paging, + {.title = "Address", .text = G_monero_vstate.ux_address}); -#define ADDR_TYPE G_monero_vstate.ux_address+108 -#define ADDR_MAJOR G_monero_vstate.ux_address+124 -#define ADDR_MINOR G_monero_vstate.ux_address+140 -#define ADDR_IDSTR G_monero_vstate.ux_address+124 -#define ADDR_ID G_monero_vstate.ux_address+140 - -UX_STEP_NOCB( - ux_menu_pubaddr_01_step, - nn, - { - ADDR_TYPE, - "Address", - }); - - -UX_STEP_NOCB( - ux_menu_pubaddr_02_step, - nn, - { - ADDR_MAJOR, - ADDR_MINOR, - }); - -UX_STEP_NOCB( - ux_menu_pubaddr_1_step, - bnnn_paging, - { - .title = "Address", - .text = G_monero_vstate.ux_address - }); - -UX_STEP_CB( - ux_menu_pubaddr_2_step, - pb, - ui_menu_pubaddr_action(0), - { - &C_icon_back, - "Ok" - }); - -UX_FLOW(ux_flow_pubaddr, - &ux_menu_pubaddr_01_step, - &ux_menu_pubaddr_02_step, - &ux_menu_pubaddr_1_step, - &ux_menu_pubaddr_2_step - ); +UX_STEP_CB(ux_menu_pubaddr_2_step, pb, ui_menu_pubaddr_action(0), {&C_icon_back, "Ok"}); + +UX_FLOW(ux_flow_pubaddr, &ux_menu_pubaddr_01_step, &ux_menu_pubaddr_02_step, + &ux_menu_pubaddr_1_step, &ux_menu_pubaddr_2_step); void ui_menu_pubaddr_action(unsigned int value) { - - if (G_monero_vstate.disp_addr_mode) { - monero_io_insert_u16(SW_OK); - monero_io_do(IO_RETURN_AFTER_TX); - } - G_monero_vstate.disp_addr_mode = 0; - ui_menu_main_display(0); + if (G_monero_vstate.disp_addr_mode) { + monero_io_insert_u16(SW_OK); + monero_io_do(IO_RETURN_AFTER_TX); + } + G_monero_vstate.disp_addr_mode = 0; + ui_menu_main_display(0); } /** * */ -void ui_menu_any_pubaddr_display(unsigned int value, unsigned char* pub_view, unsigned char *pub_spend, unsigned char is_subbadress, unsigned char *paymanetID){ - memset(G_monero_vstate.ux_address,0,sizeof(G_monero_vstate.ux_address)); - - switch (G_monero_vstate.disp_addr_mode) { - case 0: - case DISP_MAIN: - os_memmove(ADDR_TYPE, "Main", 4); - os_memmove(ADDR_MAJOR, "Major: 0", 8); - os_memmove(ADDR_MINOR, "minor: 0", 8); - break; - - case DISP_SUB: - os_memmove(ADDR_TYPE, "Sub", 3); - snprintf(ADDR_MAJOR, 16, "Major: %d", G_monero_vstate.disp_addr_M); - snprintf(ADDR_MINOR, 16, "minor: %d", G_monero_vstate.disp_addr_m); - break; - - case DISP_INTEGRATED: - os_memmove(ADDR_TYPE, "Integrated", 10); - os_memmove(ADDR_IDSTR, "Payment ID", 10); - os_memmove(ADDR_ID, G_monero_vstate.payment_id, 16); - break; - } +void ui_menu_any_pubaddr_display(unsigned int value, unsigned char* pub_view, + unsigned char* pub_spend, unsigned char is_subbadress, + unsigned char* paymanetID) { + memset(G_monero_vstate.ux_address, 0, sizeof(G_monero_vstate.ux_address)); + + switch (G_monero_vstate.disp_addr_mode) { + case 0: + case DISP_MAIN: + os_memmove(ADDR_TYPE, "Main", 4); + os_memmove(ADDR_MAJOR, "Major: 0", 8); + os_memmove(ADDR_MINOR, "minor: 0", 8); + break; + + case DISP_SUB: + os_memmove(ADDR_TYPE, "Sub", 3); + snprintf(ADDR_MAJOR, 16, "Major: %d", G_monero_vstate.disp_addr_M); + snprintf(ADDR_MINOR, 16, "minor: %d", G_monero_vstate.disp_addr_m); + break; + + case DISP_INTEGRATED: + os_memmove(ADDR_TYPE, "Integrated", 10); + os_memmove(ADDR_IDSTR, "Payment ID", 10); + os_memmove(ADDR_ID, G_monero_vstate.payment_id, 16); + break; + } - monero_base58_public_key(G_monero_vstate.ux_address+strlen(G_monero_vstate.ux_address), pub_view, pub_spend, is_subbadress, paymanetID); - ux_layout_bnnn_paging_reset(); - ux_flow_init(0, ux_flow_pubaddr, NULL); + monero_base58_public_key(G_monero_vstate.ux_address + strlen(G_monero_vstate.ux_address), + pub_view, pub_spend, is_subbadress, paymanetID); + ux_layout_bnnn_paging_reset(); + ux_flow_init(0, ux_flow_pubaddr, NULL); } void ui_menu_pubaddr_display(unsigned int value) { - G_monero_vstate.disp_addr_mode = 0; - G_monero_vstate.disp_addr_M = 0; - G_monero_vstate.disp_addr_M = 0; - ui_menu_any_pubaddr_display(value, G_monero_vstate.A,G_monero_vstate.B, 0, NULL); + G_monero_vstate.disp_addr_mode = 0; + G_monero_vstate.disp_addr_M = 0; + G_monero_vstate.disp_addr_M = 0; + ui_menu_any_pubaddr_display(value, G_monero_vstate.A, G_monero_vstate.B, 0, NULL); } #undef ADDR_TYPE @@ -879,65 +639,35 @@ void ui_menu_pubaddr_display(unsigned int value) { /* --------------------------------- MAIN UX --------------------------------- */ -UX_STEP_CB( - ux_menu_main_1_step, - pbb, - ui_menu_pubaddr_display(0), - { - &C_icon_monero, - G_monero_vstate.ux_wallet_account_name, - G_monero_vstate.ux_wallet_public_short_address - }); - -UX_STEP_CB( - ux_menu_main_2_step, - pb, - ux_menulist_init(G_ux.stack_count-1, settings_submenu_getter, settings_submenu_selector), - { - &C_icon_coggle, - "Settings" - }); - -UX_STEP_CB( - ux_menu_main_3_step, - pb, - ui_menu_about_display(0), - { - &C_icon_certificate, - "About" - }); - -UX_STEP_CB( - ux_menu_main_4_step, - pb, - os_sched_exit(0), - { - &C_icon_dashboard_x, - "Quit app" - }); - - -UX_FLOW(ux_flow_main, - &ux_menu_main_1_step, - &ux_menu_main_2_step, - &ux_menu_main_3_step, - &ux_menu_main_4_step); +UX_STEP_CB(ux_menu_main_1_step, pbb, ui_menu_pubaddr_display(0), + {&C_icon_monero, G_monero_vstate.ux_wallet_account_name, + G_monero_vstate.ux_wallet_public_short_address}); + +UX_STEP_CB(ux_menu_main_2_step, pb, + ux_menulist_init(G_ux.stack_count - 1, settings_submenu_getter, + settings_submenu_selector), + {&C_icon_coggle, "Settings"}); + +UX_STEP_CB(ux_menu_main_3_step, pb, ui_menu_about_display(0), {&C_icon_certificate, "About"}); + +UX_STEP_CB(ux_menu_main_4_step, pb, os_sched_exit(0), {&C_icon_dashboard_x, "Quit app"}); + +UX_FLOW(ux_flow_main, &ux_menu_main_1_step, &ux_menu_main_2_step, &ux_menu_main_3_step, + &ux_menu_main_4_step); void ui_menu_main_display(unsigned int value) { - // reserve a display stack slot if none yet - if(G_ux.stack_count == 0) { + // reserve a display stack slot if none yet + if (G_ux.stack_count == 0) { ux_stack_push(); } - ux_flow_init(0, ux_flow_main, NULL); + ux_flow_init(0, ux_flow_main, NULL); } /* --- INIT --- */ -void ui_init(void) { - ui_menu_main_display(0); -} +void ui_init(void) { ui_menu_main_display(0); } -void io_seproxyhal_display(const bagl_element_t *element) { - io_seproxyhal_display_default((bagl_element_t *)element); +void io_seproxyhal_display(const bagl_element_t* element) { + io_seproxyhal_display_default((bagl_element_t*)element); } #endif diff --git a/src/monero_ux_nanos.c b/src/monero_ux_nanos.c index 713b391..ec4829e 100644 --- a/src/monero_ux_nanos.c +++ b/src/monero_ux_nanos.c @@ -13,7 +13,6 @@ * limitations under the License. */ - #ifdef UI_NANO_S #include "os.h" @@ -27,43 +26,43 @@ #include "string.h" #include "glyphs.h" - /* ----------------------------------------------------------------------- */ /* --- NanoS UI layout --- */ /* ----------------------------------------------------------------------- */ -const ux_menu_entry_t ui_menu_reset[] ; +const ux_menu_entry_t ui_menu_reset[]; void ui_menu_reset_action(unsigned int value); -const ux_menu_entry_t ui_menu_settings[] ; +const ux_menu_entry_t ui_menu_settings[]; const ux_menu_entry_t ui_menu_main[]; -void ui_menu_main_display(unsigned int value) ; -const bagl_element_t* ui_menu_main_preprocessor(const ux_menu_entry_t* entry, bagl_element_t* element); +void ui_menu_main_display(unsigned int value); +const bagl_element_t* ui_menu_main_preprocessor(const ux_menu_entry_t* entry, + bagl_element_t* element); void ui_menu_settings_display(unsigned int value); /* -------------------------------------- LOCK--------------------------------------- */ void ui_menu_pinlock_display() { - struct { - bolos_ux_t ux_id; - // length of parameters in the u union to be copied during the syscall - unsigned int len; - union { - struct { - unsigned int cancellable; - } validate_pin; - } u; - } ux_params; - - os_global_pin_invalidate(); - G_monero_vstate.protocol_barrier = PROTOCOL_LOCKED_UNLOCKABLE; - ux_params.ux_id = BOLOS_UX_VALIDATE_PIN; - ux_params.len = sizeof(ux_params.u.validate_pin); - ux_params.u.validate_pin.cancellable = 0; - os_ux((bolos_ux_params_t*)&ux_params); - ui_menu_main_display(0); + struct { + bolos_ux_t ux_id; + // length of parameters in the u union to be copied during the syscall + unsigned int len; + union { + struct { + unsigned int cancellable; + } validate_pin; + } u; + } ux_params; + + os_global_pin_invalidate(); + G_monero_vstate.protocol_barrier = PROTOCOL_LOCKED_UNLOCKABLE; + ux_params.ux_id = BOLOS_UX_VALIDATE_PIN; + ux_params.len = sizeof(ux_params.u.validate_pin); + ux_params.u.validate_pin.cancellable = 0; + os_ux((bolos_ux_params_t*)&ux_params); + ui_menu_main_display(0); } /* -------------------------------------- 25 WORDS --------------------------------------- */ @@ -71,145 +70,142 @@ void ui_menu_words_clear(unsigned int value); void ui_menu_words_back(unsigned int value); #define WORDS N_monero_pstate->words const ux_menu_entry_t ui_menu_words[] = { - {NULL, ui_menu_words_back, 0, NULL, "", "", 0, 0}, - {NULL, ui_menu_words_back, 2, NULL, "", "", 0, 0}, - {NULL, ui_menu_words_back, 4, NULL, "", "", 0, 0}, - {NULL, ui_menu_words_back, 6, NULL, "", "", 0, 0}, - {NULL, ui_menu_words_back, 8, NULL, "", "", 0, 0}, - {NULL, ui_menu_words_back, 10, NULL, "", "", 0, 0}, - {NULL, ui_menu_words_back, 12, NULL, "", "", 0, 0}, - {NULL, ui_menu_words_back, 14, NULL, "", "", 0, 0}, - {NULL, ui_menu_words_back, 16, NULL, "", "", 0, 0}, - {NULL, ui_menu_words_back, 18, NULL, "", "", 0, 0}, - {NULL, ui_menu_words_back, 20, NULL, "", "", 0, 0}, - {NULL, ui_menu_words_back, 22, NULL, "", "", 0, 0}, - {NULL, ui_menu_words_back, 24, NULL, "", "", 0, 0}, - {NULL, ui_menu_words_clear, -1, NULL, "CLEAR WORDS", "(NO WIPE)", 0, 0}, - UX_MENU_END -}; - -const bagl_element_t* ui_menu_words_preprocessor(const ux_menu_entry_t* entry, bagl_element_t* element) { - if ((entry->userid >= 0) && (entry->userid <25)) { - - if(element->component.userid==0x21) { - element->text = N_monero_pstate->words[entry->userid]; - } - - if ((element->component.userid==0x22)&&(entry->userid<24)) { - element->text = N_monero_pstate->words[entry->userid+1]; + {NULL, ui_menu_words_back, 0, NULL, "", "", 0, 0}, + {NULL, ui_menu_words_back, 2, NULL, "", "", 0, 0}, + {NULL, ui_menu_words_back, 4, NULL, "", "", 0, 0}, + {NULL, ui_menu_words_back, 6, NULL, "", "", 0, 0}, + {NULL, ui_menu_words_back, 8, NULL, "", "", 0, 0}, + {NULL, ui_menu_words_back, 10, NULL, "", "", 0, 0}, + {NULL, ui_menu_words_back, 12, NULL, "", "", 0, 0}, + {NULL, ui_menu_words_back, 14, NULL, "", "", 0, 0}, + {NULL, ui_menu_words_back, 16, NULL, "", "", 0, 0}, + {NULL, ui_menu_words_back, 18, NULL, "", "", 0, 0}, + {NULL, ui_menu_words_back, 20, NULL, "", "", 0, 0}, + {NULL, ui_menu_words_back, 22, NULL, "", "", 0, 0}, + {NULL, ui_menu_words_back, 24, NULL, "", "", 0, 0}, + {NULL, ui_menu_words_clear, -1, NULL, "CLEAR WORDS", "(NO WIPE)", 0, 0}, + UX_MENU_END}; + +const bagl_element_t* ui_menu_words_preprocessor(const ux_menu_entry_t* entry, + bagl_element_t* element) { + if ((entry->userid >= 0) && (entry->userid < 25)) { + if (element->component.userid == 0x21) { + element->text = N_monero_pstate->words[entry->userid]; + } + + if ((element->component.userid == 0x22) && (entry->userid < 24)) { + element->text = N_monero_pstate->words[entry->userid + 1]; + } } - } - return element; + return element; } void ui_menu_words_display(unsigned int value) { - UX_MENU_DISPLAY(0, ui_menu_words, ui_menu_words_preprocessor); + UX_MENU_DISPLAY(0, ui_menu_words, ui_menu_words_preprocessor); } void ui_menu_words_clear(unsigned int value) { - monero_clear_words(); - ui_menu_main_display(0); + monero_clear_words(); + ui_menu_main_display(0); } -void ui_menu_words_back(unsigned int value) { - ui_menu_settings_display(1); -} +void ui_menu_words_back(unsigned int value) { ui_menu_settings_display(1); } /* -------------------------------- INFO UX --------------------------------- */ unsigned int ui_menu_info_button(unsigned int button_mask, unsigned int button_mask_counter); const bagl_element_t ui_menu_info[] = { - // type userid x y w h str rad fill fg bg font_id icon_id - { {BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, - NULL}, + // type userid x y w h str rad fill fg bg + // font_id icon_id + {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, NULL}, - { {BAGL_LABELINE, 0x01, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, - G_monero_vstate.ux_info1}, + {{BAGL_LABELINE, 0x01, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + G_monero_vstate.ux_info1}, - { {BAGL_LABELINE, 0x02, 0, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, - G_monero_vstate.ux_info2}, + {{BAGL_LABELINE, 0x02, 0, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + G_monero_vstate.ux_info2}, }; unsigned int ui_menu_info_button(unsigned int button_mask, unsigned int button_mask_counter) { - switch(button_mask) { - case BUTTON_EVT_RELEASED|BUTTON_RIGHT: - case BUTTON_EVT_RELEASED|BUTTON_LEFT: - if (G_monero_vstate.protocol_barrier == PROTOCOL_LOCKED) { - ui_menu_pinlock_display(); - } else { - ui_menu_main_display(0); + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: + case BUTTON_EVT_RELEASED | BUTTON_LEFT: + if (G_monero_vstate.protocol_barrier == PROTOCOL_LOCKED) { + ui_menu_pinlock_display(); + } else { + ui_menu_main_display(0); + } + break; + + default: + return 0; } - break; - - default: return 0; - } - return 0; } void ui_menu_info_display2(unsigned int value, char* line1, char* line2) { - snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "%s", line1); - snprintf(G_monero_vstate.ux_info2, sizeof(G_monero_vstate.ux_info2), "%s", line2); - UX_DISPLAY(ui_menu_info, NULL); + snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "%s", line1); + snprintf(G_monero_vstate.ux_info2, sizeof(G_monero_vstate.ux_info2), "%s", line2); + UX_DISPLAY(ui_menu_info, NULL); } -void ui_menu_info_display(unsigned int value) { - UX_DISPLAY(ui_menu_info, NULL); -} +void ui_menu_info_display(unsigned int value) { UX_DISPLAY(ui_menu_info, NULL); } /* -------------------------------- OPEN TX UX --------------------------------- */ unsigned int ui_menu_opentx_button(unsigned int button_mask, unsigned int button_mask_counter); const bagl_element_t ui_menu_opentx[] = { - // type userid x y w h str rad fill fg bg font_id icon_id - { {BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, - NULL}, + // type userid x y w h str rad fill fg bg + // font_id icon_id + {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, NULL}, - { {BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CROSS }, - NULL}, + {{BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CROSS}, NULL}, - { {BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CHECK }, - NULL}, + {{BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CHECK}, NULL}, - { {BAGL_LABELINE, 0x01, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, - "Process"}, + {{BAGL_LABELINE, 0x01, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "Process"}, - { {BAGL_LABELINE, 0x02, 0, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, - "new TX ?"}, + {{BAGL_LABELINE, 0x02, 0, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "new TX ?"}, }; -unsigned int ui_menu_opentx_button(unsigned int button_mask, unsigned int button_mask_counter) { - unsigned int sw; - unsigned char x[32]; +unsigned int ui_menu_opentx_button(unsigned int button_mask, unsigned int button_mask_counter) { + unsigned int sw; + unsigned char x[32]; - monero_io_discard(0); - os_memset(x,0,32); - sw = SW_OK; + monero_io_discard(0); + os_memset(x, 0, 32); + sw = SW_OK; - switch(button_mask) { - case BUTTON_EVT_RELEASED|BUTTON_LEFT: // CANCEL - monero_abort_tx(); - sw = SW_DENY; - break; + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT: // CANCEL + monero_abort_tx(); + sw = SW_DENY; + break; - case BUTTON_EVT_RELEASED|BUTTON_RIGHT: // OK - sw = monero_apdu_open_tx_cont(); - break; + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: // OK + sw = monero_apdu_open_tx_cont(); + break; - default: + default: + return 0; + } + monero_io_insert_u16(sw); + monero_io_do(IO_RETURN_AFTER_TX); + if (sw == SW_OK) { + ui_menu_info_display2(0, "Processing TX", "..."); + } else { + ui_menu_info_display2(0, "Tansaction", "aborted"); + } return 0; - } - monero_io_insert_u16(sw); - monero_io_do(IO_RETURN_AFTER_TX); - if (sw == SW_OK) { - ui_menu_info_display2(0, "Processing TX", "..."); - } else { - ui_menu_info_display2(0, "Tansaction", "aborted"); - } - return 0; } #if 0 void ui_menu_opentx_display(unsigned int value) { @@ -223,378 +219,371 @@ void ui_menu_opentx_display(unsigned int value) { } #else void ui_menu_opentx_display(unsigned int value) { - uint32_t i; - if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { - snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "Processing TX"); - } else { - snprintf(G_monero_vstate.ux_info1, sizeof(G_monero_vstate.ux_info1), "Preparing TX"); - } - for (i = 0; (icomponent.userid==0x22) { - element->text = G_monero_vstate.ux_amount; + {NULL, NULL, 1, NULL, " Timelock", "?...?", 0, 0}, + {NULL, ui_menu_amount_validation_action, REJECT, NULL, "Reject", "Timelock", 0, 0}, + {NULL, ui_menu_amount_validation_action, ACCEPT, NULL, "Accept", "Timelock", 0, 0}, + UX_MENU_END}; +const bagl_element_t* ui_menu_amount_validation_preprocessor(const ux_menu_entry_t* entry, + bagl_element_t* element) { + /* --- Amount --- */ + if ((entry == &ui_menu_fee_validation[0]) || (entry == &ui_menu_change_validation[0]) || + (entry == &ui_menu_timelock_validation[0])) { + if (element->component.userid == 0x22) { + element->text = G_monero_vstate.ux_amount; + } } - } - return element; + return element; } void ui_menu_amount_validation_action(unsigned int value) { - unsigned short sw; - if (value == ACCEPT) { - sw = SW_OK; - } else { - sw = SW_DENY; - monero_abort_tx(); - } - monero_io_insert_u16(sw); - monero_io_do(IO_RETURN_AFTER_TX); - ui_menu_info_display2(0, "Processing TX", "..."); + unsigned short sw; + if (value == ACCEPT) { + sw = SW_OK; + } else { + sw = SW_DENY; + monero_abort_tx(); + } + monero_io_insert_u16(sw); + monero_io_do(IO_RETURN_AFTER_TX); + ui_menu_info_display2(0, "Processing TX", "..."); } void ui_menu_fee_validation_display(unsigned int value) { - UX_MENU_DISPLAY(0, ui_menu_fee_validation, ui_menu_amount_validation_preprocessor); + UX_MENU_DISPLAY(0, ui_menu_fee_validation, ui_menu_amount_validation_preprocessor); } void ui_menu_change_validation_display(unsigned int value) { - UX_MENU_DISPLAY(0, ui_menu_change_validation, ui_menu_amount_validation_preprocessor); + UX_MENU_DISPLAY(0, ui_menu_change_validation, ui_menu_amount_validation_preprocessor); } void ui_menu_timelock_validation_display(unsigned int value) { - UX_MENU_DISPLAY(0, ui_menu_timelock_validation, ui_menu_amount_validation_preprocessor); + UX_MENU_DISPLAY(0, ui_menu_timelock_validation, ui_menu_amount_validation_preprocessor); } /* ----------------------------- USER DEST/AMOUNT VALIDATION ----------------------------- */ void ui_menu_validation_action(unsigned int value); const ux_menu_entry_t ui_menu_validation[] = { - {NULL, NULL, 1, NULL, " Amount", "?xmr?", 0, 0}, - {NULL, NULL, 3, NULL, "Destination", "?dest.1?", 0, 0}, - {NULL, NULL, 4, NULL, "?dest.2?", "?dest.2?", 0, 0}, - {NULL, NULL, 5, NULL, "?dest.3?", "?dest.3?", 0, 0}, - {NULL, NULL, 6, NULL, "?dest.4?", "?dest.4?", 0, 0}, - {NULL, NULL, 7, NULL, "?dest.5?", "?dest.5?", 0, 0}, - {NULL, ui_menu_validation_action, REJECT, NULL, "Reject", "TX", 0, 0}, - {NULL, ui_menu_validation_action, ACCEPT, NULL, "Accept", "TX", 0, 0}, - UX_MENU_END -}; - -const bagl_element_t* ui_menu_validation_preprocessor(const ux_menu_entry_t* entry, bagl_element_t* element) { - - /* --- Amount --- */ - if (entry == &ui_menu_validation[0]) { - if(element->component.userid==0x22) { - element->text = G_monero_vstate.ux_amount; + {NULL, NULL, 1, NULL, " Amount", "?xmr?", 0, 0}, + {NULL, NULL, 3, NULL, "Destination", "?dest.1?", 0, 0}, + {NULL, NULL, 4, NULL, "?dest.2?", "?dest.2?", 0, 0}, + {NULL, NULL, 5, NULL, "?dest.3?", "?dest.3?", 0, 0}, + {NULL, NULL, 6, NULL, "?dest.4?", "?dest.4?", 0, 0}, + {NULL, NULL, 7, NULL, "?dest.5?", "?dest.5?", 0, 0}, + {NULL, ui_menu_validation_action, REJECT, NULL, "Reject", "TX", 0, 0}, + {NULL, ui_menu_validation_action, ACCEPT, NULL, "Accept", "TX", 0, 0}, + UX_MENU_END}; + +const bagl_element_t* ui_menu_validation_preprocessor(const ux_menu_entry_t* entry, + bagl_element_t* element) { + /* --- Amount --- */ + if (entry == &ui_menu_validation[0]) { + if (element->component.userid == 0x22) { + element->text = G_monero_vstate.ux_amount; + } } - } - /* --- Destination --- */ - if (entry == &ui_menu_validation[1]) { - if(element->component.userid==0x22) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)) ; - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*0, 11); - element->text = G_monero_vstate.ux_menu; + /* --- Destination --- */ + if (entry == &ui_menu_validation[1]) { + if (element->component.userid == 0x22) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 0, 11); + element->text = G_monero_vstate.ux_menu; + } } - } - if (entry == &ui_menu_validation[2]) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)) ; - if(element->component.userid==0x21) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*1, 11); - } - if(element->component.userid==0x22) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*2, 11); + if (entry == &ui_menu_validation[2]) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + if (element->component.userid == 0x21) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 1, 11); + } + if (element->component.userid == 0x22) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 2, 11); + } + element->text = G_monero_vstate.ux_menu; } - element->text = G_monero_vstate.ux_menu; - } - if (entry == &ui_menu_validation[3]) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)) ; - if(element->component.userid==0x21) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*3, 11); + if (entry == &ui_menu_validation[3]) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + if (element->component.userid == 0x21) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 3, 11); + } + if (element->component.userid == 0x22) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 4, 11); + } + element->text = G_monero_vstate.ux_menu; } - if(element->component.userid==0x22) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*4, 11); + if (entry == &ui_menu_validation[4]) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + if (element->component.userid == 0x21) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 5, 11); + } + if (element->component.userid == 0x22) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 6, 11); + } + element->text = G_monero_vstate.ux_menu; } - element->text = G_monero_vstate.ux_menu; - } - if (entry == &ui_menu_validation[4]) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)) ; - if(element->component.userid==0x21) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*5, 11); - } - if(element->component.userid==0x22) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*6, 11); - } - element->text = G_monero_vstate.ux_menu; - } - if (entry == &ui_menu_validation[5]) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)) ; - if(element->component.userid==0x21) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*7, 11); + if (entry == &ui_menu_validation[5]) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + if (element->component.userid == 0x21) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 7, 11); + } + if (element->component.userid == 0x22) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 8, 7); + } + element->text = G_monero_vstate.ux_menu; } - if(element->component.userid==0x22) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*8, 7); - } - element->text = G_monero_vstate.ux_menu; - } - return element; + return element; } void ui_menu_validation_display(unsigned int value) { - UX_MENU_DISPLAY(0, ui_menu_validation, ui_menu_validation_preprocessor); + UX_MENU_DISPLAY(0, ui_menu_validation, ui_menu_validation_preprocessor); } void ui_menu_validation_action(unsigned int value) { - unsigned short sw; - if (value == ACCEPT) { - sw = SW_OK; - } else { - sw = SW_DENY; - monero_abort_tx(); - } - monero_io_insert_u16(sw); - monero_io_do(IO_RETURN_AFTER_TX); - ui_menu_info_display2(0, "Processing TX", "..."); + unsigned short sw; + if (value == ACCEPT) { + sw = SW_OK; + } else { + sw = SW_DENY; + monero_abort_tx(); + } + monero_io_insert_u16(sw); + monero_io_do(IO_RETURN_AFTER_TX); + ui_menu_info_display2(0, "Processing TX", "..."); } - - /* -------------------------------- EXPORT VIEW KEY UX --------------------------------- */ -unsigned int ui_export_viewkey_prepro(const bagl_element_t* element); +unsigned int ui_export_viewkey_prepro(const bagl_element_t* element); unsigned int ui_export_viewkey_button(unsigned int button_mask, unsigned int button_mask_counter); - const bagl_element_t ui_export_viewkey[] = { - // type userid x y w h str rad fill fg bg font_id icon_id - { {BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, - NULL}, + // type userid x y w h str rad fill fg bg + // font_id icon_id + {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, NULL}, - { {BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CROSS }, - NULL}, + {{BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CROSS}, NULL}, - { {BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CHECK }, - NULL}, + {{BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CHECK}, NULL}, - { {BAGL_LABELINE, 0x01, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, - G_monero_vstate.ux_menu}, + {{BAGL_LABELINE, 0x01, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + G_monero_vstate.ux_menu}, - { {BAGL_LABELINE, 0x02, 0, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, - G_monero_vstate.ux_menu}, + {{BAGL_LABELINE, 0x02, 0, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + G_monero_vstate.ux_menu}, }; void ui_export_viewkey_display(unsigned int value) { - UX_DISPLAY(ui_export_viewkey, (void*)ui_export_viewkey_prepro); + UX_DISPLAY(ui_export_viewkey, (void*)ui_export_viewkey_prepro); } -unsigned int ui_export_viewkey_prepro(const bagl_element_t* element) { - if (element->component.userid == 1) { - snprintf(G_monero_vstate.ux_menu, sizeof(G_monero_vstate.ux_menu), "Export"); - return 1; - } - if (element->component.userid == 2) { - snprintf(G_monero_vstate.ux_menu, sizeof(G_monero_vstate.ux_menu), "View Key"); +unsigned int ui_export_viewkey_prepro(const bagl_element_t* element) { + if (element->component.userid == 1) { + snprintf(G_monero_vstate.ux_menu, sizeof(G_monero_vstate.ux_menu), "Export"); + return 1; + } + if (element->component.userid == 2) { + snprintf(G_monero_vstate.ux_menu, sizeof(G_monero_vstate.ux_menu), "View Key"); + return 1; + } + snprintf(G_monero_vstate.ux_menu, sizeof(G_monero_vstate.ux_menu), "Please Cancel"); return 1; - } - snprintf(G_monero_vstate.ux_menu, sizeof(G_monero_vstate.ux_menu), "Please Cancel"); - return 1; } unsigned int ui_export_viewkey_button(unsigned int button_mask, unsigned int button_mask_counter) { - unsigned int sw; - unsigned char x[32]; + unsigned int sw; + unsigned char x[32]; - monero_io_discard(0); - os_memset(x,0,32); - sw = SW_OK; + monero_io_discard(0); + os_memset(x, 0, 32); + sw = SW_OK; - switch(button_mask) { - case BUTTON_EVT_RELEASED|BUTTON_LEFT: // CANCEL - monero_io_insert(x, 32); - G_monero_vstate.export_view_key = 0; - break; + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT: // CANCEL + monero_io_insert(x, 32); + G_monero_vstate.export_view_key = 0; + break; - case BUTTON_EVT_RELEASED|BUTTON_RIGHT: // OK - monero_io_insert(G_monero_vstate.a, 32); - G_monero_vstate.export_view_key = EXPORT_VIEW_KEY; - break; + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: // OK + monero_io_insert(G_monero_vstate.a, 32); + G_monero_vstate.export_view_key = EXPORT_VIEW_KEY; + break; - default: + default: + return 0; + } + monero_io_insert_u16(sw); + monero_io_do(IO_RETURN_AFTER_TX); + ui_menu_main_display(0); return 0; - } - monero_io_insert_u16(sw); - monero_io_do(IO_RETURN_AFTER_TX); - ui_menu_main_display(0); - return 0; } - /* -------------------------------- ACCOUNT UX --------------------------------- */ void ui_menu_account_action(unsigned int value); const ux_menu_entry_t ui_menu_account[] = { - {NULL, NULL, 0, NULL, "It will reset", "the application!", 0, 0}, - {NULL, ui_menu_main_display, 0, &C_badge_back, "Abort", NULL, 61, 40}, - {NULL, ui_menu_account_action, 0, NULL, "0", NULL, 0, 0}, - {NULL, ui_menu_account_action, 1, NULL, "1", NULL, 0, 0}, - {NULL, ui_menu_account_action, 2, NULL, "2", NULL, 0, 0}, - {NULL, ui_menu_account_action, 3, NULL, "3", NULL, 0, 0}, - {NULL, ui_menu_account_action, 4, NULL, "4", NULL, 0, 0}, - {NULL, ui_menu_account_action, 5, NULL, "5", NULL, 0, 0}, - {NULL, ui_menu_account_action, 6, NULL, "6", NULL, 0, 0}, - {NULL, ui_menu_account_action, 7, NULL, "7", NULL, 0, 0}, - {NULL, ui_menu_account_action, 8, NULL, "8", NULL, 0, 0}, - {NULL, ui_menu_account_action, 9, NULL, "9", NULL, 0, 0}, - UX_MENU_END -}; - -const bagl_element_t* ui_menu_account_preprocessor(const ux_menu_entry_t* entry, bagl_element_t* element) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); - for (unsigned int i = 2; i<12; i++) { - if ((entry == &ui_menu_account[i]) && (element->component.userid==0x20) && (N_monero_pstate->account_id == (i-2))) { - G_monero_vstate.ux_menu[0] = '0' + (i-2); - G_monero_vstate.ux_menu[1] = ' '; - G_monero_vstate.ux_menu[2] = '+'; - element->text = G_monero_vstate.ux_menu; + {NULL, NULL, 0, NULL, "It will reset", "the application!", 0, 0}, + {NULL, ui_menu_main_display, 0, &C_badge_back, "Abort", NULL, 61, 40}, + {NULL, ui_menu_account_action, 0, NULL, "0", NULL, 0, 0}, + {NULL, ui_menu_account_action, 1, NULL, "1", NULL, 0, 0}, + {NULL, ui_menu_account_action, 2, NULL, "2", NULL, 0, 0}, + {NULL, ui_menu_account_action, 3, NULL, "3", NULL, 0, 0}, + {NULL, ui_menu_account_action, 4, NULL, "4", NULL, 0, 0}, + {NULL, ui_menu_account_action, 5, NULL, "5", NULL, 0, 0}, + {NULL, ui_menu_account_action, 6, NULL, "6", NULL, 0, 0}, + {NULL, ui_menu_account_action, 7, NULL, "7", NULL, 0, 0}, + {NULL, ui_menu_account_action, 8, NULL, "8", NULL, 0, 0}, + {NULL, ui_menu_account_action, 9, NULL, "9", NULL, 0, 0}, + UX_MENU_END}; + +const bagl_element_t* ui_menu_account_preprocessor(const ux_menu_entry_t* entry, + bagl_element_t* element) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + for (unsigned int i = 2; i < 12; i++) { + if ((entry == &ui_menu_account[i]) && (element->component.userid == 0x20) && + (N_monero_pstate->account_id == (i - 2))) { + G_monero_vstate.ux_menu[0] = '0' + (i - 2); + G_monero_vstate.ux_menu[1] = ' '; + G_monero_vstate.ux_menu[2] = '+'; + element->text = G_monero_vstate.ux_menu; + } } - } - return element; + return element; } void ui_menu_account_action(unsigned int value) { - monero_nvm_write((void*)&N_monero_pstate->account_id, &value, sizeof(unsigned int)); - monero_init(); - ui_menu_main_display(0); + monero_nvm_write((void*)&N_monero_pstate->account_id, &value, sizeof(unsigned int)); + monero_init(); + ui_menu_main_display(0); } void ui_menu_account_display(unsigned int value) { - UX_MENU_DISPLAY(value, ui_menu_account, ui_menu_account_preprocessor); + UX_MENU_DISPLAY(value, ui_menu_account, ui_menu_account_preprocessor); } - /* -------------------------------- NETWORK UX --------------------------------- */ void ui_menu_network_action(unsigned int value); const ux_menu_entry_t ui_menu_network[] = { - {NULL, NULL, 0, NULL, "It will reset", "the application!", 0, 0}, - {NULL, ui_menu_main_display, 0, &C_badge_back, "Abort", NULL, 61, 40}, - {NULL, ui_menu_network_action, TESTNET, NULL, "Test Network ", NULL, 0, 0}, - {NULL, ui_menu_network_action, STAGENET, NULL, "Stage Network", NULL, 0, 0}, - #ifndef MONERO_ALPHA - {NULL, ui_menu_network_action, MAINNET, NULL, "Main Network", NULL, 0, 0}, - #endif - UX_MENU_END -}; - -const bagl_element_t* ui_menu_network_preprocessor(const ux_menu_entry_t* entry, bagl_element_t* element) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); - if ((entry == &ui_menu_network[2]) && (element->component.userid==0x20) && (N_monero_pstate->network_id == TESTNET)) { - os_memmove(G_monero_vstate.ux_menu, "Test Network ", 14); - G_monero_vstate.ux_menu[13] = '+'; - element->text = G_monero_vstate.ux_menu; - } - if ((entry == &ui_menu_network[3]) && (element->component.userid==0x20) && (N_monero_pstate->network_id == STAGENET)) { - os_memmove(G_monero_vstate.ux_menu, "Stage Network ", 14); - G_monero_vstate.ux_menu[13] = '+'; - element->text = G_monero_vstate.ux_menu; - } - #ifndef MONERO_ALPHA - if ((entry == &ui_menu_network[4]) && (element->component.userid==0x20) && (N_monero_pstate->network_id == MAINNET)) { - os_memmove(G_monero_vstate.ux_menu, "Main Network ", 14); - G_monero_vstate.ux_menu[13] = '+'; - element->text = G_monero_vstate.ux_menu; - } - #endif - return element; + {NULL, NULL, 0, NULL, "It will reset", "the application!", 0, 0}, + {NULL, ui_menu_main_display, 0, &C_badge_back, "Abort", NULL, 61, 40}, + {NULL, ui_menu_network_action, TESTNET, NULL, "Test Network ", NULL, 0, 0}, + {NULL, ui_menu_network_action, STAGENET, NULL, "Stage Network", NULL, 0, 0}, +#ifndef MONERO_ALPHA + {NULL, ui_menu_network_action, MAINNET, NULL, "Main Network", NULL, 0, 0}, +#endif + UX_MENU_END}; + +const bagl_element_t* ui_menu_network_preprocessor(const ux_menu_entry_t* entry, + bagl_element_t* element) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + if ((entry == &ui_menu_network[2]) && (element->component.userid == 0x20) && + (N_monero_pstate->network_id == TESTNET)) { + os_memmove(G_monero_vstate.ux_menu, "Test Network ", 14); + G_monero_vstate.ux_menu[13] = '+'; + element->text = G_monero_vstate.ux_menu; + } + if ((entry == &ui_menu_network[3]) && (element->component.userid == 0x20) && + (N_monero_pstate->network_id == STAGENET)) { + os_memmove(G_monero_vstate.ux_menu, "Stage Network ", 14); + G_monero_vstate.ux_menu[13] = '+'; + element->text = G_monero_vstate.ux_menu; + } +#ifndef MONERO_ALPHA + if ((entry == &ui_menu_network[4]) && (element->component.userid == 0x20) && + (N_monero_pstate->network_id == MAINNET)) { + os_memmove(G_monero_vstate.ux_menu, "Main Network ", 14); + G_monero_vstate.ux_menu[13] = '+'; + element->text = G_monero_vstate.ux_menu; + } +#endif + return element; } void ui_menu_network_action(unsigned int value) { - monero_install(value); - monero_init(); - ui_menu_main_display(0); + monero_install(value); + monero_init(); + ui_menu_main_display(0); } void ui_menu_network_display(unsigned int value) { - UX_MENU_DISPLAY(value, ui_menu_network, ui_menu_network_preprocessor); + UX_MENU_DISPLAY(value, ui_menu_network, ui_menu_network_preprocessor); } /* -------------------------------- RESET UX --------------------------------- */ const ux_menu_entry_t ui_menu_reset[] = { - {NULL, NULL, 0, NULL, "Really Reset ?", NULL, 0, 0}, - {NULL, ui_menu_main_display, 0, &C_badge_back, "No", NULL, 61, 40}, - {NULL, ui_menu_reset_action, 0, NULL, "Yes", NULL, 0, 0}, - UX_MENU_END -}; + {NULL, NULL, 0, NULL, "Really Reset ?", NULL, 0, 0}, + {NULL, ui_menu_main_display, 0, &C_badge_back, "No", NULL, 61, 40}, + {NULL, ui_menu_reset_action, 0, NULL, "Yes", NULL, 0, 0}, + UX_MENU_END}; void ui_menu_reset_action(unsigned int value) { - unsigned char magic[4]; - magic[0] = 0; magic[1] = 0; magic[2] = 0; magic[3] = 0; - monero_nvm_write(N_monero_pstate->magic, magic, 4); - monero_init(); - ui_menu_main_display(0); + unsigned char magic[4]; + magic[0] = 0; + magic[1] = 0; + magic[2] = 0; + magic[3] = 0; + monero_nvm_write(N_monero_pstate->magic, magic, 4); + monero_init(); + ui_menu_main_display(0); } /* ------------------------------- SETTINGS UX ------------------------------- */ const ux_menu_entry_t ui_menu_settings[] = { - {NULL, ui_menu_account_display, 0, NULL, "Select Account", NULL, 0, 0}, - {NULL, ui_menu_network_display, 0, NULL, "Select Network", NULL, 0, 0}, - {NULL, ui_menu_words_display, 0, NULL, "Show 25 words", NULL, 0, 0}, - {ui_menu_reset, NULL, 0, NULL, "Reset", NULL, 0, 0}, - {NULL, ui_menu_main_display, 2, &C_badge_back, "Back", NULL, 61, 40}, - UX_MENU_END -}; + {NULL, ui_menu_account_display, 0, NULL, "Select Account", NULL, 0, 0}, + {NULL, ui_menu_network_display, 0, NULL, "Select Network", NULL, 0, 0}, + {NULL, ui_menu_words_display, 0, NULL, "Show 25 words", NULL, 0, 0}, + {ui_menu_reset, NULL, 0, NULL, "Reset", NULL, 0, 0}, + {NULL, ui_menu_main_display, 2, &C_badge_back, "Back", NULL, 61, 40}, + UX_MENU_END}; void ui_menu_settings_display(unsigned int value) { - UX_MENU_DISPLAY(value, ui_menu_settings, NULL); + UX_MENU_DISPLAY(value, ui_menu_settings, NULL); } /* --------------------------------- INFO UX --------------------------------- */ - #define STR(x) #x #define XSTR(x) STR(x) const ux_menu_entry_t ui_menu_about[] = { - {NULL, NULL, -1, NULL, "Monero", NULL, 0, 0}, - {NULL, NULL, -1, NULL, "(c) Ledger SAS", NULL, 0, 0}, - {NULL, NULL, -1, NULL, "Spec " XSTR(SPEC_VERSION), NULL, 0, 0}, - {NULL, NULL, -1, NULL, "App " XSTR(MONERO_VERSION), NULL, 0, 0}, - {NULL, ui_menu_main_display, 3, &C_badge_back, "Back", NULL, 61, 40}, - UX_MENU_END -}; + {NULL, NULL, -1, NULL, "Monero", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "(c) Ledger SAS", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "Spec " XSTR(SPEC_VERSION), NULL, 0, 0}, + {NULL, NULL, -1, NULL, "App " XSTR(MONERO_VERSION), NULL, 0, 0}, + {NULL, ui_menu_main_display, 3, &C_badge_back, "Back", NULL, 61, 40}, + UX_MENU_END}; #undef STR #undef XSTR @@ -604,192 +593,187 @@ const ux_menu_entry_t ui_menu_about[] = { void ui_menu_pubaddr_action(unsigned int value); const ux_menu_entry_t ui_menu_pubaddr[] = { - {NULL, NULL, 3, NULL, "t1.1", "t1.2", 0, 0}, - - {NULL, NULL, 3, NULL, "i1.1", "i1.2", 0, 0}, - - {NULL, NULL, 5, NULL, "l1.1", "l1.2", 0, 0}, - {NULL, NULL, 6, NULL, "l2.1", "l2.2", 0, 0}, - {NULL, NULL, 7, NULL, "l3.1", "l3.2", 0, 0}, - {NULL, NULL, 6, NULL, "l4.1", "l4.2", 0, 0}, - {NULL, NULL, 7, NULL, "l5.1", "l5.2", 0, 0}, - //{NULL, ui_menu_main_display, 0, &C_badge_back, "Back", NULL, 61, 40}, - {NULL, ui_menu_pubaddr_action, 0, &C_badge_back, "Ok", NULL, 61, 40}, - UX_MENU_END -}; - -const bagl_element_t* ui_menu_pubaddr_preprocessor(const ux_menu_entry_t* entry, bagl_element_t* element) { - - /* --- address --- */ - if (entry == &ui_menu_pubaddr[0]) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)) ; - if(element->component.userid==0x21) { - switch (G_monero_vstate.disp_addr_mode) { - case 0: - case DISP_MAIN: - os_memmove(G_monero_vstate.ux_menu, "Main", 4); - break; - case DISP_SUB: - os_memmove(G_monero_vstate.ux_menu, "Sub", 3); - break; - case DISP_INTEGRATED: - os_memmove(G_monero_vstate.ux_menu, "Integrated", 10); - break; - } - element->text = G_monero_vstate.ux_menu; - } - if(element->component.userid==0x22) { - os_memmove(G_monero_vstate.ux_menu, "Address", 7); - element->text = G_monero_vstate.ux_menu; + {NULL, NULL, 3, NULL, "t1.1", "t1.2", 0, 0}, + + {NULL, NULL, 3, NULL, "i1.1", "i1.2", 0, 0}, + + {NULL, NULL, 5, NULL, "l1.1", "l1.2", 0, 0}, + {NULL, NULL, 6, NULL, "l2.1", "l2.2", 0, 0}, + {NULL, NULL, 7, NULL, "l3.1", "l3.2", 0, 0}, + {NULL, NULL, 6, NULL, "l4.1", "l4.2", 0, 0}, + {NULL, NULL, 7, NULL, "l5.1", "l5.2", 0, 0}, + //{NULL, ui_menu_main_display, 0, &C_badge_back, "Back", NULL, 61, 40}, + {NULL, ui_menu_pubaddr_action, 0, &C_badge_back, "Ok", NULL, 61, 40}, + UX_MENU_END}; + +const bagl_element_t* ui_menu_pubaddr_preprocessor(const ux_menu_entry_t* entry, + bagl_element_t* element) { + /* --- address --- */ + if (entry == &ui_menu_pubaddr[0]) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + if (element->component.userid == 0x21) { + switch (G_monero_vstate.disp_addr_mode) { + case 0: + case DISP_MAIN: + os_memmove(G_monero_vstate.ux_menu, "Main", 4); + break; + case DISP_SUB: + os_memmove(G_monero_vstate.ux_menu, "Sub", 3); + break; + case DISP_INTEGRATED: + os_memmove(G_monero_vstate.ux_menu, "Integrated", 10); + break; + } + element->text = G_monero_vstate.ux_menu; + } + if (element->component.userid == 0x22) { + os_memmove(G_monero_vstate.ux_menu, "Address", 7); + element->text = G_monero_vstate.ux_menu; + } } - } - if (entry == &ui_menu_pubaddr[1]) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)) ; - if(element->component.userid==0x21) { - switch (G_monero_vstate.disp_addr_mode) { - case 0: - case DISP_MAIN: - case DISP_SUB: - snprintf(G_monero_vstate.ux_menu, sizeof(G_monero_vstate.ux_menu), - "Major: %d", - G_monero_vstate.disp_addr_M); - break; - case DISP_INTEGRATED: - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.payment_id, 8); - break; - } - element->text = G_monero_vstate.ux_menu; - } - if(element->component.userid==0x22) { - switch (G_monero_vstate.disp_addr_mode) { - case 0: - case DISP_MAIN: - case DISP_SUB: - snprintf(G_monero_vstate.ux_menu, sizeof(G_monero_vstate.ux_menu), - "minor: %d", - G_monero_vstate.disp_addr_m); - break; - case DISP_INTEGRATED: - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.payment_id+8, 8); - break; - } - element->text = G_monero_vstate.ux_menu; + if (entry == &ui_menu_pubaddr[1]) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + if (element->component.userid == 0x21) { + switch (G_monero_vstate.disp_addr_mode) { + case 0: + case DISP_MAIN: + case DISP_SUB: + snprintf(G_monero_vstate.ux_menu, sizeof(G_monero_vstate.ux_menu), "Major: %d", + G_monero_vstate.disp_addr_M); + break; + case DISP_INTEGRATED: + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.payment_id, 8); + break; + } + element->text = G_monero_vstate.ux_menu; + } + if (element->component.userid == 0x22) { + switch (G_monero_vstate.disp_addr_mode) { + case 0: + case DISP_MAIN: + case DISP_SUB: + snprintf(G_monero_vstate.ux_menu, sizeof(G_monero_vstate.ux_menu), "minor: %d", + G_monero_vstate.disp_addr_m); + break; + case DISP_INTEGRATED: + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.payment_id + 8, 8); + break; + } + element->text = G_monero_vstate.ux_menu; + } + element->text = G_monero_vstate.ux_menu; } - element->text = G_monero_vstate.ux_menu; - } - - if (entry == &ui_menu_pubaddr[2]) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)) ; - if(element->component.userid==0x21) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*0, 11); - } - if(element->component.userid==0x22) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*1, 11); - } - element->text = G_monero_vstate.ux_menu; - } - if (entry == &ui_menu_pubaddr[3]) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)) ; - if(element->component.userid==0x21) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*2, 11); - } - if(element->component.userid==0x22) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*3, 11); + if (entry == &ui_menu_pubaddr[2]) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + if (element->component.userid == 0x21) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 0, 11); + } + if (element->component.userid == 0x22) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 1, 11); + } + element->text = G_monero_vstate.ux_menu; } - element->text = G_monero_vstate.ux_menu; - } - if (entry == &ui_menu_pubaddr[4]) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)) ; - if(element->component.userid==0x21) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*4, 11); + if (entry == &ui_menu_pubaddr[3]) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + if (element->component.userid == 0x21) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 2, 11); + } + if (element->component.userid == 0x22) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 3, 11); + } + element->text = G_monero_vstate.ux_menu; } - if(element->component.userid==0x22) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*5, 11); + if (entry == &ui_menu_pubaddr[4]) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + if (element->component.userid == 0x21) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 4, 11); + } + if (element->component.userid == 0x22) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 5, 11); + } + element->text = G_monero_vstate.ux_menu; } - element->text = G_monero_vstate.ux_menu; - } - if (entry == &ui_menu_pubaddr[5]) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)) ; - if(element->component.userid==0x21) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*6, 11); + if (entry == &ui_menu_pubaddr[5]) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + if (element->component.userid == 0x21) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 6, 11); + } + if (element->component.userid == 0x22) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 7, 11); + } + element->text = G_monero_vstate.ux_menu; } - if(element->component.userid==0x22) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*7, 11); - } - element->text = G_monero_vstate.ux_menu; - } - if (entry == &ui_menu_pubaddr[6]) { - os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)) ; - if(element->component.userid==0x21) { - if (G_monero_vstate.disp_addr_mode== DISP_INTEGRATED) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*8, 11); - } else { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*8, 7); - } - } - if(element->component.userid==0x22) { - if (G_monero_vstate.disp_addr_mode== DISP_INTEGRATED) { - os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address+11*9, 7); - } + if (entry == &ui_menu_pubaddr[6]) { + os_memset(G_monero_vstate.ux_menu, 0, sizeof(G_monero_vstate.ux_menu)); + if (element->component.userid == 0x21) { + if (G_monero_vstate.disp_addr_mode == DISP_INTEGRATED) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 8, 11); + } else { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 8, 7); + } + } + if (element->component.userid == 0x22) { + if (G_monero_vstate.disp_addr_mode == DISP_INTEGRATED) { + os_memmove(G_monero_vstate.ux_menu, G_monero_vstate.ux_address + 11 * 9, 7); + } + } + element->text = G_monero_vstate.ux_menu; } - element->text = G_monero_vstate.ux_menu; - } - - return element; + return element; } void ui_menu_pubaddr_action(unsigned int value) { - - if (G_monero_vstate.disp_addr_mode) { - monero_io_insert_u16(SW_OK); - monero_io_do(IO_RETURN_AFTER_TX); - } - G_monero_vstate.disp_addr_mode = 0; - G_monero_vstate.disp_addr_M = 0; - G_monero_vstate.disp_addr_m = 0; - ui_menu_main_display(0); + if (G_monero_vstate.disp_addr_mode) { + monero_io_insert_u16(SW_OK); + monero_io_do(IO_RETURN_AFTER_TX); + } + G_monero_vstate.disp_addr_mode = 0; + G_monero_vstate.disp_addr_M = 0; + G_monero_vstate.disp_addr_m = 0; + ui_menu_main_display(0); } -void ui_menu_any_pubaddr_display(unsigned int value, unsigned char* pub_view, unsigned char *pub_spend, unsigned char is_subbadress, unsigned char *paymanetID){ - monero_base58_public_key(G_monero_vstate.ux_address+strlen(G_monero_vstate.ux_address), pub_view, pub_spend, is_subbadress, paymanetID); - UX_MENU_DISPLAY(value, ui_menu_pubaddr, ui_menu_pubaddr_preprocessor); +void ui_menu_any_pubaddr_display(unsigned int value, unsigned char* pub_view, + unsigned char* pub_spend, unsigned char is_subbadress, + unsigned char* paymanetID) { + monero_base58_public_key(G_monero_vstate.ux_address + strlen(G_monero_vstate.ux_address), + pub_view, pub_spend, is_subbadress, paymanetID); + UX_MENU_DISPLAY(value, ui_menu_pubaddr, ui_menu_pubaddr_preprocessor); } void ui_menu_pubaddr_display(unsigned int value) { - monero_base58_public_key(G_monero_vstate.ux_address, G_monero_vstate.A, G_monero_vstate.B, 0, NULL); - G_monero_vstate.disp_addr_mode = 0; - G_monero_vstate.disp_addr_M = 0; - G_monero_vstate.disp_addr_m = 0; - UX_MENU_DISPLAY(value, ui_menu_pubaddr, ui_menu_pubaddr_preprocessor); + monero_base58_public_key(G_monero_vstate.ux_address, G_monero_vstate.A, G_monero_vstate.B, 0, + NULL); + G_monero_vstate.disp_addr_mode = 0; + G_monero_vstate.disp_addr_M = 0; + G_monero_vstate.disp_addr_m = 0; + UX_MENU_DISPLAY(value, ui_menu_pubaddr, ui_menu_pubaddr_preprocessor); } - /* --------------------------------- MAIN UX --------------------------------- */ const ux_menu_entry_t ui_menu_main[] = { - {NULL, ui_menu_pubaddr_display, 0, NULL, G_monero_vstate.ux_wallet_account_name, G_monero_vstate.ux_wallet_public_short_address, 0, 0}, - {ui_menu_settings, NULL, 0, NULL, "Settings", NULL, 0, 0}, - {ui_menu_about, NULL, 0, NULL, "About", NULL, 0, 0}, - {NULL, (void*)os_sched_exit, 0, &C_icon_dashboard, "Quit app" , NULL, 50, 29}, - UX_MENU_END -}; + {NULL, ui_menu_pubaddr_display, 0, NULL, G_monero_vstate.ux_wallet_account_name, + G_monero_vstate.ux_wallet_public_short_address, 0, 0}, + {ui_menu_settings, NULL, 0, NULL, "Settings", NULL, 0, 0}, + {ui_menu_about, NULL, 0, NULL, "About", NULL, 0, 0}, + {NULL, (void*)os_sched_exit, 0, &C_icon_dashboard, "Quit app", NULL, 50, 29}, + UX_MENU_END}; -void ui_menu_main_display(unsigned int value) { - UX_MENU_DISPLAY(value, ui_menu_main, NULL); -} +void ui_menu_main_display(unsigned int value) { UX_MENU_DISPLAY(value, ui_menu_main, NULL); } void ui_init(void) { - ui_menu_main_display(0); - // setup the first screen changing - UX_CALLBACK_SET_INTERVAL(1000); + ui_menu_main_display(0); + // setup the first screen changing + UX_CALLBACK_SET_INTERVAL(1000); } -void io_seproxyhal_display(const bagl_element_t *element) { - io_seproxyhal_display_default((bagl_element_t *)element); +void io_seproxyhal_display(const bagl_element_t* element) { + io_seproxyhal_display_default((bagl_element_t*)element); } #endif diff --git a/src/monero_vars.h b/src/monero_vars.h index ac7ee50..997e17a 100644 --- a/src/monero_vars.h +++ b/src/monero_vars.h @@ -22,15 +22,14 @@ #include "monero_types.h" #include "monero_api.h" - -extern monero_v_state_t G_monero_vstate; +extern monero_v_state_t G_monero_vstate; #ifdef TARGET_NANOX extern const monero_nv_state_t N_state_pic; -#define N_monero_pstate ((volatile monero_nv_state_t *)PIC(&N_state_pic)) +#define N_monero_pstate ((volatile monero_nv_state_t *)PIC(&N_state_pic)) #else extern monero_nv_state_t N_state_pic; -#define N_monero_pstate ((WIDE monero_nv_state_t *)PIC(&N_state_pic)) +#define N_monero_pstate ((WIDE monero_nv_state_t *)PIC(&N_state_pic)) #endif #ifdef MONERO_DEBUG_MAIN From 6ed7e3008136443dd5f9a5ac03c4e6b1b9c089b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 2 Jul 2020 10:59:46 +0200 Subject: [PATCH 23/76] Add clang-format check with GitHub Actions --- .github/workflows/lint-workflow.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/lint-workflow.yml diff --git a/.github/workflows/lint-workflow.yml b/.github/workflows/lint-workflow.yml new file mode 100644 index 0000000..67a2024 --- /dev/null +++ b/.github/workflows/lint-workflow.yml @@ -0,0 +1,19 @@ +name: Code style check + +on: [push, pull_request] + +jobs: + job_lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Clone + uses: actions/checkout@v2 + + - name: Lint + uses: DoozyX/clang-format-lint-action@v0.5 + with: + source: './src' + extensions: 'h,c' + clangFormatVersion: 9 From 84017d7f7ff575b5787e3832542d8eb732feacd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 2 Jul 2020 11:15:39 +0200 Subject: [PATCH 24/76] Add compilation with GitHub Actions --- .github/workflows/ci-workflow.yml | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/ci-workflow.yml diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml new file mode 100644 index 0000000..89eee7e --- /dev/null +++ b/.github/workflows/ci-workflow.yml @@ -0,0 +1,32 @@ +name: Compilation + +on: + push: + branches: + - master + - develop + pull_request: + - master + - develop + +jobs: + job_build_debug: + name: Build debug + runs-on: ubuntu-latest + + container: + image: docker://ledgerhq/ledger-app-builder:1.6.0 + + steps: + - name: Clone + uses: actions/checkout@v2 + + - name: Build + run: | + make DEBUG=1 + + - name: Upload app binary + uses: actions/upload-artifact@v2 + with: + name: monero-app-debug + path: bin From b5e6908e40cbeb8011246ec6d57085ba7621a982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 2 Jul 2020 11:36:25 +0200 Subject: [PATCH 25/76] Update licence --- src/monero_api.h | 25 ++++++++++++++----------- src/monero_blind.c | 25 ++++++++++++++----------- src/monero_crypto.c | 25 ++++++++++++++----------- src/monero_dispatch.c | 25 ++++++++++++++----------- src/monero_init.c | 25 ++++++++++++++----------- src/monero_io.c | 25 ++++++++++++++----------- src/monero_key.c | 25 ++++++++++++++----------- src/monero_main.c | 25 ++++++++++++++----------- src/monero_mlsag.c | 25 ++++++++++++++----------- src/monero_monero.c | 26 +++++++++++++++----------- src/monero_nvram.c | 25 ++++++++++++++----------- src/monero_open_tx.c | 25 ++++++++++++++----------- src/monero_prefix.c | 25 ++++++++++++++----------- src/monero_prehash.c | 25 ++++++++++++++----------- src/monero_proof.c | 25 ++++++++++++++----------- src/monero_ram.c | 25 ++++++++++++++----------- src/monero_stealth.c | 25 ++++++++++++++----------- src/monero_types.h | 25 ++++++++++++++----------- src/monero_ux_msg.c | 25 ++++++++++++++----------- src/monero_ux_msg.h | 25 ++++++++++++++----------- src/monero_ux_nano.c | 25 ++++++++++++++----------- src/monero_ux_nanos.c | 25 ++++++++++++++----------- src/monero_vars.h | 25 ++++++++++++++----------- 23 files changed, 323 insertions(+), 253 deletions(-) diff --git a/src/monero_api.h b/src/monero_api.h index 63e602c..a86aa4f 100644 --- a/src/monero_api.h +++ b/src/monero_api.h @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #ifndef MONERO_API_H #define MONERO_API_H diff --git a/src/monero_blind.c b/src/monero_blind.c index f3f1e5d..63ea7c2 100644 --- a/src/monero_blind.c +++ b/src/monero_blind.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #include "os.h" #include "cx.h" diff --git a/src/monero_crypto.c b/src/monero_crypto.c index 2720cf1..07cd897 100644 --- a/src/monero_crypto.c +++ b/src/monero_crypto.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #include "os.h" #include "cx.h" diff --git a/src/monero_dispatch.c b/src/monero_dispatch.c index 08dbc5c..fcbea88 100644 --- a/src/monero_dispatch.c +++ b/src/monero_dispatch.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #include "os.h" #include "cx.h" diff --git a/src/monero_init.c b/src/monero_init.c index f4ede52..45d643d 100644 --- a/src/monero_init.c +++ b/src/monero_init.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #include "os.h" #include "cx.h" diff --git a/src/monero_io.c b/src/monero_io.c index 36ddd41..65c9b5a 100644 --- a/src/monero_io.c +++ b/src/monero_io.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #include "os.h" #include "cx.h" diff --git a/src/monero_key.c b/src/monero_key.c index 1ee2034..1187f9e 100644 --- a/src/monero_key.c +++ b/src/monero_key.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #include "os.h" #include "cx.h" diff --git a/src/monero_main.c b/src/monero_main.c index ea1d993..d94ac5e 100644 --- a/src/monero_main.c +++ b/src/monero_main.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #ifndef MONERO_DEBUG_MAIN diff --git a/src/monero_mlsag.c b/src/monero_mlsag.c index bf1a225..415cc93 100644 --- a/src/monero_mlsag.c +++ b/src/monero_mlsag.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #include "os.h" #include "cx.h" diff --git a/src/monero_monero.c b/src/monero_monero.c index a2fcfe5..1f753bd 100644 --- a/src/monero_monero.c +++ b/src/monero_monero.c @@ -1,17 +1,21 @@ -/* Copyright 2017-2018 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + #include "os.h" #include "monero_types.h" #include "monero_api.h" diff --git a/src/monero_nvram.c b/src/monero_nvram.c index 9dd8473..c13a4b5 100644 --- a/src/monero_nvram.c +++ b/src/monero_nvram.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #include "os.h" #include "cx.h" diff --git a/src/monero_open_tx.c b/src/monero_open_tx.c index 2967dda..093eec0 100644 --- a/src/monero_open_tx.c +++ b/src/monero_open_tx.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #include "os.h" #include "cx.h" diff --git a/src/monero_prefix.c b/src/monero_prefix.c index 7ec0945..de662c5 100644 --- a/src/monero_prefix.c +++ b/src/monero_prefix.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ /* * Client: rctSigs.cpp.c -> get_pre_mlsag_hash diff --git a/src/monero_prehash.c b/src/monero_prehash.c index 717a812..231ffcc 100644 --- a/src/monero_prehash.c +++ b/src/monero_prehash.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ /* * Client: rctSigs.cpp.c -> get_pre_mlsag_hash diff --git a/src/monero_proof.c b/src/monero_proof.c index 1ab1b21..30eb028 100644 --- a/src/monero_proof.c +++ b/src/monero_proof.c @@ -1,17 +1,20 @@ -/* Copyright 2019 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #include "os.h" #include "cx.h" diff --git a/src/monero_ram.c b/src/monero_ram.c index a69d26a..7f7fd49 100644 --- a/src/monero_ram.c +++ b/src/monero_ram.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #include "os.h" #include "cx.h" diff --git a/src/monero_stealth.c b/src/monero_stealth.c index 609b8e1..7d82e2a 100644 --- a/src/monero_stealth.c +++ b/src/monero_stealth.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #include "os.h" #include "cx.h" diff --git a/src/monero_types.h b/src/monero_types.h index af6745c..b98d789 100644 --- a/src/monero_types.h +++ b/src/monero_types.h @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #ifndef MONERO_TYPES_H #define MONERO_TYPES_H diff --git a/src/monero_ux_msg.c b/src/monero_ux_msg.c index 826a48b..e9948a1 100644 --- a/src/monero_ux_msg.c +++ b/src/monero_ux_msg.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ const char* const C_OK = "OK"; const char* const C_NOK = "NOK"; diff --git a/src/monero_ux_msg.h b/src/monero_ux_msg.h index ac4a757..bafdf48 100644 --- a/src/monero_ux_msg.h +++ b/src/monero_ux_msg.h @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #ifndef MONERO_UX_MSG_H #define MONERO_UX_MSG_H diff --git a/src/monero_ux_nano.c b/src/monero_ux_nano.c index 84702a9..8d3d7b6 100644 --- a/src/monero_ux_nano.c +++ b/src/monero_ux_nano.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #if defined(UI_NANO_X) || defined(UI_NANO_SX) diff --git a/src/monero_ux_nanos.c b/src/monero_ux_nanos.c index ec4829e..4869616 100644 --- a/src/monero_ux_nanos.c +++ b/src/monero_ux_nanos.c @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #ifdef UI_NANO_S diff --git a/src/monero_vars.h b/src/monero_vars.h index 997e17a..6d047f0 100644 --- a/src/monero_vars.h +++ b/src/monero_vars.h @@ -1,17 +1,20 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #ifndef MONERO_VARS_H #define MONERO_VARS_H From a32d867e3f6f4faaf7b2f4d08b2b5692f1c06f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 2 Jul 2020 14:37:07 +0200 Subject: [PATCH 26/76] Add .gitignore --- .gitignore | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8488aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Compilation of Ledger's app +src/glyphs.c +src/glyphs.h +bin/ +debug/ +dep/ +obj/ + + +# Editors +.vscode/ +.idea/ + +# Python +*.pyc[cod] +*.egg +__pycache__/ +*.egg-info/ +.eggs/ +.python-version From 6bb7236a80080cfe4dce3e445292a95549a4ef36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 2 Jul 2020 14:45:05 +0200 Subject: [PATCH 27/76] Add CHANGELOG.md --- CHANGELOG.md | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 127 ------------------------------------------------- 2 files changed, 130 insertions(+), 127 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6eb171d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,130 @@ +# Changelog + +All notable changes to this project will be documented in this file. + + +## 1.6.0 - 2020-06-04 + + Add Timelock verification on device + +## 1.5.1 - 2020-02-28 + + Security Enhancement + +## 1.4.2 + + Compatibility check with client version now discards the fourth sub-version number. + + Release for client 0.15.0+ + firmware LNS 1.6.0 et LNX 1.2.4 + +## 1.4.1 + + Release for client 0.15 + firmware LNS 1.6.0 et LNX 1.2.4 + +## 1.4.0 + +- Add address display +- Enhance protocol security +- Remove double ask for view key + +## 1.3.2 + +-- + +## 1.3.1 + +Add Tx proof support Monero post 0.14.0.2 + +## 1.3.0 + +Targeted Client: Monero post 0.14.0.2 + +## 1.2.2 + +Targeted Client: Monero 0.14.0.2 + +Partial bug Fixes in change destination address computation: Only one destination +is allowed in transfer command + +## 1.2.0 + +Targeted Client: Monero 0.14.0.0+ + +- V11 fork integration +- Fix change address issue. + +## 1.1.3 + +- Remove rolling address display +- Allow STEALTH instruction outside TX +- Doc fix + +## 1.1.2 + +Fix stack overflow for 1.5.5 SDK + +## 1.1.1 + +Allow transaction parsing when screen is locked + +## 1.1.0 + +Initial Release + +Targeted Client: Monero 0.13.0.0+ + +- Security fix: Screen lock management +- Optimisation: New protocol V2 for future +- Fix bug in large amount display that was truncated +- Remove confirmation for zero amount (fake sweep change) +- Better handling for change address to not display them +- Dual id (PIN based) management +- Add onscreen seed words display + + +## 1.0.0 + +Initial Release + +Targeted Client: Monero 0.13.0.0+ + + +## 0.12.4 / Beta 5 + +Targeted Client: Monero 0.12.1 + +- U2F support +- Fix Windows detection problem +- activate Mainnet 'Beta stage: USE AT YOUR OWN RISK' + +## 0.12.3 / Beta 4 + +Targeted Client: Monero 0.12.1 + +- SDK 1.4.2.1 port + +## 0.12.2 / Beta 3 + +Targeted Client: Monero 0.12.1 + +- Activate security command chain control + + +## 0.12.1 / Beta 2 + +Targeted Client: Monero 0.12.1 + +- Add second PIN support +- Remove key storage in NVRAM, always recompute secret key at boot +- Export secret viewkey, with agreement of user, to speed up tx scan +- Clean-up RAM usage +- Change some naming according to Monero client convention + +## Beta 1 + +Targeted Client: Monero 0.12.0 + +- Initial Beta. + diff --git a/README.md b/README.md index b531d7d..e4df79d 100644 --- a/README.md +++ b/README.md @@ -10,130 +10,3 @@ In order to install from sources for testing purpose you need to uncomment the t DEFINES += IODUMMYCRYPT Note this is only for testing. For production usage, use the application provided by the Live Manager. - -# Revision - -## v1.6.0 - - Add Timelock verification on device - -## v1.5.x - - Security Enhancement - -## V1.4.2 - - Compatibility check with client version now discards the fourth sub-version number. - - Release for client 0.15.0+ - firmware LNS 1.6.0 et LNX 1.2.4 - -## V1.4.1 - - Release for client 0.15 - firmware LNS 1.6.0 et LNX 1.2.4 - -## V1.4.0 - -- Add address display -- Enhance protocol security -- Remove double ask for view key - -## V1.3.2 - --- - -## V1.3.1 - -Add Tx proof support Monero post 0.14.0.2 - -## V1.3.0 - -Targeted Client: Monero post 0.14.0.2 - -## V1.2.2 - -Targeted Client: Monero 0.14.0.2 - -Partial bug Fixes in change destination address computation: Only one destination -is allowed in transfer command - -## v1.2.0 - -Targeted Client: Monero 0.14.0.0+ - -- V11 fork integration -- Fix change address issue. - -## v1.1.3 - -- Remove rolling address display -- Allow STEALTH instruction outside TX -- Doc fix - -## v1.1.2 - -Fix stack overflow for 1.5.5 SDK - -## v1.1.1 - -Allow transaction parsing when screen is locked - -## v1.1.0 - -Initial Release - -Targeted Client: Monero 0.13.0.0+ - -- Security fix: Screen lock management -- Optimisation: New protocol V2 for future -- Fix bug in large amount display that was truncated -- Remove confirmation for zero amount (fake sweep change) -- Better handling for change address to not display them -- Dual id (PIN based) management -- Add onscreen seed words display - - -## v1.0.0 - -Initial Release - -Targeted Client: Monero 0.13.0.0+ - - -## v 0.12.4 / Beta 5 - -Targeted Client: Monero 0.12.1 - -- U2F support -- Fix Windows detection problem -- activate Mainnet 'Beta stage: USE AT YOUR OWN RISK' - -## v 0.12.3 / Beta 4 - -Targeted Client: Monero 0.12.1 - -- SDK 1.4.2.1 port - -## v 0.12.2 / Beta 3 - -Targeted Client: Monero 0.12.1 - -- Activate security command chain control - - -## v 0.12.1 / Beta 2 - -Targeted Client: Monero 0.12.1 - -- Add second PIN support -- Remove key storage in NVRAM, always recompute secret key at boot -- Export secret viewkey, with agreement of user, to speed up tx scan -- Clean-up RAM usage -- Change some naming according to Monero client convention - -## Beta 1 - -Targeted Client: Monero 0.12.0 - -- Initial Beta. From a91441eadcc98a49cbb0d02b780edfe18267924c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 2 Jul 2020 15:07:01 +0200 Subject: [PATCH 28/76] Better debug mode compilation --- Makefile | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 9c0864f..0eb34f2 100644 --- a/Makefile +++ b/Makefile @@ -60,13 +60,7 @@ else DEFINES += UI_NANO_S endif - - #DEFINES += IOCRYPT -## Debug options -#DEFINES += DEBUG_HWDEVICE -#DEFINES += IODUMMYCRYPT -#DEFINES += IONOCRYPT ################ # Default rule # @@ -126,7 +120,11 @@ ifneq ($(DEBUG),0) DEFINES += HAVE_PRINTF PRINTF=screen_printf endif DEFINES += PLINE="PRINTF(\"FILE:%s..LINE:%d\n\",__FILE__,__LINE__)" - + # Debug options + DEFINES += DEBUG_HWDEVICE + DEFINES += IODUMMYCRYPT # or IONOCRYPT + # Stagenet network by default + DEFINES += MONERO_BETA else DEFINES += PRINTF\(...\)= From a05c828c8793e8b33b4abbee90025e3c297bc99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 2 Jul 2020 15:07:21 +0200 Subject: [PATCH 29/76] Update README --- README.md | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e4df79d..dc9d137 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,28 @@ -# ledger-app-monero +# Monero Ledger App -Monero wallet application for Ledger Blue and Nano S +Monero wallet application for Ledger Nano S and Nano X. -# Install from sources +## Install -In order to install from sources for testing purpose you need to uncomment the two following lines in Makefile +### Prerequisite - DEFINES += DEBUG_HWDEVICE - DEFINES += IODUMMYCRYPT +Be sure to have your environment correctly set up (see [Getting Started](https://ledger.readthedocs.io/en/latest/userspace/getting_started.html)) and [ledgerblue](https://pypi.org/project/ledgerblue/) installed. -Note this is only for testing. For production usage, use the application provided by the Live Manager. + +### Compilation + +In order to use the app with the Monero client, you need to compile in debug mode: + +``` +make DEBUG=1 +make load # load the app on the Nano using ledgerblue +``` + +Note that it's for testing only. +For production usage, use the application provided by the [Ledger Live](https://www.ledger.com/ledger-live/download/) manager. + +## Useful links + +* Monero client CLI or GUI - [https://web.getmonero.org/downloads/](https://web.getmonero.org/downloads/) + +* Ledger's developer documentation - [https://ledger.readthedocs.io](https://ledger.readthedocs.io) From f67699e3aa2b47d7cf18244340e9540a4f3de09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Fri, 3 Jul 2020 15:41:15 +0200 Subject: [PATCH 30/76] Add basic non reg tests --- setup.cfg | 4 + tests/conftest.py | 63 +++ tests/monero_client/__init__.py | 0 tests/monero_client/crypto/__init__.py | 0 tests/monero_client/crypto/hmac.py | 19 + tests/monero_client/crypto/sha3.py | 50 ++ tests/monero_client/exception/__init__.py | 137 ++++++ tests/monero_client/exception/device_error.py | 47 ++ tests/monero_client/io/__init__.py | 0 tests/monero_client/io/button.py | 33 ++ tests/monero_client/io/comm.py | 20 + tests/monero_client/io/display.py | 74 +++ tests/monero_client/io/hid_device.py | 83 ++++ tests/monero_client/io/tcp_client.py | 39 ++ tests/monero_client/io/transport.py | 55 +++ tests/monero_client/monero_cmd.py | 454 ++++++++++++++++++ tests/monero_client/monero_crypto_cmd.py | 261 ++++++++++ tests/monero_client/monero_types.py | 78 +++ tests/monero_client/utils/__init__.py | 0 tests/monero_client/utils/base58.py | 191 ++++++++ tests/monero_client/utils/varint.py | 30 ++ tests/test_version.py | 17 + 22 files changed, 1655 insertions(+) create mode 100644 setup.cfg create mode 100644 tests/conftest.py create mode 100644 tests/monero_client/__init__.py create mode 100644 tests/monero_client/crypto/__init__.py create mode 100644 tests/monero_client/crypto/hmac.py create mode 100644 tests/monero_client/crypto/sha3.py create mode 100644 tests/monero_client/exception/__init__.py create mode 100644 tests/monero_client/exception/device_error.py create mode 100644 tests/monero_client/io/__init__.py create mode 100644 tests/monero_client/io/button.py create mode 100644 tests/monero_client/io/comm.py create mode 100644 tests/monero_client/io/display.py create mode 100644 tests/monero_client/io/hid_device.py create mode 100644 tests/monero_client/io/tcp_client.py create mode 100644 tests/monero_client/io/transport.py create mode 100644 tests/monero_client/monero_cmd.py create mode 100644 tests/monero_client/monero_crypto_cmd.py create mode 100644 tests/monero_client/monero_types.py create mode 100644 tests/monero_client/utils/__init__.py create mode 100644 tests/monero_client/utils/base58.py create mode 100644 tests/monero_client/utils/varint.py create mode 100644 tests/test_version.py diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..f02153f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[tool:pytest] +testpaths = tests +addopts = --strict-markers +markers = incremental diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..86b7924 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,63 @@ +import pytest +from typing import Dict, Tuple + +from monero_client.io.button import Button, FakeButton +from monero_client.monero_cmd import MoneroCmd + + +SPECULOS: bool = True + + +@pytest.fixture(scope="module") +def monero(): + monero = MoneroCmd(debug=True, + speculos=SPECULOS) + + yield monero + + monero.device.close() + + +@pytest.fixture(scope="module") +def button(): + button = (Button(server="127.0.0.1", port=42000) + if SPECULOS else FakeButton()) + + yield button + + button.close() + + +_test_failed_incremental: Dict[str, Dict[Tuple[int, ...], str]] = {} + + +def pytest_runtest_makereport(item, call): + if "incremental" in item.keywords: + if call.excinfo is not None: + + cls_name = str(item.cls) + parametrize_index = ( + tuple(item.callspec.indices.values()) + if hasattr(item, "callspec") + else () + ) + test_name = item.originalname or item.name + _test_failed_incremental.setdefault(cls_name, {}).setdefault( + parametrize_index, test_name + ) + + +def pytest_runtest_setup(item): + if "incremental" in item.keywords: + cls_name = str(item.cls) + if cls_name in _test_failed_incremental: + parametrize_index = ( + tuple(item.callspec.indices.values()) + if hasattr(item, "callspec") + else () + ) + test_name = _test_failed_incremental[cls_name].get( + parametrize_index, None + ) + if test_name is not None: + pytest.xfail("previous test failed ({})".format(test_name)) diff --git a/tests/monero_client/__init__.py b/tests/monero_client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/monero_client/crypto/__init__.py b/tests/monero_client/crypto/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/monero_client/crypto/hmac.py b/tests/monero_client/crypto/hmac.py new file mode 100644 index 0000000..af1504a --- /dev/null +++ b/tests/monero_client/crypto/hmac.py @@ -0,0 +1,19 @@ +import hashlib +import hmac +import struct + +from monero_client.monero_types import Type + + +def encode_msg_for_hmac(msg: bytes, msg_type: Type) -> bytes: + """See monero_io_insert_hmac_for() in `monero_io.c`.""" + if msg_type == Type.ALPHA: + raise Exception("Can't compute HMAC with type ALPHA.") + + return msg + struct.pack("B", msg_type) + (b"\x00" * 4) + + +def hmac_sha256(msg: bytes, key: bytes, monero_type: Type) -> bytes: + return hmac.new(key, + msg=encode_msg_for_hmac(msg, monero_type), + digestmod=hashlib.sha256).digest() diff --git a/tests/monero_client/crypto/sha3.py b/tests/monero_client/crypto/sha3.py new file mode 100644 index 0000000..4a35782 --- /dev/null +++ b/tests/monero_client/crypto/sha3.py @@ -0,0 +1,50 @@ +"""Helpers for Monero adresses. + +mainnet address prefix: + - address: 18 + - integrated address: 19 + - subaddress: 42 + +stagenet address prefix: + - address: 24 + - integrated address: 25 + - subaddress: 36 + +testnet address prefix: + - address: 53 + - integrated address: 54 + - subaddress: 63 + + +SOFTWALLET +------------ +integrated_address 0102030405060708 +5KpvhQBFNXYe3J41sBdjvjaBUyz8qHohsQcGtRf63qEUTMBvmA45fpp5pSacMdSg7A3b71RejLzB8EkGbfjp5PELipMNxnGr3Wv1zf7dWq + +HWWALLET +-------- +integrated_address 0102030405060708 +5DhUnJjS31jgAQDY6qKUP6TeBy5JqXSJzhG4i7447yMXS7wdt4hKh6gQCTTa9FiYNpEjBoHZ9iTww3vL96P1hcmTbnu8Nst8WHC21KaMVc + + +>>> addr_prefix: bytes = b"\x19" +>>> pub_spend_key: bytes = bytes.fromhex("dae41d6b13568fdd71ec3d20c2f614c65fe819f36ca5da8d24df3bd89b2bad9d") +>>> pub_view_key: bytes = bytes.fromhex("865cbfab852a1d1ccdfc7328e4dac90f78fc2154257d07522e9b79e637326dfa") +>>> payment_id: bytes = b"\x01\x02\x03\x04\x05\x06\x07\x08" +>>> concat: bytes = addr_prefix + pub_spend_key + pub_view_key + payment_id +>>> addr: str = base58.encode(concat + sha3(concat)[:4]) + +""" + +import hashlib + + +def sc_reduce32(value: bytes) -> bytes: + n: int = int.from_bytes(value, byteorder="little") + l: int = 2**252 + 27742317777372353535851937790883648493 + + return (n % l).to_bytes(32, "little") + + +def sha3(value: bytes) -> bytes: + return hashlib.sha3_256(value).digest() diff --git a/tests/monero_client/exception/__init__.py b/tests/monero_client/exception/__init__.py new file mode 100644 index 0000000..3b67c20 --- /dev/null +++ b/tests/monero_client/exception/__init__.py @@ -0,0 +1,137 @@ +__all__ = [ + "UnknownDeviceError", + "WrongLength", + "ClientNotSupported", + "SecurityPinLocked", + "SecurityLoadKey", + "SecurityCommitmentControl", + "SecurityAmountChainControl", + "SecurityCommitmentChainControl", + "SecurityOutKeysChainControl", + "SecurityMaxOutputReached", + "SecurityHMAC", + "SecurityRangeValue", + "SecurityInternal", + "SecurityMaxSignatureReached", + "SecurityPrefixHash", + "SecurityLocked", + "CommandNotAllowed", + "SubCommandNotAllowed", + "Deny", + "KeyNotSet", + "WrongData", + "WrongDataRange", + "IOFull", + "WrongP1P2", + "InsNotSupported", + "ProtocolNotSupported", + "Unknown" +] + + +class UnknownDeviceError(Exception): + pass + + +class WrongLength(UnknownDeviceError): + pass + + +class ClientNotSupported(UnknownDeviceError): + pass + + +class SecurityPinLocked(UnknownDeviceError): + pass + + +class SecurityLoadKey(UnknownDeviceError): + pass + + +class SecurityCommitmentControl(UnknownDeviceError): + pass + + +class SecurityAmountChainControl(UnknownDeviceError): + pass + + +class SecurityCommitmentChainControl(UnknownDeviceError): + pass + + +class SecurityOutKeysChainControl(UnknownDeviceError): + pass + + +class SecurityMaxOutputReached(UnknownDeviceError): + pass + + +class SecurityHMAC(UnknownDeviceError): + pass + + +class SecurityRangeValue(UnknownDeviceError): + pass + + +class SecurityInternal(UnknownDeviceError): + pass + + +class SecurityMaxSignatureReached(UnknownDeviceError): + pass + + +class SecurityPrefixHash(UnknownDeviceError): + pass + + +class SecurityLocked(UnknownDeviceError): + pass + + +class CommandNotAllowed(UnknownDeviceError): + pass + + +class SubCommandNotAllowed(UnknownDeviceError): + pass + + +class Deny(UnknownDeviceError): + pass + + +class KeyNotSet(UnknownDeviceError): + pass + + +class WrongData(UnknownDeviceError): + pass + + +class WrongDataRange(UnknownDeviceError): + pass + + +class IOFull(UnknownDeviceError): + pass + + +class WrongP1P2(UnknownDeviceError): + pass + + +class InsNotSupported(UnknownDeviceError): + pass + + +class ProtocolNotSupported(UnknownDeviceError): + pass + + +class Unknown(UnknownDeviceError): + pass diff --git a/tests/monero_client/exception/device_error.py b/tests/monero_client/exception/device_error.py new file mode 100644 index 0000000..6349987 --- /dev/null +++ b/tests/monero_client/exception/device_error.py @@ -0,0 +1,47 @@ +import enum +from typing import Dict, Any, Optional + +from . import * + + +class DeviceError: + exc: Dict[int, Any] = { + 0x6700: WrongLength, + 0x6a30: ClientNotSupported, + 0x6910: SecurityPinLocked, + 0x6911: SecurityLoadKey, + 0x6912: SecurityCommitmentControl, + 0x6913: SecurityAmountChainControl, + 0x6914: SecurityCommitmentChainControl, + 0x6915: SecurityOutKeysChainControl, + 0x6916: SecurityMaxOutputReached, + 0x6917: SecurityHMAC, + 0x6918: SecurityRangeValue, + 0x6919: SecurityInternal, + 0x691a: SecurityMaxSignatureReached, + 0x691b: SecurityPrefixHash, + 0x69ee: SecurityLocked, + 0x6980: CommandNotAllowed, + 0x6981: SubCommandNotAllowed, + 0x6982: Deny, + 0x6983: KeyNotSet, + 0x6984: WrongData, + 0x6985: WrongDataRange, + 0x6986: IOFull, + 0x6b00: WrongP1P2, + 0x6d00: InsNotSupported, + 0x6e00: ProtocolNotSupported, + 0x6f00: Unknown + } + + def __new__(cls, + value: int, + ins: Optional[enum.IntEnum] = None, + *args, + **kwargs) -> Any: + if value in DeviceError.exc: + msg: str = f"Error in {ins!r} command" if ins else "Error in command" + + return DeviceError.exc[value](hex(value), msg, *args, **kwargs) + + return UnknownDeviceError(hex(value), *args, **kwargs) diff --git a/tests/monero_client/io/__init__.py b/tests/monero_client/io/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/monero_client/io/button.py b/tests/monero_client/io/button.py new file mode 100644 index 0000000..954150f --- /dev/null +++ b/tests/monero_client/io/button.py @@ -0,0 +1,33 @@ +import socket + + +class FakeButton: + def right_click(self): + pass + + def left_click(self): + pass + + def both_click(self): + pass + + def close(self): + pass + + +class Button: + def __init__(self, server: str, port: int) -> None: + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.connect((server, port)) + + def right_click(self): + self.socket.sendall(b"Rr") + + def left_click(self): + self.socket.sendall(b"Ll") + + def both_click(self): + self.socket.sendall(b"LRlr") + + def close(self): + self.socket.close() diff --git a/tests/monero_client/io/comm.py b/tests/monero_client/io/comm.py new file mode 100644 index 0000000..6d4d11f --- /dev/null +++ b/tests/monero_client/io/comm.py @@ -0,0 +1,20 @@ +from abc import ABCMeta, abstractmethod +from typing import Tuple + + +class Comm(metaclass=ABCMeta): + @abstractmethod + def send(self, apdus: bytes) -> None: + raise NotImplementedError + + @abstractmethod + def recv(self) -> Tuple[int, bytes]: + raise NotImplementedError + + @abstractmethod + def exchange(self, apdus: bytes) -> Tuple[int, bytes]: + raise NotImplementedError + + @abstractmethod + def close(self) -> None: + raise NotImplementedError diff --git a/tests/monero_client/io/display.py b/tests/monero_client/io/display.py new file mode 100644 index 0000000..ce4bebb --- /dev/null +++ b/tests/monero_client/io/display.py @@ -0,0 +1,74 @@ +import json +import queue +import socket +from typing import Optional + + +class FakeDisplay: + def listen(self): + pass + + def close(self): + pass + + +class Display: + def __init__(self, server: str, port: int) -> None: + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.connect((server, port)) + self.q = queue.Queue() + + def listen(self): + while True: + text: Optional[bytes] = json.loads( + self.socket.recv(1000) + ).get("text") + + if text: + self.q.put(text) + + def close(self): + self.socket.close() + + +""" +class DisplayClientProtocol(asyncio.Protocol): + def __init__(self, q, on_con_lost): + self.on_con_lost = on_con_lost + self.q = q + self.transport = None + + def connection_made(self, transport): + self.transport = transport + + def data_received(self, data): + text: Optional[bytes] = json.loads(data).get("text") + if text: + logging.debug("+ %s", text) + self.q.put(text) + + def connection_lost(self, exc): + print('The server closed the connection') + self.on_con_lost.set_result(True) + + +async def main(): + loop = asyncio.get_running_loop() + + on_con_lost = loop.create_future() + q = queue.Queue() + + transport, protocol = await loop.create_connection( + lambda: DisplayClientProtocol(q, on_con_lost), + "127.0.0.1", + 43000 + ) + + try: + await on_con_lost + finally: + transport.close() + + +asyncio.run(main()) +""" diff --git a/tests/monero_client/io/hid_device.py b/tests/monero_client/io/hid_device.py new file mode 100644 index 0000000..82fc3a2 --- /dev/null +++ b/tests/monero_client/io/hid_device.py @@ -0,0 +1,83 @@ +import logging +from typing import List, Tuple + +import hid + +from .comm import Comm + + +LEDGER_VENDOR_ID: int = 0x2C97 + + +class HID(Comm): + def __init__(self) -> None: + self.device = hid.device() + self.path = HID.enumerate_devices()[0] + self.device.open_path(self.path) + self.device.set_nonblocking(True) + self.__opened: bool = True + + @staticmethod + def enumerate_devices() -> List[bytes]: + devices: List[bytes] = [] + + for hid_device in hid.enumerate(LEDGER_VENDOR_ID, 0): + if (hid_device.get("interface_number") == 0 or + hid_device.get("usage_page") == 0xffa0): + devices.append(hid_device["path"]) + + assert (len(devices) != 0, + f"Can't find HID device with vendor id {LEDGER_VENDOR_ID}") + + return devices + + def send(self, apdus: bytes) -> None: + data: bytes = int.to_bytes(len(apdus), 2, byteorder="big") + apdus + offset: int = 0 + seq_idx: int = 0 + + while offset < len(data): + # Header: channel (0x101), tag (0x05), sequence index + header: bytes = (b"\x01\x01\x05" + + seq_idx.to_bytes(2, byteorder="big")) + data_chunk: bytes = (header + + data[offset:offset + 64 - len(header)]) + + self.device.write(b"\x00" + data_chunk) + logging.debug("=> %s", data_chunk.hex()) + offset += 64 - len(header) + seq_idx += 1 + + def recv(self, timeout: int = 1000) -> Tuple[int, bytes]: + seq_idx: int = 0 + self.device.set_nonblocking(False) + data_chunk: bytes = bytes(self.device.read(64 + 1)) + self.device.set_nonblocking(True) + + assert data_chunk[:2] == b"\x01\x01" + assert data_chunk[2] == 5 + assert data_chunk[3:5] == seq_idx.to_bytes(2, byteorder="big") + + data_len: int = int.from_bytes(data_chunk[5:7], byteorder="big") + data: bytes = data_chunk[7:] + + while len(data) < data_len: + read_bytes = bytes(self.device.read(64 + 1, timeout_ms=timeout)) + data += read_bytes[5:] + + logging.debug("<= %s", data[:data_len].hex()) + + sw: int = int.from_bytes(data[data_len-2:data_len], byteorder="big") + data: bytes = data[:data_len-2] + + return sw, data + + def exchange(self, data: bytes, timeout=1000) -> Tuple[int, bytes]: + self.send(data) + + return self.recv(timeout) + + def close(self): + if self.__opened: + self.device.close() + self.__opened = False diff --git a/tests/monero_client/io/tcp_client.py b/tests/monero_client/io/tcp_client.py new file mode 100644 index 0000000..a6b0d23 --- /dev/null +++ b/tests/monero_client/io/tcp_client.py @@ -0,0 +1,39 @@ +import logging +import socket +from typing import Tuple + +from .comm import Comm + + +class TCPClient(Comm): + def __init__(self, server: str, port: int) -> None: + self.server: str = server + self.port: int = port + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.connect((self.server, self.port)) + self.__opened: bool = True + + def send(self, apdus: bytes) -> None: + logging.debug("=> %s", apdus.hex()) + length: bytes = int.to_bytes(len(apdus), 4, byteorder="big") + self.socket.send(length + apdus) + + def recv(self) -> Tuple[int, bytes]: + length: int = int.from_bytes(self.socket.recv(4), byteorder="big") + data: bytes = self.socket.recv(length) + sw: int = int.from_bytes(self.socket.recv(2), byteorder="big") + + logging.debug("<= %s %s", data.hex(), hex(sw)[2:]) + + return sw, data + + def exchange(self, apdus: bytes) -> Tuple[int, bytes]: + self.send(apdus) + sw, response = self.recv() # type: int, bytes + + return sw, response + + def close(self): + if self.__opened: + self.socket.close() + self.__opened = False diff --git a/tests/monero_client/io/transport.py b/tests/monero_client/io/transport.py new file mode 100644 index 0000000..855b757 --- /dev/null +++ b/tests/monero_client/io/transport.py @@ -0,0 +1,55 @@ +import enum +import logging +import struct +from typing import Union, Tuple + +from monero_client.io.tcp_client import TCPClient +from monero_client.io.hid_device import HID + + +class Transport: + def __init__(self, debug: bool = False, speculos: bool = False) -> None: + if debug: + logging.basicConfig(format="%(message)s", level=logging.DEBUG) + + self.com: Union[TCPClient, HID] = (TCPClient(server="127.0.0.1", port=9999) + if speculos else HID()) + + def exchange(self, + cla: int, + ins: enum.IntEnum, + p1: int = 0, + p2: int = 0, + option: int = 0, + payload: bytes = b"") -> Tuple[int, bytes]: + header: bytes = struct.pack("BBBBBB", + cla, + ins.value, + p1, + p2, + 1 + len(payload), + option) + + return self.com.exchange(header + payload) + + def send(self, + cla: int, + ins: enum.IntEnum, + p1: int = 0, + p2: int = 0, + option: int = 0, + payload: bytes = b"") -> None: + header: bytes = struct.pack("BBBBBB", + cla, + ins.value, + p1, + p2, + 1 + len(payload), + option) + self.com.send(header + payload) + + def recv(self) -> Tuple[int, bytes]: + return self.com.recv() + + def close(self) -> None: + self.com.close() diff --git a/tests/monero_client/monero_cmd.py b/tests/monero_client/monero_cmd.py new file mode 100644 index 0000000..09dbb28 --- /dev/null +++ b/tests/monero_client/monero_cmd.py @@ -0,0 +1,454 @@ +"""Simple client for Monero application + +From test seed of 12 words: +"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + +* view public: A = 865cbfab852a1d1ccdfc7328e4dac90f78fc2154257d07522e9b79e637326dfa +* spend public: B = dae41d6b13568fdd71ec3d20c2f614c65fe819f36ca5da8d24df3bd89b2bad9d +* view secret: a = 0f3fe25d0c6d4c94dde0c0bcc214b233e9c72927f813728b0f01f28f9d5e1201 +* spend secret: b = 3b094ca7218f175e91fa2402b4ae239a2fe8262792a3e718533a1a357a1e4109 +* Stage net address: 5A8FgbMkmG2e3J41sBdjvjaBUyz8qHohsQcGtRf63qEUTMBvmA45fpp5pSacMdSg7A3b71RejLzB8EkGbfjp5PELVHCRUaE + +""" + +import struct +from typing import Tuple + +from monero_client.monero_crypto_cmd import MoneroCryptoCmd +from monero_client.monero_types import InsType, Type, SigType +from monero_client.crypto.hmac import hmac_sha256 +from monero_client.exception.device_error import DeviceError +from monero_client.io.button import Button +from monero_client.utils.varint import encode_varint + +PROTOCOL_VERSION: int = 3 + + +class MoneroCmd(MoneroCryptoCmd): + def __init__(self, debug: bool = False, speculos: bool = False) -> None: + MoneroCryptoCmd.__init__(self, debug, speculos) + + def reset_and_get_version(self, + monero_client_version: bytes + ) -> Tuple[int, int, int]: + ins: InsType = InsType.INS_RESET + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=0, + p2=0, + option=0, + payload=monero_client_version) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + assert len(response) == 3 + + major, minor, patch = struct.unpack( + "BBB", + response + ) # type: int, int, int + + self.is_in_tx_mode = False + + return major, minor, patch + + def set_signature_mode(self, sig_type: SigType) -> int: + ins: InsType = InsType.INS_SET_SIGNATURE_MODE + + payload: bytes = struct.pack("B", sig_type) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=1, + p2=0, + option=0, + payload=payload) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + assert len(response) == 4 + + sig_mode, *_ = struct.unpack(">I", response) + + if sig_mode not in (1, 2): + raise Exception(f"Signature mode should be 1 (real) or 2 (fake).") + + return SigType.REAL if sig_mode else SigType.FAKE + + def open_tx(self) -> Tuple[bytes, bytes, bytes, bytes]: + ins: InsType = InsType.INS_OPEN_TX + + # 4 bytes + account: bytes = struct.pack(">I", 0) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=1, + p2=0, + option=0, + payload=account) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + self.is_in_tx_mode = True + + assert len(response) == 224 + + tx_pub_key: bytes = response[:32] # R = r.G + _tx_priv_key: bytes = response[32:64] # r (encrypted) + hmac_tx_priv_key: bytes = response[64:96] + fake_view_key: bytes = response[96:128] + hmac_fake_view_key: bytes = response[128:160] + fake_spend_key: bytes = response[160:192] + hmac_fake_spend_key: bytes = response[192:] + + assert (hmac_tx_priv_key == hmac_sha256(_tx_priv_key, + MoneroCryptoCmd.HMAC_KEY, + Type.SCALAR)) + assert (hmac_fake_view_key == hmac_sha256(fake_view_key, + MoneroCryptoCmd.HMAC_KEY, + Type.SCALAR)) + assert (hmac_fake_spend_key == hmac_sha256(fake_spend_key, + MoneroCryptoCmd.HMAC_KEY, + Type.SCALAR)) + + return (tx_pub_key, + _tx_priv_key, + fake_view_key, + fake_spend_key) + + def close_tx(self) -> None: + ins: InsType = InsType.INS_CLOSE_TX + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=0, + p2=0, + option=0) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + self.is_in_tx_mode = False + + assert len(response) == 0 + + def gen_txout_keys(self, + _tx_priv_key: bytes, + tx_pub_key: bytes, + dst_pub_view_key: bytes, + dst_pub_spend_key: bytes, + output_index: int, + is_change_addr: bool, + is_subaddress: bool) -> Tuple[bytes, bytes]: + ins: InsType = InsType.INS_GEN_TXOUT_KEYS + + payload: bytes = b"".join(( + struct.pack('>I', 0), # tx_version + _tx_priv_key, # r (encrypted) + hmac_sha256(_tx_priv_key, + MoneroCryptoCmd.HMAC_KEY, + Type.SCALAR), # hmac + tx_pub_key, # R + dst_pub_view_key, # A_out + dst_pub_spend_key, # B_out + struct.pack('>I', output_index), + b"\x01" if is_change_addr else b"\x00", + b"\x01" if is_subaddress else b"\x00", + b"\x00" * 33, # additional_txkeys + )) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=0, + p2=0, + option=0, + payload=payload) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + assert len(response) == 96 + + _ak_amount = response[:32] + hmac_ak_amount = response[32:64] + out_ephemeral_pub_key = response[64:96] + + assert (hmac_ak_amount == hmac_sha256(_ak_amount, + MoneroCryptoCmd.HMAC_KEY, + Type.AMOUNT_KEY)) + + return _ak_amount, out_ephemeral_pub_key + + def prefix_hash_init(self, button: Button, version: int, timelock: int) -> None: + ins: InsType = InsType.INS_PREFIX_HASH + + payload: bytes = b"".join([ + encode_varint(version), + encode_varint(timelock) + ]) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=1, + p2=0, + option=0, + payload=payload) + + # "Timelock" -> go down + button.right_click() + # "Reject Timelock" -> go down + button.right_click() + # "Accept Timelock" -> accept + button.both_click() + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins, "P1=1 (init)") + + assert len(response) == 0 + + def prefix_hash_update(self, index: int, payload: bytes, is_last: bool) -> bytes: + ins: InsType = InsType.INS_PREFIX_HASH + + self.device.send(cla=PROTOCOL_VERSION, + ins=InsType.INS_PREFIX_HASH, + p1=2, + p2=index, + option=0 if is_last else 0x80, + payload=payload) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins, "P1=2 (update)") + + if is_last: + assert len(response) == 32 + else: + assert len(response) == 0 + + return response + + def gen_commitment_mask(self, _ak_amount: bytes) -> bytes: + ins: InsType = InsType.INS_GEN_COMMITMENT_MASK + + payload: bytes = b"".join([ + _ak_amount, + hmac_sha256(_ak_amount, + MoneroCryptoCmd.HMAC_KEY, + Type.AMOUNT_KEY) + ]) + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=0, + p2=0, + option=0, + payload=payload) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + assert len(response) == 32 + + return response # mask + + def blind(self, + _ak_amount: bytes, + mask: bytes, + amount: int, + is_short: bool) -> Tuple[bytes, bytes]: + ins: InsType = InsType.INS_BLIND + + payload: bytes = b"".join([ + _ak_amount, + hmac_sha256(_ak_amount, + MoneroCryptoCmd.HMAC_KEY, + Type.AMOUNT_KEY), + mask, + amount.to_bytes(32, byteorder="big") + ]) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=0, + p2=0, + option=2 if is_short else 0, + payload=payload) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + assert len(response) == 64 + + blinded_amount, blinded_mask = response[:32], response[32:] + + return blinded_mask, blinded_amount + + def unblind(self, + _ak_amount: bytes, + blinded_mask: bytes, + blinded_amount: bytes, + is_short: bool) -> Tuple[bytes, bytes]: + ins: InsType = InsType.INS_UNBLIND + + payload: bytes = b"".join([ + _ak_amount, + hmac_sha256(_ak_amount, + MoneroCryptoCmd.HMAC_KEY, + Type.AMOUNT_KEY), + blinded_mask, + blinded_amount + ]) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=0, + p2=0, + option=2 if is_short else 0, + payload=payload) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + assert len(response) == 64 + + amount, mask = response[:32], response[32:] + + return mask, amount + + def validate_prehash_init(self, + button: Button, + index: int, + txntype: int, + txnfee: int) -> None: + ins: InsType = InsType.INS_VALIDATE + + # txntype is skipped in the app + payload: bytes = struct.pack("B", txntype) + encode_varint(txnfee) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=1, + p2=index, + option=0, + payload=payload) + + # "Fee" -> go down + button.right_click() + # "Reject Fee" -> go down + button.right_click() + # "Accept Fee" -> accept + button.both_click() + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins, "P1=1 (init)") + + assert len(response) == 0 + + def validate_prehash_update(self, + index: int, + is_short: bool, + is_change_addr: bool, + is_subaddress: bool, + dst_pub_view_key: bytes, + dst_pub_spend_key: bytes, + _ak_amount: bytes, + commitment: bytes, + blinded_mask: bytes, + blinded_amount: bytes, + is_last: bool) -> None: + ins: InsType = InsType.INS_VALIDATE + + payload: bytes = b"".join(( + b"\x01" if is_subaddress else b"\x00", + b"\x01" if is_change_addr else b"\x00", + dst_pub_view_key, + dst_pub_spend_key, + _ak_amount, + hmac_sha256(_ak_amount, + MoneroCryptoCmd.HMAC_KEY, + Type.AMOUNT_KEY), + commitment, + blinded_mask, + blinded_amount + )) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=2, + p2=index, + option=(0 if is_last else 0x80) | (0x02 if is_short else 0), + payload=payload) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins, "P1=2 (update)") + + assert len(response) == 0 + + def validate_prehash_finalize(self, + index: int, + is_short: bool, + is_change_addr: bool, + is_subaddress: bool, + dst_pub_view_key: bytes, + dst_pub_spend_key: bytes, + _ak_amount: bytes, + commitment: bytes, + blinded_mask: bytes, + blinded_amount: bytes, + is_last: bool) -> None: + ins: InsType = InsType.INS_VALIDATE + + payload: bytes = b"".join(( + b"\x01" if is_subaddress else b"\x00", + b"\x01" if is_change_addr else b"\x00", + dst_pub_view_key, + dst_pub_spend_key, + _ak_amount, + hmac_sha256(_ak_amount, + MoneroCryptoCmd.HMAC_KEY, + Type.AMOUNT_KEY), + commitment, + blinded_mask, + blinded_amount + )) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=3, + p2=index, + option=(0 if is_last else 0x80) | (0x02 if is_short else 0), + payload=payload) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins, "P1=3 (finalize)") + + assert len(response) == 0 diff --git a/tests/monero_client/monero_crypto_cmd.py b/tests/monero_client/monero_crypto_cmd.py new file mode 100644 index 0000000..fc0d68c --- /dev/null +++ b/tests/monero_client/monero_crypto_cmd.py @@ -0,0 +1,261 @@ +import struct +from typing import Tuple + +from monero_client.crypto.hmac import hmac_sha256 +from monero_client.exception.device_error import DeviceError +from monero_client.monero_types import InsType +from monero_client.monero_types import Type +from monero_client.io.transport import Transport + +PROTOCOL_VERSION: int = 3 + + +class MoneroCryptoCmd: + HMAC_KEY: bytes = b"\xab" * 32 + + def __init__(self, debug: bool = False, speculos: bool = False) -> None: + self.device = Transport(debug=debug, speculos=speculos) + self.is_in_tx_mode = False + + @staticmethod + def xor_cipher(value: bytes, key: bytes) -> bytes: + assert len(key) == 1 + + return bytes([byte ^ int.from_bytes(key, byteorder="big") + for byte in value]) + + def secret_scalar_mul_key(self, + pub_key: bytes, + _scalar: bytes) -> bytes: + ins: InsType = InsType.INS_SECRET_SCAL_MUL_KEY + + payload: bytes = b"".join([ + pub_key, + _scalar, # encrypted scalar + hmac_sha256(_scalar, + MoneroCryptoCmd.HMAC_KEY, + Type.SCALAR), # hmac + ]) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=0, + p2=0, + option=0, + payload=payload) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + assert len(response) == 32 + + return response # scalar * pub_key + + def secret_scalar_mul_base(self, + _priv_key: bytes): + ins: InsType = InsType.INS_SECRET_SCAL_MUL_BASE + + payload: bytes = b"".join([ + _priv_key, # encrypted priv_key + hmac_sha256(_priv_key, + MoneroCryptoCmd.HMAC_KEY, + Type.SCALAR), # hmac + ]) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=0, + p2=0, + option=0, + payload=payload) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + assert len(response) == 32 + + return response # priv_key * G + + def get_public_keys(self) -> Tuple[bytes, bytes, str]: + ins: InsType = InsType.INS_GET_KEY + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=1, + p2=0, + option=0) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins, "P1=1") + + assert len(response) == 159 + + view_pub_key = response[:32] + spend_pub_key = response[32:64] + base58_address = response[64:159].decode("ascii") + + return view_pub_key, spend_pub_key, base58_address + + def get_private_view_key(self, button) -> bytes: + ins: InsType = InsType.INS_GET_KEY + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=2, + p2=0, + option=0) + + # "Export View Key" + button.right_click() + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins, "P1=2") + + assert len(response) == 32 + + return response # priv_view_key + + def generate_keypair(self) -> Tuple[bytes, bytes]: + ins: InsType = InsType.INS_GENERATE_KEYPAIR + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=0, + p2=0, + option=0) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + if self.is_in_tx_mode: + assert len(response) == 96 # The secret is encrypted and a MAC is used + else: + assert len(response) == 64 # The secret is only encrypted + + return response[:32], response[32:] # pub_key, _priv_key + + def verify_key(self, _priv_key: bytes, pub_key: bytes) -> bool: + ins: InsType = InsType.INS_VERIFY_KEY + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=0, + p2=0, + option=0, + payload=_priv_key + pub_key) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + assert len(response) == 4 + + # 32 bits integer (should be 0 or 1) + verified, *_ = struct.unpack(">I", response) + + return True if verified else False + + def generate_key_image(self, _priv_key: bytes, pub_key: bytes) -> bytes: + ins: InsType = InsType.INS_GEN_KEY_IMAGE + + payload: bytes = b"".join([ + pub_key, + _priv_key, + hmac_sha256(_priv_key, + MoneroCryptoCmd.HMAC_KEY, + Type.SCALAR), # hmac + ]) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=0, + p2=0, + option=0, + payload=payload) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + assert len(response) == 32 + + return response # key image + + def put_key(self, + priv_view_key: bytes, + pub_view_key: bytes, + priv_spend_key: bytes, + pub_spend_key: bytes, + address: str) -> None: + ins: InsType = InsType.INS_PUT_KEY + + payload: bytes = b"".join([ + priv_view_key, + pub_view_key, + priv_spend_key, + pub_spend_key, + address.encode("ascii") + ]) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=0, + p2=0, + option=0, + payload=payload) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + assert len(response) == 0 + + def gen_key_derivation(self, pub_key: bytes, _priv_key: bytes) -> bytes: + ins: InsType = InsType.INS_GEN_KEY_DERIVATION + + payload: bytes = b"".join([ + pub_key, + _priv_key, + hmac_sha256(_priv_key, + MoneroCryptoCmd.HMAC_KEY, + Type.SCALAR), + ]) + + self.device.send(cla=PROTOCOL_VERSION, + ins=ins, + p1=0, + p2=0, + option=0, + payload=payload) + + sw, response = self.device.recv() # type: int, bytes + + if not (sw & 0x9000): + raise DeviceError(sw, ins) + + _d_in: bytes = response[:32] # encrypted derivation + + if self.is_in_tx_mode: + assert len(response) == 64 + hmac_d_in: bytes = response[32:64] + + assert (hmac_d_in == hmac_sha256(_d_in, + MoneroCryptoCmd.HMAC_KEY, + Type.DERIVATION)) + else: + assert len(response) == 32 + + return _d_in diff --git a/tests/monero_client/monero_types.py b/tests/monero_client/monero_types.py new file mode 100644 index 0000000..9a85be2 --- /dev/null +++ b/tests/monero_client/monero_types.py @@ -0,0 +1,78 @@ +from dataclasses import dataclass +import enum +from typing import Optional + + +@dataclass +class Keys: + public_view_key: bytes + secret_view_key: bytes + public_spend_key: bytes + secret_spend_key: Optional[bytes] + addr: str + + +@enum.unique +class SigType(enum.IntEnum): + REAL = 1 + FAKE = 2 + + +@enum.unique +class Type(enum.IntEnum): + SCALAR = 1 + DERIVATION = 2 + AMOUNT_KEY = 3 + ALPHA = 4 + + +@enum.unique +class InsType(enum.IntEnum): + INS_NONE = 0x00 + INS_RESET = 0x02 + INS_LOCK_DISPLAY = 0x04 + + INS_GET_KEY = 0x20 + INS_DISPLAY_ADDRESS = 0x21 + INS_PUT_KEY = 0x22 + INS_GET_CHACHA8_PREKEY = 0x24 + INS_VERIFY_KEY = 0x26 + INS_MANAGE_SEEDWORDS = 0x28 + + INS_SECRET_KEY_TO_PUBLIC_KEY = 0x30 + INS_GEN_KEY_DERIVATION = 0x32 + INS_DERIVATION_TO_SCALAR = 0x34 + INS_DERIVE_PUBLIC_KEY = 0x36 + INS_DERIVE_SECRET_KEY = 0x38 + INS_GEN_KEY_IMAGE = 0x3A + INS_SECRET_KEY_ADD = 0x3C + INS_GENERATE_KEYPAIR = 0x40 + INS_SECRET_SCAL_MUL_KEY = 0x42 + INS_SECRET_SCAL_MUL_BASE = 0x44 + + INS_DERIVE_SUBADDRESS_PUBLIC_KEY = 0x46 + INS_GET_SUBADDRESS = 0x48 + INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY = 0x4A + INS_GET_SUBADDRESS_SECRET_KEY = 0x4C + + INS_OPEN_TX = 0x70 + INS_SET_SIGNATURE_MODE = 0x72 + INS_GET_ADDITIONAL_KEY = 0x74 + INS_STEALTH = 0x76 + INS_GEN_COMMITMENT_MASK = 0x77 + INS_BLIND = 0x78 + INS_UNBLIND = 0x7A + INS_GEN_TXOUT_KEYS = 0x7B + INS_PREFIX_HASH = 0x7D + INS_VALIDATE = 0x7C + INS_MLSAG = 0x7E + INS_CLOSE_TX = 0x80 + + INS_GET_TX_PROOF = 0xA0 + INS_GEN_SIGNATURE = 0xA2 + INS_GEN_RING_SIGNATURE = 0xA4 + + INS_GET_RESPONSE = 0xc0 + + def __repr__(self): + return f"{self.name}[{hex(self.value)}]" diff --git a/tests/monero_client/utils/__init__.py b/tests/monero_client/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/monero_client/utils/base58.py b/tests/monero_client/utils/base58.py new file mode 100644 index 0000000..b32ca8d --- /dev/null +++ b/tests/monero_client/utils/base58.py @@ -0,0 +1,191 @@ +"""MoneroPy - A python toolbox for Monero +Copyright (C) 2016 The MoneroPy Developers. + +MoneroPy is released under the BSD 3-Clause license. Use and redistribution of +this software is subject to the license terms in the LICENSE file found in the +top-level directory of this distribution. + +Modified by emesik and rooterkyberian: + + optimized + + proper exceptions instead of returning errors as results + +""" + +from typing import List + +ALPHABET: List[int] = [ + ord(s) for s in ("123456789ABCDEFGHJKLMNPQRSTUVWXYZ" + "abcdefghijkmnopqrstuvwxyz") +] +B58_BASE: int = 58 +UINT64_MAX: int = 2 ** 64 +ENCODED_BLOCK_SIZES: List[int] = [0, 2, 3, 5, 6, 7, 9, 10, 11] +FULL_BLOCK_SIZE: int = 8 +FULL_ENCODED_BLOCK_SIZE: int = 11 + + +def hex_to_bin(h: str) -> List[int]: + if len(h) % 2 != 0: + raise ValueError(f"Hex string has invalid length: {len(h)}") + + return [int(h[i:i + 2], 16) for i in range(0, len(h), 2)] + + +def bin_to_hex(b: bytes) -> str: + return "".join("%02x" % int(b) for b in b) + + +def uint8be_to_64(data: bytes) -> int: + if not (1 <= len(data) <= 8): + raise ValueError(f"Invalid input length: {len(data)}") + + res: int = 0 + + for b in data: # type: int + res = res << 8 | b + + return res + + +def uint64_to_8be(num: int, size: int) -> List[int]: + if size < 1 or size > 8: + raise ValueError(f"Invalid input length: {size}") + + res: List[int] = [0] * size + + two_pow8: int = 2 ** 8 + + for i in range(size - 1, -1, -1): # type: int + res[i] = num % two_pow8 + num = num // two_pow8 + + return res + + +def encode_block(data: bytes, buf: bytearray, index: int) -> bytearray: + l_data: int = len(data) + + if l_data < 1 or l_data > FULL_ENCODED_BLOCK_SIZE: + raise ValueError(f"Invalid block length: {l_data}") + + remainder: int + num: int = uint8be_to_64(data) + i: int = ENCODED_BLOCK_SIZES[l_data] - 1 + + while num > 0: + remainder = num % B58_BASE + num = num // B58_BASE + buf[index + i] = ALPHABET[remainder] + i -= 1 + + return buf + + +def encode(data: bytes) -> str: + """Encode data bytes as base58 (ex: encoding a Monero address).""" + l_data: int = len(data) + + if l_data == 0: + return "" + + full_block_count: int = l_data // FULL_BLOCK_SIZE + last_block_size: int = l_data % FULL_BLOCK_SIZE + res_size: int = (full_block_count * + FULL_ENCODED_BLOCK_SIZE + + ENCODED_BLOCK_SIZES[last_block_size]) + + res: bytearray = bytearray([ALPHABET[0]] * res_size) + + for i in range(full_block_count): # type: int + res = encode_block( + data[(i * FULL_BLOCK_SIZE):(i * FULL_BLOCK_SIZE + FULL_BLOCK_SIZE)], + res, + i * FULL_ENCODED_BLOCK_SIZE + ) + + if last_block_size > 0: + begin: int = full_block_count * FULL_BLOCK_SIZE + end: int = full_block_count * FULL_BLOCK_SIZE + last_block_size + res = encode_block(data[begin:end], + res, + full_block_count * FULL_ENCODED_BLOCK_SIZE) + + return bytes(res).decode("ascii") + + +def decode_block(data: bytes, buf: bytearray, index: int) -> bytearray: + l_data: int = len(data) + + if l_data < 1 or l_data > FULL_ENCODED_BLOCK_SIZE: + raise ValueError(f"Invalid block length: {l_data}") + + res_size: int = ENCODED_BLOCK_SIZES.index(l_data) + + if res_size <= 0: + raise ValueError(f"Invalid block size: {res_size}") + + res_num: int = 0 + order: int = 1 + + for i in range(l_data - 1, -1, -1): # type: int + digit: int = ALPHABET.index(data[i]) + if digit < 0: + raise ValueError(f"Invalid symbol: {data[i]}") + + product: int = order * digit + res_num + if product > UINT64_MAX: + raise ValueError( + f"Overflow: {order} * {digit} + {res_num} = {product}" + ) + + res_num: int = product + order: int = order * B58_BASE + + if res_size < FULL_BLOCK_SIZE and 2 ** (8 * res_size) <= res_num: + raise ValueError(f"Overflow: {res_num} doesn't fit in {res_size} bit(s)") + + tmp_buf: List[int] = uint64_to_8be(res_num, res_size) + buf[index:index + len(tmp_buf)] = tmp_buf + + return buf + + +def decode(data_encoded: str) -> str: + """Decode a base58 string (ex: a Monero address) into hexidecimal form.""" + data_encoded: bytearray = bytearray(data_encoded, encoding="ascii") + l_enc: int = len(data_encoded) + + if l_enc == 0: + return "" + + full_block_count: int = l_enc // FULL_ENCODED_BLOCK_SIZE + last_block_size: int = l_enc % FULL_ENCODED_BLOCK_SIZE + + try: + last_block_decoded_size: int = ENCODED_BLOCK_SIZES.index(last_block_size) + except ValueError: + raise ValueError(f"Invalid encoded length: {l_enc}") + + data_size: int = (full_block_count * + FULL_BLOCK_SIZE + + last_block_decoded_size) + + data: bytearray = bytearray(data_size) + begin: int + end: int + + for i in range(full_block_count): # type: int + begin = i * FULL_ENCODED_BLOCK_SIZE + end = i * FULL_ENCODED_BLOCK_SIZE + FULL_ENCODED_BLOCK_SIZE + data = decode_block(data_encoded[begin:end], + data, + i * FULL_BLOCK_SIZE) + + if last_block_size > 0: + begin = full_block_count * FULL_ENCODED_BLOCK_SIZE + end = full_block_count * FULL_ENCODED_BLOCK_SIZE + last_block_size + data = decode_block(data_encoded[begin:end], + data, + full_block_count * FULL_BLOCK_SIZE) + + return bin_to_hex(data) diff --git a/tests/monero_client/utils/varint.py b/tests/monero_client/utils/varint.py new file mode 100644 index 0000000..c4e3c2e --- /dev/null +++ b/tests/monero_client/utils/varint.py @@ -0,0 +1,30 @@ +from typing import List + + +def encode_varint(value: int) -> bytes: + if value == 0: + return b"\x00" + + result: List[int] = [] + + while value: + result.append(0b10000000 | (value & 0b01111111)) + value >>= 7 + + result[-1] &= 0x7f + + return bytes(result) + + +def decode_varint(value_encoded: bytes) -> int: + result: int = 0 + shift: int = 0 + + for byte in value_encoded: + result |= (byte & 0b01111111) << shift + shift += 7 + + if byte & 0b10000000 == 0: + return result + + return result diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 0000000..b4a558f --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,17 @@ +import pytest + +from monero_client.exception import * + + +def test_version(monero): + major, minor, patch = monero.reset_and_get_version( + monero_client_version=b"0.16.0.0" + ) # type: int, int, int + + assert (major, minor, patch) == (1, 6, 0) # version of the Monero app + + +@pytest.mark.xfail(raises=ClientNotSupported) +def test_old_client_version(monero): + # should raise ClientNotSupported[0x6a30] + monero.reset_and_get_version(b"0.15.0.0") From 414b13db4543bc4ee34174c9d159f2b9ac0b4ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Fri, 3 Jul 2020 16:02:16 +0200 Subject: [PATCH 31/76] Add pytest with speculos to CI --- .github/workflows/ci-workflow.yml | 38 ++++++++++++++++++++++++++++ tests/monero_client/io/hid_device.py | 8 +++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 89eee7e..16bc078 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -30,3 +30,41 @@ jobs: with: name: monero-app-debug path: bin + + job_test: + name: Test + needs: job_build_debug + runs-on: ubuntu-latest + + container: + image: docker://ledgerhq/speculos:latest + ports: + - 1234:1234 + - 9999:9999 + - 40000:40000 + - 41000:41000 + - 42000:42000 + - 43000:43000 + options: --entrypoint /bin/bash + + steps: + - name: Clone + uses: actions/checkout@v2 + + - name: Download app binary + uses: actions/download-artifact@v2 + with: + name: monero-app-debug + path: bin + + - name: Run test + run: | + nohup bash -c "python /speculos/speculos.py bin/app.elf --sdk 1.6 --seed \"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\" --apdu-port 9999 --button-port 42000 --automation-port 43000 --display headless" > speculos.log 2<&1 & + pip install pytest + pytest + + - name: Upload Speculos log + uses: actions/upload-artifact@v2 + with: + name: speculos-log + path: speculos.log diff --git a/tests/monero_client/io/hid_device.py b/tests/monero_client/io/hid_device.py index 82fc3a2..4f381e3 100644 --- a/tests/monero_client/io/hid_device.py +++ b/tests/monero_client/io/hid_device.py @@ -1,7 +1,10 @@ import logging from typing import List, Tuple -import hid +try: + import hid +except ImportError: + hid = None from .comm import Comm @@ -11,6 +14,9 @@ class HID(Comm): def __init__(self) -> None: + if hid is None: + raise ImportError("hidapi is not installed!") + self.device = hid.device() self.path = HID.enumerate_devices()[0] self.device.open_path(self.path) From 09f67c419d18e5e49c13e0124e61306327c4003e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Fri, 3 Jul 2020 16:45:41 +0200 Subject: [PATCH 32/76] Add crypto tests and draft for signature --- tests/test_crypto.py | 81 +++++++++++++++++++++++ tests/test_sig.py | 153 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 tests/test_crypto.py create mode 100644 tests/test_sig.py diff --git a/tests/test_crypto.py b/tests/test_crypto.py new file mode 100644 index 0000000..e895076 --- /dev/null +++ b/tests/test_crypto.py @@ -0,0 +1,81 @@ +def test_public_keys(monero): + (view_pub_key, + spend_pub_key, + address) = monero.get_public_keys() # type: bytes, bytes, str + + assert view_pub_key == bytes.fromhex("865cbfab852a1d1ccdfc7328e4dac90f" + "78fc2154257d07522e9b79e637326dfa") + assert spend_pub_key == bytes.fromhex("dae41d6b13568fdd71ec3d20c2f614c6" + "5fe819f36ca5da8d24df3bd89b2bad9d") + assert address == ("5A8FgbMkmG2e3J41sBdjvjaBUyz8qHohsQcGtRf63qEUTMBvmA45fp" + "p5pSacMdSg7A3b71RejLzB8EkGbfjp5PELVHCRUaE") + + +def test_private_view_key(monero, button): + view_priv_key: bytes = monero.get_private_view_key(button) + + assert view_priv_key == bytes.fromhex("0f3fe25d0c6d4c94dde0c0bcc214b233" + "e9c72927f813728b0f01f28f9d5e1201") + + +def test_keygen_and_verify(monero): + pub_key, _priv_key = monero.generate_keypair() # type: bytes, bytes + + assert monero.verify_key(_priv_key, pub_key) is True + + +def test_key_image(monero): + expected_key_img: bytes = bytes.fromhex("b8af9b11b9391f0cd921863b5f774677" + "29a188d31f8c7df37bd7f2dfd99defee") + + _priv_key: bytes = bytes.fromhex("6d2f327238555bf7d0eb25b2ef6f85e0" + "22fb59bf43f05dfeb4d3947ca9acbc53") + pub_key: bytes = bytes.fromhex("d8acd4d3bd9e556544284279817dfd4b" + "95c03f17b3c53df6dddf646abf8b4d19") + + key_image: bytes = monero.generate_key_image(_priv_key=_priv_key, + pub_key=pub_key) + + assert expected_key_img == key_image + + +# Wait for PR #69 to be merged: https://github.com/LedgerHQ/app-monero/pull/69 +# +# def test_put_key(monero): +# priv_view_key: bytes = bytes.fromhex("0f3fe25d0c6d4c94dde0c0bcc214b233" +# "e9c72927f813728b0f01f28f9d5e1201") +# pub_view_key: bytes = bytes.fromhex("865cbfab852a1d1ccdfc7328e4dac90f" +# "78fc2154257d07522e9b79e637326dfa") +# priv_spend_key: bytes = bytes.fromhex("3b094ca7218f175e91fa2402b4ae239a" +# "2fe8262792a3e718533a1a357a1e4109") +# pub_spend_key: bytes = bytes.fromhex("dae41d6b13568fdd71ec3d20c2f614c6" +# "5fe819f36ca5da8d24df3bd89b2bad9d") +# address: str = ("5A8FgbMkmG2e3J41sBdjvjaBUyz8qHohsQcGtRf63qEUTMBvm" +# "A45fpp5pSacMdSg7A3b71RejLzB8EkGbfjp5PELVHCRUaE") + +# monero.put_key(priv_view_key=priv_view_key, +# pub_view_key=pub_view_key, +# priv_spend_key=priv_spend_key, +# pub_spend_key=pub_spend_key, +# address=address) + + +def test_gen_key_derivation(monero): + # 8 * r.G + expected: bytes = bytes.fromhex("57029f1d2a7453254be1ee81d7d8b540" + "1db398f0a541315c0095fad27625ebfa") + # r + priv_key: bytes = bytes.fromhex("0f3fe25d0c6d4c94dde0c0bcc214b233" + "e9c72927f813728b0f01f28f9d5e1201") + # r.G + pub_key: bytes = bytes.fromhex("865cbfab852a1d1ccdfc7328e4dac90f" + "78fc2154257d07522e9b79e637326dfa") + # encrypt priv_key with XOR cipher (key = 0x55) + _priv_key: bytes = monero.xor_cipher(priv_key, b"\x55") + + _d_in: bytes = monero.gen_key_derivation( + pub_key=pub_key, + _priv_key=_priv_key + ) + + assert expected == monero.xor_cipher(_d_in, b"\x55") # decrypt _d_in diff --git a/tests/test_sig.py b/tests/test_sig.py new file mode 100644 index 0000000..ef7a2ee --- /dev/null +++ b/tests/test_sig.py @@ -0,0 +1,153 @@ +import pytest + +from monero_client.monero_types import SigType, Keys + + +@pytest.mark.incremental +class TestSignature: + """Monero signature test.""" + + @pytest.fixture(autouse=True, scope="class") + def state(self): + sender = Keys( + public_view_key=bytes.fromhex("865cbfab852a1d1ccdfc7328e4dac90f78" + "fc2154257d07522e9b79e637326dfa"), + public_spend_key=bytes.fromhex("dae41d6b13568fdd71ec3d20c2f614c65" + "fe819f36ca5da8d24df3bd89b2bad9d"), + secret_view_key=bytes.fromhex("0f3fe25d0c6d4c94dde0c0bcc214b233e9" + "c72927f813728b0f01f28f9d5e1201"), + secret_spend_key=bytes.fromhex("3b094ca7218f175e91fa2402b4ae239a2" + "fe8262792a3e718533a1a357a1e4109"), + addr="5A8FgbMkmG2e3J41sBdjvjaBUyz8qHohsQcGtRf63qEUTMBvmA45fpp5pSa" + "cMdSg7A3b71RejLzB8EkGbfjp5PELVHCRUaE" + ) + + receiver = Keys( + public_view_key=bytes.fromhex("2e49ad29a1bfd98ab05c88713463d55212" + "0906b1be380211745695134e183ed0"), + public_spend_key=bytes.fromhex("392c4432e5a15aea227e6579a8da7d9f4" + "6fb78565e18e7f0b278f3f1a1468696"), + secret_view_key=bytes.fromhex("57fd02a94c7486722b1f9798dc0fb931d5" + "477a605a6fd6593573b438c02a890f"), + secret_spend_key=None, + addr="53zomVuwRkDgAQDY6qKUP6TeBy5JqXSJzhG4i7447yMXS7wdt4hKh6gQCTT" + "a9FiYNpEjBoHZ9iTww3vL96P1hcmTQXvpFAo" + ) + + return {"sender": sender, + "receiver": receiver, + "amount": 10**12, # 1.0 XMR + "tx_pub_key": None, + "_tx_priv_key": None, + "_ak_amount": [], + "blinded_amount": [], + "blinded_mask": [], + "y": []} + + def test_set_sig(self, monero): + major, minor, patch = monero.reset_and_get_version( + monero_client_version=b"0.16.0.0" + ) # type: int, int, int + assert (major, minor, patch) == (1, 6, 0) # version of the Monero app + + sig_mode: SigType = monero.set_signature_mode(sig_type=SigType.REAL) + assert sig_mode == SigType.REAL + + def test_open_tx(self, monero, state): + (tx_pub_key, + _tx_priv_key, + fake_view_key, + fake_spend_key) = monero.open_tx() # type: bytes, bytes, bytes, bytes + + assert fake_view_key == b"\x00" * 32 + assert fake_spend_key == b"\xff" * 32 + + state["tx_pub_key"] = tx_pub_key + state["_tx_priv_key"] = _tx_priv_key + + def test_gen_txout_keys(self, monero, state): + _ak_amount, out_ephemeral_pub_key = monero.gen_txout_keys( + _tx_priv_key=state["_tx_priv_key"], + tx_pub_key=state["tx_pub_key"], + dst_pub_view_key=state["receiver"].public_view_key, + dst_pub_spend_key=state["receiver"].public_spend_key, + output_index=0, + is_change_addr=False, + is_subaddress=False + ) # type: bytes, bytes + + state["_ak_amount"].append(_ak_amount) # _ak_amount_t + + def test_prefix_hash(self, monero, button): + expected: bytes = bytes.fromhex("49d03a195e239b52779866b33024210f" + "c7dc66e9c2998975c0aa45c1702549d5") + # should ask for timelock validation + monero.prefix_hash_init(button=button, version=0, timelock=1) + result: bytes = monero.prefix_hash_update( + index=1, + payload=b"", + is_last=True + ) + + assert result == expected + + def test_gen_commitment_mask(self, monero, state): + assert len(state["_ak_amount"]) != 0 + + s: bytes = monero.gen_commitment_mask(state["_ak_amount"][0]) + state["y"].append(s) # y_t + + def test_blind(self, monero, state): + assert len(state["y"]) != 0 + assert len(state["_ak_amount"]) != 0 + + blinded_mask, blinded_amount = monero.blind( + _ak_amount=state["_ak_amount"][0], + mask=state["y"][0], + amount=state["amount"], + is_short=False + ) # type: bytes, bytes + + mask, amount = monero.unblind( + _ak_amount=state["_ak_amount"][0], + blinded_mask=blinded_mask, + blinded_amount=blinded_amount, + is_short=False + ) # type: bytes, bytes + + assert state["y"][0] == mask + assert state["amount"] == int.from_bytes(amount, byteorder="big") + + state["blinded_mask"].append(blinded_mask) + state["blinded_amount"].append(blinded_amount) + + def test_validate(self, monero, button, state): + assert len(state["y"]) != 0 + assert len(state["_ak_amount"]) != 0 + assert len(state["blinded_amount"]) != 0 + assert len(state["blinded_mask"]) != 0 + + fee: int = 100000000 # 0.0001 XMR + + # should ask for fee validation + monero.validate_prehash_init(button=button, + index=1, # start at 1 + txntype=0, + txnfee=fee) + + # monero_client.validate_prehash_update( + # index=1, + # is_short=False, + # is_change_addr=False, + # is_subaddress=False, + # dst_pub_view_key=state["receiver"].public_view_key, + # dst_pub_spend_key=state["receiver"].public_spend_key, + # _ak_amount=state["_ak_amount"][0], + # commitment=..., + # blinded_amount=state["blinded_amount"][0], + # blinded_mask=state["blinded_mask"][0], + # is_last=True + # ) + + def test_close_tx(self, monero): + monero.close_tx() From 0a63d66111475fc1e1f5934d629147fb14807af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Fri, 3 Jul 2020 17:21:26 +0200 Subject: [PATCH 33/76] Fix bad syntax in YAML for GitHub Actions --- .github/workflows/ci-workflow.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 16bc078..5300341 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -5,7 +5,8 @@ on: branches: - master - develop - pull_request: + pull_request: + branches: - master - develop From 1ae395533a3ed68049eb563a3efbb08d7f570f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 6 Jul 2020 15:50:35 +0200 Subject: [PATCH 34/76] Fix python tests module according to pylint and mypy --- setup.cfg | 18 ++++ tests/conftest.py | 20 ++--- tests/monero_client/crypto/sha3.py | 18 ++-- tests/monero_client/exception/device_error.py | 21 +++-- tests/monero_client/io/display.py | 82 +++++++++-------- tests/monero_client/io/hid_device.py | 20 ++--- tests/monero_client/io/tcp_client.py | 2 +- tests/monero_client/monero_cmd.py | 88 ++++++++++--------- tests/monero_client/monero_crypto_cmd.py | 30 +++---- tests/monero_client/utils/base58.py | 10 +-- tests/test_sig.py | 27 ++++-- tests/test_version.py | 1 + 12 files changed, 187 insertions(+), 150 deletions(-) diff --git a/setup.cfg b/setup.cfg index f02153f..6705b41 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,3 +2,21 @@ testpaths = tests addopts = --strict-markers markers = incremental + +[pylint] +disable = C0114, # missing-module-docstring + C0115, # missing-class-docstring + C0116, # missing-function-docstring + C0103, # invalid-name + R0801, # duplicate-code + R0913 # too-many-arguments +extension-pkg-whitelist=hid + +[pycodestyle] +max-line-length = 90 + +[mypy-hid.*] +ignore_missing_imports = True + +[mypy-pytest.*] +ignore_missing_imports = True diff --git a/tests/conftest.py b/tests/conftest.py index 86b7924..975d8c5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,31 +1,30 @@ -import pytest from typing import Dict, Tuple +import pytest from monero_client.io.button import Button, FakeButton from monero_client.monero_cmd import MoneroCmd - SPECULOS: bool = True @pytest.fixture(scope="module") def monero(): - monero = MoneroCmd(debug=True, - speculos=SPECULOS) + monero_client = MoneroCmd(debug=True, + speculos=SPECULOS) - yield monero + yield monero_client - monero.device.close() + monero_client.device.close() @pytest.fixture(scope="module") def button(): - button = (Button(server="127.0.0.1", port=42000) - if SPECULOS else FakeButton()) + button_client = (Button(server="127.0.0.1", port=42000) + if SPECULOS else FakeButton()) - yield button + yield button_client - button.close() + button_client.close() _test_failed_incremental: Dict[str, Dict[Tuple[int, ...], str]] = {} @@ -34,7 +33,6 @@ def button(): def pytest_runtest_makereport(item, call): if "incremental" in item.keywords: if call.excinfo is not None: - cls_name = str(item.cls) parametrize_index = ( tuple(item.callspec.indices.values()) diff --git a/tests/monero_client/crypto/sha3.py b/tests/monero_client/crypto/sha3.py index 4a35782..680d912 100644 --- a/tests/monero_client/crypto/sha3.py +++ b/tests/monero_client/crypto/sha3.py @@ -18,18 +18,24 @@ SOFTWALLET ------------ -integrated_address 0102030405060708 -5KpvhQBFNXYe3J41sBdjvjaBUyz8qHohsQcGtRf63qEUTMBvmA45fpp5pSacMdSg7A3b71RejLzB8EkGbfjp5PELipMNxnGr3Wv1zf7dWq +payment_id: 0102030405060708 +addr: 5KpvhQBFNXYe3J41sBdjvjaBUyz8qHohsQcGtRf63qEUTMBv + mA45fpp5pSacMdSg7A3b71RejLzB8EkGbfjp5PELipMNxnGr3Wv1zf7dWq HWWALLET -------- -integrated_address 0102030405060708 -5DhUnJjS31jgAQDY6qKUP6TeBy5JqXSJzhG4i7447yMXS7wdt4hKh6gQCTTa9FiYNpEjBoHZ9iTww3vL96P1hcmTbnu8Nst8WHC21KaMVc +payment_id: 0102030405060708 +addr: 5DhUnJjS31jgAQDY6qKUP6TeBy5JqXSJzhG4i7447yMXS7wd + t4hKh6gQCTTa9FiYNpEjBoHZ9iTww3vL96P1hcmTbnu8Nst8WHC21KaMVc >>> addr_prefix: bytes = b"\x19" ->>> pub_spend_key: bytes = bytes.fromhex("dae41d6b13568fdd71ec3d20c2f614c65fe819f36ca5da8d24df3bd89b2bad9d") ->>> pub_view_key: bytes = bytes.fromhex("865cbfab852a1d1ccdfc7328e4dac90f78fc2154257d07522e9b79e637326dfa") +>>> pub_spend_key: bytes = bytes.fromhex( +... "dae41d6b13568fdd71ec3d20c2f614c65fe819f36ca5da8d24df3bd89b2bad9d" +... ) +>>> pub_view_key: bytes = bytes.fromhex( +... "865cbfab852a1d1ccdfc7328e4dac90f78fc2154257d07522e9b79e637326dfa" +... ) >>> payment_id: bytes = b"\x01\x02\x03\x04\x05\x06\x07\x08" >>> concat: bytes = addr_prefix + pub_spend_key + pub_view_key + payment_id >>> addr: str = base58.encode(concat + sha3(concat)[:4]) diff --git a/tests/monero_client/exception/device_error.py b/tests/monero_client/exception/device_error.py index 6349987..4c65d1a 100644 --- a/tests/monero_client/exception/device_error.py +++ b/tests/monero_client/exception/device_error.py @@ -1,10 +1,10 @@ import enum from typing import Dict, Any, Optional -from . import * +from . import * # pylint: disable=wildcard-import, unused-wildcard-import -class DeviceError: +class DeviceError(Exception): # pylint: disable=too-few-public-methods exc: Dict[int, Any] = { 0x6700: WrongLength, 0x6a30: ClientNotSupported, @@ -35,13 +35,16 @@ class DeviceError: } def __new__(cls, - value: int, + error_code: int = 0x6f00, ins: Optional[enum.IntEnum] = None, - *args, - **kwargs) -> Any: - if value in DeviceError.exc: - msg: str = f"Error in {ins!r} command" if ins else "Error in command" + message: str = "" + ) -> Any: + error_message: str = (f"Error in {ins!r} command" + if ins else "Error in command") - return DeviceError.exc[value](hex(value), msg, *args, **kwargs) + if error_code in DeviceError.exc: + return DeviceError.exc[error_code](hex(error_code), + error_message, + message) - return UnknownDeviceError(hex(value), *args, **kwargs) + return UnknownDeviceError(hex(error_code), error_message, message) diff --git a/tests/monero_client/io/display.py b/tests/monero_client/io/display.py index ce4bebb..1d69188 100644 --- a/tests/monero_client/io/display.py +++ b/tests/monero_client/io/display.py @@ -16,7 +16,7 @@ class Display: def __init__(self, server: str, port: int) -> None: self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect((server, port)) - self.q = queue.Queue() + self.q: queue.Queue = queue.Queue() def listen(self): while True: @@ -31,44 +31,42 @@ def close(self): self.socket.close() -""" -class DisplayClientProtocol(asyncio.Protocol): - def __init__(self, q, on_con_lost): - self.on_con_lost = on_con_lost - self.q = q - self.transport = None - - def connection_made(self, transport): - self.transport = transport - - def data_received(self, data): - text: Optional[bytes] = json.loads(data).get("text") - if text: - logging.debug("+ %s", text) - self.q.put(text) - - def connection_lost(self, exc): - print('The server closed the connection') - self.on_con_lost.set_result(True) - - -async def main(): - loop = asyncio.get_running_loop() - - on_con_lost = loop.create_future() - q = queue.Queue() - - transport, protocol = await loop.create_connection( - lambda: DisplayClientProtocol(q, on_con_lost), - "127.0.0.1", - 43000 - ) - - try: - await on_con_lost - finally: - transport.close() - - -asyncio.run(main()) -""" +# class DisplayClientProtocol(asyncio.Protocol): +# def __init__(self, q, on_con_lost): +# self.on_con_lost = on_con_lost +# self.q = q +# self.transport = None +# +# def connection_made(self, transport): +# self.transport = transport +# +# def data_received(self, data): +# text: Optional[bytes] = json.loads(data).get("text") +# if text: +# logging.debug("+ %s", text) +# self.q.put(text) +# +# def connection_lost(self, exc): +# print('The server closed the connection') +# self.on_con_lost.set_result(True) +# +# +# async def main(): +# loop = asyncio.get_running_loop() +# +# on_con_lost = loop.create_future() +# q = queue.Queue() +# +# transport, protocol = await loop.create_connection( +# lambda: DisplayClientProtocol(q, on_con_lost), +# "127.0.0.1", +# 43000 +# ) +# +# try: +# await on_con_lost +# finally: +# transport.close() +# +# +# asyncio.run(main()) diff --git a/tests/monero_client/io/hid_device.py b/tests/monero_client/io/hid_device.py index 4f381e3..a52508e 100644 --- a/tests/monero_client/io/hid_device.py +++ b/tests/monero_client/io/hid_device.py @@ -6,8 +6,7 @@ except ImportError: hid = None -from .comm import Comm - +from monero_client.io.comm import Comm LEDGER_VENDOR_ID: int = 0x2C97 @@ -32,8 +31,7 @@ def enumerate_devices() -> List[bytes]: hid_device.get("usage_page") == 0xffa0): devices.append(hid_device["path"]) - assert (len(devices) != 0, - f"Can't find HID device with vendor id {LEDGER_VENDOR_ID}") + assert len(devices) != 0, f"Can't find device with vendor_id {LEDGER_VENDOR_ID}" return devices @@ -54,7 +52,7 @@ def send(self, apdus: bytes) -> None: offset += 64 - len(header) seq_idx += 1 - def recv(self, timeout: int = 1000) -> Tuple[int, bytes]: + def recv(self) -> Tuple[int, bytes]: seq_idx: int = 0 self.device.set_nonblocking(False) data_chunk: bytes = bytes(self.device.read(64 + 1)) @@ -68,20 +66,20 @@ def recv(self, timeout: int = 1000) -> Tuple[int, bytes]: data: bytes = data_chunk[7:] while len(data) < data_len: - read_bytes = bytes(self.device.read(64 + 1, timeout_ms=timeout)) + read_bytes = bytes(self.device.read(64 + 1, timeout_ms=1000)) data += read_bytes[5:] logging.debug("<= %s", data[:data_len].hex()) - sw: int = int.from_bytes(data[data_len-2:data_len], byteorder="big") - data: bytes = data[:data_len-2] + sw: int = int.from_bytes(data[data_len - 2:data_len], byteorder="big") + data = data[:data_len - 2] return sw, data - def exchange(self, data: bytes, timeout=1000) -> Tuple[int, bytes]: - self.send(data) + def exchange(self, apdus: bytes) -> Tuple[int, bytes]: + self.send(apdus) - return self.recv(timeout) + return self.recv() def close(self): if self.__opened: diff --git a/tests/monero_client/io/tcp_client.py b/tests/monero_client/io/tcp_client.py index a6b0d23..a25341c 100644 --- a/tests/monero_client/io/tcp_client.py +++ b/tests/monero_client/io/tcp_client.py @@ -2,7 +2,7 @@ import socket from typing import Tuple -from .comm import Comm +from monero_client.io.comm import Comm class TCPClient(Comm): diff --git a/tests/monero_client/monero_cmd.py b/tests/monero_client/monero_cmd.py index 09dbb28..41230f6 100644 --- a/tests/monero_client/monero_cmd.py +++ b/tests/monero_client/monero_cmd.py @@ -1,25 +1,31 @@ """Simple client for Monero application -From test seed of 12 words: -"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" - -* view public: A = 865cbfab852a1d1ccdfc7328e4dac90f78fc2154257d07522e9b79e637326dfa -* spend public: B = dae41d6b13568fdd71ec3d20c2f614c65fe819f36ca5da8d24df3bd89b2bad9d -* view secret: a = 0f3fe25d0c6d4c94dde0c0bcc214b233e9c72927f813728b0f01f28f9d5e1201 -* spend secret: b = 3b094ca7218f175e91fa2402b4ae239a2fe8262792a3e718533a1a357a1e4109 -* Stage net address: 5A8FgbMkmG2e3J41sBdjvjaBUyz8qHohsQcGtRf63qEUTMBvmA45fpp5pSacMdSg7A3b71RejLzB8EkGbfjp5PELVHCRUaE +From test seed of 12 words: "abandon abandon abandon abandon abandon abandon +abandon abandon abandon abandon abandon about". + +* view public: + A = 865cbfab852a1d1ccdfc7328e4dac90f78fc2154257d07522e9b79e637326dfa +* spend public: + B = dae41d6b13568fdd71ec3d20c2f614c65fe819f36ca5da8d24df3bd89b2bad9d +* view secret: + a = 0f3fe25d0c6d4c94dde0c0bcc214b233e9c72927f813728b0f01f28f9d5e1201 +* spend secret: + b = 3b094ca7218f175e91fa2402b4ae239a2fe8262792a3e718533a1a357a1e4109 +* Stage net address: + 5A8FgbMkmG2e3J41sBdjvjaBUyz8qHohsQcGtRf63qEUTMBv + mA45fpp5pSacMdSg7A3b71RejLzB8EkGbfjp5PELVHCRUaE """ import struct from typing import Tuple -from monero_client.monero_crypto_cmd import MoneroCryptoCmd -from monero_client.monero_types import InsType, Type, SigType -from monero_client.crypto.hmac import hmac_sha256 -from monero_client.exception.device_error import DeviceError -from monero_client.io.button import Button -from monero_client.utils.varint import encode_varint +from .monero_crypto_cmd import MoneroCryptoCmd +from .monero_types import InsType, Type, SigType +from .crypto.hmac import hmac_sha256 +from .exception.device_error import DeviceError +from .io.button import Button +from .utils.varint import encode_varint PROTOCOL_VERSION: int = 3 @@ -42,8 +48,8 @@ def reset_and_get_version(self, sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): - raise DeviceError(sw, ins) + if not sw & 0x9000: + raise DeviceError(error_code=sw, ins=ins) assert len(response) == 3 @@ -70,15 +76,15 @@ def set_signature_mode(self, sig_type: SigType) -> int: sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): - raise DeviceError(sw, ins) + if not sw & 0x9000: + raise DeviceError(error_code=sw, ins=ins) assert len(response) == 4 sig_mode, *_ = struct.unpack(">I", response) if sig_mode not in (1, 2): - raise Exception(f"Signature mode should be 1 (real) or 2 (fake).") + raise Exception("Signature mode should be 1 (real) or 2 (fake).") return SigType.REAL if sig_mode else SigType.FAKE @@ -97,8 +103,8 @@ def open_tx(self) -> Tuple[bytes, bytes, bytes, bytes]: sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): - raise DeviceError(sw, ins) + if not sw & 0x9000: + raise DeviceError(error_code=sw, ins=ins) self.is_in_tx_mode = True @@ -138,8 +144,8 @@ def close_tx(self) -> None: sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): - raise DeviceError(sw, ins) + if not sw & 0x9000: + raise DeviceError(error_code=sw, ins=ins) self.is_in_tx_mode = False @@ -179,8 +185,8 @@ def gen_txout_keys(self, sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): - raise DeviceError(sw, ins) + if not sw & 0x9000: + raise DeviceError(error_code=sw, ins=ins) assert len(response) == 96 @@ -218,8 +224,8 @@ def prefix_hash_init(self, button: Button, version: int, timelock: int) -> None: sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): - raise DeviceError(sw, ins, "P1=1 (init)") + if not sw & 0x9000: + raise DeviceError(error_code=sw, ins=ins, message="P1=1 (init)") assert len(response) == 0 @@ -235,8 +241,8 @@ def prefix_hash_update(self, index: int, payload: bytes, is_last: bool) -> bytes sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): - raise DeviceError(sw, ins, "P1=2 (update)") + if not sw & 0x9000: + raise DeviceError(error_code=sw, ins=ins, message="P1=2 (update)") if is_last: assert len(response) == 32 @@ -263,8 +269,8 @@ def gen_commitment_mask(self, _ak_amount: bytes) -> bytes: sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): - raise DeviceError(sw, ins) + if not sw & 0x9000: + raise DeviceError(error_code=sw, ins=ins) assert len(response) == 32 @@ -295,8 +301,8 @@ def blind(self, sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): - raise DeviceError(sw, ins) + if not sw & 0x9000: + raise DeviceError(error_code=sw, ins=ins) assert len(response) == 64 @@ -329,8 +335,8 @@ def unblind(self, sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): - raise DeviceError(sw, ins) + if not sw & 0x9000: + raise DeviceError(error_code=sw, ins=ins) assert len(response) == 64 @@ -364,8 +370,8 @@ def validate_prehash_init(self, sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): - raise DeviceError(sw, ins, "P1=1 (init)") + if not sw & 0x9000: + raise DeviceError(error_code=sw, ins=ins, message="P1=1 (init)") assert len(response) == 0 @@ -406,8 +412,8 @@ def validate_prehash_update(self, sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): - raise DeviceError(sw, ins, "P1=2 (update)") + if not sw & 0x9000: + raise DeviceError(error_code=sw, ins=ins, message="P1=2 (update)") assert len(response) == 0 @@ -448,7 +454,7 @@ def validate_prehash_finalize(self, sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): - raise DeviceError(sw, ins, "P1=3 (finalize)") + if not sw & 0x9000: + raise DeviceError(error_code=sw, ins=ins, message="P1=3 (finalize)") assert len(response) == 0 diff --git a/tests/monero_client/monero_crypto_cmd.py b/tests/monero_client/monero_crypto_cmd.py index fc0d68c..33cd512 100644 --- a/tests/monero_client/monero_crypto_cmd.py +++ b/tests/monero_client/monero_crypto_cmd.py @@ -1,11 +1,11 @@ import struct from typing import Tuple -from monero_client.crypto.hmac import hmac_sha256 -from monero_client.exception.device_error import DeviceError -from monero_client.monero_types import InsType -from monero_client.monero_types import Type -from monero_client.io.transport import Transport +from .crypto.hmac import hmac_sha256 +from .exception.device_error import DeviceError +from .monero_types import InsType +from .monero_types import Type +from .io.transport import Transport PROTOCOL_VERSION: int = 3 @@ -46,7 +46,7 @@ def secret_scalar_mul_key(self, sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): + if not sw & 0x9000: raise DeviceError(sw, ins) assert len(response) == 32 @@ -73,7 +73,7 @@ def secret_scalar_mul_base(self, sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): + if not sw & 0x9000: raise DeviceError(sw, ins) assert len(response) == 32 @@ -91,7 +91,7 @@ def get_public_keys(self) -> Tuple[bytes, bytes, str]: sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): + if not sw & 0x9000: raise DeviceError(sw, ins, "P1=1") assert len(response) == 159 @@ -116,7 +116,7 @@ def get_private_view_key(self, button) -> bytes: sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): + if not sw & 0x9000: raise DeviceError(sw, ins, "P1=2") assert len(response) == 32 @@ -134,7 +134,7 @@ def generate_keypair(self) -> Tuple[bytes, bytes]: sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): + if not sw & 0x9000: raise DeviceError(sw, ins) if self.is_in_tx_mode: @@ -156,7 +156,7 @@ def verify_key(self, _priv_key: bytes, pub_key: bytes) -> bool: sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): + if not sw & 0x9000: raise DeviceError(sw, ins) assert len(response) == 4 @@ -164,7 +164,7 @@ def verify_key(self, _priv_key: bytes, pub_key: bytes) -> bool: # 32 bits integer (should be 0 or 1) verified, *_ = struct.unpack(">I", response) - return True if verified else False + return bool(verified) def generate_key_image(self, _priv_key: bytes, pub_key: bytes) -> bytes: ins: InsType = InsType.INS_GEN_KEY_IMAGE @@ -186,7 +186,7 @@ def generate_key_image(self, _priv_key: bytes, pub_key: bytes) -> bytes: sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): + if not sw & 0x9000: raise DeviceError(sw, ins) assert len(response) == 32 @@ -218,7 +218,7 @@ def put_key(self, sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): + if not sw & 0x9000: raise DeviceError(sw, ins) assert len(response) == 0 @@ -243,7 +243,7 @@ def gen_key_derivation(self, pub_key: bytes, _priv_key: bytes) -> bytes: sw, response = self.device.recv() # type: int, bytes - if not (sw & 0x9000): + if not sw & 0x9000: raise DeviceError(sw, ins) _d_in: bytes = response[:32] # encrypted derivation diff --git a/tests/monero_client/utils/base58.py b/tests/monero_client/utils/base58.py index b32ca8d..62c2cc1 100644 --- a/tests/monero_client/utils/base58.py +++ b/tests/monero_client/utils/base58.py @@ -36,7 +36,7 @@ def bin_to_hex(b: bytes) -> str: def uint8be_to_64(data: bytes) -> int: - if not (1 <= len(data) <= 8): + if not 1 <= len(data) <= 8: raise ValueError(f"Invalid input length: {len(data)}") res: int = 0 @@ -138,8 +138,8 @@ def decode_block(data: bytes, buf: bytearray, index: int) -> bytearray: f"Overflow: {order} * {digit} + {res_num} = {product}" ) - res_num: int = product - order: int = order * B58_BASE + res_num = product + order = order * B58_BASE if res_size < FULL_BLOCK_SIZE and 2 ** (8 * res_size) <= res_num: raise ValueError(f"Overflow: {res_num} doesn't fit in {res_size} bit(s)") @@ -150,9 +150,9 @@ def decode_block(data: bytes, buf: bytearray, index: int) -> bytearray: return buf -def decode(data_encoded: str) -> str: +def decode(encoded_value: str) -> str: """Decode a base58 string (ex: a Monero address) into hexidecimal form.""" - data_encoded: bytearray = bytearray(data_encoded, encoding="ascii") + data_encoded: bytearray = bytearray(encoded_value, encoding="ascii") l_enc: int = len(data_encoded) if l_enc == 0: diff --git a/tests/test_sig.py b/tests/test_sig.py index ef7a2ee..03cd4c2 100644 --- a/tests/test_sig.py +++ b/tests/test_sig.py @@ -7,8 +7,9 @@ class TestSignature: """Monero signature test.""" + @staticmethod @pytest.fixture(autouse=True, scope="class") - def state(self): + def state(): sender = Keys( public_view_key=bytes.fromhex("865cbfab852a1d1ccdfc7328e4dac90f78" "fc2154257d07522e9b79e637326dfa"), @@ -44,7 +45,8 @@ def state(self): "blinded_mask": [], "y": []} - def test_set_sig(self, monero): + @staticmethod + def test_set_sig(monero): major, minor, patch = monero.reset_and_get_version( monero_client_version=b"0.16.0.0" ) # type: int, int, int @@ -53,7 +55,8 @@ def test_set_sig(self, monero): sig_mode: SigType = monero.set_signature_mode(sig_type=SigType.REAL) assert sig_mode == SigType.REAL - def test_open_tx(self, monero, state): + @staticmethod + def test_open_tx(monero, state): (tx_pub_key, _tx_priv_key, fake_view_key, @@ -65,7 +68,8 @@ def test_open_tx(self, monero, state): state["tx_pub_key"] = tx_pub_key state["_tx_priv_key"] = _tx_priv_key - def test_gen_txout_keys(self, monero, state): + @staticmethod + def test_gen_txout_keys(monero, state): _ak_amount, out_ephemeral_pub_key = monero.gen_txout_keys( _tx_priv_key=state["_tx_priv_key"], tx_pub_key=state["tx_pub_key"], @@ -78,7 +82,8 @@ def test_gen_txout_keys(self, monero, state): state["_ak_amount"].append(_ak_amount) # _ak_amount_t - def test_prefix_hash(self, monero, button): + @staticmethod + def test_prefix_hash(monero, button): expected: bytes = bytes.fromhex("49d03a195e239b52779866b33024210f" "c7dc66e9c2998975c0aa45c1702549d5") # should ask for timelock validation @@ -91,13 +96,15 @@ def test_prefix_hash(self, monero, button): assert result == expected - def test_gen_commitment_mask(self, monero, state): + @staticmethod + def test_gen_commitment_mask(monero, state): assert len(state["_ak_amount"]) != 0 s: bytes = monero.gen_commitment_mask(state["_ak_amount"][0]) state["y"].append(s) # y_t - def test_blind(self, monero, state): + @staticmethod + def test_blind(monero, state): assert len(state["y"]) != 0 assert len(state["_ak_amount"]) != 0 @@ -121,7 +128,8 @@ def test_blind(self, monero, state): state["blinded_mask"].append(blinded_mask) state["blinded_amount"].append(blinded_amount) - def test_validate(self, monero, button, state): + @staticmethod + def test_validate(monero, button, state): assert len(state["y"]) != 0 assert len(state["_ak_amount"]) != 0 assert len(state["blinded_amount"]) != 0 @@ -149,5 +157,6 @@ def test_validate(self, monero, button, state): # is_last=True # ) - def test_close_tx(self, monero): + @staticmethod + def test_close_tx(monero): monero.close_tx() diff --git a/tests/test_version.py b/tests/test_version.py index b4a558f..4eeb9cf 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,5 +1,6 @@ import pytest +# pylint: disable=wildcard-import, unused-wildcard-import from monero_client.exception import * From 3e0e486dc94e32e1403d7e0ca1d06eef606159d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 2 Jul 2020 12:05:29 +0200 Subject: [PATCH 35/76] Fix display of subaddresses on Nano S --- src/monero_ux_nanos.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/monero_ux_nanos.c b/src/monero_ux_nanos.c index 4869616..ea3777d 100644 --- a/src/monero_ux_nanos.c +++ b/src/monero_ux_nanos.c @@ -743,8 +743,8 @@ void ui_menu_pubaddr_action(unsigned int value) { void ui_menu_any_pubaddr_display(unsigned int value, unsigned char* pub_view, unsigned char* pub_spend, unsigned char is_subbadress, unsigned char* paymanetID) { - monero_base58_public_key(G_monero_vstate.ux_address + strlen(G_monero_vstate.ux_address), - pub_view, pub_spend, is_subbadress, paymanetID); + monero_base58_public_key(G_monero_vstate.ux_address, pub_view, pub_spend, is_subbadress, + paymanetID); UX_MENU_DISPLAY(value, ui_menu_pubaddr, ui_menu_pubaddr_preprocessor); } From 499f41fc0341048d34947fc41c2cffd25043ebd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Fri, 3 Jul 2020 17:06:30 +0200 Subject: [PATCH 36/76] Uncomment test for INS_PUT_KEY --- tests/test_crypto.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/tests/test_crypto.py b/tests/test_crypto.py index e895076..18ab9b5 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -39,25 +39,23 @@ def test_key_image(monero): assert expected_key_img == key_image -# Wait for PR #69 to be merged: https://github.com/LedgerHQ/app-monero/pull/69 -# -# def test_put_key(monero): -# priv_view_key: bytes = bytes.fromhex("0f3fe25d0c6d4c94dde0c0bcc214b233" -# "e9c72927f813728b0f01f28f9d5e1201") -# pub_view_key: bytes = bytes.fromhex("865cbfab852a1d1ccdfc7328e4dac90f" -# "78fc2154257d07522e9b79e637326dfa") -# priv_spend_key: bytes = bytes.fromhex("3b094ca7218f175e91fa2402b4ae239a" -# "2fe8262792a3e718533a1a357a1e4109") -# pub_spend_key: bytes = bytes.fromhex("dae41d6b13568fdd71ec3d20c2f614c6" -# "5fe819f36ca5da8d24df3bd89b2bad9d") -# address: str = ("5A8FgbMkmG2e3J41sBdjvjaBUyz8qHohsQcGtRf63qEUTMBvm" -# "A45fpp5pSacMdSg7A3b71RejLzB8EkGbfjp5PELVHCRUaE") - -# monero.put_key(priv_view_key=priv_view_key, -# pub_view_key=pub_view_key, -# priv_spend_key=priv_spend_key, -# pub_spend_key=pub_spend_key, -# address=address) +def test_put_key(monero): + priv_view_key: bytes = bytes.fromhex("0f3fe25d0c6d4c94dde0c0bcc214b233" + "e9c72927f813728b0f01f28f9d5e1201") + pub_view_key: bytes = bytes.fromhex("865cbfab852a1d1ccdfc7328e4dac90f" + "78fc2154257d07522e9b79e637326dfa") + priv_spend_key: bytes = bytes.fromhex("3b094ca7218f175e91fa2402b4ae239a" + "2fe8262792a3e718533a1a357a1e4109") + pub_spend_key: bytes = bytes.fromhex("dae41d6b13568fdd71ec3d20c2f614c6" + "5fe819f36ca5da8d24df3bd89b2bad9d") + address: str = ("5A8FgbMkmG2e3J41sBdjvjaBUyz8qHohsQcGtRf63qEUTMBvm" + "A45fpp5pSacMdSg7A3b71RejLzB8EkGbfjp5PELVHCRUaE") + + monero.put_key(priv_view_key=priv_view_key, + pub_view_key=pub_view_key, + priv_spend_key=priv_spend_key, + pub_spend_key=pub_spend_key, + address=address) def test_gen_key_derivation(monero): From 32944b485a5522d516d5cbd6de989425d838e51b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Wed, 1 Jul 2020 16:10:40 +0200 Subject: [PATCH 37/76] Fix io_lenght check for command INS_PUT_KEY --- src/monero_key.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/monero_key.c b/src/monero_key.c index 1187f9e..f80a4d5 100644 --- a/src/monero_key.c +++ b/src/monero_key.c @@ -253,7 +253,8 @@ int monero_apdu_put_key() { unsigned char pub[32]; unsigned char sec[32]; - if (G_monero_vstate.io_length != (32 * 2 + 32 * 2 + 95)) { + // option + priv/pub view key + priv/pub spend key + base58 address + if (G_monero_vstate.io_length != (1 + 32 * 2 + 32 * 2 + 95)) { THROW(SW_WRONG_LENGTH); return SW_WRONG_LENGTH; } From 44d7d7a0dd49925c5cf01233b66573eff30dbd30 Mon Sep 17 00:00:00 2001 From: Ryan <3649077-RyanCKY@users.noreply.gitlab.com> Date: Mon, 21 Sep 2020 09:29:59 +0800 Subject: [PATCH 38/76] Deleted .github folder --- .github/workflows/ci-workflow.yml | 71 ----------------------------- .github/workflows/lint-workflow.yml | 19 -------- 2 files changed, 90 deletions(-) delete mode 100644 .github/workflows/ci-workflow.yml delete mode 100644 .github/workflows/lint-workflow.yml diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml deleted file mode 100644 index 5300341..0000000 --- a/.github/workflows/ci-workflow.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Compilation - -on: - push: - branches: - - master - - develop - pull_request: - branches: - - master - - develop - -jobs: - job_build_debug: - name: Build debug - runs-on: ubuntu-latest - - container: - image: docker://ledgerhq/ledger-app-builder:1.6.0 - - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Build - run: | - make DEBUG=1 - - - name: Upload app binary - uses: actions/upload-artifact@v2 - with: - name: monero-app-debug - path: bin - - job_test: - name: Test - needs: job_build_debug - runs-on: ubuntu-latest - - container: - image: docker://ledgerhq/speculos:latest - ports: - - 1234:1234 - - 9999:9999 - - 40000:40000 - - 41000:41000 - - 42000:42000 - - 43000:43000 - options: --entrypoint /bin/bash - - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Download app binary - uses: actions/download-artifact@v2 - with: - name: monero-app-debug - path: bin - - - name: Run test - run: | - nohup bash -c "python /speculos/speculos.py bin/app.elf --sdk 1.6 --seed \"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\" --apdu-port 9999 --button-port 42000 --automation-port 43000 --display headless" > speculos.log 2<&1 & - pip install pytest - pytest - - - name: Upload Speculos log - uses: actions/upload-artifact@v2 - with: - name: speculos-log - path: speculos.log diff --git a/.github/workflows/lint-workflow.yml b/.github/workflows/lint-workflow.yml deleted file mode 100644 index 67a2024..0000000 --- a/.github/workflows/lint-workflow.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Code style check - -on: [push, pull_request] - -jobs: - job_lint: - name: Lint - runs-on: ubuntu-latest - - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Lint - uses: DoozyX/clang-format-lint-action@v0.5 - with: - source: './src' - extensions: 'h,c' - clangFormatVersion: 9 From 6b85fcca23a6512acfedb953fb7913c5f79b1aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 21 Sep 2020 22:52:14 +0200 Subject: [PATCH 39/76] Bug exception WrongDataRange with high timelock value --- tests/test_sig.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_sig.py b/tests/test_sig.py index 03cd4c2..0e2178d 100644 --- a/tests/test_sig.py +++ b/tests/test_sig.py @@ -84,10 +84,10 @@ def test_gen_txout_keys(monero, state): @staticmethod def test_prefix_hash(monero, button): - expected: bytes = bytes.fromhex("49d03a195e239b52779866b33024210f" - "c7dc66e9c2998975c0aa45c1702549d5") + expected: bytes = bytes.fromhex("9a259973bf721120aceae3d8d40696c0" + "7470331e386028753123f37fee36926b") # should ask for timelock validation - monero.prefix_hash_init(button=button, version=0, timelock=1) + monero.prefix_hash_init(button=button, version=0, timelock=2147483650) result: bytes = monero.prefix_hash_update( index=1, payload=b"", From c749daf91b7a0280065e3bbb0a350687de806586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 21 Sep 2020 23:00:52 +0200 Subject: [PATCH 40/76] Fix integer overflow when decoding varint --- src/monero_crypto.c | 4 ++-- src/monero_prefix.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/monero_crypto.c b/src/monero_crypto.c index 07cd897..f5352fb 100644 --- a/src/monero_crypto.c +++ b/src/monero_crypto.c @@ -110,11 +110,11 @@ unsigned int monero_decode_varint(unsigned char *varint, unsigned int max_len, u if (len == (max_len - 1)) { THROW(SW_WRONG_DATA_RANGE); } - v = v + (((varint[len]) & 0x7f) << (len * 7)); + v = v + ((uint64_t)((varint[len]) & 0x7f) << (len * 7)); len++; } - v = v + (((varint[len]) & 0x7f) << (len * 7)); + v = v + ((uint64_t)((varint[len]) & 0x7f) << (len * 7)); *value = v; return len + 1; } diff --git a/src/monero_prefix.c b/src/monero_prefix.c index de662c5..dac5e50 100644 --- a/src/monero_prefix.c +++ b/src/monero_prefix.c @@ -30,7 +30,7 @@ /* --- --- */ /* ----------------------------------------------------------------------- */ int monero_apdu_prefix_hash_init() { - int timelock; + uint64_t timelock; monero_keccak_update_H(G_monero_vstate.io_buffer + G_monero_vstate.io_offset, G_monero_vstate.io_length - G_monero_vstate.io_offset); From 87181614426550408a0e93d199caf8e138f15484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 10 Aug 2020 15:03:44 +0200 Subject: [PATCH 41/76] Add CLSAG signature --- src/monero_api.h | 5 ++ src/monero_clsag.c | 168 ++++++++++++++++++++++++++++++++++++++++++ src/monero_crypto.c | 2 +- src/monero_dispatch.c | 1 + 4 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 src/monero_clsag.c diff --git a/src/monero_api.h b/src/monero_api.h index a86aa4f..e37a785 100644 --- a/src/monero_api.h +++ b/src/monero_api.h @@ -68,6 +68,10 @@ int monero_apdu_mlsag_prehash_init(void); int monero_apdu_mlsag_prehash_update(void); int monero_apdu_mlsag_prehash_finalize(void); +int monero_apdu_clsag_prepare(void); +int monero_apdu_clsag_hash(void); +int monero_apdu_clsag_sign(void); + int monero_apu_generate_txout_keys(void); int monero_apdu_prefix_hash_init(); @@ -129,6 +133,7 @@ extern const unsigned char C_FAKE_SEC_SPEND_KEY[32]; int is_fake_view_key(unsigned char *s); int is_fake_spend_key(unsigned char *s); +void monero_ge_fromfe_frombytes(unsigned char *ge, unsigned char *bytes); void monero_sc_add(unsigned char *r, unsigned char *s1, unsigned char *s2); void monero_hash_to_scalar(unsigned char *scalar, unsigned char *raw, unsigned int len); void monero_hash_to_ec(unsigned char *ec, unsigned char *ec_pub); diff --git a/src/monero_clsag.c b/src/monero_clsag.c new file mode 100644 index 0000000..3e03d34 --- /dev/null +++ b/src/monero_clsag.c @@ -0,0 +1,168 @@ +/***************************************************************************** + * Ledger Monero App. + * (c) 2017-2020 Cedric Mesnil , Ledger SAS. + * (c) 2020 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +#include "os.h" +#include "cx.h" +#include "monero_types.h" +#include "monero_api.h" +#include "monero_vars.h" + +/* ----------------------------------------------------------------------- */ +/* --- --- */ +/* ----------------------------------------------------------------------- */ +/* + bool device_default::clsag_prepare(const rct::key &p, const rct::key &z, rct::key &I, rct::key + &D, const rct::key &H, rct::key &a, rct::key &aG, rct::key &aH) { rct::skpkGen(a,aG); // aG = a*G + rct::scalarmultKey(aH,H,a); // aH = a*H + rct::scalarmultKey(I,H,p); // I = p*H + rct::scalarmultKey(D,H,z); // D = z*H + return true; + } +*/ +int monero_apdu_clsag_prepare() { + int options; + unsigned char a[32]; + unsigned char p[32]; + unsigned char z[32]; + unsigned char H[32]; + unsigned char W[32]; + + G_monero_vstate.tx_sign_cnt++; + if (G_monero_vstate.tx_sign_cnt == 0) { + monero_lock_and_throw(SW_SECURITY_MAX_SIGNATURE_REACHED); + } + + monero_io_fetch_decrypt(p, 32, TYPE_SCALAR); + monero_io_fetch_decrypt(z, 32, TYPE_SCALAR); + monero_io_fetch(H, 32); + monero_io_discard(1); + + // H + monero_ge_fromfe_frombytes(H, H); + + // a + monero_rng_mod_order(a); + monero_io_insert_encrypt(a, 32, TYPE_ALPHA); + + // a.G + monero_ecmul_G(W, a); + monero_io_insert(W, 32); + // a.H + monero_ecmul_k(W, a, H); + monero_io_insert(W, 32); + // I = p.H + monero_ecmul_k(W, p, H); + monero_io_insert(W, 32); + // I = z.H + monero_ecmul(W, z, H); + monero_io_insert(W, 32); + + return SW_OK; +} + +/* ----------------------------------------------------------------------- */ +/* --- --- */ +/* ----------------------------------------------------------------------- */ +int monero_apdu_clsag_hash() { + unsigned char msg[32]; + unsigned char c[32]; + + if (G_monero_vstate.io_p2 == 1) { + monero_keccak_init_H(); + os_memmove(msg, G_monero_vstate.mlsagH, 32); + } else { + monero_io_fetch(msg, 32); + } + monero_io_discard(1); + + monero_keccak_update_H(msg, 32); + if ((G_monero_vstate.options & 0x80) == 0) { + monero_keccak_final_H(c); + monero_reduce(c, c); + monero_io_insert(c, 32); + os_memmove(G_monero_vstate.c, c, 32); + } + return SW_OK; +} + +/* ----------------------------------------------------------------------- */ +/* --- --- */ +/* ----------------------------------------------------------------------- */ +/* + bool device_default::clsag_sign(const rct::key &c, const rct::key &a, const rct::key &p, const + rct::key &z, const rct::key &mu_P, const rct::key &mu_C, rct::key &s) { rct::key s0_p_mu_P; + sc_mul(s0_p_mu_P.bytes,mu_P.bytes,p.bytes); + rct::key s0_add_z_mu_C; + sc_muladd(s0_add_z_mu_C.bytes,mu_C.bytes,z.bytes,s0_p_mu_P.bytes); + sc_mulsub(s.bytes,c.bytes,s0_add_z_mu_C.bytes,a.bytes); + + return true; + } +*/ +int monero_apdu_clsag_sign() { + unsigned char s[32]; + unsigned char a[32]; + unsigned char p[32]; + unsigned char z[32]; + unsigned char mu_P[32]; + unsigned char mu_C[32]; + + if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_FAKE) { + monero_io_fetch(a, 32); + monero_io_fetch(p, 32); + monero_io_fetch(z, 32); + monero_io_fetch(mu_P, 32); + monero_io_fetch(mu_C, 32); + } else if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { + monero_io_fetch_decrypt(a, 32, TYPE_ALPHA); + monero_io_fetch_decrypt(p, 32, TYPE_SCALAR); + monero_io_fetch_decrypt(z, 32, TYPE_SCALAR); + monero_io_fetch(mu_P, 32); + monero_io_fetch(mu_C, 32); + } else { + monero_lock_and_throw(SW_SECURITY_INTERNAL); + } + monero_io_discard(1); + + // check xin and alpha are not null + monero_check_scalar_range_1N(a); + monero_check_scalar_range_1N(p); + monero_check_scalar_range_1N(z); + + monero_reduce(a, a); + monero_reduce(p, p); + monero_reduce(z, z); + monero_reduce(mu_P, mu_P); + monero_reduce(mu_C, mu_C); + monero_reduce(G_monero_vstate.c, G_monero_vstate.c); + + // s0_p_mu_P = mu_P*p ->s + monero_multm(s, p, mu_P); + + // s0_add_z_mu_C = mu_C*z + s0_p_mu_P + monero_multm(mu_P, mu_C, z); + monero_addm(s, s, mu_P); + + // s = c*s0_add_z_mu_C + a + monero_multm(mu_P, G_monero_vstate.c, s); + monero_addm(s, mu_P, a); + + monero_io_insert(s, 32); + monero_io_insert_u32(G_monero_vstate.tx_sig_mode); + return SW_OK; +} diff --git a/src/monero_crypto.c b/src/monero_crypto.c index f5352fb..c1397fd 100644 --- a/src/monero_crypto.c +++ b/src/monero_crypto.c @@ -275,7 +275,7 @@ const unsigned char C_fe_qm5div8[] = { 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd}; -static void monero_ge_fromfe_frombytes(unsigned char *ge, unsigned char *bytes) { +void monero_ge_fromfe_frombytes(unsigned char *ge, unsigned char *bytes) { #define MOD (unsigned char *)C_ED25519_FIELD, 32 #define fe_isnegative(f) (f[31] & 1) #if 0 diff --git a/src/monero_dispatch.c b/src/monero_dispatch.c index fcbea88..caaf82f 100644 --- a/src/monero_dispatch.c +++ b/src/monero_dispatch.c @@ -50,6 +50,7 @@ int check_potocol() { switch (G_monero_vstate.io_protocol_version) { case 0x00: /* the first one: PCSC epoch */ case 0x03: /* protocol V3 */ + case 0x04: /* protocol V4 */ if (G_monero_vstate.protocol == 0xff) { G_monero_vstate.protocol = G_monero_vstate.io_protocol_version; } From d4278ef9c743c3e2bdf69df57248c23cda770bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 10 Aug 2020 15:33:16 +0200 Subject: [PATCH 42/76] Bump version to 1.7.0 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0eb34f2..a2c99ce 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ endif #DEFINES += MONERO_BETA APPVERSION_M=1 -APPVERSION_N=6 +APPVERSION_N=7 APPVERSION_P=0 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) From eaf4ba1ea2ce29add9659567188e68094e40df8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 13 Aug 2020 18:48:20 +0200 Subject: [PATCH 43/76] Fix bad function call in monero_apdu_clsag_prepare() --- src/monero_clsag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monero_clsag.c b/src/monero_clsag.c index 3e03d34..cdd22e7 100644 --- a/src/monero_clsag.c +++ b/src/monero_clsag.c @@ -69,7 +69,7 @@ int monero_apdu_clsag_prepare() { monero_ecmul_k(W, p, H); monero_io_insert(W, 32); // I = z.H - monero_ecmul(W, z, H); + monero_ecmul_k(W, z, H); monero_io_insert(W, 32); return SW_OK; From de38f6a4b2568b33bb837e938450ba30712e8db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 13 Aug 2020 19:19:39 +0200 Subject: [PATCH 44/76] Add INS_CLSAG to dispatcher --- src/monero_dispatch.c | 50 +++++++++++++++++++++++++++++++++++++++++-- src/monero_types.h | 1 + 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/monero_dispatch.c b/src/monero_dispatch.c index caaf82f..f469a9a 100644 --- a/src/monero_dispatch.c +++ b/src/monero_dispatch.c @@ -112,6 +112,7 @@ int check_ins_access() { case INS_BLIND: case INS_VALIDATE: case INS_MLSAG: + case INS_CLSAG: case INS_GEN_COMMITMENT_MASK: if (os_global_pin_is_validated() != PIN_VERIFIED) { return SW_SECURITY_PIN_LOCKED; @@ -427,8 +428,9 @@ int monero_dispatch() { /* --- MLSAG --- */ case INS_MLSAG: // 1. state machine check - if ((G_monero_vstate.tx_state_ins != INS_VALIDATE) && - (G_monero_vstate.tx_state_ins != INS_MLSAG)) { + if ((G_monero_vstate.tx_state_ins != INS_VALIDATE) && // + (G_monero_vstate.tx_state_ins != INS_MLSAG) && // + (G_monero_vstate.protocol != 3)) { THROW(SW_COMMAND_NOT_ALLOWED); } if (G_monero_vstate.tx_state_ins == INS_VALIDATE) { @@ -467,6 +469,50 @@ int monero_dispatch() { update_protocol(); break; + /* --- CLSAG --- */ + case INS_CLSAG: + // 1. state machine check + if ((G_monero_vstate.tx_state_ins != INS_VALIDATE) && // + (G_monero_vstate.tx_state_ins != INS_CLSAG) && // + (G_monero_vstate.protocol != 4)) { + THROW(SW_COMMAND_NOT_ALLOWED); + } + if (G_monero_vstate.tx_state_ins == INS_VALIDATE) { + if ((G_monero_vstate.tx_state_p1 != 3) || (G_monero_vstate.io_p1 != 1) || + (G_monero_vstate.io_p2 != 0)) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else { + if (G_monero_vstate.tx_state_p1 == 1) { + if (2 != G_monero_vstate.io_p1) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else if (G_monero_vstate.tx_state_p1 == 2) { + if ((2 != G_monero_vstate.io_p1) && (3 != G_monero_vstate.io_p1)) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else if (G_monero_vstate.tx_state_p1 == 3) { + if (1 != G_monero_vstate.io_p1) { + THROW(SW_SUBCOMMAND_NOT_ALLOWED); + } + } else { + THROW(SW_COMMAND_NOT_ALLOWED); + } + } + + // 2. command process + if (G_monero_vstate.io_p1 == 1) { + sw = monero_apdu_clsag_prepare(); + } else if (G_monero_vstate.io_p1 == 2) { + sw = monero_apdu_clsag_hash(); + } else if (G_monero_vstate.io_p1 == 3) { + sw = monero_apdu_clsag_sign(); + } else { + THROW(SW_WRONG_P1P2); + } + update_protocol(); + break; + /* --- KEYS --- */ default: diff --git a/src/monero_types.h b/src/monero_types.h index b98d789..4e46a8a 100644 --- a/src/monero_types.h +++ b/src/monero_types.h @@ -274,6 +274,7 @@ typedef struct monero_v_state_s monero_v_state_t; #define INS_PREFIX_HASH 0x7D #define INS_VALIDATE 0x7C #define INS_MLSAG 0x7E +#define INS_CLSAG 0x7F #define INS_CLOSE_TX 0x80 #define INS_GET_TX_PROOF 0xA0 From c80b4ebb1b0afa2b7cb0518eefbe48f8d1ece8ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 24 Aug 2020 18:50:59 +0200 Subject: [PATCH 45/76] Fix INS_MLSAG must be compatible with protocol v4 --- src/monero_dispatch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monero_dispatch.c b/src/monero_dispatch.c index f469a9a..839e57f 100644 --- a/src/monero_dispatch.c +++ b/src/monero_dispatch.c @@ -430,7 +430,7 @@ int monero_dispatch() { // 1. state machine check if ((G_monero_vstate.tx_state_ins != INS_VALIDATE) && // (G_monero_vstate.tx_state_ins != INS_MLSAG) && // - (G_monero_vstate.protocol != 3)) { + ((G_monero_vstate.protocol != 3) || (G_monero_vstate.protocol != 4))) { THROW(SW_COMMAND_NOT_ALLOWED); } if (G_monero_vstate.tx_state_ins == INS_VALIDATE) { From f7b2d8a138aa21172722c1363f888310ebfc048c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 24 Aug 2020 18:55:53 +0200 Subject: [PATCH 46/76] Fix scalar z is not encrypted in the Monero client --- src/monero_clsag.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/monero_clsag.c b/src/monero_clsag.c index cdd22e7..66d1ec0 100644 --- a/src/monero_clsag.c +++ b/src/monero_clsag.c @@ -48,7 +48,7 @@ int monero_apdu_clsag_prepare() { } monero_io_fetch_decrypt(p, 32, TYPE_SCALAR); - monero_io_fetch_decrypt(z, 32, TYPE_SCALAR); + monero_io_fetch(z, 32); monero_io_fetch(H, 32); monero_io_discard(1); @@ -131,7 +131,7 @@ int monero_apdu_clsag_sign() { } else if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { monero_io_fetch_decrypt(a, 32, TYPE_ALPHA); monero_io_fetch_decrypt(p, 32, TYPE_SCALAR); - monero_io_fetch_decrypt(z, 32, TYPE_SCALAR); + monero_io_fetch(z, 32); monero_io_fetch(mu_P, 32); monero_io_fetch(mu_C, 32); } else { From feb5dab735646c89b21f0346464163f27b677946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 24 Aug 2020 18:57:42 +0200 Subject: [PATCH 47/76] Fix wrong parameter order for monero_ecmul_k() --- src/monero_clsag.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/monero_clsag.c b/src/monero_clsag.c index 66d1ec0..dcd3542 100644 --- a/src/monero_clsag.c +++ b/src/monero_clsag.c @@ -58,18 +58,18 @@ int monero_apdu_clsag_prepare() { // a monero_rng_mod_order(a); monero_io_insert_encrypt(a, 32, TYPE_ALPHA); - // a.G monero_ecmul_G(W, a); monero_io_insert(W, 32); // a.H - monero_ecmul_k(W, a, H); + monero_ecmul_k(W, H, a); monero_io_insert(W, 32); // I = p.H - monero_ecmul_k(W, p, H); + monero_ecmul_k(W, H, p); monero_io_insert(W, 32); - // I = z.H - monero_ecmul_k(W, z, H); + + // D = z.H + monero_ecmul_k(W, H, z); monero_io_insert(W, 32); return SW_OK; From f55e71c42918f1eb112ba94f8d335f8401251d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 24 Aug 2020 19:03:15 +0200 Subject: [PATCH 48/76] Fix tx_sig_mode is not expected by the Monero client --- src/monero_clsag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monero_clsag.c b/src/monero_clsag.c index dcd3542..eaeebd5 100644 --- a/src/monero_clsag.c +++ b/src/monero_clsag.c @@ -163,6 +163,6 @@ int monero_apdu_clsag_sign() { monero_addm(s, mu_P, a); monero_io_insert(s, 32); - monero_io_insert_u32(G_monero_vstate.tx_sig_mode); + return SW_OK; } From f6f9805444038140c686c7a22cad86f8ab5b9805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 24 Aug 2020 19:07:30 +0200 Subject: [PATCH 49/76] Fix temporarly just check if is scalars are not 0 --- src/monero_clsag.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/monero_clsag.c b/src/monero_clsag.c index eaeebd5..236cc6d 100644 --- a/src/monero_clsag.c +++ b/src/monero_clsag.c @@ -137,12 +137,12 @@ int monero_apdu_clsag_sign() { } else { monero_lock_and_throw(SW_SECURITY_INTERNAL); } + monero_io_discard(1); - // check xin and alpha are not null - monero_check_scalar_range_1N(a); - monero_check_scalar_range_1N(p); - monero_check_scalar_range_1N(z); + monero_check_scalar_not_null(a); + monero_check_scalar_not_null(p); + monero_check_scalar_not_null(z); monero_reduce(a, a); monero_reduce(p, p); From 4233e1dcf74359d4be75725ff22bb3cb43dd68bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 24 Aug 2020 19:09:21 +0200 Subject: [PATCH 50/76] Fix 'a - mu_P' instead of 'a + mu_P' --- src/monero_clsag.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/monero_clsag.c b/src/monero_clsag.c index 236cc6d..3b960ce 100644 --- a/src/monero_clsag.c +++ b/src/monero_clsag.c @@ -160,7 +160,8 @@ int monero_apdu_clsag_sign() { // s = c*s0_add_z_mu_C + a monero_multm(mu_P, G_monero_vstate.c, s); - monero_addm(s, mu_P, a); + // s = a - c*(p*mu_P + mu_C*z) + monero_subm(s, a, mu_P); monero_io_insert(s, 32); From ddd939f6a9f88a85d44f62dca85d3956fcfbd89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 24 Aug 2020 19:12:37 +0200 Subject: [PATCH 51/76] Add more comments --- src/monero_clsag.c | 53 +++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/monero_clsag.c b/src/monero_clsag.c index 3b960ce..b912da9 100644 --- a/src/monero_clsag.c +++ b/src/monero_clsag.c @@ -26,11 +26,16 @@ /* --- --- */ /* ----------------------------------------------------------------------- */ /* - bool device_default::clsag_prepare(const rct::key &p, const rct::key &z, rct::key &I, rct::key - &D, const rct::key &H, rct::key &a, rct::key &aG, rct::key &aH) { rct::skpkGen(a,aG); // aG = a*G - rct::scalarmultKey(aH,H,a); // aH = a*H - rct::scalarmultKey(I,H,p); // I = p*H - rct::scalarmultKey(D,H,z); // D = z*H + bool device_default::clsag_prepare(const rct::key &p, + const rct::key &z, + rct::key &I, rct::key &D, + const rct::key &H, + rct::key &a, rct::key &aG, + rct::key &aH) { + rct::skpkGen(a, aG); // aG = a*G + rct::scalarmultKey(aH, H, a); // aH = a*H + rct::scalarmultKey(I, H, p); // I = p*H + rct::scalarmultKey(D, H, z); // D = z*H return true; } */ @@ -78,6 +83,13 @@ int monero_apdu_clsag_prepare() { /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ +/* + bool device_default::clsag_hash(const rct::keyV &data, + rct::key &hash) { + hash = rct::hash_to_scalar(data); + return true; + } +*/ int monero_apdu_clsag_hash() { unsigned char msg[32]; unsigned char c[32]; @@ -104,12 +116,18 @@ int monero_apdu_clsag_hash() { /* --- --- */ /* ----------------------------------------------------------------------- */ /* - bool device_default::clsag_sign(const rct::key &c, const rct::key &a, const rct::key &p, const - rct::key &z, const rct::key &mu_P, const rct::key &mu_C, rct::key &s) { rct::key s0_p_mu_P; - sc_mul(s0_p_mu_P.bytes,mu_P.bytes,p.bytes); +bool device_default::clsag_sign(const rct::key &c, + const rct::key &a, + const rct::key &p, const + rct::key &z, + const rct::key &mu_P, + const rct::key &mu_C, + rct::key &s) { + rct::key s0_p_mu_P; + sc_mul(s0_p_mu_P.bytes, mu_P.bytes, p.bytes); rct::key s0_add_z_mu_C; - sc_muladd(s0_add_z_mu_C.bytes,mu_C.bytes,z.bytes,s0_p_mu_P.bytes); - sc_mulsub(s.bytes,c.bytes,s0_add_z_mu_C.bytes,a.bytes); + sc_muladd(s0_add_z_mu_C.bytes, mu_C.bytes, z.bytes, s0_p_mu_P.bytes); + sc_mulsub(s.bytes, c.bytes, s0_add_z_mu_C.bytes, a.bytes); return true; } @@ -151,14 +169,19 @@ int monero_apdu_clsag_sign() { monero_reduce(mu_C, mu_C); monero_reduce(G_monero_vstate.c, G_monero_vstate.c); - // s0_p_mu_P = mu_P*p ->s - monero_multm(s, p, mu_P); - + // s0_p_mu_P = mu_P*p // s0_add_z_mu_C = mu_C*z + s0_p_mu_P + // + // s = a - c*s0_add_z_mu_C + // = a - c*(mu_C*z + mu_P*p) + + // s = p*mu_P + monero_multm(s, p, mu_P); + // mu_P = mu_C*z monero_multm(mu_P, mu_C, z); + // s = p*mu_P + mu_C*z monero_addm(s, s, mu_P); - - // s = c*s0_add_z_mu_C + a + // mu_P = c * (p*mu_P + mu_C*z) monero_multm(mu_P, G_monero_vstate.c, s); // s = a - c*(p*mu_P + mu_C*z) monero_subm(s, a, mu_P); From a8f881e1ad93ab55f5338b21067fb1d5910f549f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Wed, 26 Aug 2020 20:13:47 +0200 Subject: [PATCH 52/76] Fix wrong call to ge_fromfe_frombytes(H, H) --- src/monero_clsag.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/monero_clsag.c b/src/monero_clsag.c index b912da9..baa0912 100644 --- a/src/monero_clsag.c +++ b/src/monero_clsag.c @@ -57,9 +57,6 @@ int monero_apdu_clsag_prepare() { monero_io_fetch(H, 32); monero_io_discard(1); - // H - monero_ge_fromfe_frombytes(H, H); - // a monero_rng_mod_order(a); monero_io_insert_encrypt(a, 32, TYPE_ALPHA); From 725b10a00a9918ccaad11548cc52eec99d963970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Tue, 1 Sep 2020 16:05:57 +0200 Subject: [PATCH 53/76] Fix check prefix hash with protocol v4 --- src/monero_prehash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monero_prehash.c b/src/monero_prehash.c index 231ffcc..399cb48 100644 --- a/src/monero_prehash.c +++ b/src/monero_prehash.c @@ -191,7 +191,7 @@ int monero_apdu_mlsag_prehash_finalize() { monero_io_fetch(proof, 32); monero_io_discard(1); monero_keccak_init_H(); - if (G_monero_vstate.io_protocol_version == 3) { + if (G_monero_vstate.io_protocol_version >= 3) { if (os_memcmp(message, G_monero_vstate.prefixH, 32) != 0) { monero_lock_and_throw(SW_SECURITY_PREFIX_HASH); } From c1ec34dcba28c1e65636885df3ebb8c3cdc05200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Tue, 1 Sep 2020 16:08:25 +0200 Subject: [PATCH 54/76] Update INS_GET_TX_PROOF to InProofv2 --- src/monero_proof.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/monero_proof.c b/src/monero_proof.c index 30eb028..c7edd1c 100644 --- a/src/monero_proof.c +++ b/src/monero_proof.c @@ -25,16 +25,6 @@ /* ----------------------------------------------------------------------- */ /* --- --- */ /* ----------------------------------------------------------------------- */ -/* - * pick random k - * if B: - * compute X = k*B - * else: - * compute X = k*G - * compute Y = k*A - * sig.c = Hs(Msg || D || X || Y) - * sig.r = k - sig.c*r - */ int monero_apdu_get_tx_proof() { unsigned char *msg; unsigned char *R; @@ -45,6 +35,7 @@ int monero_apdu_get_tx_proof() { unsigned char XY[32]; unsigned char sig_c[32]; unsigned char sig_r[32]; + unsigned char sep[32]; #define k (G_monero_vstate.tmp + 256) msg = G_monero_vstate.io_buffer + G_monero_vstate.io_offset; @@ -61,23 +52,43 @@ int monero_apdu_get_tx_proof() { monero_io_discard(0); + // Generate random k monero_rng_mod_order(k); + // tmp = msg os_memmove(G_monero_vstate.tmp + 32 * 0, msg, 32); + // tmp = msg || D os_memmove(G_monero_vstate.tmp + 32 * 1, D, 32); if (G_monero_vstate.options & 1) { + // X = kB monero_ecmul_k(XY, B, k); } else { + // X = kG monero_ecmul_G(XY, k); } + // tmp = msg || D || X os_memmove(G_monero_vstate.tmp + 32 * 2, XY, 32); + // Y = kA monero_ecmul_k(XY, A, k); + // tmp = msg || D || X || Y os_memmove(G_monero_vstate.tmp + 32 * 3, XY, 32); + monero_keccak_H("TXPROOF_V2", 10, sep); + // tmp = msg || D || X || Y || sep + os_memmove(G_monero_vstate.tmp + 32 * 4, sep, 32); + // tmp = msg || D || X || Y || sep || R + os_memmove(G_monero_vstate.tmp + 32 * 5, R, 32); + // tmp = msg || D || X || Y || sep || R || A + os_memmove(G_monero_vstate.tmp + 32 * 6, A, 32); + // tmp = msg || D || X || Y || sep || R || B or [0] + os_memmove(G_monero_vstate.tmp + 32 * 7, B, 32); - monero_hash_to_scalar(sig_c, &G_monero_vstate.tmp[0], 32 * 4); + // sig_c = H_n(tmp) + monero_hash_to_scalar(sig_c, &G_monero_vstate.tmp[0], 32 * 8); + // sig_c*r monero_multm(XY, sig_c, r); + // sig_r = k - sig_c*r monero_subm(sig_r, k, XY); monero_io_insert(sig_c, 32); From 9e2f8eb8c884cff155e97f003e437604fd114c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Wed, 2 Sep 2020 18:03:02 +0200 Subject: [PATCH 55/76] Add next minimum version for Monero client --- src/monero_init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monero_init.c b/src/monero_init.c index 45d643d..7208456 100644 --- a/src/monero_init.c +++ b/src/monero_init.c @@ -179,7 +179,7 @@ void monero_install(unsigned char netId) { /* ----------------------------------------------------------------------- */ #define MONERO_SUPPORTED_CLIENT_SIZE 1 const char* const monero_supported_client[MONERO_SUPPORTED_CLIENT_SIZE] = { - "0.16.0.", + "0.17.0.", }; int monero_apdu_reset() { From 6878abc9995b49ede07c829eeda175df8daf2f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Wed, 2 Sep 2020 18:03:37 +0200 Subject: [PATCH 56/76] Update unit tests with new versions --- tests/monero_client/monero_types.py | 1 + tests/test_sig.py | 4 ++-- tests/test_version.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/monero_client/monero_types.py b/tests/monero_client/monero_types.py index 9a85be2..42697ec 100644 --- a/tests/monero_client/monero_types.py +++ b/tests/monero_client/monero_types.py @@ -66,6 +66,7 @@ class InsType(enum.IntEnum): INS_PREFIX_HASH = 0x7D INS_VALIDATE = 0x7C INS_MLSAG = 0x7E + INS_CLSAG = 0x7F INS_CLOSE_TX = 0x80 INS_GET_TX_PROOF = 0xA0 diff --git a/tests/test_sig.py b/tests/test_sig.py index 0e2178d..1645774 100644 --- a/tests/test_sig.py +++ b/tests/test_sig.py @@ -48,9 +48,9 @@ def state(): @staticmethod def test_set_sig(monero): major, minor, patch = monero.reset_and_get_version( - monero_client_version=b"0.16.0.0" + monero_client_version=b"0.17.0.0" ) # type: int, int, int - assert (major, minor, patch) == (1, 6, 0) # version of the Monero app + assert (major, minor, patch) == (1, 7, 0) # version of the Monero app sig_mode: SigType = monero.set_signature_mode(sig_type=SigType.REAL) assert sig_mode == SigType.REAL diff --git a/tests/test_version.py b/tests/test_version.py index 4eeb9cf..e05a2d7 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -6,10 +6,10 @@ def test_version(monero): major, minor, patch = monero.reset_and_get_version( - monero_client_version=b"0.16.0.0" + monero_client_version=b"0.17.0.0" ) # type: int, int, int - assert (major, minor, patch) == (1, 6, 0) # version of the Monero app + assert (major, minor, patch) == (1, 7, 0) # version of the Monero app @pytest.mark.xfail(raises=ClientNotSupported) From c6084303138ab0141a9aa8ff4f208b777d2db8e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Wed, 23 Sep 2020 19:14:14 +0200 Subject: [PATCH 57/76] Fix wrong type parameter, need to be cast --- src/monero_proof.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monero_proof.c b/src/monero_proof.c index c7edd1c..9708e47 100644 --- a/src/monero_proof.c +++ b/src/monero_proof.c @@ -73,7 +73,7 @@ int monero_apdu_get_tx_proof() { monero_ecmul_k(XY, A, k); // tmp = msg || D || X || Y os_memmove(G_monero_vstate.tmp + 32 * 3, XY, 32); - monero_keccak_H("TXPROOF_V2", 10, sep); + monero_keccak_H((unsigned char *)"TXPROOF_V2", 10, sep); // tmp = msg || D || X || Y || sep os_memmove(G_monero_vstate.tmp + 32 * 4, sep, 32); // tmp = msg || D || X || Y || sep || R From c3bbd7764e9f21696a963bcc2c3eb0b0d66a9d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 24 Sep 2020 18:54:26 +0200 Subject: [PATCH 58/76] Fix wrong first 32 bytes input in clsag_hash() Assumption was right for MLSAG in mlsag_hash() but not in CLSAG. --- src/monero_clsag.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/monero_clsag.c b/src/monero_clsag.c index baa0912..4db70df 100644 --- a/src/monero_clsag.c +++ b/src/monero_clsag.c @@ -93,10 +93,9 @@ int monero_apdu_clsag_hash() { if (G_monero_vstate.io_p2 == 1) { monero_keccak_init_H(); - os_memmove(msg, G_monero_vstate.mlsagH, 32); - } else { - monero_io_fetch(msg, 32); } + + monero_io_fetch(msg, 32); monero_io_discard(1); monero_keccak_update_H(msg, 32); From a7ffd0cf73910e3ad38da213e9a6564e85eae3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 24 Sep 2020 19:11:57 +0200 Subject: [PATCH 59/76] Update CHANGELOG --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb171d..9a16def 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. +## 1.7.1 - 2020-09-24 + +- Fix `clsag_hash()` behavior which is different than MLSAG + +## 1.7.0 - 2020-09-13 + +- Update to protocol v4 to support both MLSAG and CLSAG +- Add CLSAG signature algorithm with `INS_CLSAG` +- Update InProofv1 to InProofv2 ## 1.6.0 - 2020-06-04 From 7f6524834ef620d8b300168e5807d01889c30f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 24 Sep 2020 19:24:58 +0200 Subject: [PATCH 60/76] Bump version to 1.7.1 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a2c99ce..16239cd 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif APPVERSION_M=1 APPVERSION_N=7 -APPVERSION_P=0 +APPVERSION_P=1 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) SPECVERSION="1.0" From 6a0e31ce23eb8b63ba90b6b0b69f2d08db978924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Thu, 24 Sep 2020 19:32:10 +0200 Subject: [PATCH 61/76] Fix unit tests to only check major and minor version --- tests/test_sig.py | 2 +- tests/test_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_sig.py b/tests/test_sig.py index 1645774..0313da9 100644 --- a/tests/test_sig.py +++ b/tests/test_sig.py @@ -50,7 +50,7 @@ def test_set_sig(monero): major, minor, patch = monero.reset_and_get_version( monero_client_version=b"0.17.0.0" ) # type: int, int, int - assert (major, minor, patch) == (1, 7, 0) # version of the Monero app + assert (major, minor) == (1, 7) # version of the Monero app sig_mode: SigType = monero.set_signature_mode(sig_type=SigType.REAL) assert sig_mode == SigType.REAL diff --git a/tests/test_version.py b/tests/test_version.py index e05a2d7..c6b98c9 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -9,7 +9,7 @@ def test_version(monero): monero_client_version=b"0.17.0.0" ) # type: int, int, int - assert (major, minor, patch) == (1, 7, 0) # version of the Monero app + assert (major, minor) == (1, 7) # version of the Monero app @pytest.mark.xfail(raises=ClientNotSupported) From 788e0c7f5818b5258ed219f354abae321528e156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Fri, 2 Oct 2020 11:46:12 +0200 Subject: [PATCH 62/76] Fix mlsag_prehash() do not output hash without DEBUG_HWDEVICE --- src/monero_prehash.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/monero_prehash.c b/src/monero_prehash.c index 399cb48..7e3cb96 100644 --- a/src/monero_prehash.c +++ b/src/monero_prehash.c @@ -172,10 +172,6 @@ int monero_apdu_mlsag_prehash_finalize() { monero_io_discard(1); monero_keccak_update_H(H, 32); monero_sha256_commitment_update(H, 32); -#ifdef DEBUG_HWDEVICE - monero_io_insert(H, 32); -#endif - } else { // Finalize and check commitment hash control if (G_monero_vstate.tx_sig_mode == TRANSACTION_CREATE_REAL) { @@ -200,10 +196,8 @@ int monero_apdu_mlsag_prehash_finalize() { monero_keccak_update_H(H, 32); monero_keccak_update_H(proof, 32); monero_keccak_final_H(G_monero_vstate.mlsagH); -#ifdef DEBUG_HWDEVICE + monero_io_insert(G_monero_vstate.mlsagH, 32); - monero_io_insert(H, 32); -#endif } return SW_OK; From d6765d696463c61fed9b417b2252e7c26d98da7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Fri, 2 Oct 2020 11:52:15 +0200 Subject: [PATCH 63/76] Bump version to 1.7.2 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 16239cd..6059616 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif APPVERSION_M=1 APPVERSION_N=7 -APPVERSION_P=1 +APPVERSION_P=2 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) SPECVERSION="1.0" From 9553396410d1d41dfe334125bff0f06b30fa66ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Tue, 6 Oct 2020 15:11:43 +0200 Subject: [PATCH 64/76] Fix bad display of base58 address --- src/monero_monero.c | 8 ++++++++ src/monero_ux_nano.c | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/monero_monero.c b/src/monero_monero.c index 1f753bd..61011af 100644 --- a/src/monero_monero.c +++ b/src/monero_monero.c @@ -64,6 +64,8 @@ const unsigned int encoded_block_sizes[] = {0, 2, 3, 5, 6, 7, 9, 10, 11}; #define FULL_BLOCK_SIZE 8 //(sizeof(encoded_block_sizes) / sizeof(encoded_block_sizes[0]) - 1) #define FULL_ENCODED_BLOCK_SIZE 11 // encoded_block_sizes[full_block_size]; #define ADDR_CHECKSUM_SIZE 4 +#define ADDR_LEN 95 +#define INTEGRATED_ADDR_LEN 106 static uint64_t uint_8be_to_64(const unsigned char* data, size_t size) { uint64_t res = 0; @@ -170,5 +172,11 @@ int monero_base58_public_key(char* str_b58, unsigned char* view, unsigned char* &str_b58[full_block_count * FULL_ENCODED_BLOCK_SIZE]); } + if (paymanetID) { + str_b58[INTEGRATED_ADDR_LEN] = '\0'; + } else { + str_b58[ADDR_LEN] = '\0'; + } + return 0; } diff --git a/src/monero_ux_nano.c b/src/monero_ux_nano.c index 8d3d7b6..1f78dbd 100644 --- a/src/monero_ux_nano.c +++ b/src/monero_ux_nano.c @@ -621,8 +621,8 @@ void ui_menu_any_pubaddr_display(unsigned int value, unsigned char* pub_view, break; } - monero_base58_public_key(G_monero_vstate.ux_address + strlen(G_monero_vstate.ux_address), - pub_view, pub_spend, is_subbadress, paymanetID); + monero_base58_public_key(G_monero_vstate.ux_address, pub_view, pub_spend, is_subbadress, + paymanetID); ux_layout_bnnn_paging_reset(); ux_flow_init(0, ux_flow_pubaddr, NULL); } From 7261848bde827578dc6360711655459b403efaa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Tue, 6 Oct 2020 18:04:05 +0200 Subject: [PATCH 65/76] Bump version to 1.7.3 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6059616..470b74f 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif APPVERSION_M=1 APPVERSION_N=7 -APPVERSION_P=2 +APPVERSION_P=3 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) SPECVERSION="1.0" From 6ce4d0d43a432b2dac870ebc58a6835552cf78dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Tue, 13 Oct 2020 16:07:07 +0200 Subject: [PATCH 66/76] Support of Monero client 0.17.1.* --- src/monero_init.c | 6 ++---- tests/test_version.py | 7 +++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/monero_init.c b/src/monero_init.c index 7208456..d7975e4 100644 --- a/src/monero_init.c +++ b/src/monero_init.c @@ -177,10 +177,8 @@ void monero_install(unsigned char netId) { /* ----------------------------------------------------------------------- */ /* --- Reset --- */ /* ----------------------------------------------------------------------- */ -#define MONERO_SUPPORTED_CLIENT_SIZE 1 -const char* const monero_supported_client[MONERO_SUPPORTED_CLIENT_SIZE] = { - "0.17.0.", -}; +#define MONERO_SUPPORTED_CLIENT_SIZE 2 +const char* const monero_supported_client[MONERO_SUPPORTED_CLIENT_SIZE] = {"0.17.0.", "0.17.1."}; int monero_apdu_reset() { unsigned int client_version_len; diff --git a/tests/test_version.py b/tests/test_version.py index c6b98c9..d34be04 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -11,6 +11,13 @@ def test_version(monero): assert (major, minor) == (1, 7) # version of the Monero app + # another compatible version of the Monero client + major, minor, patch = monero.reset_and_get_version( + monero_client_version=b"0.17.1.0" + ) # type: int, int, int + + assert (major, minor) == (1, 7) # version of the Monero app + @pytest.mark.xfail(raises=ClientNotSupported) def test_old_client_version(monero): From b7a2108e3682814ad06bf7eed8fd5149abb1e845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Tue, 13 Oct 2020 16:19:32 +0200 Subject: [PATCH 67/76] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a16def..e4cf660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to this project will be documented in this file. + +## 1.7.4 - 2020-10-13 + +- Support of Monero client version `0.17.1.*` + +## 1.7.3 - 2020-10-06 + +- Fix garbage when displaying destination address on Nano + +## 1.7.2 - 2020-10-02 + +- Fix behavior without `DEBUG_HWDEVICE` flag + + ## 1.7.1 - 2020-09-24 - Fix `clsag_hash()` behavior which is different than MLSAG From d9b11ff5adb19f8244cdae8fb1dec100414cb370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Tue, 13 Oct 2020 16:19:45 +0200 Subject: [PATCH 68/76] Bump version to 1.7.4 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 470b74f..c33c025 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif APPVERSION_M=1 APPVERSION_N=7 -APPVERSION_P=3 +APPVERSION_P=4 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) SPECVERSION="1.0" From 2129576169d39774c205a529b288e9e7f7d09c4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 16 Nov 2020 17:14:16 +0100 Subject: [PATCH 69/76] Add support of new CX API --- CHANGELOG.md | 3 +++ src/monero_types.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4cf660..9557ef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## 1.7.5 - 2020-11-16 + +- Support of firmware 1.2.4-5 ## 1.7.4 - 2020-10-13 diff --git a/src/monero_types.h b/src/monero_types.h index 4e46a8a..8050f0e 100644 --- a/src/monero_types.h +++ b/src/monero_types.h @@ -23,7 +23,7 @@ #if CX_APILEVEL == 8 #define PIN_VERIFIED (!0) -#elif CX_APILEVEL == 9 || CX_APILEVEL == 10 +#elif CX_APILEVEL >= 9 #define PIN_VERIFIED BOLOS_UX_OK #else From 05c9cd52347f1b24fa20a0e877bc770d589150b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Colas?= Date: Mon, 16 Nov 2020 17:14:52 +0100 Subject: [PATCH 70/76] Bump version to 1.7.5 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c33c025..f824a8e 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif APPVERSION_M=1 APPVERSION_N=7 -APPVERSION_P=4 +APPVERSION_P=5 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) SPECVERSION="1.0" From 4976be0aad7baeffcabb5050f3bb910921aaf372 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Tue, 30 Mar 2021 15:37:05 +0200 Subject: [PATCH 71/76] Support of Monero client 0.17.2.* --- src/monero_init.c | 5 +++-- tests/test_version.py | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/monero_init.c b/src/monero_init.c index d7975e4..1ce042f 100644 --- a/src/monero_init.c +++ b/src/monero_init.c @@ -177,8 +177,9 @@ void monero_install(unsigned char netId) { /* ----------------------------------------------------------------------- */ /* --- Reset --- */ /* ----------------------------------------------------------------------- */ -#define MONERO_SUPPORTED_CLIENT_SIZE 2 -const char* const monero_supported_client[MONERO_SUPPORTED_CLIENT_SIZE] = {"0.17.0.", "0.17.1."}; +#define MONERO_SUPPORTED_CLIENT_SIZE 3 +const char* const monero_supported_client[MONERO_SUPPORTED_CLIENT_SIZE] = {"0.17.0.", "0.17.1.", + "0.17.2."}; int monero_apdu_reset() { unsigned int client_version_len; diff --git a/tests/test_version.py b/tests/test_version.py index d34be04..8d592ba 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -18,6 +18,13 @@ def test_version(monero): assert (major, minor) == (1, 7) # version of the Monero app + # another compatible version of the Monero client + major, minor, patch = monero.reset_and_get_version( + monero_client_version=b"0.17.2.0" + ) # type: int, int, int + + assert (major, minor) == (1, 7) # version of the Monero app + @pytest.mark.xfail(raises=ClientNotSupported) def test_old_client_version(monero): From 11df3740d143cb629de087c9fb0a9ff74bf307b6 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Tue, 30 Mar 2021 15:37:56 +0200 Subject: [PATCH 72/76] Bump version to 1.7.6 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f824a8e..a7069e7 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif APPVERSION_M=1 APPVERSION_N=7 -APPVERSION_P=5 +APPVERSION_P=6 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) SPECVERSION="1.0" From 77b75b2fb572e02b9f2903c05ff033a05a67c19f Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Tue, 30 Mar 2021 22:39:45 +0200 Subject: [PATCH 73/76] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9557ef2..e7aaa17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 1.7.6 - 2021-03-30 + +- Support of Monero client version `0.17.2.*` + ## 1.7.5 - 2020-11-16 - Support of firmware 1.2.4-5 From f756b2091d0c423e0fef3e2bf0a9234fa5978cb7 Mon Sep 17 00:00:00 2001 From: Edouard Merle Date: Tue, 1 Sep 2020 13:53:22 +0200 Subject: [PATCH 74/76] add cx and ux missing includes --- src/monero_monero.c | 1 + src/monero_vars.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/monero_monero.c b/src/monero_monero.c index 61011af..783f21b 100644 --- a/src/monero_monero.c +++ b/src/monero_monero.c @@ -17,6 +17,7 @@ *****************************************************************************/ #include "os.h" +#include "cx.h" #include "monero_types.h" #include "monero_api.h" #include "monero_vars.h" diff --git a/src/monero_vars.h b/src/monero_vars.h index 6d047f0..4b70388 100644 --- a/src/monero_vars.h +++ b/src/monero_vars.h @@ -21,6 +21,7 @@ #include "os.h" #include "cx.h" +#include "ux.h" #include "os_io_seproxyhal.h" #include "monero_types.h" #include "monero_api.h" From f4a7e3596a9eef32e4cdadcdc7b1f1527942e1ca Mon Sep 17 00:00:00 2001 From: Edouard Merle Date: Mon, 30 Nov 2020 09:33:19 +0100 Subject: [PATCH 75/76] CX_APILEVEL >= 9 compatibility --- src/monero_types.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/monero_types.h b/src/monero_types.h index 8050f0e..febb84f 100644 --- a/src/monero_types.h +++ b/src/monero_types.h @@ -24,7 +24,6 @@ #if CX_APILEVEL == 8 #define PIN_VERIFIED (!0) #elif CX_APILEVEL >= 9 - #define PIN_VERIFIED BOLOS_UX_OK #else #error CX_APILEVEL not supported From cab07948de2ed64b5bff868da07d317356b639a8 Mon Sep 17 00:00:00 2001 From: Edouard Merle Date: Fri, 26 Feb 2021 10:58:23 +0100 Subject: [PATCH 76/76] remove useless algo assignment --- Makefile | 1 - src/monero_crypto.c | 1 - 2 files changed, 2 deletions(-) diff --git a/Makefile b/Makefile index a7069e7..7745499 100644 --- a/Makefile +++ b/Makefile @@ -159,7 +159,6 @@ CFLAGS += -O3 -Os AS := $(GCCPATH)arm-none-eabi-gcc LD := $(GCCPATH)arm-none-eabi-gcc -#SCRIPT_LD:=script.ld #LDFLAGS += -O0 -gdwarf-2 -gstrict-dwarf LDFLAGS += -O3 -Os diff --git a/src/monero_crypto.c b/src/monero_crypto.c index c1397fd..8776fed 100644 --- a/src/monero_crypto.c +++ b/src/monero_crypto.c @@ -158,7 +158,6 @@ int monero_hash_final(cx_hash_t *hasher, unsigned char *out) { /* ----------------------------------------------------------------------- */ int monero_hash(unsigned int algo, cx_hash_t *hasher, unsigned char *buf, unsigned int len, unsigned char *out) { - hasher->algo = algo; if (algo == CX_SHA256) { cx_sha256_init((cx_sha256_t *)hasher); } else {