From ba44de3cfdcfa9df347622425cf828ef5c32f7d8 Mon Sep 17 00:00:00 2001 From: Vexta Project Date: Sat, 5 Sep 2026 21:22:13 +0000 Subject: [PATCH] Add post-quantum wallet support and PQ transaction signing --- .gitignore | 2 + app/build.gradle.kts | 13 + app/src/main/assets/pq-core-mldsa.sig | Bin 0 -> 3309 bytes app/src/main/assets/pq-core-sphincs.sig | Bin 0 -> 7856 bytes app/src/main/cpp/CMakeLists.txt | 57 + app/src/main/cpp/pq/mldsa65/LICENSE | 7 + app/src/main/cpp/pq/mldsa65/api.h | 98 + app/src/main/cpp/pq/mldsa65/config.h | 27 + app/src/main/cpp/pq/mldsa65/fips202.c | 774 ++++++++ app/src/main/cpp/pq/mldsa65/fips202.h | 57 + app/src/main/cpp/pq/mldsa65/ntt.c | 98 + app/src/main/cpp/pq/mldsa65/ntt.h | 13 + app/src/main/cpp/pq/mldsa65/packing.c | 237 +++ app/src/main/cpp/pq/mldsa65/packing.h | 38 + app/src/main/cpp/pq/mldsa65/params.h | 80 + app/src/main/cpp/pq/mldsa65/poly.c | 907 +++++++++ app/src/main/cpp/pq/mldsa65/poly.h | 79 + app/src/main/cpp/pq/mldsa65/polyvec.c | 389 ++++ app/src/main/cpp/pq/mldsa65/polyvec.h | 93 + app/src/main/cpp/pq/mldsa65/randombytes.h | 15 + app/src/main/cpp/pq/mldsa65/reduce.c | 69 + app/src/main/cpp/pq/mldsa65/reduce.h | 22 + app/src/main/cpp/pq/mldsa65/rounding.c | 102 ++ app/src/main/cpp/pq/mldsa65/rounding.h | 19 + app/src/main/cpp/pq/mldsa65/sign.c | 477 +++++ app/src/main/cpp/pq/mldsa65/sign.h | 56 + app/src/main/cpp/pq/mldsa65/symmetric-shake.c | 28 + app/src/main/cpp/pq/mldsa65/symmetric.h | 34 + app/src/main/cpp/pq/pq.cpp | 37 + app/src/main/cpp/pq/pq.h | 30 + app/src/main/cpp/pq/sphincsplus/LICENSE | 2 + app/src/main/cpp/pq/sphincsplus/address.c | 104 ++ app/src/main/cpp/pq/sphincsplus/address.h | 51 + app/src/main/cpp/pq/sphincsplus/api.h | 77 + app/src/main/cpp/pq/sphincsplus/context.h | 28 + app/src/main/cpp/pq/sphincsplus/fips202.c | 598 ++++++ app/src/main/cpp/pq/sphincsplus/fips202.h | 47 + app/src/main/cpp/pq/sphincsplus/fors.c | 161 ++ app/src/main/cpp/pq/sphincsplus/fors.h | 32 + app/src/main/cpp/pq/sphincsplus/hash.h | 27 + app/src/main/cpp/pq/sphincsplus/hash_sha2.c | 197 ++ app/src/main/cpp/pq/sphincsplus/merkle.c | 61 + app/src/main/cpp/pq/sphincsplus/merkle.h | 18 + app/src/main/cpp/pq/sphincsplus/params.h | 5 + .../params/params-sphincs-sha2-128s.h | 85 + app/src/main/cpp/pq/sphincsplus/randombytes.h | 15 + app/src/main/cpp/pq/sphincsplus/sha2.c | 700 +++++++ app/src/main/cpp/pq/sphincsplus/sha2.h | 43 + .../main/cpp/pq/sphincsplus/sha2_offsets.h | 20 + app/src/main/cpp/pq/sphincsplus/sign.c | 293 +++ app/src/main/cpp/pq/sphincsplus/thash.h | 13 + .../cpp/pq/sphincsplus/thash_sha2_simple.c | 59 + app/src/main/cpp/pq/sphincsplus/utils.c | 154 ++ app/src/main/cpp/pq/sphincsplus/utils.h | 64 + app/src/main/cpp/pq/sphincsplus/utilsx1.c | 100 + app/src/main/cpp/pq/sphincsplus/utilsx1.h | 26 + app/src/main/cpp/pq/sphincsplus/wots.c | 112 ++ app/src/main/cpp/pq/sphincsplus/wots.h | 25 + app/src/main/cpp/pq/sphincsplus/wotsx1.c | 73 + app/src/main/cpp/pq/sphincsplus/wotsx1.h | 36 + app/src/main/cpp/vexta_pq_jni.cpp | 345 ++++ app/src/main/cpp/vexta_pq_randombytes.cpp | 93 + .../org/vextaproject/wallet/BlockScanner.kt | 32 +- .../java/org/vextaproject/wallet/VextaPQ.kt | 39 + .../wallet/VextaTransactionSender.kt | 783 +++++++- .../org/vextaproject/wallet/WalletActivity.kt | 1618 +++++++++++++++-- .../wallet/WalletMonitoringService.kt | 323 +++- 67 files changed, 10075 insertions(+), 242 deletions(-) create mode 100644 app/src/main/assets/pq-core-mldsa.sig create mode 100644 app/src/main/assets/pq-core-sphincs.sig create mode 100644 app/src/main/cpp/CMakeLists.txt create mode 100644 app/src/main/cpp/pq/mldsa65/LICENSE create mode 100644 app/src/main/cpp/pq/mldsa65/api.h create mode 100644 app/src/main/cpp/pq/mldsa65/config.h create mode 100644 app/src/main/cpp/pq/mldsa65/fips202.c create mode 100644 app/src/main/cpp/pq/mldsa65/fips202.h create mode 100644 app/src/main/cpp/pq/mldsa65/ntt.c create mode 100644 app/src/main/cpp/pq/mldsa65/ntt.h create mode 100644 app/src/main/cpp/pq/mldsa65/packing.c create mode 100644 app/src/main/cpp/pq/mldsa65/packing.h create mode 100644 app/src/main/cpp/pq/mldsa65/params.h create mode 100644 app/src/main/cpp/pq/mldsa65/poly.c create mode 100644 app/src/main/cpp/pq/mldsa65/poly.h create mode 100644 app/src/main/cpp/pq/mldsa65/polyvec.c create mode 100644 app/src/main/cpp/pq/mldsa65/polyvec.h create mode 100644 app/src/main/cpp/pq/mldsa65/randombytes.h create mode 100644 app/src/main/cpp/pq/mldsa65/reduce.c create mode 100644 app/src/main/cpp/pq/mldsa65/reduce.h create mode 100644 app/src/main/cpp/pq/mldsa65/rounding.c create mode 100644 app/src/main/cpp/pq/mldsa65/rounding.h create mode 100644 app/src/main/cpp/pq/mldsa65/sign.c create mode 100644 app/src/main/cpp/pq/mldsa65/sign.h create mode 100644 app/src/main/cpp/pq/mldsa65/symmetric-shake.c create mode 100644 app/src/main/cpp/pq/mldsa65/symmetric.h create mode 100644 app/src/main/cpp/pq/pq.cpp create mode 100644 app/src/main/cpp/pq/pq.h create mode 100644 app/src/main/cpp/pq/sphincsplus/LICENSE create mode 100644 app/src/main/cpp/pq/sphincsplus/address.c create mode 100644 app/src/main/cpp/pq/sphincsplus/address.h create mode 100644 app/src/main/cpp/pq/sphincsplus/api.h create mode 100644 app/src/main/cpp/pq/sphincsplus/context.h create mode 100644 app/src/main/cpp/pq/sphincsplus/fips202.c create mode 100644 app/src/main/cpp/pq/sphincsplus/fips202.h create mode 100644 app/src/main/cpp/pq/sphincsplus/fors.c create mode 100644 app/src/main/cpp/pq/sphincsplus/fors.h create mode 100644 app/src/main/cpp/pq/sphincsplus/hash.h create mode 100644 app/src/main/cpp/pq/sphincsplus/hash_sha2.c create mode 100644 app/src/main/cpp/pq/sphincsplus/merkle.c create mode 100644 app/src/main/cpp/pq/sphincsplus/merkle.h create mode 100644 app/src/main/cpp/pq/sphincsplus/params.h create mode 100644 app/src/main/cpp/pq/sphincsplus/params/params-sphincs-sha2-128s.h create mode 100644 app/src/main/cpp/pq/sphincsplus/randombytes.h create mode 100644 app/src/main/cpp/pq/sphincsplus/sha2.c create mode 100644 app/src/main/cpp/pq/sphincsplus/sha2.h create mode 100644 app/src/main/cpp/pq/sphincsplus/sha2_offsets.h create mode 100644 app/src/main/cpp/pq/sphincsplus/sign.c create mode 100644 app/src/main/cpp/pq/sphincsplus/thash.h create mode 100644 app/src/main/cpp/pq/sphincsplus/thash_sha2_simple.c create mode 100644 app/src/main/cpp/pq/sphincsplus/utils.c create mode 100644 app/src/main/cpp/pq/sphincsplus/utils.h create mode 100644 app/src/main/cpp/pq/sphincsplus/utilsx1.c create mode 100644 app/src/main/cpp/pq/sphincsplus/utilsx1.h create mode 100644 app/src/main/cpp/pq/sphincsplus/wots.c create mode 100644 app/src/main/cpp/pq/sphincsplus/wots.h create mode 100644 app/src/main/cpp/pq/sphincsplus/wotsx1.c create mode 100644 app/src/main/cpp/pq/sphincsplus/wotsx1.h create mode 100644 app/src/main/cpp/vexta_pq_jni.cpp create mode 100644 app/src/main/cpp/vexta_pq_randombytes.cpp create mode 100644 app/src/main/java/org/vextaproject/wallet/VextaPQ.kt diff --git a/.gitignore b/.gitignore index 93eef40..261b1d9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ local.properties *.keystore keystore.properties app/build.gradle.kts.backup-* + +app/.cxx/ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b4dbbe8..9446936 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,6 +17,7 @@ if (keystorePropertiesFile.exists()) { android { namespace = "org.vextaproject.wallet" compileSdk = 36 + ndkVersion = "27.3.13750724" defaultConfig { applicationId = "org.vextaproject.wallet" @@ -42,7 +43,19 @@ android { } } + externalNativeBuild { + cmake { + version = "3.22.1" + path = file("src/main/cpp/CMakeLists.txt") + } + } + buildTypes { + getByName("debug") { + applicationIdSuffix = ".debug" + versionNameSuffix = "-debug" + } + getByName("release") { if (keystorePropertiesFile.exists()) { signingConfig = signingConfigs.getByName("release") diff --git a/app/src/main/assets/pq-core-mldsa.sig b/app/src/main/assets/pq-core-mldsa.sig new file mode 100644 index 0000000000000000000000000000000000000000..2511d9b81e2b280029bd434ef2f6d009dc073fda GIT binary patch literal 3309 zcmVgkBN7j1Eu25ZC5nIw3D(DgG7jHr|XKsz3OX@d0yPuYsi43 zM&3d7Hv1pqQ91~C%%&aPd~`2g=^kgPq#D!5s}uj1hu7J3#0GR33}JoHm95_x$$rO3 zh&b#522vNyAkjyP+;>j!%YNDKzEE$~dV}}a&8%w2{7wzuC0RydJ^hU=D2};+JMQW? z+-W7e|0)L9AsqX=+BcJCx1iI2eR^QM2r_~!9Q|yvIjr~JPk%1~ZM&9uX)xyI%I-P1 z38^~=+Z-;VExPBtApYbtq>vGQV1WGwhH6Zxv%_#aD zkWdt)-1IV?tuZ~shV5V|=;@Qtj2z)LrAu~UWA6cL@5u~Gt4D#B>m9r#k}Twhoea?U zr zL)w~+M8%H_X*4|oVVZnwh=nPNAEto9w~UrSZ4wDfi+lSJ)8iRd*$-A3Ov6o@R$e#7 z?YaYl=N{#Kl&YhDSz>N?{I{!i`P1U$FE=z_#_<-_HQiyV=rgiH)Q07obs?CV=meBq zJL@Yhcx7Nzpp#E8DUbbeYo*ylAUBO(-fh(FDgO>f0U!c#Dfxirkt1eogQHR1ZO%hZ zH~!pja`4u8<|r$Qpu?SZJs4;5lOO!Z7=Xqd4xb3(AQV7lJrNDKYF7$4z4Vqhjre-% zgt0BBj2xhaW?-g0ypKM^A9YjLtbHygARMm(RLoFIHRf^oONV@mlyp#I6DuY*3gAGE zr?xBe8C)ienGr|ke(w-xDKnx*_`L_{>VYULWG&IWf$eVrXgw93ta|e@S$-=XGNV>W zb$!V%-NkHPNFHp~Gh*&2u#9xDvvwxq_Ux#F{3S#OU`Term*X*l&=wVbSQecs7y!os z-J#?|P$d;SB$}koPlfZL9WwM1jp|9v=?a=97d=_Y$c4Hy-QwwT?Le*T8v`H#R(oaC16mQsV3e;mzDXQkOZ`~o)R zkFoU~>}x45ep$Nuw-24pf0Z4NU8ot*DH#0?s4q>&9%ZwM+-sEU`qL6)00pQteE84P zg~d;w@y-L~SLDFJ#;I%M)o#@jn6U?tS}h0Gp2dA1-Xwqozk!Gc?axvLnr14pEtqiY zbQUi5eL=~pxkpZ7ap#Xj+RtA0@dijE=euvD{LN_vE4M^^tM`cyZ*hiH;gGi>4ZZ zV)*w=AWlxaDRKWrPnHvMTGc`QfRdF=qtooh74%X*79ESIm)vjJVB|jxz_)zYry)uL|XV3ngRp zd4sHweOY{ymmpkiP>Xj{7hjZ>i=~f)10RJPa*}L4k7)wsoJ~VD-U4n_-P?M5-#1uP z%Yl%a`TO~L?6G~*H{FIB`p9GR!D_jJGe7|PX$5$kzA6c76&6_ro9>+A&ihNC5E7?- zqhHA#*1S!c81DFz%U^#Cs>FzjkcrRu%143=5rVhsot0Vqc|=+1jB-e{0@PIu7YEEJ z$#_Y#6Ehvc>cX5_i7m(x3nlw4Rn3sAf?-Yj?B}*wl30Q$!+d+2^2AcJqJBc)6B`x;_Na1vM^ zQ7uC#@*c;%NhVy5PfiM~NqhjBUauwS@AYoou%#q%;ZJ;Wa7XbzUWpA(r|18wRwTl& z2S19VK~z=cEx1nqJgv_9YfdwEHufaN?SbvJs5QZ7@8GpT1umXb;`jfg;{k5C{$@82> zRD}(hH+)^(Xmo|3VpW>IAi^E$$)yYMP6`0m9cj~4zT&%4NeVPsy|KcZ%Tu_K89tDT z7v`;rLIb?`mqxVJ*^_<@Rp7^S79m*Uz)NgOq$*o>BNSO40c+V25s0ylF#5svOK5hG zI-=zk-P<6-*&I0PRCSbCfRq^TiZbymT*db6XXQJQr$DejpU>RL<&aoSHGPtrB%FLd z`@W>7eJWR7^WWQ#_^EC4G*j0ubeMR(|BO{~t;)Vvo_KP!5=NM~O~fqkC&0D3oH76t zam`FE{gCx|COfqHZS)HW02@Yvc+!+D%P)`j{0O+%r>-hZ;KNh?KA~d0;rd@4{h}2? zQVk$YGRw@O&bAO)gMPvyf{G?AlQi)I>uu~oMC;n++wA1--?8TKXg zGD+}g?$hPQ!q@-;vix^+02u9j=_m0h1oZp@aP4>aX^|9FzWn_Y(b>Juzs8R3Wk01T zU%-sz)-O}x-If{TTg#ZSH$(kCLQ6{{J?FTTEAumcF1+-6ptoMAVSg%mC zN4@Zhr%HSkM|&)#xpy$;aWZISwS88*s!oj@Lb6L46|iSfQUgu$uKhwSW$MekY35U( zt<6nC@(lT`gl^@HMkA-!uHlRi?XfDHPULK<0zx`_zBG(MzNrcQDG*2yCD*)HB;M6l zsa?80Ea>+q5?SM1`P*tS10HVVz^+(AcL+8Uet6UjOrd8xN0H96*X{&Aua`{Yp}moR z-Etl(K;A$YR04XNal-JzztC5M53TYIy3?eEYOvTZmDVn1XFLEpw=V||BK5|`_mdM( zfgRKn6e1hX?y%reGI{Ob6ehQN?Y<>D#8F7&YR}>z08K-VVhjZ%>e^`2epNgJB~iNr+m2H)Rxk&v~)+vnsrQA^Mgw0G>l;8>jp6O)&2ihqmJ&nE;kig47=x>O}LM z!9fjzYNG59b0oAb%y8*%=ex@QxMR;|fr3!bGd3&{N0?jQgyLFf7mHo?xf>)!g4h$hquLgtiXW~tXrDugd0vs^qz=iq0 z&C0Hz)Wqr@6KWd>M3@FfB;4oa(BqMFGFQ-7Kt00000000000000000000000UR7aScUWBhJ8 literal 0 HcmV?d00001 diff --git a/app/src/main/assets/pq-core-sphincs.sig b/app/src/main/assets/pq-core-sphincs.sig new file mode 100644 index 0000000000000000000000000000000000000000..15317594b67a5b65aa400345ad8a1b6494d51391 GIT binary patch literal 7856 zcmV;h9#7#BL%GKTTO~~AxV(K#DZKpcLCWzg@QL*g;LAIwAKQ=ktiP_Y!fPU-Ws+ax z!06UtS%&5>G6(2;$Ku%JT;T;{!bYgorhaTnMh#2|nWL|X4u6ZYFAR4AMtoT$176jP z(dCqWTcu}bNS6?Z(G#6U#_n@QcqgJOb8Un559lj$CzqbwG25r;ZK{UuT?VTy;N6>~ zou4OKn_8BQXmKJ{@4lsdLo5Nbw#IxA_6|q!*R2L`5oPs;GJ_2n^9inF_%}5->~?L* zMV#6?_S*8rn+Fn1=Dqh0y6^wxeu@=#E zIKp@np-YraMfOZcnmW)T>IpJG^No{HLn!*|Ua}j)2M5FBYxfkm6vx4|@eX%GmTL}; zAZ{BI){wxu!>rfJ2M-J^I?Txyj|fRb`N8}nlyi!m#AIkc;2F2)0MuudP1#@gY?+ou z4v-Rs-s89-5RLZYR-Bf0Q&Qr^QnTu{oLKg0KteJ@qi6{{El5`Xbh0d`uNp`$D;e&a zD{ktl5fd=|v+ijh;>s=jvgZ8qWmitdw~8QL=kUm}wvJg62ATOVX*~9BslSfQy%dvO zmZ(nydTEboHBW~XDiQm}+_^fiK44}?ysjDY3`W`3E2Wniv$Yho&Zc|?^`kL%p|OR+ zBVmIyFip73PkewsGZd;I%J7Ce^5&Nuz36siSx6R9F&Jrj}4 zJdBQ;&YR%rwB`lx&qdNQaQnX5EAHu)M zlC%#?Mn&w}R3IXm38z1`|97pzOJ8K)!Z|-kh_AiBXu;fHW}^v@NfAF~vX-6X`p_Yj z?mc|;FNL4#$Hva-&XUG(UdgsfmpXL$=B|PjS@Fk3n=xNFRqa2@kbB8h6oky{~VAq%?L|6*BaPXl1eSQvIPwcg)$j9EwX zep@!pM7zyqwjG)=J7yyJ5NhWV)%E}Nr}8w;fRLFut*b}O<0sy=omX1n|IIY%rhOL- zqa}Up4i}n2(5&Icj5sBobB0s)^p?JnxaU9FAQ4rMp22vV#xJ$ft~680tlBVZeNm+P zRMcae-jZ=`ralT>f1>YSt2qU5Yg9Xgwf1$-GO9rks_@jO`*^rJ*JUzp4E-sKqMzd2T+D4I&wQB0B|aYOaiAWlt+Ug(%uBc%ubkizebNYFiBQr0uE@etJHV{-f5y zIsz#K0sjX&s6C_o$Uylz)G}uxf>)a!MvUzRqE!Z%nHFGZ%E0qymNSTEZ@37{u(Le6 zmYsr{m8voh(lgipw@tXraR<*C{;EwA)i1G6yAfDdkh~i=Jpk%3qI;H&mH{5;O6?MN z8#ovzIQf`}PUi*osBqhx4~(wWPJQ4Vz;CRPDgGrlt@h!b1Y#qJj1VShb`(s@K_LPs zu(7zF);o=vcU@{OA&XJ)tWL3m-FyZSX{R9&B;jBp*r|X>YOdpBWkSmc1%oWCN&ST) z_#JFD?W*V>)y+#o^<WAm$cM)loZ3|g#x-9UY1iq{|YE5jp$hPLL zeXFM{qA|p1=(si#x0_u$3r9rA{K_(GgNPk&18ciDztI;-eOkoT1{{f$Q?;6)XR2K{FVb&| z{NE!W>01kDGWNT4D z@)K&uB`_etzmIymZZ1-I8200H1Z=sy_?R+l>oZcP_kuTx#Iqb~5BSsIAtTi)kP{48 z4@P4P>`sFyLN}s(L_s$OkbZd{lfD~_mu&Bvvcba1EQ5ehj2#etMJl_kIUq!^t|iiS z*(Dt0@#`{r^JU{ti5h%3NVpwoyxp2uOtJ$YL|_26K?*#9r^Ev;^)_^4F?A>jWl4>0 zP%7W$>(9pxGfOM;++pm!sv)N1(5He6jE6q{xu1}s43fGQ#57zDXgot^r;h9E(9oDP>Z8jbcsXu;{ z+gDc+rR?-Ki)wNm0McLqLMv)-<0;K>Gh+T5du%Bb?i*yXrqMhQ$lF!>!|?u=V0E<` zqo}wp5_AF?#&ffWgda9Q?-0$hi5F;`^S9*|M5X)iDgR)*X^Qdc6e8KXg-$Prr@Kq? zidEr4 zF%JQkCmiyAqr-`_EISVxrqxR4!aj zZ{q+ob4iM0XV;OQ7pO*EP!G=hQ6=Y|F!6JUwt+fx{MmJKntOt+FpXI_ivOrW@LFe> zwlb_Q0HUzbf!DYL{PMG85qA|uSNZSxh?!cHrK4-`j4V}gBTy4|PqY$B7lD7p@Jx8E zLMP_8j{;ZET@Hnz{qm*3O6i?XDBPIT^KD{ofR2s0xL1aF-|@WhEo=!$+L3R2UQN7j zSdh~dLnfOC5bw?dLi|Z`uPfIFyVg^mAjkHviVN!Cn&pk}>k0UBCWgb_AW5oufGnND zpASGzV1x_Bjs@;@N!=wBO4h za^@V2q1G?L8M&K%&s{;}i$Tv^Giq3qsk6Iz6wYo*dF9VJI{gbRP zVD1T1Tv$Ve?`eX&BYch>DC*AHm0081o9?%Fc^Fp=5;9{yLRMXvv)82lqG@t}+Hs^e z$8?nGU&uQTyDY&A%)rtSFm8#=Aje+PD*d3gf$$&?!rgS3^b|v=dq7dS@wEz+1(e+* z*Ml9f>u-OFWL;JtyODaqek?xMRzL@8vO!H==b^h@$ zSLd70saLY(@wojvQ1@#y^l6u7Y<@p(Cq44vpOpvF@*kMYMY zSHli!3{GzV6T!tls>Y9`gUknCiP-#c?%!=G_9^&Ct6GiON@9hSor>w(%sJIg5vI1 zKV&FVX5AKyi-$39q!0g8z?HD@lG%y}#!!5S()ft5mvU8KL~#nx%a{$zh2@>gGjS4R z$xSSq(+F@yKxKObJI`il>jwPRnbKlU(nG9!-GE2Ahx*fxCR~;P;hl6;4FMD*^dR9< zGV9y{E$X;$-bi|BBXv896uDRyrAc5mX3gym>Mj+L+U5*mPMbirB>cKWp790J_(oU} zB(KE_yK8}>O6x;URyKf%GBu=0DN6qWSfFw^sw-s1-OTV77;}paJr)gdrqv(KG)YM!)qFh&-Pgc~YuAugHQCT&0d$ zT+XG#`UMigb@TX~-1lbfw`b5XtVN$~{NkP}Kz@1)|Cj1v&2EUYJYb$wE)MEvelb%E zMzmWiPqD@zZLwouCs8Q|2VKnH#X8Vm+s_ex+gXuuMB;AD9_Z=N3j7q6-g_OuY*mh+YoyZxjG{}(G6Z^@&)xJtfB_B3ShxlO zH2Qa3L7}-it2|%!qvv6-0!{=_OH*XI%ve6sY!9T7nQxZB=Ca{IQxn;xR1*A_rKk~C zHDlUl;ajkC$E&p~G~+=}lg>^&{o6+`Whc?Mh-Qj)G{x0a29J*pLg7TM@`zHWB^3{c z!vPd?hcETHNdSJ44Se{6;LW_R^KOa$@ZJ$GgI_FqA!7uT-lGo7aD>wRBV`bhI|r?n zRT2UNd{Vb9;uqP9=K<-BUrBtF%!;vT7f z_varJV+@(4I+dA#B<`NMZ!)%)II?b< z1Ag-!eGtQIma^u5L{|6Ea&d3ECD{7as6Ju8f@>4-2fz8H3(w-nwC{ViP2Tg0j6%)3 znIbTi)_pa0(C}Es>7~;RzjN9xvc&S~nOJMS8{h<_N>O}oL*$*{LCf06CMO-()UYxg zf1qwz`d746W+|9O_H0S!%U_v|UG}~(NeBQitDC!)$*@X7s8>NW1J zC2=0tdyR*wlT1nveB~hWKzK;YPbTo!Sh#>X{<`Gyb!$BV3HSkBM zQ&GXWRb1325>4o?v~S9H-n$enN37Dk?@cVZ9|_Q#>2@@P6ClBWESdR+`ssN^Es;-d z>y_-9xsH=3xtJV zG9Z-0nF=-*7lm=pfd8B|6e}4?hlK!CqG3@x^vaL@nUH8Mo994XX#7Whz>AfK`$a+4 z@t|>tRh?!@u5x-xk(ksLr~w#V9pLY-S}}cqU4d|8$MVYuzH6aj4Ur_uci|4}X_f{+ z5d?P4p7_5oLT7IYt!Eg24uxRSjx_=qgnkBr%YqTAoeNQsi#HR8CeeMB|6uB@6xXlm z*lGe~lghW30O+F+;uq>*3$;^cgzuRtFa>~Hqu>O7R$?z1i>|9oFU2;2#Fnj_f*9%* zC!Ds!W)Kia)p#fVc0_Kabp>1Z?rNAr+2%!uz|%2-Jc2|*orD0qw&%f98Qlg);1Q)q zv50>0Z0(-FYQZ@h7PdP}L=J>+u`I%gXJ|?N@Ugukq8H`@Pj)eU{(|4n06HAj4R#Vk zkg-!Gu1WS&758_Ib7Vgwi*5DTx@}?IqB@T39YlT@aR=ycFcpt)5PEXm@+c?D0om+6 zwE2@NsiDqo=4_f=Ahz-PpdN7QJIGOOgjflMLIfs9lAcxH|3#2r`_5YVk6%9lO+hD3 zCr;&@s9ECzBveck#9!)%DN~=Y$j}bM1U)=Av*!1HON2te_*+I$q35eB=V}h$L|Vs4 zrAoz#gN~r(!cdhl{IYxpRPHYB5N8Q)LYF()HCKz@a|de?pnhw9`e(J)FlO)jX(HE$ zlNm%VX{;sC#zJt!s=A$lc8A$b!%A!d!ps}kwyb3|!q4d6)`!srX6tU?+uqr>m#V2W zQ7losggmXrkz2fGu_+JsbRO!!%c+c4K2{TCK&Afg$K#&&_{e-q=KhnBgna|BgG)_` zM+flwRnVf^yc_K`)k*FYdK6gUO*GmV&|=_NYK=*k7QF)rMpVV*AU+^kneWm7BE`IPw>I0;QxY>Z^q^sG@bW3&tN~X(oEzUXtgP z^2{Xbp2cwU=qMYcg$j}Q$Zx$ozT*B^v`zir=l^Y+eJ3iN$4orIZuR+OJQ|5*cY3SRcE1e^0@T`@{@fw(XUz6ptj z*=b1)0}c54m~E#O#VH4qP)%M{e|#nkB(t&|x$~`=Lcp&Vz1!@p%E$>@Ry&C*)bvvP znJ}z(2hBh9nVghHzb#GvL9_g2jCWJc=$N&8<5m`W6Pv+0&Gj9g@rfxC$;&{3F9FRh z+%2Mf&Jf{qBM)X*zjJA?O!Cc~L#CR+QNAEw&?29v`V?yj=MIq+htfJ4q*-1_0MQ0}DSvZfhe zDm7y3WIb1eegcju2pwW&yj~Q{(Pc)iF~0z4lAZrXuK2583O_j(_n3CT8>Y-EBg*R< zXLjFUD%yAJ-OD2jx&oqokfxg`%Hal!&-qg9SdRAm76)MztdeS( zd-1K@3cI>=#_~qNljjN*#WBO!g1N+;J&QXtyY8se|x9}tU9E?(G>73wLl-{#}X}1!biy_xYXp$0r=RFmGtp106wXuTMc}YVMN0$JaUTgwaFmQN;1wCHm9p zT1(b#^<_=0o;d%LY?T;IL(K8|hynCz{|pT4(gqnQLr?gY@mt%$q(0qNZ1Hayh<;+D z)ojmUIN+%yLSUVVel-=`V>9GQqkqsrhPl3MTH(< zJB~#X3U|is(5wrnjyDO%WZinBGKPf<)6q(KR=}ukiUCOYVJZSz6XBk$vK3oozKCf( zr51;&w94+^5Jpc0l>5ga^zaM6N=CV99y_iFjnon%U>9L7V+3FJ8yOPY8A~PJnyFjQ zflaU_*gEd9@WI9^ov@Oox^bW{47XdhNQSCFR)ri8%Y+pHwlYW64k+(fWA;nd>d*g- zHVzYZVL~0BzmxNfEq~@@%Xe@;3W(fvsk7Bx9=o!{bnH(;j%yOOz;l7E>g6m+*zZsB zz!sF;suZS%`=*;({vayulICq{m^`LHHMbiy@takEH)))^uc^`w-oZ(L2Tz1!Km$=R-v zujFaF&>Cb-QKI58cvlFgjC)?XKqJJQ_}J-IFX5{TD>!N34Bi{7{SFvXQac-OtkV`P*6;b+evcN_#zP$uvw2=tcvQe6d>`qdE&(||gcw2nJi z`Tyb-m-Bxk2y=5l^@62-m)$oD0a%GfrBt09^{uPn;Ke|>?7uHO*SPo}U&4OJX%~7~ zp)i93(s~(~Z!BfBM6-D=)Et2ZV5sH7x$D-~zkD(6ipC@PMtbimAT|>X`3Ia99CvsA z72zTGJd%&POyQks6sA(*LQ}5@4*`G`lpN)Efs%2qh)tojn1F~0$WvPV(qlwZtO|`h z0QXIK7wz;5SZaXPrnHX%N^L;Bm+2QNk`g3RdlA_=>HJX>T+)jQ*%bP!AYCoqQ)`Au zVRLFukPEFNRC|U<7jlcG?rH#YP(C3hve5I@e~RbTSuaW}-t*WRngA05QX9@mcp z>s(C)sqA@;0si1nwN`nX>O*EdCLf401BQi13h6JcWm!hMadxm;twU8pPdSX-|Bv{# z;byk9!a_fk%0!Af`KJ)#XigTuIE`ikxdZ9g7YCsGkw|uB;&ScKf{;9?9)(VvVi!D& zKHsN~pSjiA2IhhI(mo-VYI&wiM=12Ojrsq OB`MtQ0i=$UjjBEYJv1`_ literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..4500d34 --- /dev/null +++ b/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.22.1) + +project(vextapq LANGUAGES C CXX) + +add_library( + vextapq + SHARED + + vexta_pq_jni.cpp + vexta_pq_randombytes.cpp + pq/pq.cpp + + pq/mldsa65/fips202.c + pq/mldsa65/ntt.c + pq/mldsa65/packing.c + pq/mldsa65/poly.c + pq/mldsa65/polyvec.c + pq/mldsa65/reduce.c + pq/mldsa65/rounding.c + pq/mldsa65/sign.c + pq/mldsa65/symmetric-shake.c + + pq/sphincsplus/address.c + pq/sphincsplus/fips202.c + pq/sphincsplus/fors.c + pq/sphincsplus/hash_sha2.c + pq/sphincsplus/merkle.c + pq/sphincsplus/sha2.c + pq/sphincsplus/sign.c + pq/sphincsplus/thash_sha2_simple.c + pq/sphincsplus/utils.c + pq/sphincsplus/utilsx1.c + pq/sphincsplus/wots.c + pq/sphincsplus/wotsx1.c +) + +target_include_directories( + vextapq + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/pq + ${CMAKE_CURRENT_SOURCE_DIR}/pq/mldsa65 + ${CMAKE_CURRENT_SOURCE_DIR}/pq/sphincsplus +) + +target_compile_definitions( + vextapq + PRIVATE + DILITHIUM_MODE=3 + PARAMS=sphincs-sha2-128s +) + +find_library(log-lib log) + +target_link_libraries( + vextapq + ${log-lib} +) diff --git a/app/src/main/cpp/pq/mldsa65/LICENSE b/app/src/main/cpp/pq/mldsa65/LICENSE new file mode 100644 index 0000000..c086379 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/LICENSE @@ -0,0 +1,7 @@ +Public Domain (https://creativecommons.org/share-your-work/public-domain/cc0/); +or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html) or GPL 2.0 License (http://www.gnu.org/licenses/gpl-2.0.html). + +For Keccak and the random number generator +we are using public-domain code from sources +and by authors listed in comments on top of +the respective files. diff --git a/app/src/main/cpp/pq/mldsa65/api.h b/app/src/main/cpp/pq/mldsa65/api.h new file mode 100644 index 0000000..032fa9f --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/api.h @@ -0,0 +1,98 @@ +#ifndef API_H +#define API_H + +#include +#include + +#define pqcrystals_dilithium2_PUBLICKEYBYTES 1312 +#define pqcrystals_dilithium2_SECRETKEYBYTES 2560 +#define pqcrystals_dilithium2_BYTES 2420 + +#define pqcrystals_dilithium2_ref_PUBLICKEYBYTES pqcrystals_dilithium2_PUBLICKEYBYTES +#define pqcrystals_dilithium2_ref_SECRETKEYBYTES pqcrystals_dilithium2_SECRETKEYBYTES +#define pqcrystals_dilithium2_ref_BYTES pqcrystals_dilithium2_BYTES + +int pqcrystals_dilithium2_ref_keypair(uint8_t *pk, uint8_t *sk); + +int pqcrystals_dilithium2_ref_signature(uint8_t *sig, size_t *siglen, + const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *sk); + +int pqcrystals_dilithium2_ref(uint8_t *sm, size_t *smlen, + const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *sk); + +int pqcrystals_dilithium2_ref_verify(const uint8_t *sig, size_t siglen, + const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *pk); + +int pqcrystals_dilithium2_ref_open(uint8_t *m, size_t *mlen, + const uint8_t *sm, size_t smlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *pk); + +#define pqcrystals_dilithium3_PUBLICKEYBYTES 1952 +#define pqcrystals_dilithium3_SECRETKEYBYTES 4032 +#define pqcrystals_dilithium3_BYTES 3309 + +#define pqcrystals_dilithium3_ref_PUBLICKEYBYTES pqcrystals_dilithium3_PUBLICKEYBYTES +#define pqcrystals_dilithium3_ref_SECRETKEYBYTES pqcrystals_dilithium3_SECRETKEYBYTES +#define pqcrystals_dilithium3_ref_BYTES pqcrystals_dilithium3_BYTES + +int pqcrystals_dilithium3_ref_keypair(uint8_t *pk, uint8_t *sk); + +int pqcrystals_dilithium3_ref_signature(uint8_t *sig, size_t *siglen, + const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *sk); + +int pqcrystals_dilithium3_ref(uint8_t *sm, size_t *smlen, + const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *sk); + +int pqcrystals_dilithium3_ref_verify(const uint8_t *sig, size_t siglen, + const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *pk); + +int pqcrystals_dilithium3_ref_open(uint8_t *m, size_t *mlen, + const uint8_t *sm, size_t smlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *pk); + +#define pqcrystals_dilithium5_PUBLICKEYBYTES 2592 +#define pqcrystals_dilithium5_SECRETKEYBYTES 4896 +#define pqcrystals_dilithium5_BYTES 4627 + +#define pqcrystals_dilithium5_ref_PUBLICKEYBYTES pqcrystals_dilithium5_PUBLICKEYBYTES +#define pqcrystals_dilithium5_ref_SECRETKEYBYTES pqcrystals_dilithium5_SECRETKEYBYTES +#define pqcrystals_dilithium5_ref_BYTES pqcrystals_dilithium5_BYTES + +int pqcrystals_dilithium5_ref_keypair(uint8_t *pk, uint8_t *sk); + +int pqcrystals_dilithium5_ref_signature(uint8_t *sig, size_t *siglen, + const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *sk); + +int pqcrystals_dilithium5_ref(uint8_t *sm, size_t *smlen, + const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *sk); + +int pqcrystals_dilithium5_ref_verify(const uint8_t *sig, size_t siglen, + const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *pk); + +int pqcrystals_dilithium5_ref_open(uint8_t *m, size_t *mlen, + const uint8_t *sm, size_t smlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *pk); + + +#endif diff --git a/app/src/main/cpp/pq/mldsa65/config.h b/app/src/main/cpp/pq/mldsa65/config.h new file mode 100644 index 0000000..98b8ccb --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/config.h @@ -0,0 +1,27 @@ +#ifndef CONFIG_H +#define CONFIG_H + +//#define DILITHIUM_MODE 2 +#define DILITHIUM_RANDOMIZED_SIGNING +//#define USE_RDPMC +//#define DBENCH + +#ifndef DILITHIUM_MODE +#define DILITHIUM_MODE 2 +#endif + +#if DILITHIUM_MODE == 2 +#define CRYPTO_ALGNAME "Dilithium2" +#define DILITHIUM_NAMESPACETOP pqcrystals_dilithium2_ref +#define DILITHIUM_NAMESPACE(s) pqcrystals_dilithium2_ref_##s +#elif DILITHIUM_MODE == 3 +#define CRYPTO_ALGNAME "Dilithium3" +#define DILITHIUM_NAMESPACETOP pqcrystals_dilithium3_ref +#define DILITHIUM_NAMESPACE(s) pqcrystals_dilithium3_ref_##s +#elif DILITHIUM_MODE == 5 +#define CRYPTO_ALGNAME "Dilithium5" +#define DILITHIUM_NAMESPACETOP pqcrystals_dilithium5_ref +#define DILITHIUM_NAMESPACE(s) pqcrystals_dilithium5_ref_##s +#endif + +#endif diff --git a/app/src/main/cpp/pq/mldsa65/fips202.c b/app/src/main/cpp/pq/mldsa65/fips202.c new file mode 100644 index 0000000..2afe799 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/fips202.c @@ -0,0 +1,774 @@ +/* Based on the public domain implementation in crypto_hash/keccakc512/simple/ from + * http://bench.cr.yp.to/supercop.html by Ronny Van Keer and the public domain "TweetFips202" + * implementation from https://twitter.com/tweetfips202 by Gilles Van Assche, Daniel J. Bernstein, + * and Peter Schwabe */ + +#include +#include +#include "fips202.h" + +#define NROUNDS 24 +#define ROL(a, offset) ((a << offset) ^ (a >> (64-offset))) + +/************************************************* +* Name: load64 +* +* Description: Load 8 bytes into uint64_t in little-endian order +* +* Arguments: - const uint8_t *x: pointer to input byte array +* +* Returns the loaded 64-bit unsigned integer +**************************************************/ +static uint64_t load64(const uint8_t x[8]) { + unsigned int i; + uint64_t r = 0; + + for(i=0;i<8;i++) + r |= (uint64_t)x[i] << 8*i; + + return r; +} + +/************************************************* +* Name: store64 +* +* Description: Store a 64-bit integer to array of 8 bytes in little-endian order +* +* Arguments: - uint8_t *x: pointer to the output byte array (allocated) +* - uint64_t u: input 64-bit unsigned integer +**************************************************/ +static void store64(uint8_t x[8], uint64_t u) { + unsigned int i; + + for(i=0;i<8;i++) + x[i] = u >> 8*i; +} + +/* Keccak round constants */ +const uint64_t KeccakF_RoundConstants[NROUNDS] = { + (uint64_t)0x0000000000000001ULL, + (uint64_t)0x0000000000008082ULL, + (uint64_t)0x800000000000808aULL, + (uint64_t)0x8000000080008000ULL, + (uint64_t)0x000000000000808bULL, + (uint64_t)0x0000000080000001ULL, + (uint64_t)0x8000000080008081ULL, + (uint64_t)0x8000000000008009ULL, + (uint64_t)0x000000000000008aULL, + (uint64_t)0x0000000000000088ULL, + (uint64_t)0x0000000080008009ULL, + (uint64_t)0x000000008000000aULL, + (uint64_t)0x000000008000808bULL, + (uint64_t)0x800000000000008bULL, + (uint64_t)0x8000000000008089ULL, + (uint64_t)0x8000000000008003ULL, + (uint64_t)0x8000000000008002ULL, + (uint64_t)0x8000000000000080ULL, + (uint64_t)0x000000000000800aULL, + (uint64_t)0x800000008000000aULL, + (uint64_t)0x8000000080008081ULL, + (uint64_t)0x8000000000008080ULL, + (uint64_t)0x0000000080000001ULL, + (uint64_t)0x8000000080008008ULL +}; + +/************************************************* +* Name: KeccakF1600_StatePermute +* +* Description: The Keccak F1600 Permutation +* +* Arguments: - uint64_t *state: pointer to input/output Keccak state +**************************************************/ +static void KeccakF1600_StatePermute(uint64_t state[25]) +{ + int round; + + uint64_t Aba, Abe, Abi, Abo, Abu; + uint64_t Aga, Age, Agi, Ago, Agu; + uint64_t Aka, Ake, Aki, Ako, Aku; + uint64_t Ama, Ame, Ami, Amo, Amu; + uint64_t Asa, Ase, Asi, Aso, Asu; + uint64_t BCa, BCe, BCi, BCo, BCu; + uint64_t Da, De, Di, Do, Du; + uint64_t Eba, Ebe, Ebi, Ebo, Ebu; + uint64_t Ega, Ege, Egi, Ego, Egu; + uint64_t Eka, Eke, Eki, Eko, Eku; + uint64_t Ema, Eme, Emi, Emo, Emu; + uint64_t Esa, Ese, Esi, Eso, Esu; + + //copyFromState(A, state) + Aba = state[ 0]; + Abe = state[ 1]; + Abi = state[ 2]; + Abo = state[ 3]; + Abu = state[ 4]; + Aga = state[ 5]; + Age = state[ 6]; + Agi = state[ 7]; + Ago = state[ 8]; + Agu = state[ 9]; + Aka = state[10]; + Ake = state[11]; + Aki = state[12]; + Ako = state[13]; + Aku = state[14]; + Ama = state[15]; + Ame = state[16]; + Ami = state[17]; + Amo = state[18]; + Amu = state[19]; + Asa = state[20]; + Ase = state[21]; + Asi = state[22]; + Aso = state[23]; + Asu = state[24]; + + for(round = 0; round < NROUNDS; round += 2) { + // prepareTheta + BCa = Aba^Aga^Aka^Ama^Asa; + BCe = Abe^Age^Ake^Ame^Ase; + BCi = Abi^Agi^Aki^Ami^Asi; + BCo = Abo^Ago^Ako^Amo^Aso; + BCu = Abu^Agu^Aku^Amu^Asu; + + //thetaRhoPiChiIotaPrepareTheta(round, A, E) + Da = BCu^ROL(BCe, 1); + De = BCa^ROL(BCi, 1); + Di = BCe^ROL(BCo, 1); + Do = BCi^ROL(BCu, 1); + Du = BCo^ROL(BCa, 1); + + Aba ^= Da; + BCa = Aba; + Age ^= De; + BCe = ROL(Age, 44); + Aki ^= Di; + BCi = ROL(Aki, 43); + Amo ^= Do; + BCo = ROL(Amo, 21); + Asu ^= Du; + BCu = ROL(Asu, 14); + Eba = BCa ^((~BCe)& BCi ); + Eba ^= (uint64_t)KeccakF_RoundConstants[round]; + Ebe = BCe ^((~BCi)& BCo ); + Ebi = BCi ^((~BCo)& BCu ); + Ebo = BCo ^((~BCu)& BCa ); + Ebu = BCu ^((~BCa)& BCe ); + + Abo ^= Do; + BCa = ROL(Abo, 28); + Agu ^= Du; + BCe = ROL(Agu, 20); + Aka ^= Da; + BCi = ROL(Aka, 3); + Ame ^= De; + BCo = ROL(Ame, 45); + Asi ^= Di; + BCu = ROL(Asi, 61); + Ega = BCa ^((~BCe)& BCi ); + Ege = BCe ^((~BCi)& BCo ); + Egi = BCi ^((~BCo)& BCu ); + Ego = BCo ^((~BCu)& BCa ); + Egu = BCu ^((~BCa)& BCe ); + + Abe ^= De; + BCa = ROL(Abe, 1); + Agi ^= Di; + BCe = ROL(Agi, 6); + Ako ^= Do; + BCi = ROL(Ako, 25); + Amu ^= Du; + BCo = ROL(Amu, 8); + Asa ^= Da; + BCu = ROL(Asa, 18); + Eka = BCa ^((~BCe)& BCi ); + Eke = BCe ^((~BCi)& BCo ); + Eki = BCi ^((~BCo)& BCu ); + Eko = BCo ^((~BCu)& BCa ); + Eku = BCu ^((~BCa)& BCe ); + + Abu ^= Du; + BCa = ROL(Abu, 27); + Aga ^= Da; + BCe = ROL(Aga, 36); + Ake ^= De; + BCi = ROL(Ake, 10); + Ami ^= Di; + BCo = ROL(Ami, 15); + Aso ^= Do; + BCu = ROL(Aso, 56); + Ema = BCa ^((~BCe)& BCi ); + Eme = BCe ^((~BCi)& BCo ); + Emi = BCi ^((~BCo)& BCu ); + Emo = BCo ^((~BCu)& BCa ); + Emu = BCu ^((~BCa)& BCe ); + + Abi ^= Di; + BCa = ROL(Abi, 62); + Ago ^= Do; + BCe = ROL(Ago, 55); + Aku ^= Du; + BCi = ROL(Aku, 39); + Ama ^= Da; + BCo = ROL(Ama, 41); + Ase ^= De; + BCu = ROL(Ase, 2); + Esa = BCa ^((~BCe)& BCi ); + Ese = BCe ^((~BCi)& BCo ); + Esi = BCi ^((~BCo)& BCu ); + Eso = BCo ^((~BCu)& BCa ); + Esu = BCu ^((~BCa)& BCe ); + + // prepareTheta + BCa = Eba^Ega^Eka^Ema^Esa; + BCe = Ebe^Ege^Eke^Eme^Ese; + BCi = Ebi^Egi^Eki^Emi^Esi; + BCo = Ebo^Ego^Eko^Emo^Eso; + BCu = Ebu^Egu^Eku^Emu^Esu; + + //thetaRhoPiChiIotaPrepareTheta(round+1, E, A) + Da = BCu^ROL(BCe, 1); + De = BCa^ROL(BCi, 1); + Di = BCe^ROL(BCo, 1); + Do = BCi^ROL(BCu, 1); + Du = BCo^ROL(BCa, 1); + + Eba ^= Da; + BCa = Eba; + Ege ^= De; + BCe = ROL(Ege, 44); + Eki ^= Di; + BCi = ROL(Eki, 43); + Emo ^= Do; + BCo = ROL(Emo, 21); + Esu ^= Du; + BCu = ROL(Esu, 14); + Aba = BCa ^((~BCe)& BCi ); + Aba ^= (uint64_t)KeccakF_RoundConstants[round+1]; + Abe = BCe ^((~BCi)& BCo ); + Abi = BCi ^((~BCo)& BCu ); + Abo = BCo ^((~BCu)& BCa ); + Abu = BCu ^((~BCa)& BCe ); + + Ebo ^= Do; + BCa = ROL(Ebo, 28); + Egu ^= Du; + BCe = ROL(Egu, 20); + Eka ^= Da; + BCi = ROL(Eka, 3); + Eme ^= De; + BCo = ROL(Eme, 45); + Esi ^= Di; + BCu = ROL(Esi, 61); + Aga = BCa ^((~BCe)& BCi ); + Age = BCe ^((~BCi)& BCo ); + Agi = BCi ^((~BCo)& BCu ); + Ago = BCo ^((~BCu)& BCa ); + Agu = BCu ^((~BCa)& BCe ); + + Ebe ^= De; + BCa = ROL(Ebe, 1); + Egi ^= Di; + BCe = ROL(Egi, 6); + Eko ^= Do; + BCi = ROL(Eko, 25); + Emu ^= Du; + BCo = ROL(Emu, 8); + Esa ^= Da; + BCu = ROL(Esa, 18); + Aka = BCa ^((~BCe)& BCi ); + Ake = BCe ^((~BCi)& BCo ); + Aki = BCi ^((~BCo)& BCu ); + Ako = BCo ^((~BCu)& BCa ); + Aku = BCu ^((~BCa)& BCe ); + + Ebu ^= Du; + BCa = ROL(Ebu, 27); + Ega ^= Da; + BCe = ROL(Ega, 36); + Eke ^= De; + BCi = ROL(Eke, 10); + Emi ^= Di; + BCo = ROL(Emi, 15); + Eso ^= Do; + BCu = ROL(Eso, 56); + Ama = BCa ^((~BCe)& BCi ); + Ame = BCe ^((~BCi)& BCo ); + Ami = BCi ^((~BCo)& BCu ); + Amo = BCo ^((~BCu)& BCa ); + Amu = BCu ^((~BCa)& BCe ); + + Ebi ^= Di; + BCa = ROL(Ebi, 62); + Ego ^= Do; + BCe = ROL(Ego, 55); + Eku ^= Du; + BCi = ROL(Eku, 39); + Ema ^= Da; + BCo = ROL(Ema, 41); + Ese ^= De; + BCu = ROL(Ese, 2); + Asa = BCa ^((~BCe)& BCi ); + Ase = BCe ^((~BCi)& BCo ); + Asi = BCi ^((~BCo)& BCu ); + Aso = BCo ^((~BCu)& BCa ); + Asu = BCu ^((~BCa)& BCe ); + } + + //copyToState(state, A) + state[ 0] = Aba; + state[ 1] = Abe; + state[ 2] = Abi; + state[ 3] = Abo; + state[ 4] = Abu; + state[ 5] = Aga; + state[ 6] = Age; + state[ 7] = Agi; + state[ 8] = Ago; + state[ 9] = Agu; + state[10] = Aka; + state[11] = Ake; + state[12] = Aki; + state[13] = Ako; + state[14] = Aku; + state[15] = Ama; + state[16] = Ame; + state[17] = Ami; + state[18] = Amo; + state[19] = Amu; + state[20] = Asa; + state[21] = Ase; + state[22] = Asi; + state[23] = Aso; + state[24] = Asu; +} + +/************************************************* +* Name: keccak_init +* +* Description: Initializes the Keccak state. +* +* Arguments: - uint64_t *s: pointer to Keccak state +**************************************************/ +static void keccak_init(uint64_t s[25]) +{ + unsigned int i; + for(i=0;i<25;i++) + s[i] = 0; +} + +/************************************************* +* Name: keccak_absorb +* +* Description: Absorb step of Keccak; incremental. +* +* Arguments: - uint64_t *s: pointer to Keccak state +* - unsigned int pos: position in current block to be absorbed +* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) +* - const uint8_t *in: pointer to input to be absorbed into s +* - size_t inlen: length of input in bytes +* +* Returns new position pos in current block +**************************************************/ +static unsigned int keccak_absorb(uint64_t s[25], + unsigned int pos, + unsigned int r, + const uint8_t *in, + size_t inlen) +{ + unsigned int i; + + while(pos+inlen >= r) { + for(i=pos;i> 8*(i%8); + outlen -= i-pos; + pos = i; + } + + return pos; +} + + +/************************************************* +* Name: keccak_absorb_once +* +* Description: Absorb step of Keccak; +* non-incremental, starts by zeroeing the state. +* +* Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak state +* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128) +* - const uint8_t *in: pointer to input to be absorbed into s +* - size_t inlen: length of input in bytes +* - uint8_t p: domain-separation byte for different Keccak-derived functions +**************************************************/ +static void keccak_absorb_once(uint64_t s[25], + unsigned int r, + const uint8_t *in, + size_t inlen, + uint8_t p) +{ + unsigned int i; + + for(i=0;i<25;i++) + s[i] = 0; + + while(inlen >= r) { + for(i=0;is); + state->pos = 0; +} + +/************************************************* +* Name: shake128_absorb +* +* Description: Absorb step of the SHAKE128 XOF; incremental. +* +* Arguments: - keccak_state *state: pointer to (initialized) output Keccak state +* - const uint8_t *in: pointer to input to be absorbed into s +* - size_t inlen: length of input in bytes +**************************************************/ +void shake128_absorb(keccak_state *state, const uint8_t *in, size_t inlen) +{ + state->pos = keccak_absorb(state->s, state->pos, SHAKE128_RATE, in, inlen); +} + +/************************************************* +* Name: shake128_finalize +* +* Description: Finalize absorb step of the SHAKE128 XOF. +* +* Arguments: - keccak_state *state: pointer to Keccak state +**************************************************/ +void shake128_finalize(keccak_state *state) +{ + keccak_finalize(state->s, state->pos, SHAKE128_RATE, 0x1F); + state->pos = SHAKE128_RATE; +} + +/************************************************* +* Name: shake128_squeeze +* +* Description: Squeeze step of SHAKE128 XOF. Squeezes arbitraily many +* bytes. Can be called multiple times to keep squeezing. +* +* Arguments: - uint8_t *out: pointer to output blocks +* - size_t outlen : number of bytes to be squeezed (written to output) +* - keccak_state *s: pointer to input/output Keccak state +**************************************************/ +void shake128_squeeze(uint8_t *out, size_t outlen, keccak_state *state) +{ + state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE128_RATE); +} + +/************************************************* +* Name: shake128_absorb_once +* +* Description: Initialize, absorb into and finalize SHAKE128 XOF; non-incremental. +* +* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state +* - const uint8_t *in: pointer to input to be absorbed into s +* - size_t inlen: length of input in bytes +**************************************************/ +void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen) +{ + keccak_absorb_once(state->s, SHAKE128_RATE, in, inlen, 0x1F); + state->pos = SHAKE128_RATE; +} + +/************************************************* +* Name: shake128_squeezeblocks +* +* Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of +* SHAKE128_RATE bytes each. Can be called multiple times +* to keep squeezing. Assumes new block has not yet been +* started (state->pos = SHAKE128_RATE). +* +* Arguments: - uint8_t *out: pointer to output blocks +* - size_t nblocks: number of blocks to be squeezed (written to output) +* - keccak_state *s: pointer to input/output Keccak state +**************************************************/ +void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) +{ + keccak_squeezeblocks(out, nblocks, state->s, SHAKE128_RATE); +} + +/************************************************* +* Name: shake256_init +* +* Description: Initilizes Keccak state for use as SHAKE256 XOF +* +* Arguments: - keccak_state *state: pointer to (uninitialized) Keccak state +**************************************************/ +void shake256_init(keccak_state *state) +{ + keccak_init(state->s); + state->pos = 0; +} + +/************************************************* +* Name: shake256_absorb +* +* Description: Absorb step of the SHAKE256 XOF; incremental. +* +* Arguments: - keccak_state *state: pointer to (initialized) output Keccak state +* - const uint8_t *in: pointer to input to be absorbed into s +* - size_t inlen: length of input in bytes +**************************************************/ +void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen) +{ + state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen); +} + +/************************************************* +* Name: shake256_finalize +* +* Description: Finalize absorb step of the SHAKE256 XOF. +* +* Arguments: - keccak_state *state: pointer to Keccak state +**************************************************/ +void shake256_finalize(keccak_state *state) +{ + keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F); + state->pos = SHAKE256_RATE; +} + +/************************************************* +* Name: shake256_squeeze +* +* Description: Squeeze step of SHAKE256 XOF. Squeezes arbitraily many +* bytes. Can be called multiple times to keep squeezing. +* +* Arguments: - uint8_t *out: pointer to output blocks +* - size_t outlen : number of bytes to be squeezed (written to output) +* - keccak_state *s: pointer to input/output Keccak state +**************************************************/ +void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state) +{ + state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE); +} + +/************************************************* +* Name: shake256_absorb_once +* +* Description: Initialize, absorb into and finalize SHAKE256 XOF; non-incremental. +* +* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state +* - const uint8_t *in: pointer to input to be absorbed into s +* - size_t inlen: length of input in bytes +**************************************************/ +void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen) +{ + keccak_absorb_once(state->s, SHAKE256_RATE, in, inlen, 0x1F); + state->pos = SHAKE256_RATE; +} + +/************************************************* +* Name: shake256_squeezeblocks +* +* Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of +* SHAKE256_RATE bytes each. Can be called multiple times +* to keep squeezing. Assumes next block has not yet been +* started (state->pos = SHAKE256_RATE). +* +* Arguments: - uint8_t *out: pointer to output blocks +* - size_t nblocks: number of blocks to be squeezed (written to output) +* - keccak_state *s: pointer to input/output Keccak state +**************************************************/ +void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) +{ + keccak_squeezeblocks(out, nblocks, state->s, SHAKE256_RATE); +} + +/************************************************* +* Name: shake128 +* +* Description: SHAKE128 XOF with non-incremental API +* +* Arguments: - uint8_t *out: pointer to output +* - size_t outlen: requested output length in bytes +* - const uint8_t *in: pointer to input +* - size_t inlen: length of input in bytes +**************************************************/ +void shake128(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) +{ + size_t nblocks; + keccak_state state; + + shake128_absorb_once(&state, in, inlen); + nblocks = outlen/SHAKE128_RATE; + shake128_squeezeblocks(out, nblocks, &state); + outlen -= nblocks*SHAKE128_RATE; + out += nblocks*SHAKE128_RATE; + shake128_squeeze(out, outlen, &state); +} + +/************************************************* +* Name: shake256 +* +* Description: SHAKE256 XOF with non-incremental API +* +* Arguments: - uint8_t *out: pointer to output +* - size_t outlen: requested output length in bytes +* - const uint8_t *in: pointer to input +* - size_t inlen: length of input in bytes +**************************************************/ +void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) +{ + size_t nblocks; + keccak_state state; + + shake256_absorb_once(&state, in, inlen); + nblocks = outlen/SHAKE256_RATE; + shake256_squeezeblocks(out, nblocks, &state); + outlen -= nblocks*SHAKE256_RATE; + out += nblocks*SHAKE256_RATE; + shake256_squeeze(out, outlen, &state); +} + +/************************************************* +* Name: sha3_256 +* +* Description: SHA3-256 with non-incremental API +* +* Arguments: - uint8_t *h: pointer to output (32 bytes) +* - const uint8_t *in: pointer to input +* - size_t inlen: length of input in bytes +**************************************************/ +void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen) +{ + unsigned int i; + uint64_t s[25]; + + keccak_absorb_once(s, SHA3_256_RATE, in, inlen, 0x06); + KeccakF1600_StatePermute(s); + for(i=0;i<4;i++) + store64(h+8*i,s[i]); +} + +/************************************************* +* Name: sha3_512 +* +* Description: SHA3-512 with non-incremental API +* +* Arguments: - uint8_t *h: pointer to output (64 bytes) +* - const uint8_t *in: pointer to input +* - size_t inlen: length of input in bytes +**************************************************/ +void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen) +{ + unsigned int i; + uint64_t s[25]; + + keccak_absorb_once(s, SHA3_512_RATE, in, inlen, 0x06); + KeccakF1600_StatePermute(s); + for(i=0;i<8;i++) + store64(h+8*i,s[i]); +} diff --git a/app/src/main/cpp/pq/mldsa65/fips202.h b/app/src/main/cpp/pq/mldsa65/fips202.h new file mode 100644 index 0000000..c37f535 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/fips202.h @@ -0,0 +1,57 @@ +#ifndef FIPS202_H +#define FIPS202_H + +#include +#include + +#define SHAKE128_RATE 168 +#define SHAKE256_RATE 136 +#define SHA3_256_RATE 136 +#define SHA3_512_RATE 72 + +#define FIPS202_NAMESPACE(s) pqcrystals_dilithium_fips202_ref_##s + +typedef struct { + uint64_t s[25]; + unsigned int pos; +} keccak_state; + +#define KeccakF_RoundConstants FIPS202_NAMESPACE(KeccakF_RoundConstants) +extern const uint64_t KeccakF_RoundConstants[]; + +#define shake128_init FIPS202_NAMESPACE(shake128_init) +void shake128_init(keccak_state *state); +#define shake128_absorb FIPS202_NAMESPACE(shake128_absorb) +void shake128_absorb(keccak_state *state, const uint8_t *in, size_t inlen); +#define shake128_finalize FIPS202_NAMESPACE(shake128_finalize) +void shake128_finalize(keccak_state *state); +#define shake128_squeeze FIPS202_NAMESPACE(shake128_squeeze) +void shake128_squeeze(uint8_t *out, size_t outlen, keccak_state *state); +#define shake128_absorb_once FIPS202_NAMESPACE(shake128_absorb_once) +void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen); +#define shake128_squeezeblocks FIPS202_NAMESPACE(shake128_squeezeblocks) +void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state); + +#define shake256_init FIPS202_NAMESPACE(shake256_init) +void shake256_init(keccak_state *state); +#define shake256_absorb FIPS202_NAMESPACE(shake256_absorb) +void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen); +#define shake256_finalize FIPS202_NAMESPACE(shake256_finalize) +void shake256_finalize(keccak_state *state); +#define shake256_squeeze FIPS202_NAMESPACE(shake256_squeeze) +void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state); +#define shake256_absorb_once FIPS202_NAMESPACE(shake256_absorb_once) +void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen); +#define shake256_squeezeblocks FIPS202_NAMESPACE(shake256_squeezeblocks) +void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state); + +#define shake128 FIPS202_NAMESPACE(shake128) +void shake128(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen); +#define shake256 FIPS202_NAMESPACE(shake256) +void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen); +#define sha3_256 FIPS202_NAMESPACE(sha3_256) +void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen); +#define sha3_512 FIPS202_NAMESPACE(sha3_512) +void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen); + +#endif diff --git a/app/src/main/cpp/pq/mldsa65/ntt.c b/app/src/main/cpp/pq/mldsa65/ntt.c new file mode 100644 index 0000000..5ea8b53 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/ntt.c @@ -0,0 +1,98 @@ +#include +#include "params.h" +#include "ntt.h" +#include "reduce.h" + +static const int32_t zetas[N] = { + 0, 25847, -2608894, -518909, 237124, -777960, -876248, 466468, + 1826347, 2353451, -359251, -2091905, 3119733, -2884855, 3111497, 2680103, + 2725464, 1024112, -1079900, 3585928, -549488, -1119584, 2619752, -2108549, + -2118186, -3859737, -1399561, -3277672, 1757237, -19422, 4010497, 280005, + 2706023, 95776, 3077325, 3530437, -1661693, -3592148, -2537516, 3915439, + -3861115, -3043716, 3574422, -2867647, 3539968, -300467, 2348700, -539299, + -1699267, -1643818, 3505694, -3821735, 3507263, -2140649, -1600420, 3699596, + 811944, 531354, 954230, 3881043, 3900724, -2556880, 2071892, -2797779, + -3930395, -1528703, -3677745, -3041255, -1452451, 3475950, 2176455, -1585221, + -1257611, 1939314, -4083598, -1000202, -3190144, -3157330, -3632928, 126922, + 3412210, -983419, 2147896, 2715295, -2967645, -3693493, -411027, -2477047, + -671102, -1228525, -22981, -1308169, -381987, 1349076, 1852771, -1430430, + -3343383, 264944, 508951, 3097992, 44288, -1100098, 904516, 3958618, + -3724342, -8578, 1653064, -3249728, 2389356, -210977, 759969, -1316856, + 189548, -3553272, 3159746, -1851402, -2409325, -177440, 1315589, 1341330, + 1285669, -1584928, -812732, -1439742, -3019102, -3881060, -3628969, 3839961, + 2091667, 3407706, 2316500, 3817976, -3342478, 2244091, -2446433, -3562462, + 266997, 2434439, -1235728, 3513181, -3520352, -3759364, -1197226, -3193378, + 900702, 1859098, 909542, 819034, 495491, -1613174, -43260, -522500, + -655327, -3122442, 2031748, 3207046, -3556995, -525098, -768622, -3595838, + 342297, 286988, -2437823, 4108315, 3437287, -3342277, 1735879, 203044, + 2842341, 2691481, -2590150, 1265009, 4055324, 1247620, 2486353, 1595974, + -3767016, 1250494, 2635921, -3548272, -2994039, 1869119, 1903435, -1050970, + -1333058, 1237275, -3318210, -1430225, -451100, 1312455, 3306115, -1962642, + -1279661, 1917081, -2546312, -1374803, 1500165, 777191, 2235880, 3406031, + -542412, -2831860, -1671176, -1846953, -2584293, -3724270, 594136, -3776993, + -2013608, 2432395, 2454455, -164721, 1957272, 3369112, 185531, -1207385, + -3183426, 162844, 1616392, 3014001, 810149, 1652634, -3694233, -1799107, + -3038916, 3523897, 3866901, 269760, 2213111, -975884, 1717735, 472078, + -426683, 1723600, -1803090, 1910376, -1667432, -1104333, -260646, -3833893, + -2939036, -2235985, -420899, -2286327, 183443, -976891, 1612842, -3545687, + -554416, 3919660, -48306, -1362209, 3937738, 1400424, -846154, 1976782 +}; + +/************************************************* +* Name: ntt +* +* Description: Forward NTT, in-place. No modular reduction is performed after +* additions or subtractions. Output vector is in bitreversed order. +* +* Arguments: - uint32_t p[N]: input/output coefficient array +**************************************************/ +void ntt(int32_t a[N]) { + unsigned int len, start, j, k; + int32_t zeta, t; + + k = 0; + for(len = 128; len > 0; len >>= 1) { + for(start = 0; start < N; start = j + len) { + zeta = zetas[++k]; + for(j = start; j < start + len; ++j) { + t = montgomery_reduce((int64_t)zeta * a[j + len]); + a[j + len] = a[j] - t; + a[j] = a[j] + t; + } + } + } +} + +/************************************************* +* Name: invntt_tomont +* +* Description: Inverse NTT and multiplication by Montgomery factor 2^32. +* In-place. No modular reductions after additions or +* subtractions; input coefficients need to be smaller than +* Q in absolute value. Output coefficient are smaller than Q in +* absolute value. +* +* Arguments: - uint32_t p[N]: input/output coefficient array +**************************************************/ +void invntt_tomont(int32_t a[N]) { + unsigned int start, len, j, k; + int32_t t, zeta; + const int32_t f = 41978; // mont^2/256 + + k = 256; + for(len = 1; len < N; len <<= 1) { + for(start = 0; start < N; start = j + len) { + zeta = -zetas[--k]; + for(j = start; j < start + len; ++j) { + t = a[j]; + a[j] = t + a[j + len]; + a[j + len] = t - a[j + len]; + a[j + len] = montgomery_reduce((int64_t)zeta * a[j + len]); + } + } + } + + for(j = 0; j < N; ++j) { + a[j] = montgomery_reduce((int64_t)f * a[j]); + } +} diff --git a/app/src/main/cpp/pq/mldsa65/ntt.h b/app/src/main/cpp/pq/mldsa65/ntt.h new file mode 100644 index 0000000..731132d --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/ntt.h @@ -0,0 +1,13 @@ +#ifndef NTT_H +#define NTT_H + +#include +#include "params.h" + +#define ntt DILITHIUM_NAMESPACE(ntt) +void ntt(int32_t a[N]); + +#define invntt_tomont DILITHIUM_NAMESPACE(invntt_tomont) +void invntt_tomont(int32_t a[N]); + +#endif diff --git a/app/src/main/cpp/pq/mldsa65/packing.c b/app/src/main/cpp/pq/mldsa65/packing.c new file mode 100644 index 0000000..039a686 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/packing.c @@ -0,0 +1,237 @@ +#include "params.h" +#include "packing.h" +#include "polyvec.h" +#include "poly.h" + +/************************************************* +* Name: pack_pk +* +* Description: Bit-pack public key pk = (rho, t1). +* +* Arguments: - uint8_t pk[]: output byte array +* - const uint8_t rho[]: byte array containing rho +* - const polyveck *t1: pointer to vector t1 +**************************************************/ +void pack_pk(uint8_t pk[CRYPTO_PUBLICKEYBYTES], + const uint8_t rho[SEEDBYTES], + const polyveck *t1) +{ + unsigned int i; + + for(i = 0; i < SEEDBYTES; ++i) + pk[i] = rho[i]; + pk += SEEDBYTES; + + for(i = 0; i < K; ++i) + polyt1_pack(pk + i*POLYT1_PACKEDBYTES, &t1->vec[i]); +} + +/************************************************* +* Name: unpack_pk +* +* Description: Unpack public key pk = (rho, t1). +* +* Arguments: - const uint8_t rho[]: output byte array for rho +* - const polyveck *t1: pointer to output vector t1 +* - uint8_t pk[]: byte array containing bit-packed pk +**************************************************/ +void unpack_pk(uint8_t rho[SEEDBYTES], + polyveck *t1, + const uint8_t pk[CRYPTO_PUBLICKEYBYTES]) +{ + unsigned int i; + + for(i = 0; i < SEEDBYTES; ++i) + rho[i] = pk[i]; + pk += SEEDBYTES; + + for(i = 0; i < K; ++i) + polyt1_unpack(&t1->vec[i], pk + i*POLYT1_PACKEDBYTES); +} + +/************************************************* +* Name: pack_sk +* +* Description: Bit-pack secret key sk = (rho, tr, key, t0, s1, s2). +* +* Arguments: - uint8_t sk[]: output byte array +* - const uint8_t rho[]: byte array containing rho +* - const uint8_t tr[]: byte array containing tr +* - const uint8_t key[]: byte array containing key +* - const polyveck *t0: pointer to vector t0 +* - const polyvecl *s1: pointer to vector s1 +* - const polyveck *s2: pointer to vector s2 +**************************************************/ +void pack_sk(uint8_t sk[CRYPTO_SECRETKEYBYTES], + const uint8_t rho[SEEDBYTES], + const uint8_t tr[TRBYTES], + const uint8_t key[SEEDBYTES], + const polyveck *t0, + const polyvecl *s1, + const polyveck *s2) +{ + unsigned int i; + + for(i = 0; i < SEEDBYTES; ++i) + sk[i] = rho[i]; + sk += SEEDBYTES; + + for(i = 0; i < SEEDBYTES; ++i) + sk[i] = key[i]; + sk += SEEDBYTES; + + for(i = 0; i < TRBYTES; ++i) + sk[i] = tr[i]; + sk += TRBYTES; + + for(i = 0; i < L; ++i) + polyeta_pack(sk + i*POLYETA_PACKEDBYTES, &s1->vec[i]); + sk += L*POLYETA_PACKEDBYTES; + + for(i = 0; i < K; ++i) + polyeta_pack(sk + i*POLYETA_PACKEDBYTES, &s2->vec[i]); + sk += K*POLYETA_PACKEDBYTES; + + for(i = 0; i < K; ++i) + polyt0_pack(sk + i*POLYT0_PACKEDBYTES, &t0->vec[i]); +} + +/************************************************* +* Name: unpack_sk +* +* Description: Unpack secret key sk = (rho, tr, key, t0, s1, s2). +* +* Arguments: - const uint8_t rho[]: output byte array for rho +* - const uint8_t tr[]: output byte array for tr +* - const uint8_t key[]: output byte array for key +* - const polyveck *t0: pointer to output vector t0 +* - const polyvecl *s1: pointer to output vector s1 +* - const polyveck *s2: pointer to output vector s2 +* - uint8_t sk[]: byte array containing bit-packed sk +**************************************************/ +void unpack_sk(uint8_t rho[SEEDBYTES], + uint8_t tr[TRBYTES], + uint8_t key[SEEDBYTES], + polyveck *t0, + polyvecl *s1, + polyveck *s2, + const uint8_t sk[CRYPTO_SECRETKEYBYTES]) +{ + unsigned int i; + + for(i = 0; i < SEEDBYTES; ++i) + rho[i] = sk[i]; + sk += SEEDBYTES; + + for(i = 0; i < SEEDBYTES; ++i) + key[i] = sk[i]; + sk += SEEDBYTES; + + for(i = 0; i < TRBYTES; ++i) + tr[i] = sk[i]; + sk += TRBYTES; + + for(i=0; i < L; ++i) + polyeta_unpack(&s1->vec[i], sk + i*POLYETA_PACKEDBYTES); + sk += L*POLYETA_PACKEDBYTES; + + for(i=0; i < K; ++i) + polyeta_unpack(&s2->vec[i], sk + i*POLYETA_PACKEDBYTES); + sk += K*POLYETA_PACKEDBYTES; + + for(i=0; i < K; ++i) + polyt0_unpack(&t0->vec[i], sk + i*POLYT0_PACKEDBYTES); +} + +/************************************************* +* Name: pack_sig +* +* Description: Bit-pack signature sig = (c, z, h). +* +* Arguments: - uint8_t sig[]: output byte array +* - const uint8_t *c: pointer to challenge hash length SEEDBYTES +* - const polyvecl *z: pointer to vector z +* - const polyveck *h: pointer to hint vector h +**************************************************/ +void pack_sig(uint8_t sig[CRYPTO_BYTES], + const uint8_t c[CTILDEBYTES], + const polyvecl *z, + const polyveck *h) +{ + unsigned int i, j, k; + + for(i=0; i < CTILDEBYTES; ++i) + sig[i] = c[i]; + sig += CTILDEBYTES; + + for(i = 0; i < L; ++i) + polyz_pack(sig + i*POLYZ_PACKEDBYTES, &z->vec[i]); + sig += L*POLYZ_PACKEDBYTES; + + /* Encode h */ + for(i = 0; i < OMEGA + K; ++i) + sig[i] = 0; + + k = 0; + for(i = 0; i < K; ++i) { + for(j = 0; j < N; ++j) + if(h->vec[i].coeffs[j] != 0) + sig[k++] = j; + + sig[OMEGA + i] = k; + } +} + +/************************************************* +* Name: unpack_sig +* +* Description: Unpack signature sig = (c, z, h). +* +* Arguments: - uint8_t *c: pointer to output challenge hash +* - polyvecl *z: pointer to output vector z +* - polyveck *h: pointer to output hint vector h +* - const uint8_t sig[]: byte array containing +* bit-packed signature +* +* Returns 1 in case of malformed signature; otherwise 0. +**************************************************/ +int unpack_sig(uint8_t c[CTILDEBYTES], + polyvecl *z, + polyveck *h, + const uint8_t sig[CRYPTO_BYTES]) +{ + unsigned int i, j, k; + + for(i = 0; i < CTILDEBYTES; ++i) + c[i] = sig[i]; + sig += CTILDEBYTES; + + for(i = 0; i < L; ++i) + polyz_unpack(&z->vec[i], sig + i*POLYZ_PACKEDBYTES); + sig += L*POLYZ_PACKEDBYTES; + + /* Decode h */ + k = 0; + for(i = 0; i < K; ++i) { + for(j = 0; j < N; ++j) + h->vec[i].coeffs[j] = 0; + + if(sig[OMEGA + i] < k || sig[OMEGA + i] > OMEGA) + return 1; + + for(j = k; j < sig[OMEGA + i]; ++j) { + /* Coefficients are ordered for strong unforgeability */ + if(j > k && sig[j] <= sig[j-1]) return 1; + h->vec[i].coeffs[sig[j]] = 1; + } + + k = sig[OMEGA + i]; + } + + /* Extra indices are zero for strong unforgeability */ + for(j = k; j < OMEGA; ++j) + if(sig[j]) + return 1; + + return 0; +} diff --git a/app/src/main/cpp/pq/mldsa65/packing.h b/app/src/main/cpp/pq/mldsa65/packing.h new file mode 100644 index 0000000..8e47728 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/packing.h @@ -0,0 +1,38 @@ +#ifndef PACKING_H +#define PACKING_H + +#include +#include "params.h" +#include "polyvec.h" + +#define pack_pk DILITHIUM_NAMESPACE(pack_pk) +void pack_pk(uint8_t pk[CRYPTO_PUBLICKEYBYTES], const uint8_t rho[SEEDBYTES], const polyveck *t1); + +#define pack_sk DILITHIUM_NAMESPACE(pack_sk) +void pack_sk(uint8_t sk[CRYPTO_SECRETKEYBYTES], + const uint8_t rho[SEEDBYTES], + const uint8_t tr[TRBYTES], + const uint8_t key[SEEDBYTES], + const polyveck *t0, + const polyvecl *s1, + const polyveck *s2); + +#define pack_sig DILITHIUM_NAMESPACE(pack_sig) +void pack_sig(uint8_t sig[CRYPTO_BYTES], const uint8_t c[CTILDEBYTES], const polyvecl *z, const polyveck *h); + +#define unpack_pk DILITHIUM_NAMESPACE(unpack_pk) +void unpack_pk(uint8_t rho[SEEDBYTES], polyveck *t1, const uint8_t pk[CRYPTO_PUBLICKEYBYTES]); + +#define unpack_sk DILITHIUM_NAMESPACE(unpack_sk) +void unpack_sk(uint8_t rho[SEEDBYTES], + uint8_t tr[TRBYTES], + uint8_t key[SEEDBYTES], + polyveck *t0, + polyvecl *s1, + polyveck *s2, + const uint8_t sk[CRYPTO_SECRETKEYBYTES]); + +#define unpack_sig DILITHIUM_NAMESPACE(unpack_sig) +int unpack_sig(uint8_t c[CTILDEBYTES], polyvecl *z, polyveck *h, const uint8_t sig[CRYPTO_BYTES]); + +#endif diff --git a/app/src/main/cpp/pq/mldsa65/params.h b/app/src/main/cpp/pq/mldsa65/params.h new file mode 100644 index 0000000..1e8a7b5 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/params.h @@ -0,0 +1,80 @@ +#ifndef PARAMS_H +#define PARAMS_H + +#include "config.h" + +#define SEEDBYTES 32 +#define CRHBYTES 64 +#define TRBYTES 64 +#define RNDBYTES 32 +#define N 256 +#define Q 8380417 +#define D 13 +#define ROOT_OF_UNITY 1753 + +#if DILITHIUM_MODE == 2 +#define K 4 +#define L 4 +#define ETA 2 +#define TAU 39 +#define BETA 78 +#define GAMMA1 (1 << 17) +#define GAMMA2 ((Q-1)/88) +#define OMEGA 80 +#define CTILDEBYTES 32 + +#elif DILITHIUM_MODE == 3 +#define K 6 +#define L 5 +#define ETA 4 +#define TAU 49 +#define BETA 196 +#define GAMMA1 (1 << 19) +#define GAMMA2 ((Q-1)/32) +#define OMEGA 55 +#define CTILDEBYTES 48 + +#elif DILITHIUM_MODE == 5 +#define K 8 +#define L 7 +#define ETA 2 +#define TAU 60 +#define BETA 120 +#define GAMMA1 (1 << 19) +#define GAMMA2 ((Q-1)/32) +#define OMEGA 75 +#define CTILDEBYTES 64 + +#endif + +#define POLYT1_PACKEDBYTES 320 +#define POLYT0_PACKEDBYTES 416 +#define POLYVECH_PACKEDBYTES (OMEGA + K) + +#if GAMMA1 == (1 << 17) +#define POLYZ_PACKEDBYTES 576 +#elif GAMMA1 == (1 << 19) +#define POLYZ_PACKEDBYTES 640 +#endif + +#if GAMMA2 == (Q-1)/88 +#define POLYW1_PACKEDBYTES 192 +#elif GAMMA2 == (Q-1)/32 +#define POLYW1_PACKEDBYTES 128 +#endif + +#if ETA == 2 +#define POLYETA_PACKEDBYTES 96 +#elif ETA == 4 +#define POLYETA_PACKEDBYTES 128 +#endif + +#define CRYPTO_PUBLICKEYBYTES (SEEDBYTES + K*POLYT1_PACKEDBYTES) +#define CRYPTO_SECRETKEYBYTES (2*SEEDBYTES \ + + TRBYTES \ + + L*POLYETA_PACKEDBYTES \ + + K*POLYETA_PACKEDBYTES \ + + K*POLYT0_PACKEDBYTES) +#define CRYPTO_BYTES (CTILDEBYTES + L*POLYZ_PACKEDBYTES + POLYVECH_PACKEDBYTES) + +#endif diff --git a/app/src/main/cpp/pq/mldsa65/poly.c b/app/src/main/cpp/pq/mldsa65/poly.c new file mode 100644 index 0000000..0db4f42 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/poly.c @@ -0,0 +1,907 @@ +#include +#include "params.h" +#include "poly.h" +#include "ntt.h" +#include "reduce.h" +#include "rounding.h" +#include "symmetric.h" + +#ifdef DBENCH +#include "test/cpucycles.h" +extern const uint64_t timing_overhead; +extern uint64_t *tred, *tadd, *tmul, *tround, *tsample, *tpack; +#define DBENCH_START() uint64_t time = cpucycles() +#define DBENCH_STOP(t) t += cpucycles() - time - timing_overhead +#else +#define DBENCH_START() +#define DBENCH_STOP(t) +#endif + +/************************************************* +* Name: poly_reduce +* +* Description: Inplace reduction of all coefficients of polynomial to +* representative in [-6283008,6283008]. +* +* Arguments: - poly *a: pointer to input/output polynomial +**************************************************/ +void poly_reduce(poly *a) { + unsigned int i; + DBENCH_START(); + + for(i = 0; i < N; ++i) + a->coeffs[i] = reduce32(a->coeffs[i]); + + DBENCH_STOP(*tred); +} + +/************************************************* +* Name: poly_caddq +* +* Description: For all coefficients of in/out polynomial add Q if +* coefficient is negative. +* +* Arguments: - poly *a: pointer to input/output polynomial +**************************************************/ +void poly_caddq(poly *a) { + unsigned int i; + DBENCH_START(); + + for(i = 0; i < N; ++i) + a->coeffs[i] = caddq(a->coeffs[i]); + + DBENCH_STOP(*tred); +} + +/************************************************* +* Name: poly_add +* +* Description: Add polynomials. No modular reduction is performed. +* +* Arguments: - poly *c: pointer to output polynomial +* - const poly *a: pointer to first summand +* - const poly *b: pointer to second summand +**************************************************/ +void poly_add(poly *c, const poly *a, const poly *b) { + unsigned int i; + DBENCH_START(); + + for(i = 0; i < N; ++i) + c->coeffs[i] = a->coeffs[i] + b->coeffs[i]; + + DBENCH_STOP(*tadd); +} + +/************************************************* +* Name: poly_sub +* +* Description: Subtract polynomials. No modular reduction is +* performed. +* +* Arguments: - poly *c: pointer to output polynomial +* - const poly *a: pointer to first input polynomial +* - const poly *b: pointer to second input polynomial to be +* subtraced from first input polynomial +**************************************************/ +void poly_sub(poly *c, const poly *a, const poly *b) { + unsigned int i; + DBENCH_START(); + + for(i = 0; i < N; ++i) + c->coeffs[i] = a->coeffs[i] - b->coeffs[i]; + + DBENCH_STOP(*tadd); +} + +/************************************************* +* Name: poly_shiftl +* +* Description: Multiply polynomial by 2^D without modular reduction. Assumes +* input coefficients to be less than 2^{31-D} in absolute value. +* +* Arguments: - poly *a: pointer to input/output polynomial +**************************************************/ +void poly_shiftl(poly *a) { + unsigned int i; + DBENCH_START(); + + for(i = 0; i < N; ++i) + a->coeffs[i] <<= D; + + DBENCH_STOP(*tmul); +} + +/************************************************* +* Name: poly_ntt +* +* Description: Inplace forward NTT. Coefficients can grow by +* 8*Q in absolute value. +* +* Arguments: - poly *a: pointer to input/output polynomial +**************************************************/ +void poly_ntt(poly *a) { + DBENCH_START(); + + ntt(a->coeffs); + + DBENCH_STOP(*tmul); +} + +/************************************************* +* Name: poly_invntt_tomont +* +* Description: Inplace inverse NTT and multiplication by 2^{32}. +* Input coefficients need to be less than Q in absolute +* value and output coefficients are again bounded by Q. +* +* Arguments: - poly *a: pointer to input/output polynomial +**************************************************/ +void poly_invntt_tomont(poly *a) { + DBENCH_START(); + + invntt_tomont(a->coeffs); + + DBENCH_STOP(*tmul); +} + +/************************************************* +* Name: poly_pointwise_montgomery +* +* Description: Pointwise multiplication of polynomials in NTT domain +* representation and multiplication of resulting polynomial +* by 2^{-32}. +* +* Arguments: - poly *c: pointer to output polynomial +* - const poly *a: pointer to first input polynomial +* - const poly *b: pointer to second input polynomial +**************************************************/ +void poly_pointwise_montgomery(poly *c, const poly *a, const poly *b) { + unsigned int i; + DBENCH_START(); + + for(i = 0; i < N; ++i) + c->coeffs[i] = montgomery_reduce((int64_t)a->coeffs[i] * b->coeffs[i]); + + DBENCH_STOP(*tmul); +} + +/************************************************* +* Name: poly_power2round +* +* Description: For all coefficients c of the input polynomial, +* compute c0, c1 such that c mod Q = c1*2^D + c0 +* with -2^{D-1} < c0 <= 2^{D-1}. Assumes coefficients to be +* standard representatives. +* +* Arguments: - poly *a1: pointer to output polynomial with coefficients c1 +* - poly *a0: pointer to output polynomial with coefficients c0 +* - const poly *a: pointer to input polynomial +**************************************************/ +void poly_power2round(poly *a1, poly *a0, const poly *a) { + unsigned int i; + DBENCH_START(); + + for(i = 0; i < N; ++i) + a1->coeffs[i] = power2round(&a0->coeffs[i], a->coeffs[i]); + + DBENCH_STOP(*tround); +} + +/************************************************* +* Name: poly_decompose +* +* Description: For all coefficients c of the input polynomial, +* compute high and low bits c0, c1 such c mod Q = c1*ALPHA + c0 +* with -ALPHA/2 < c0 <= ALPHA/2 except c1 = (Q-1)/ALPHA where we +* set c1 = 0 and -ALPHA/2 <= c0 = c mod Q - Q < 0. +* Assumes coefficients to be standard representatives. +* +* Arguments: - poly *a1: pointer to output polynomial with coefficients c1 +* - poly *a0: pointer to output polynomial with coefficients c0 +* - const poly *a: pointer to input polynomial +**************************************************/ +void poly_decompose(poly *a1, poly *a0, const poly *a) { + unsigned int i; + DBENCH_START(); + + for(i = 0; i < N; ++i) + a1->coeffs[i] = decompose(&a0->coeffs[i], a->coeffs[i]); + + DBENCH_STOP(*tround); +} + +/************************************************* +* Name: poly_make_hint +* +* Description: Compute hint polynomial. The coefficients of which indicate +* whether the low bits of the corresponding coefficient of +* the input polynomial overflow into the high bits. +* +* Arguments: - poly *h: pointer to output hint polynomial +* - const poly *a0: pointer to low part of input polynomial +* - const poly *a1: pointer to high part of input polynomial +* +* Returns number of 1 bits. +**************************************************/ +unsigned int poly_make_hint(poly *h, const poly *a0, const poly *a1) { + unsigned int i, s = 0; + DBENCH_START(); + + for(i = 0; i < N; ++i) { + h->coeffs[i] = make_hint(a0->coeffs[i], a1->coeffs[i]); + s += h->coeffs[i]; + } + + DBENCH_STOP(*tround); + return s; +} + +/************************************************* +* Name: poly_use_hint +* +* Description: Use hint polynomial to correct the high bits of a polynomial. +* +* Arguments: - poly *b: pointer to output polynomial with corrected high bits +* - const poly *a: pointer to input polynomial +* - const poly *h: pointer to input hint polynomial +**************************************************/ +void poly_use_hint(poly *b, const poly *a, const poly *h) { + unsigned int i; + DBENCH_START(); + + for(i = 0; i < N; ++i) + b->coeffs[i] = use_hint(a->coeffs[i], h->coeffs[i]); + + DBENCH_STOP(*tround); +} + +/************************************************* +* Name: poly_chknorm +* +* Description: Check infinity norm of polynomial against given bound. +* Assumes input coefficients were reduced by reduce32(). +* +* Arguments: - const poly *a: pointer to polynomial +* - int32_t B: norm bound +* +* Returns 0 if norm is strictly smaller than B <= (Q-1)/8 and 1 otherwise. +**************************************************/ +int poly_chknorm(const poly *a, int32_t B) { + unsigned int i; + int32_t t; + DBENCH_START(); + + if(B > (Q-1)/8) + return 1; + + /* It is ok to leak which coefficient violates the bound since + the probability for each coefficient is independent of secret + data but we must not leak the sign of the centralized representative. */ + for(i = 0; i < N; ++i) { + /* Absolute value */ + t = a->coeffs[i] >> 31; + t = a->coeffs[i] - (t & 2*a->coeffs[i]); + + if(t >= B) { + DBENCH_STOP(*tsample); + return 1; + } + } + + DBENCH_STOP(*tsample); + return 0; +} + +/************************************************* +* Name: rej_uniform +* +* Description: Sample uniformly random coefficients in [0, Q-1] by +* performing rejection sampling on array of random bytes. +* +* Arguments: - int32_t *a: pointer to output array (allocated) +* - unsigned int len: number of coefficients to be sampled +* - const uint8_t *buf: array of random bytes +* - unsigned int buflen: length of array of random bytes +* +* Returns number of sampled coefficients. Can be smaller than len if not enough +* random bytes were given. +**************************************************/ +static unsigned int rej_uniform(int32_t *a, + unsigned int len, + const uint8_t *buf, + unsigned int buflen) +{ + unsigned int ctr, pos; + uint32_t t; + DBENCH_START(); + + ctr = pos = 0; + while(ctr < len && pos + 3 <= buflen) { + t = buf[pos++]; + t |= (uint32_t)buf[pos++] << 8; + t |= (uint32_t)buf[pos++] << 16; + t &= 0x7FFFFF; + + if(t < Q) + a[ctr++] = t; + } + + DBENCH_STOP(*tsample); + return ctr; +} + +/************************************************* +* Name: poly_uniform +* +* Description: Sample polynomial with uniformly random coefficients +* in [0,Q-1] by performing rejection sampling on the +* output stream of SHAKE128(seed|nonce) +* +* Arguments: - poly *a: pointer to output polynomial +* - const uint8_t seed[]: byte array with seed of length SEEDBYTES +* - uint16_t nonce: 2-byte nonce +**************************************************/ +#define POLY_UNIFORM_NBLOCKS ((768 + STREAM128_BLOCKBYTES - 1)/STREAM128_BLOCKBYTES) +void poly_uniform(poly *a, + const uint8_t seed[SEEDBYTES], + uint16_t nonce) +{ + unsigned int i, ctr, off; + unsigned int buflen = POLY_UNIFORM_NBLOCKS*STREAM128_BLOCKBYTES; + uint8_t buf[POLY_UNIFORM_NBLOCKS*STREAM128_BLOCKBYTES + 2]; + stream128_state state; + + stream128_init(&state, seed, nonce); + stream128_squeezeblocks(buf, POLY_UNIFORM_NBLOCKS, &state); + + ctr = rej_uniform(a->coeffs, N, buf, buflen); + + while(ctr < N) { + off = buflen % 3; + for(i = 0; i < off; ++i) + buf[i] = buf[buflen - off + i]; + + stream128_squeezeblocks(buf + off, 1, &state); + buflen = STREAM128_BLOCKBYTES + off; + ctr += rej_uniform(a->coeffs + ctr, N - ctr, buf, buflen); + } +} + +/************************************************* +* Name: rej_eta +* +* Description: Sample uniformly random coefficients in [-ETA, ETA] by +* performing rejection sampling on array of random bytes. +* +* Arguments: - int32_t *a: pointer to output array (allocated) +* - unsigned int len: number of coefficients to be sampled +* - const uint8_t *buf: array of random bytes +* - unsigned int buflen: length of array of random bytes +* +* Returns number of sampled coefficients. Can be smaller than len if not enough +* random bytes were given. +**************************************************/ +static unsigned int rej_eta(int32_t *a, + unsigned int len, + const uint8_t *buf, + unsigned int buflen) +{ + unsigned int ctr, pos; + uint32_t t0, t1; + DBENCH_START(); + + ctr = pos = 0; + while(ctr < len && pos < buflen) { + t0 = buf[pos] & 0x0F; + t1 = buf[pos++] >> 4; + +#if ETA == 2 + if(t0 < 15) { + t0 = t0 - (205*t0 >> 10)*5; + a[ctr++] = 2 - t0; + } + if(t1 < 15 && ctr < len) { + t1 = t1 - (205*t1 >> 10)*5; + a[ctr++] = 2 - t1; + } +#elif ETA == 4 + if(t0 < 9) + a[ctr++] = 4 - t0; + if(t1 < 9 && ctr < len) + a[ctr++] = 4 - t1; +#endif + } + + DBENCH_STOP(*tsample); + return ctr; +} + +/************************************************* +* Name: poly_uniform_eta +* +* Description: Sample polynomial with uniformly random coefficients +* in [-ETA,ETA] by performing rejection sampling on the +* output stream from SHAKE256(seed|nonce) +* +* Arguments: - poly *a: pointer to output polynomial +* - const uint8_t seed[]: byte array with seed of length CRHBYTES +* - uint16_t nonce: 2-byte nonce +**************************************************/ +#if ETA == 2 +#define POLY_UNIFORM_ETA_NBLOCKS ((136 + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES) +#elif ETA == 4 +#define POLY_UNIFORM_ETA_NBLOCKS ((227 + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES) +#endif +void poly_uniform_eta(poly *a, + const uint8_t seed[CRHBYTES], + uint16_t nonce) +{ + unsigned int ctr; + unsigned int buflen = POLY_UNIFORM_ETA_NBLOCKS*STREAM256_BLOCKBYTES; + uint8_t buf[POLY_UNIFORM_ETA_NBLOCKS*STREAM256_BLOCKBYTES]; + stream256_state state; + + stream256_init(&state, seed, nonce); + stream256_squeezeblocks(buf, POLY_UNIFORM_ETA_NBLOCKS, &state); + + ctr = rej_eta(a->coeffs, N, buf, buflen); + + while(ctr < N) { + stream256_squeezeblocks(buf, 1, &state); + ctr += rej_eta(a->coeffs + ctr, N - ctr, buf, STREAM256_BLOCKBYTES); + } +} + +/************************************************* +* Name: poly_uniform_gamma1m1 +* +* Description: Sample polynomial with uniformly random coefficients +* in [-(GAMMA1 - 1), GAMMA1] by unpacking output stream +* of SHAKE256(seed|nonce) +* +* Arguments: - poly *a: pointer to output polynomial +* - const uint8_t seed[]: byte array with seed of length CRHBYTES +* - uint16_t nonce: 16-bit nonce +**************************************************/ +#define POLY_UNIFORM_GAMMA1_NBLOCKS ((POLYZ_PACKEDBYTES + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES) +void poly_uniform_gamma1(poly *a, + const uint8_t seed[CRHBYTES], + uint16_t nonce) +{ + uint8_t buf[POLY_UNIFORM_GAMMA1_NBLOCKS*STREAM256_BLOCKBYTES]; + stream256_state state; + + stream256_init(&state, seed, nonce); + stream256_squeezeblocks(buf, POLY_UNIFORM_GAMMA1_NBLOCKS, &state); + polyz_unpack(a, buf); +} + +/************************************************* +* Name: challenge +* +* Description: Implementation of H. Samples polynomial with TAU nonzero +* coefficients in {-1,1} using the output stream of +* SHAKE256(seed). +* +* Arguments: - poly *c: pointer to output polynomial +* - const uint8_t mu[]: byte array containing seed of length CTILDEBYTES +**************************************************/ +void poly_challenge(poly *c, const uint8_t seed[CTILDEBYTES]) { + unsigned int i, b, pos; + uint64_t signs; + uint8_t buf[SHAKE256_RATE]; + keccak_state state; + + shake256_init(&state); + shake256_absorb(&state, seed, CTILDEBYTES); + shake256_finalize(&state); + shake256_squeezeblocks(buf, 1, &state); + + signs = 0; + for(i = 0; i < 8; ++i) + signs |= (uint64_t)buf[i] << 8*i; + pos = 8; + + for(i = 0; i < N; ++i) + c->coeffs[i] = 0; + for(i = N-TAU; i < N; ++i) { + do { + if(pos >= SHAKE256_RATE) { + shake256_squeezeblocks(buf, 1, &state); + pos = 0; + } + + b = buf[pos++]; + } while(b > i); + + c->coeffs[i] = c->coeffs[b]; + c->coeffs[b] = 1 - 2*(signs & 1); + signs >>= 1; + } +} + +/************************************************* +* Name: polyeta_pack +* +* Description: Bit-pack polynomial with coefficients in [-ETA,ETA]. +* +* Arguments: - uint8_t *r: pointer to output byte array with at least +* POLYETA_PACKEDBYTES bytes +* - const poly *a: pointer to input polynomial +**************************************************/ +void polyeta_pack(uint8_t *r, const poly *a) { + unsigned int i; + uint8_t t[8]; + DBENCH_START(); + +#if ETA == 2 + for(i = 0; i < N/8; ++i) { + t[0] = ETA - a->coeffs[8*i+0]; + t[1] = ETA - a->coeffs[8*i+1]; + t[2] = ETA - a->coeffs[8*i+2]; + t[3] = ETA - a->coeffs[8*i+3]; + t[4] = ETA - a->coeffs[8*i+4]; + t[5] = ETA - a->coeffs[8*i+5]; + t[6] = ETA - a->coeffs[8*i+6]; + t[7] = ETA - a->coeffs[8*i+7]; + + r[3*i+0] = (t[0] >> 0) | (t[1] << 3) | (t[2] << 6); + r[3*i+1] = (t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7); + r[3*i+2] = (t[5] >> 1) | (t[6] << 2) | (t[7] << 5); + } +#elif ETA == 4 + for(i = 0; i < N/2; ++i) { + t[0] = ETA - a->coeffs[2*i+0]; + t[1] = ETA - a->coeffs[2*i+1]; + r[i] = t[0] | (t[1] << 4); + } +#endif + + DBENCH_STOP(*tpack); +} + +/************************************************* +* Name: polyeta_unpack +* +* Description: Unpack polynomial with coefficients in [-ETA,ETA]. +* +* Arguments: - poly *r: pointer to output polynomial +* - const uint8_t *a: byte array with bit-packed polynomial +**************************************************/ +void polyeta_unpack(poly *r, const uint8_t *a) { + unsigned int i; + DBENCH_START(); + +#if ETA == 2 + for(i = 0; i < N/8; ++i) { + r->coeffs[8*i+0] = (a[3*i+0] >> 0) & 7; + r->coeffs[8*i+1] = (a[3*i+0] >> 3) & 7; + r->coeffs[8*i+2] = ((a[3*i+0] >> 6) | (a[3*i+1] << 2)) & 7; + r->coeffs[8*i+3] = (a[3*i+1] >> 1) & 7; + r->coeffs[8*i+4] = (a[3*i+1] >> 4) & 7; + r->coeffs[8*i+5] = ((a[3*i+1] >> 7) | (a[3*i+2] << 1)) & 7; + r->coeffs[8*i+6] = (a[3*i+2] >> 2) & 7; + r->coeffs[8*i+7] = (a[3*i+2] >> 5) & 7; + + r->coeffs[8*i+0] = ETA - r->coeffs[8*i+0]; + r->coeffs[8*i+1] = ETA - r->coeffs[8*i+1]; + r->coeffs[8*i+2] = ETA - r->coeffs[8*i+2]; + r->coeffs[8*i+3] = ETA - r->coeffs[8*i+3]; + r->coeffs[8*i+4] = ETA - r->coeffs[8*i+4]; + r->coeffs[8*i+5] = ETA - r->coeffs[8*i+5]; + r->coeffs[8*i+6] = ETA - r->coeffs[8*i+6]; + r->coeffs[8*i+7] = ETA - r->coeffs[8*i+7]; + } +#elif ETA == 4 + for(i = 0; i < N/2; ++i) { + r->coeffs[2*i+0] = a[i] & 0x0F; + r->coeffs[2*i+1] = a[i] >> 4; + r->coeffs[2*i+0] = ETA - r->coeffs[2*i+0]; + r->coeffs[2*i+1] = ETA - r->coeffs[2*i+1]; + } +#endif + + DBENCH_STOP(*tpack); +} + +/************************************************* +* Name: polyt1_pack +* +* Description: Bit-pack polynomial t1 with coefficients fitting in 10 bits. +* Input coefficients are assumed to be standard representatives. +* +* Arguments: - uint8_t *r: pointer to output byte array with at least +* POLYT1_PACKEDBYTES bytes +* - const poly *a: pointer to input polynomial +**************************************************/ +void polyt1_pack(uint8_t *r, const poly *a) { + unsigned int i; + DBENCH_START(); + + for(i = 0; i < N/4; ++i) { + r[5*i+0] = (a->coeffs[4*i+0] >> 0); + r[5*i+1] = (a->coeffs[4*i+0] >> 8) | (a->coeffs[4*i+1] << 2); + r[5*i+2] = (a->coeffs[4*i+1] >> 6) | (a->coeffs[4*i+2] << 4); + r[5*i+3] = (a->coeffs[4*i+2] >> 4) | (a->coeffs[4*i+3] << 6); + r[5*i+4] = (a->coeffs[4*i+3] >> 2); + } + + DBENCH_STOP(*tpack); +} + +/************************************************* +* Name: polyt1_unpack +* +* Description: Unpack polynomial t1 with 10-bit coefficients. +* Output coefficients are standard representatives. +* +* Arguments: - poly *r: pointer to output polynomial +* - const uint8_t *a: byte array with bit-packed polynomial +**************************************************/ +void polyt1_unpack(poly *r, const uint8_t *a) { + unsigned int i; + DBENCH_START(); + + for(i = 0; i < N/4; ++i) { + r->coeffs[4*i+0] = ((a[5*i+0] >> 0) | ((uint32_t)a[5*i+1] << 8)) & 0x3FF; + r->coeffs[4*i+1] = ((a[5*i+1] >> 2) | ((uint32_t)a[5*i+2] << 6)) & 0x3FF; + r->coeffs[4*i+2] = ((a[5*i+2] >> 4) | ((uint32_t)a[5*i+3] << 4)) & 0x3FF; + r->coeffs[4*i+3] = ((a[5*i+3] >> 6) | ((uint32_t)a[5*i+4] << 2)) & 0x3FF; + } + + DBENCH_STOP(*tpack); +} + +/************************************************* +* Name: polyt0_pack +* +* Description: Bit-pack polynomial t0 with coefficients in ]-2^{D-1}, 2^{D-1}]. +* +* Arguments: - uint8_t *r: pointer to output byte array with at least +* POLYT0_PACKEDBYTES bytes +* - const poly *a: pointer to input polynomial +**************************************************/ +void polyt0_pack(uint8_t *r, const poly *a) { + unsigned int i; + uint32_t t[8]; + DBENCH_START(); + + for(i = 0; i < N/8; ++i) { + t[0] = (1 << (D-1)) - a->coeffs[8*i+0]; + t[1] = (1 << (D-1)) - a->coeffs[8*i+1]; + t[2] = (1 << (D-1)) - a->coeffs[8*i+2]; + t[3] = (1 << (D-1)) - a->coeffs[8*i+3]; + t[4] = (1 << (D-1)) - a->coeffs[8*i+4]; + t[5] = (1 << (D-1)) - a->coeffs[8*i+5]; + t[6] = (1 << (D-1)) - a->coeffs[8*i+6]; + t[7] = (1 << (D-1)) - a->coeffs[8*i+7]; + + r[13*i+ 0] = t[0]; + r[13*i+ 1] = t[0] >> 8; + r[13*i+ 1] |= t[1] << 5; + r[13*i+ 2] = t[1] >> 3; + r[13*i+ 3] = t[1] >> 11; + r[13*i+ 3] |= t[2] << 2; + r[13*i+ 4] = t[2] >> 6; + r[13*i+ 4] |= t[3] << 7; + r[13*i+ 5] = t[3] >> 1; + r[13*i+ 6] = t[3] >> 9; + r[13*i+ 6] |= t[4] << 4; + r[13*i+ 7] = t[4] >> 4; + r[13*i+ 8] = t[4] >> 12; + r[13*i+ 8] |= t[5] << 1; + r[13*i+ 9] = t[5] >> 7; + r[13*i+ 9] |= t[6] << 6; + r[13*i+10] = t[6] >> 2; + r[13*i+11] = t[6] >> 10; + r[13*i+11] |= t[7] << 3; + r[13*i+12] = t[7] >> 5; + } + + DBENCH_STOP(*tpack); +} + +/************************************************* +* Name: polyt0_unpack +* +* Description: Unpack polynomial t0 with coefficients in ]-2^{D-1}, 2^{D-1}]. +* +* Arguments: - poly *r: pointer to output polynomial +* - const uint8_t *a: byte array with bit-packed polynomial +**************************************************/ +void polyt0_unpack(poly *r, const uint8_t *a) { + unsigned int i; + DBENCH_START(); + + for(i = 0; i < N/8; ++i) { + r->coeffs[8*i+0] = a[13*i+0]; + r->coeffs[8*i+0] |= (uint32_t)a[13*i+1] << 8; + r->coeffs[8*i+0] &= 0x1FFF; + + r->coeffs[8*i+1] = a[13*i+1] >> 5; + r->coeffs[8*i+1] |= (uint32_t)a[13*i+2] << 3; + r->coeffs[8*i+1] |= (uint32_t)a[13*i+3] << 11; + r->coeffs[8*i+1] &= 0x1FFF; + + r->coeffs[8*i+2] = a[13*i+3] >> 2; + r->coeffs[8*i+2] |= (uint32_t)a[13*i+4] << 6; + r->coeffs[8*i+2] &= 0x1FFF; + + r->coeffs[8*i+3] = a[13*i+4] >> 7; + r->coeffs[8*i+3] |= (uint32_t)a[13*i+5] << 1; + r->coeffs[8*i+3] |= (uint32_t)a[13*i+6] << 9; + r->coeffs[8*i+3] &= 0x1FFF; + + r->coeffs[8*i+4] = a[13*i+6] >> 4; + r->coeffs[8*i+4] |= (uint32_t)a[13*i+7] << 4; + r->coeffs[8*i+4] |= (uint32_t)a[13*i+8] << 12; + r->coeffs[8*i+4] &= 0x1FFF; + + r->coeffs[8*i+5] = a[13*i+8] >> 1; + r->coeffs[8*i+5] |= (uint32_t)a[13*i+9] << 7; + r->coeffs[8*i+5] &= 0x1FFF; + + r->coeffs[8*i+6] = a[13*i+9] >> 6; + r->coeffs[8*i+6] |= (uint32_t)a[13*i+10] << 2; + r->coeffs[8*i+6] |= (uint32_t)a[13*i+11] << 10; + r->coeffs[8*i+6] &= 0x1FFF; + + r->coeffs[8*i+7] = a[13*i+11] >> 3; + r->coeffs[8*i+7] |= (uint32_t)a[13*i+12] << 5; + r->coeffs[8*i+7] &= 0x1FFF; + + r->coeffs[8*i+0] = (1 << (D-1)) - r->coeffs[8*i+0]; + r->coeffs[8*i+1] = (1 << (D-1)) - r->coeffs[8*i+1]; + r->coeffs[8*i+2] = (1 << (D-1)) - r->coeffs[8*i+2]; + r->coeffs[8*i+3] = (1 << (D-1)) - r->coeffs[8*i+3]; + r->coeffs[8*i+4] = (1 << (D-1)) - r->coeffs[8*i+4]; + r->coeffs[8*i+5] = (1 << (D-1)) - r->coeffs[8*i+5]; + r->coeffs[8*i+6] = (1 << (D-1)) - r->coeffs[8*i+6]; + r->coeffs[8*i+7] = (1 << (D-1)) - r->coeffs[8*i+7]; + } + + DBENCH_STOP(*tpack); +} + +/************************************************* +* Name: polyz_pack +* +* Description: Bit-pack polynomial with coefficients +* in [-(GAMMA1 - 1), GAMMA1]. +* +* Arguments: - uint8_t *r: pointer to output byte array with at least +* POLYZ_PACKEDBYTES bytes +* - const poly *a: pointer to input polynomial +**************************************************/ +void polyz_pack(uint8_t *r, const poly *a) { + unsigned int i; + uint32_t t[4]; + DBENCH_START(); + +#if GAMMA1 == (1 << 17) + for(i = 0; i < N/4; ++i) { + t[0] = GAMMA1 - a->coeffs[4*i+0]; + t[1] = GAMMA1 - a->coeffs[4*i+1]; + t[2] = GAMMA1 - a->coeffs[4*i+2]; + t[3] = GAMMA1 - a->coeffs[4*i+3]; + + r[9*i+0] = t[0]; + r[9*i+1] = t[0] >> 8; + r[9*i+2] = t[0] >> 16; + r[9*i+2] |= t[1] << 2; + r[9*i+3] = t[1] >> 6; + r[9*i+4] = t[1] >> 14; + r[9*i+4] |= t[2] << 4; + r[9*i+5] = t[2] >> 4; + r[9*i+6] = t[2] >> 12; + r[9*i+6] |= t[3] << 6; + r[9*i+7] = t[3] >> 2; + r[9*i+8] = t[3] >> 10; + } +#elif GAMMA1 == (1 << 19) + for(i = 0; i < N/2; ++i) { + t[0] = GAMMA1 - a->coeffs[2*i+0]; + t[1] = GAMMA1 - a->coeffs[2*i+1]; + + r[5*i+0] = t[0]; + r[5*i+1] = t[0] >> 8; + r[5*i+2] = t[0] >> 16; + r[5*i+2] |= t[1] << 4; + r[5*i+3] = t[1] >> 4; + r[5*i+4] = t[1] >> 12; + } +#endif + + DBENCH_STOP(*tpack); +} + +/************************************************* +* Name: polyz_unpack +* +* Description: Unpack polynomial z with coefficients +* in [-(GAMMA1 - 1), GAMMA1]. +* +* Arguments: - poly *r: pointer to output polynomial +* - const uint8_t *a: byte array with bit-packed polynomial +**************************************************/ +void polyz_unpack(poly *r, const uint8_t *a) { + unsigned int i; + DBENCH_START(); + +#if GAMMA1 == (1 << 17) + for(i = 0; i < N/4; ++i) { + r->coeffs[4*i+0] = a[9*i+0]; + r->coeffs[4*i+0] |= (uint32_t)a[9*i+1] << 8; + r->coeffs[4*i+0] |= (uint32_t)a[9*i+2] << 16; + r->coeffs[4*i+0] &= 0x3FFFF; + + r->coeffs[4*i+1] = a[9*i+2] >> 2; + r->coeffs[4*i+1] |= (uint32_t)a[9*i+3] << 6; + r->coeffs[4*i+1] |= (uint32_t)a[9*i+4] << 14; + r->coeffs[4*i+1] &= 0x3FFFF; + + r->coeffs[4*i+2] = a[9*i+4] >> 4; + r->coeffs[4*i+2] |= (uint32_t)a[9*i+5] << 4; + r->coeffs[4*i+2] |= (uint32_t)a[9*i+6] << 12; + r->coeffs[4*i+2] &= 0x3FFFF; + + r->coeffs[4*i+3] = a[9*i+6] >> 6; + r->coeffs[4*i+3] |= (uint32_t)a[9*i+7] << 2; + r->coeffs[4*i+3] |= (uint32_t)a[9*i+8] << 10; + r->coeffs[4*i+3] &= 0x3FFFF; + + r->coeffs[4*i+0] = GAMMA1 - r->coeffs[4*i+0]; + r->coeffs[4*i+1] = GAMMA1 - r->coeffs[4*i+1]; + r->coeffs[4*i+2] = GAMMA1 - r->coeffs[4*i+2]; + r->coeffs[4*i+3] = GAMMA1 - r->coeffs[4*i+3]; + } +#elif GAMMA1 == (1 << 19) + for(i = 0; i < N/2; ++i) { + r->coeffs[2*i+0] = a[5*i+0]; + r->coeffs[2*i+0] |= (uint32_t)a[5*i+1] << 8; + r->coeffs[2*i+0] |= (uint32_t)a[5*i+2] << 16; + r->coeffs[2*i+0] &= 0xFFFFF; + + r->coeffs[2*i+1] = a[5*i+2] >> 4; + r->coeffs[2*i+1] |= (uint32_t)a[5*i+3] << 4; + r->coeffs[2*i+1] |= (uint32_t)a[5*i+4] << 12; + /* r->coeffs[2*i+1] &= 0xFFFFF; */ /* No effect, since we're anyway at 20 bits */ + + r->coeffs[2*i+0] = GAMMA1 - r->coeffs[2*i+0]; + r->coeffs[2*i+1] = GAMMA1 - r->coeffs[2*i+1]; + } +#endif + + DBENCH_STOP(*tpack); +} + +/************************************************* +* Name: polyw1_pack +* +* Description: Bit-pack polynomial w1 with coefficients in [0,15] or [0,43]. +* Input coefficients are assumed to be standard representatives. +* +* Arguments: - uint8_t *r: pointer to output byte array with at least +* POLYW1_PACKEDBYTES bytes +* - const poly *a: pointer to input polynomial +**************************************************/ +void polyw1_pack(uint8_t *r, const poly *a) { + unsigned int i; + DBENCH_START(); + +#if GAMMA2 == (Q-1)/88 + for(i = 0; i < N/4; ++i) { + r[3*i+0] = a->coeffs[4*i+0]; + r[3*i+0] |= a->coeffs[4*i+1] << 6; + r[3*i+1] = a->coeffs[4*i+1] >> 2; + r[3*i+1] |= a->coeffs[4*i+2] << 4; + r[3*i+2] = a->coeffs[4*i+2] >> 4; + r[3*i+2] |= a->coeffs[4*i+3] << 2; + } +#elif GAMMA2 == (Q-1)/32 + for(i = 0; i < N/2; ++i) + r[i] = a->coeffs[2*i+0] | (a->coeffs[2*i+1] << 4); +#endif + + DBENCH_STOP(*tpack); +} diff --git a/app/src/main/cpp/pq/mldsa65/poly.h b/app/src/main/cpp/pq/mldsa65/poly.h new file mode 100644 index 0000000..904baa1 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/poly.h @@ -0,0 +1,79 @@ +#ifndef POLY_H +#define POLY_H + +#include +#include "params.h" + +typedef struct { + int32_t coeffs[N]; +} poly; + +#define poly_reduce DILITHIUM_NAMESPACE(poly_reduce) +void poly_reduce(poly *a); +#define poly_caddq DILITHIUM_NAMESPACE(poly_caddq) +void poly_caddq(poly *a); + +#define poly_add DILITHIUM_NAMESPACE(poly_add) +void poly_add(poly *c, const poly *a, const poly *b); +#define poly_sub DILITHIUM_NAMESPACE(poly_sub) +void poly_sub(poly *c, const poly *a, const poly *b); +#define poly_shiftl DILITHIUM_NAMESPACE(poly_shiftl) +void poly_shiftl(poly *a); + +#define poly_ntt DILITHIUM_NAMESPACE(poly_ntt) +void poly_ntt(poly *a); +#define poly_invntt_tomont DILITHIUM_NAMESPACE(poly_invntt_tomont) +void poly_invntt_tomont(poly *a); +#define poly_pointwise_montgomery DILITHIUM_NAMESPACE(poly_pointwise_montgomery) +void poly_pointwise_montgomery(poly *c, const poly *a, const poly *b); + +#define poly_power2round DILITHIUM_NAMESPACE(poly_power2round) +void poly_power2round(poly *a1, poly *a0, const poly *a); +#define poly_decompose DILITHIUM_NAMESPACE(poly_decompose) +void poly_decompose(poly *a1, poly *a0, const poly *a); +#define poly_make_hint DILITHIUM_NAMESPACE(poly_make_hint) +unsigned int poly_make_hint(poly *h, const poly *a0, const poly *a1); +#define poly_use_hint DILITHIUM_NAMESPACE(poly_use_hint) +void poly_use_hint(poly *b, const poly *a, const poly *h); + +#define poly_chknorm DILITHIUM_NAMESPACE(poly_chknorm) +int poly_chknorm(const poly *a, int32_t B); +#define poly_uniform DILITHIUM_NAMESPACE(poly_uniform) +void poly_uniform(poly *a, + const uint8_t seed[SEEDBYTES], + uint16_t nonce); +#define poly_uniform_eta DILITHIUM_NAMESPACE(poly_uniform_eta) +void poly_uniform_eta(poly *a, + const uint8_t seed[CRHBYTES], + uint16_t nonce); +#define poly_uniform_gamma1 DILITHIUM_NAMESPACE(poly_uniform_gamma1) +void poly_uniform_gamma1(poly *a, + const uint8_t seed[CRHBYTES], + uint16_t nonce); +#define poly_challenge DILITHIUM_NAMESPACE(poly_challenge) +void poly_challenge(poly *c, const uint8_t seed[CTILDEBYTES]); + +#define polyeta_pack DILITHIUM_NAMESPACE(polyeta_pack) +void polyeta_pack(uint8_t *r, const poly *a); +#define polyeta_unpack DILITHIUM_NAMESPACE(polyeta_unpack) +void polyeta_unpack(poly *r, const uint8_t *a); + +#define polyt1_pack DILITHIUM_NAMESPACE(polyt1_pack) +void polyt1_pack(uint8_t *r, const poly *a); +#define polyt1_unpack DILITHIUM_NAMESPACE(polyt1_unpack) +void polyt1_unpack(poly *r, const uint8_t *a); + +#define polyt0_pack DILITHIUM_NAMESPACE(polyt0_pack) +void polyt0_pack(uint8_t *r, const poly *a); +#define polyt0_unpack DILITHIUM_NAMESPACE(polyt0_unpack) +void polyt0_unpack(poly *r, const uint8_t *a); + +#define polyz_pack DILITHIUM_NAMESPACE(polyz_pack) +void polyz_pack(uint8_t *r, const poly *a); +#define polyz_unpack DILITHIUM_NAMESPACE(polyz_unpack) +void polyz_unpack(poly *r, const uint8_t *a); + +#define polyw1_pack DILITHIUM_NAMESPACE(polyw1_pack) +void polyw1_pack(uint8_t *r, const poly *a); + +#endif diff --git a/app/src/main/cpp/pq/mldsa65/polyvec.c b/app/src/main/cpp/pq/mldsa65/polyvec.c new file mode 100644 index 0000000..241f618 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/polyvec.c @@ -0,0 +1,389 @@ +#include +#include "params.h" +#include "polyvec.h" +#include "poly.h" + +/************************************************* +* Name: expand_mat +* +* Description: Implementation of ExpandA. Generates matrix A with uniformly +* random coefficients a_{i,j} by performing rejection +* sampling on the output stream of SHAKE128(rho|j|i) +* +* Arguments: - polyvecl mat[K]: output matrix +* - const uint8_t rho[]: byte array containing seed rho +**************************************************/ +void polyvec_matrix_expand(polyvecl mat[K], const uint8_t rho[SEEDBYTES]) { + unsigned int i, j; + + for(i = 0; i < K; ++i) + for(j = 0; j < L; ++j) + poly_uniform(&mat[i].vec[j], rho, (i << 8) + j); +} + +void polyvec_matrix_pointwise_montgomery(polyveck *t, const polyvecl mat[K], const polyvecl *v) { + unsigned int i; + + for(i = 0; i < K; ++i) + polyvecl_pointwise_acc_montgomery(&t->vec[i], &mat[i], v); +} + +/**************************************************************/ +/************ Vectors of polynomials of length L **************/ +/**************************************************************/ + +void polyvecl_uniform_eta(polyvecl *v, const uint8_t seed[CRHBYTES], uint16_t nonce) { + unsigned int i; + + for(i = 0; i < L; ++i) + poly_uniform_eta(&v->vec[i], seed, nonce++); +} + +void polyvecl_uniform_gamma1(polyvecl *v, const uint8_t seed[CRHBYTES], uint16_t nonce) { + unsigned int i; + + for(i = 0; i < L; ++i) + poly_uniform_gamma1(&v->vec[i], seed, L*nonce + i); +} + +void polyvecl_reduce(polyvecl *v) { + unsigned int i; + + for(i = 0; i < L; ++i) + poly_reduce(&v->vec[i]); +} + +/************************************************* +* Name: polyvecl_add +* +* Description: Add vectors of polynomials of length L. +* No modular reduction is performed. +* +* Arguments: - polyvecl *w: pointer to output vector +* - const polyvecl *u: pointer to first summand +* - const polyvecl *v: pointer to second summand +**************************************************/ +void polyvecl_add(polyvecl *w, const polyvecl *u, const polyvecl *v) { + unsigned int i; + + for(i = 0; i < L; ++i) + poly_add(&w->vec[i], &u->vec[i], &v->vec[i]); +} + +/************************************************* +* Name: polyvecl_ntt +* +* Description: Forward NTT of all polynomials in vector of length L. Output +* coefficients can be up to 16*Q larger than input coefficients. +* +* Arguments: - polyvecl *v: pointer to input/output vector +**************************************************/ +void polyvecl_ntt(polyvecl *v) { + unsigned int i; + + for(i = 0; i < L; ++i) + poly_ntt(&v->vec[i]); +} + +void polyvecl_invntt_tomont(polyvecl *v) { + unsigned int i; + + for(i = 0; i < L; ++i) + poly_invntt_tomont(&v->vec[i]); +} + +void polyvecl_pointwise_poly_montgomery(polyvecl *r, const poly *a, const polyvecl *v) { + unsigned int i; + + for(i = 0; i < L; ++i) + poly_pointwise_montgomery(&r->vec[i], a, &v->vec[i]); +} + +/************************************************* +* Name: polyvecl_pointwise_acc_montgomery +* +* Description: Pointwise multiply vectors of polynomials of length L, multiply +* resulting vector by 2^{-32} and add (accumulate) polynomials +* in it. Input/output vectors are in NTT domain representation. +* +* Arguments: - poly *w: output polynomial +* - const polyvecl *u: pointer to first input vector +* - const polyvecl *v: pointer to second input vector +**************************************************/ +void polyvecl_pointwise_acc_montgomery(poly *w, + const polyvecl *u, + const polyvecl *v) +{ + unsigned int i; + poly t; + + poly_pointwise_montgomery(w, &u->vec[0], &v->vec[0]); + for(i = 1; i < L; ++i) { + poly_pointwise_montgomery(&t, &u->vec[i], &v->vec[i]); + poly_add(w, w, &t); + } +} + +/************************************************* +* Name: polyvecl_chknorm +* +* Description: Check infinity norm of polynomials in vector of length L. +* Assumes input polyvecl to be reduced by polyvecl_reduce(). +* +* Arguments: - const polyvecl *v: pointer to vector +* - int32_t B: norm bound +* +* Returns 0 if norm of all polynomials is strictly smaller than B <= (Q-1)/8 +* and 1 otherwise. +**************************************************/ +int polyvecl_chknorm(const polyvecl *v, int32_t bound) { + unsigned int i; + + for(i = 0; i < L; ++i) + if(poly_chknorm(&v->vec[i], bound)) + return 1; + + return 0; +} + +/**************************************************************/ +/************ Vectors of polynomials of length K **************/ +/**************************************************************/ + +void polyveck_uniform_eta(polyveck *v, const uint8_t seed[CRHBYTES], uint16_t nonce) { + unsigned int i; + + for(i = 0; i < K; ++i) + poly_uniform_eta(&v->vec[i], seed, nonce++); +} + +/************************************************* +* Name: polyveck_reduce +* +* Description: Reduce coefficients of polynomials in vector of length K +* to representatives in [-6283008,6283008]. +* +* Arguments: - polyveck *v: pointer to input/output vector +**************************************************/ +void polyveck_reduce(polyveck *v) { + unsigned int i; + + for(i = 0; i < K; ++i) + poly_reduce(&v->vec[i]); +} + +/************************************************* +* Name: polyveck_caddq +* +* Description: For all coefficients of polynomials in vector of length K +* add Q if coefficient is negative. +* +* Arguments: - polyveck *v: pointer to input/output vector +**************************************************/ +void polyveck_caddq(polyveck *v) { + unsigned int i; + + for(i = 0; i < K; ++i) + poly_caddq(&v->vec[i]); +} + +/************************************************* +* Name: polyveck_add +* +* Description: Add vectors of polynomials of length K. +* No modular reduction is performed. +* +* Arguments: - polyveck *w: pointer to output vector +* - const polyveck *u: pointer to first summand +* - const polyveck *v: pointer to second summand +**************************************************/ +void polyveck_add(polyveck *w, const polyveck *u, const polyveck *v) { + unsigned int i; + + for(i = 0; i < K; ++i) + poly_add(&w->vec[i], &u->vec[i], &v->vec[i]); +} + +/************************************************* +* Name: polyveck_sub +* +* Description: Subtract vectors of polynomials of length K. +* No modular reduction is performed. +* +* Arguments: - polyveck *w: pointer to output vector +* - const polyveck *u: pointer to first input vector +* - const polyveck *v: pointer to second input vector to be +* subtracted from first input vector +**************************************************/ +void polyveck_sub(polyveck *w, const polyveck *u, const polyveck *v) { + unsigned int i; + + for(i = 0; i < K; ++i) + poly_sub(&w->vec[i], &u->vec[i], &v->vec[i]); +} + +/************************************************* +* Name: polyveck_shiftl +* +* Description: Multiply vector of polynomials of Length K by 2^D without modular +* reduction. Assumes input coefficients to be less than 2^{31-D}. +* +* Arguments: - polyveck *v: pointer to input/output vector +**************************************************/ +void polyveck_shiftl(polyveck *v) { + unsigned int i; + + for(i = 0; i < K; ++i) + poly_shiftl(&v->vec[i]); +} + +/************************************************* +* Name: polyveck_ntt +* +* Description: Forward NTT of all polynomials in vector of length K. Output +* coefficients can be up to 16*Q larger than input coefficients. +* +* Arguments: - polyveck *v: pointer to input/output vector +**************************************************/ +void polyveck_ntt(polyveck *v) { + unsigned int i; + + for(i = 0; i < K; ++i) + poly_ntt(&v->vec[i]); +} + +/************************************************* +* Name: polyveck_invntt_tomont +* +* Description: Inverse NTT and multiplication by 2^{32} of polynomials +* in vector of length K. Input coefficients need to be less +* than 2*Q. +* +* Arguments: - polyveck *v: pointer to input/output vector +**************************************************/ +void polyveck_invntt_tomont(polyveck *v) { + unsigned int i; + + for(i = 0; i < K; ++i) + poly_invntt_tomont(&v->vec[i]); +} + +void polyveck_pointwise_poly_montgomery(polyveck *r, const poly *a, const polyveck *v) { + unsigned int i; + + for(i = 0; i < K; ++i) + poly_pointwise_montgomery(&r->vec[i], a, &v->vec[i]); +} + + +/************************************************* +* Name: polyveck_chknorm +* +* Description: Check infinity norm of polynomials in vector of length K. +* Assumes input polyveck to be reduced by polyveck_reduce(). +* +* Arguments: - const polyveck *v: pointer to vector +* - int32_t B: norm bound +* +* Returns 0 if norm of all polynomials are strictly smaller than B <= (Q-1)/8 +* and 1 otherwise. +**************************************************/ +int polyveck_chknorm(const polyveck *v, int32_t bound) { + unsigned int i; + + for(i = 0; i < K; ++i) + if(poly_chknorm(&v->vec[i], bound)) + return 1; + + return 0; +} + +/************************************************* +* Name: polyveck_power2round +* +* Description: For all coefficients a of polynomials in vector of length K, +* compute a0, a1 such that a mod^+ Q = a1*2^D + a0 +* with -2^{D-1} < a0 <= 2^{D-1}. Assumes coefficients to be +* standard representatives. +* +* Arguments: - polyveck *v1: pointer to output vector of polynomials with +* coefficients a1 +* - polyveck *v0: pointer to output vector of polynomials with +* coefficients a0 +* - const polyveck *v: pointer to input vector +**************************************************/ +void polyveck_power2round(polyveck *v1, polyveck *v0, const polyveck *v) { + unsigned int i; + + for(i = 0; i < K; ++i) + poly_power2round(&v1->vec[i], &v0->vec[i], &v->vec[i]); +} + +/************************************************* +* Name: polyveck_decompose +* +* Description: For all coefficients a of polynomials in vector of length K, +* compute high and low bits a0, a1 such a mod^+ Q = a1*ALPHA + a0 +* with -ALPHA/2 < a0 <= ALPHA/2 except a1 = (Q-1)/ALPHA where we +* set a1 = 0 and -ALPHA/2 <= a0 = a mod Q - Q < 0. +* Assumes coefficients to be standard representatives. +* +* Arguments: - polyveck *v1: pointer to output vector of polynomials with +* coefficients a1 +* - polyveck *v0: pointer to output vector of polynomials with +* coefficients a0 +* - const polyveck *v: pointer to input vector +**************************************************/ +void polyveck_decompose(polyveck *v1, polyveck *v0, const polyveck *v) { + unsigned int i; + + for(i = 0; i < K; ++i) + poly_decompose(&v1->vec[i], &v0->vec[i], &v->vec[i]); +} + +/************************************************* +* Name: polyveck_make_hint +* +* Description: Compute hint vector. +* +* Arguments: - polyveck *h: pointer to output vector +* - const polyveck *v0: pointer to low part of input vector +* - const polyveck *v1: pointer to high part of input vector +* +* Returns number of 1 bits. +**************************************************/ +unsigned int polyveck_make_hint(polyveck *h, + const polyveck *v0, + const polyveck *v1) +{ + unsigned int i, s = 0; + + for(i = 0; i < K; ++i) + s += poly_make_hint(&h->vec[i], &v0->vec[i], &v1->vec[i]); + + return s; +} + +/************************************************* +* Name: polyveck_use_hint +* +* Description: Use hint vector to correct the high bits of input vector. +* +* Arguments: - polyveck *w: pointer to output vector of polynomials with +* corrected high bits +* - const polyveck *u: pointer to input vector +* - const polyveck *h: pointer to input hint vector +**************************************************/ +void polyveck_use_hint(polyveck *w, const polyveck *u, const polyveck *h) { + unsigned int i; + + for(i = 0; i < K; ++i) + poly_use_hint(&w->vec[i], &u->vec[i], &h->vec[i]); +} + +void polyveck_pack_w1(uint8_t r[K*POLYW1_PACKEDBYTES], const polyveck *w1) { + unsigned int i; + + for(i = 0; i < K; ++i) + polyw1_pack(&r[i*POLYW1_PACKEDBYTES], &w1->vec[i]); +} diff --git a/app/src/main/cpp/pq/mldsa65/polyvec.h b/app/src/main/cpp/pq/mldsa65/polyvec.h new file mode 100644 index 0000000..615ac52 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/polyvec.h @@ -0,0 +1,93 @@ +#ifndef POLYVEC_H +#define POLYVEC_H + +#include +#include "params.h" +#include "poly.h" + +/* Vectors of polynomials of length L */ +typedef struct { + poly vec[L]; +} polyvecl; + +#define polyvecl_uniform_eta DILITHIUM_NAMESPACE(polyvecl_uniform_eta) +void polyvecl_uniform_eta(polyvecl *v, const uint8_t seed[CRHBYTES], uint16_t nonce); + +#define polyvecl_uniform_gamma1 DILITHIUM_NAMESPACE(polyvecl_uniform_gamma1) +void polyvecl_uniform_gamma1(polyvecl *v, const uint8_t seed[CRHBYTES], uint16_t nonce); + +#define polyvecl_reduce DILITHIUM_NAMESPACE(polyvecl_reduce) +void polyvecl_reduce(polyvecl *v); + +#define polyvecl_add DILITHIUM_NAMESPACE(polyvecl_add) +void polyvecl_add(polyvecl *w, const polyvecl *u, const polyvecl *v); + +#define polyvecl_ntt DILITHIUM_NAMESPACE(polyvecl_ntt) +void polyvecl_ntt(polyvecl *v); +#define polyvecl_invntt_tomont DILITHIUM_NAMESPACE(polyvecl_invntt_tomont) +void polyvecl_invntt_tomont(polyvecl *v); +#define polyvecl_pointwise_poly_montgomery DILITHIUM_NAMESPACE(polyvecl_pointwise_poly_montgomery) +void polyvecl_pointwise_poly_montgomery(polyvecl *r, const poly *a, const polyvecl *v); +#define polyvecl_pointwise_acc_montgomery \ + DILITHIUM_NAMESPACE(polyvecl_pointwise_acc_montgomery) +void polyvecl_pointwise_acc_montgomery(poly *w, + const polyvecl *u, + const polyvecl *v); + + +#define polyvecl_chknorm DILITHIUM_NAMESPACE(polyvecl_chknorm) +int polyvecl_chknorm(const polyvecl *v, int32_t B); + + + +/* Vectors of polynomials of length K */ +typedef struct { + poly vec[K]; +} polyveck; + +#define polyveck_uniform_eta DILITHIUM_NAMESPACE(polyveck_uniform_eta) +void polyveck_uniform_eta(polyveck *v, const uint8_t seed[CRHBYTES], uint16_t nonce); + +#define polyveck_reduce DILITHIUM_NAMESPACE(polyveck_reduce) +void polyveck_reduce(polyveck *v); +#define polyveck_caddq DILITHIUM_NAMESPACE(polyveck_caddq) +void polyveck_caddq(polyveck *v); + +#define polyveck_add DILITHIUM_NAMESPACE(polyveck_add) +void polyveck_add(polyveck *w, const polyveck *u, const polyveck *v); +#define polyveck_sub DILITHIUM_NAMESPACE(polyveck_sub) +void polyveck_sub(polyveck *w, const polyveck *u, const polyveck *v); +#define polyveck_shiftl DILITHIUM_NAMESPACE(polyveck_shiftl) +void polyveck_shiftl(polyveck *v); + +#define polyveck_ntt DILITHIUM_NAMESPACE(polyveck_ntt) +void polyveck_ntt(polyveck *v); +#define polyveck_invntt_tomont DILITHIUM_NAMESPACE(polyveck_invntt_tomont) +void polyveck_invntt_tomont(polyveck *v); +#define polyveck_pointwise_poly_montgomery DILITHIUM_NAMESPACE(polyveck_pointwise_poly_montgomery) +void polyveck_pointwise_poly_montgomery(polyveck *r, const poly *a, const polyveck *v); + +#define polyveck_chknorm DILITHIUM_NAMESPACE(polyveck_chknorm) +int polyveck_chknorm(const polyveck *v, int32_t B); + +#define polyveck_power2round DILITHIUM_NAMESPACE(polyveck_power2round) +void polyveck_power2round(polyveck *v1, polyveck *v0, const polyveck *v); +#define polyveck_decompose DILITHIUM_NAMESPACE(polyveck_decompose) +void polyveck_decompose(polyveck *v1, polyveck *v0, const polyveck *v); +#define polyveck_make_hint DILITHIUM_NAMESPACE(polyveck_make_hint) +unsigned int polyveck_make_hint(polyveck *h, + const polyveck *v0, + const polyveck *v1); +#define polyveck_use_hint DILITHIUM_NAMESPACE(polyveck_use_hint) +void polyveck_use_hint(polyveck *w, const polyveck *v, const polyveck *h); + +#define polyveck_pack_w1 DILITHIUM_NAMESPACE(polyveck_pack_w1) +void polyveck_pack_w1(uint8_t r[K*POLYW1_PACKEDBYTES], const polyveck *w1); + +#define polyvec_matrix_expand DILITHIUM_NAMESPACE(polyvec_matrix_expand) +void polyvec_matrix_expand(polyvecl mat[K], const uint8_t rho[SEEDBYTES]); + +#define polyvec_matrix_pointwise_montgomery DILITHIUM_NAMESPACE(polyvec_matrix_pointwise_montgomery) +void polyvec_matrix_pointwise_montgomery(polyveck *t, const polyvecl mat[K], const polyvecl *v); + +#endif diff --git a/app/src/main/cpp/pq/mldsa65/randombytes.h b/app/src/main/cpp/pq/mldsa65/randombytes.h new file mode 100644 index 0000000..3308bdc --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/randombytes.h @@ -0,0 +1,15 @@ +#ifndef VEXTA_PQ_RANDOMBYTES_H +#define VEXTA_PQ_RANDOMBYTES_H +#include +#include +#ifdef __cplusplus +extern "C" { +#endif +/* VEXTA: unified prototype. Definition lives in pq_randombytes.cpp and is + backed by Bitcoin's CSPRNG (GetStrongRandBytes). The upstream test-only + randombytes.c files were deleted — they must never ship in a wallet. */ +void randombytes(uint8_t *out, size_t outlen); +#ifdef __cplusplus +} +#endif +#endif diff --git a/app/src/main/cpp/pq/mldsa65/reduce.c b/app/src/main/cpp/pq/mldsa65/reduce.c new file mode 100644 index 0000000..8479a22 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/reduce.c @@ -0,0 +1,69 @@ +#include +#include "params.h" +#include "reduce.h" + +/************************************************* +* Name: montgomery_reduce +* +* Description: For finite field element a with -2^{31}Q <= a <= Q*2^31, +* compute r \equiv a*2^{-32} (mod Q) such that -Q < r < Q. +* +* Arguments: - int64_t: finite field element a +* +* Returns r. +**************************************************/ +int32_t montgomery_reduce(int64_t a) { + int32_t t; + + t = (int64_t)(int32_t)a*QINV; + t = (a - (int64_t)t*Q) >> 32; + return t; +} + +/************************************************* +* Name: reduce32 +* +* Description: For finite field element a with a <= 2^{31} - 2^{22} - 1, +* compute r \equiv a (mod Q) such that -6283008 <= r <= 6283008. +* +* Arguments: - int32_t: finite field element a +* +* Returns r. +**************************************************/ +int32_t reduce32(int32_t a) { + int32_t t; + + t = (a + (1 << 22)) >> 23; + t = a - t*Q; + return t; +} + +/************************************************* +* Name: caddq +* +* Description: Add Q if input coefficient is negative. +* +* Arguments: - int32_t: finite field element a +* +* Returns r. +**************************************************/ +int32_t caddq(int32_t a) { + a += (a >> 31) & Q; + return a; +} + +/************************************************* +* Name: freeze +* +* Description: For finite field element a, compute standard +* representative r = a mod^+ Q. +* +* Arguments: - int32_t: finite field element a +* +* Returns r. +**************************************************/ +int32_t freeze(int32_t a) { + a = reduce32(a); + a = caddq(a); + return a; +} diff --git a/app/src/main/cpp/pq/mldsa65/reduce.h b/app/src/main/cpp/pq/mldsa65/reduce.h new file mode 100644 index 0000000..26d9b4e --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/reduce.h @@ -0,0 +1,22 @@ +#ifndef REDUCE_H +#define REDUCE_H + +#include +#include "params.h" + +#define MONT -4186625 // 2^32 % Q +#define QINV 58728449 // q^(-1) mod 2^32 + +#define montgomery_reduce DILITHIUM_NAMESPACE(montgomery_reduce) +int32_t montgomery_reduce(int64_t a); + +#define reduce32 DILITHIUM_NAMESPACE(reduce32) +int32_t reduce32(int32_t a); + +#define caddq DILITHIUM_NAMESPACE(caddq) +int32_t caddq(int32_t a); + +#define freeze DILITHIUM_NAMESPACE(freeze) +int32_t freeze(int32_t a); + +#endif diff --git a/app/src/main/cpp/pq/mldsa65/rounding.c b/app/src/main/cpp/pq/mldsa65/rounding.c new file mode 100644 index 0000000..889f0a2 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/rounding.c @@ -0,0 +1,102 @@ +#include +#include "params.h" +#include "rounding.h" + +/************************************************* +* Name: power2round +* +* Description: For finite field element a, compute a0, a1 such that +* a mod^+ Q = a1*2^D + a0 with -2^{D-1} < a0 <= 2^{D-1}. +* Assumes a to be standard representative. +* +* Arguments: - int32_t a: input element +* - int32_t *a0: pointer to output element a0 +* +* Returns a1. +**************************************************/ +int32_t power2round(int32_t *a0, int32_t a) { + int32_t a1; + + a1 = (a + (1 << (D-1)) - 1) >> D; + *a0 = a - (a1 << D); + return a1; +} + +/************************************************* +* Name: decompose +* +* Description: For finite field element a, compute high and low bits a0, a1 such +* that a mod^+ Q = a1*ALPHA + a0 with -ALPHA/2 < a0 <= ALPHA/2 except +* if a1 = (Q-1)/ALPHA where we set a1 = 0 and +* -ALPHA/2 <= a0 = a mod^+ Q - Q < 0. Assumes a to be standard +* representative. +* +* Arguments: - int32_t a: input element +* - int32_t *a0: pointer to output element a0 +* +* Returns a1. +**************************************************/ +int32_t decompose(int32_t *a0, int32_t a) { + int32_t a1; + + a1 = (a + 127) >> 7; +#if GAMMA2 == (Q-1)/32 + a1 = (a1*1025 + (1 << 21)) >> 22; + a1 &= 15; +#elif GAMMA2 == (Q-1)/88 + a1 = (a1*11275 + (1 << 23)) >> 24; + a1 ^= ((43 - a1) >> 31) & a1; +#endif + + *a0 = a - a1*2*GAMMA2; + *a0 -= (((Q-1)/2 - *a0) >> 31) & Q; + return a1; +} + +/************************************************* +* Name: make_hint +* +* Description: Compute hint bit indicating whether the low bits of the +* input element overflow into the high bits. +* +* Arguments: - int32_t a0: low bits of input element +* - int32_t a1: high bits of input element +* +* Returns 1 if overflow. +**************************************************/ +unsigned int make_hint(int32_t a0, int32_t a1) { + if(a0 > GAMMA2 || a0 < -GAMMA2 || (a0 == -GAMMA2 && a1 != 0)) + return 1; + + return 0; +} + +/************************************************* +* Name: use_hint +* +* Description: Correct high bits according to hint. +* +* Arguments: - int32_t a: input element +* - unsigned int hint: hint bit +* +* Returns corrected high bits. +**************************************************/ +int32_t use_hint(int32_t a, unsigned int hint) { + int32_t a0, a1; + + a1 = decompose(&a0, a); + if(hint == 0) + return a1; + +#if GAMMA2 == (Q-1)/32 + if(a0 > 0) + return (a1 + 1) & 15; + else + return (a1 - 1) & 15; +#elif GAMMA2 == (Q-1)/88 + if(a0 > 0) + return (a1 == 43) ? 0 : a1 + 1; + else + return (a1 == 0) ? 43 : a1 - 1; +#endif +} diff --git a/app/src/main/cpp/pq/mldsa65/rounding.h b/app/src/main/cpp/pq/mldsa65/rounding.h new file mode 100644 index 0000000..b72e8e8 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/rounding.h @@ -0,0 +1,19 @@ +#ifndef ROUNDING_H +#define ROUNDING_H + +#include +#include "params.h" + +#define power2round DILITHIUM_NAMESPACE(power2round) +int32_t power2round(int32_t *a0, int32_t a); + +#define decompose DILITHIUM_NAMESPACE(decompose) +int32_t decompose(int32_t *a0, int32_t a); + +#define make_hint DILITHIUM_NAMESPACE(make_hint) +unsigned int make_hint(int32_t a0, int32_t a1); + +#define use_hint DILITHIUM_NAMESPACE(use_hint) +int32_t use_hint(int32_t a, unsigned int hint); + +#endif diff --git a/app/src/main/cpp/pq/mldsa65/sign.c b/app/src/main/cpp/pq/mldsa65/sign.c new file mode 100644 index 0000000..3429797 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/sign.c @@ -0,0 +1,477 @@ +#include +#include "params.h" +#include "sign.h" +#include "packing.h" +#include "polyvec.h" +#include "poly.h" +#include "randombytes.h" +#include "symmetric.h" +#include "fips202.h" + +#ifndef VEXTA_PQ_VERIFY_ONLY +/************************************************* +* Name: crypto_sign_keypair +* +* Description: Generates public and private key. +* +* Arguments: - uint8_t *pk: pointer to output public key (allocated +* array of CRYPTO_PUBLICKEYBYTES bytes) +* - uint8_t *sk: pointer to output private key (allocated +* array of CRYPTO_SECRETKEYBYTES bytes) +* +* Returns 0 (success) +**************************************************/ +int crypto_sign_keypair_from_seed(uint8_t *pk, uint8_t *sk, const uint8_t *seed) { + uint8_t seedbuf[2*SEEDBYTES + CRHBYTES]; + uint8_t tr[TRBYTES]; + const uint8_t *rho, *rhoprime, *key; + polyvecl mat[K]; + polyvecl s1, s1hat; + polyveck s2, t1, t0; + for (int _i=0;_i OMEGA) + goto rej; + + /* Write signature */ + pack_sig(sig, sig, &z, &h); + *siglen = CRYPTO_BYTES; + return 0; +} + +/************************************************* +* Name: crypto_sign_signature +* +* Description: Computes signature. +* +* Arguments: - uint8_t *sig: pointer to output signature (of length CRYPTO_BYTES) +* - size_t *siglen: pointer to output length of signature +* - uint8_t *m: pointer to message to be signed +* - size_t mlen: length of message +* - uint8_t *ctx: pointer to contex string +* - size_t ctxlen: length of contex string +* - uint8_t *sk: pointer to bit-packed secret key +* +* Returns 0 (success) or -1 (context string too long) +**************************************************/ +int crypto_sign_signature(uint8_t *sig, + size_t *siglen, + const uint8_t *m, + size_t mlen, + const uint8_t *ctx, + size_t ctxlen, + const uint8_t *sk) +{ + size_t i; + uint8_t pre[257]; + uint8_t rnd[RNDBYTES]; + + if(ctxlen > 255) + return -1; + + /* Prepare pre = (0, ctxlen, ctx) */ + pre[0] = 0; + pre[1] = ctxlen; + for(i = 0; i < ctxlen; i++) + pre[2 + i] = ctx[i]; + +#ifdef DILITHIUM_RANDOMIZED_SIGNING + randombytes(rnd, RNDBYTES); +#else + for(i=0;i 255) + return -1; + + pre[0] = 0; + pre[1] = ctxlen; + for(i = 0; i < ctxlen; i++) + pre[2 + i] = ctx[i]; + + return crypto_sign_verify_internal(sig,siglen,m,mlen,pre,2+ctxlen,pk); +} + +/************************************************* +* Name: crypto_sign_open +* +* Description: Verify signed message. +* +* Arguments: - uint8_t *m: pointer to output message (allocated +* array with smlen bytes), can be equal to sm +* - size_t *mlen: pointer to output length of message +* - const uint8_t *sm: pointer to signed message +* - size_t smlen: length of signed message +* - const uint8_t *ctx: pointer to context tring +* - size_t ctxlen: length of context string +* - const uint8_t *pk: pointer to bit-packed public key +* +* Returns 0 if signed message could be verified correctly and -1 otherwise +**************************************************/ +int crypto_sign_open(uint8_t *m, + size_t *mlen, + const uint8_t *sm, + size_t smlen, + const uint8_t *ctx, + size_t ctxlen, + const uint8_t *pk) +{ + size_t i; + + if(smlen < CRYPTO_BYTES) + goto badsig; + + *mlen = smlen - CRYPTO_BYTES; + if(crypto_sign_verify(sm, CRYPTO_BYTES, sm + CRYPTO_BYTES, *mlen, ctx, ctxlen, pk)) + goto badsig; + else { + /* All good, copy msg, return 0 */ + for(i = 0; i < *mlen; ++i) + m[i] = sm[CRYPTO_BYTES + i]; + return 0; + } + +badsig: + /* Signature verification failed */ + *mlen = 0; + for(i = 0; i < smlen; ++i) + m[i] = 0; + + return -1; +} diff --git a/app/src/main/cpp/pq/mldsa65/sign.h b/app/src/main/cpp/pq/mldsa65/sign.h new file mode 100644 index 0000000..2741e8f --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/sign.h @@ -0,0 +1,56 @@ +#ifndef SIGN_H +#define SIGN_H + +#include +#include +#include "params.h" +#include "polyvec.h" +#include "poly.h" + +#define crypto_sign_keypair DILITHIUM_NAMESPACE(keypair) +int crypto_sign_keypair(uint8_t *pk, uint8_t *sk); + +#define crypto_sign_signature_internal DILITHIUM_NAMESPACE(signature_internal) +int crypto_sign_signature_internal(uint8_t *sig, + size_t *siglen, + const uint8_t *m, + size_t mlen, + const uint8_t *pre, + size_t prelen, + const uint8_t rnd[RNDBYTES], + const uint8_t *sk); + +#define crypto_sign_signature DILITHIUM_NAMESPACE(signature) +int crypto_sign_signature(uint8_t *sig, size_t *siglen, + const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *sk); + +#define crypto_sign DILITHIUM_NAMESPACETOP +int crypto_sign(uint8_t *sm, size_t *smlen, + const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *sk); + +#define crypto_sign_verify_internal DILITHIUM_NAMESPACE(verify_internal) +int crypto_sign_verify_internal(const uint8_t *sig, + size_t siglen, + const uint8_t *m, + size_t mlen, + const uint8_t *pre, + size_t prelen, + const uint8_t *pk); + +#define crypto_sign_verify DILITHIUM_NAMESPACE(verify) +int crypto_sign_verify(const uint8_t *sig, size_t siglen, + const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *pk); + +#define crypto_sign_open DILITHIUM_NAMESPACE(open) +int crypto_sign_open(uint8_t *m, size_t *mlen, + const uint8_t *sm, size_t smlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t *pk); + +#endif diff --git a/app/src/main/cpp/pq/mldsa65/symmetric-shake.c b/app/src/main/cpp/pq/mldsa65/symmetric-shake.c new file mode 100644 index 0000000..11ec09c --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/symmetric-shake.c @@ -0,0 +1,28 @@ +#include +#include "params.h" +#include "symmetric.h" +#include "fips202.h" + +void dilithium_shake128_stream_init(keccak_state *state, const uint8_t seed[SEEDBYTES], uint16_t nonce) +{ + uint8_t t[2]; + t[0] = nonce; + t[1] = nonce >> 8; + + shake128_init(state); + shake128_absorb(state, seed, SEEDBYTES); + shake128_absorb(state, t, 2); + shake128_finalize(state); +} + +void dilithium_shake256_stream_init(keccak_state *state, const uint8_t seed[CRHBYTES], uint16_t nonce) +{ + uint8_t t[2]; + t[0] = nonce; + t[1] = nonce >> 8; + + shake256_init(state); + shake256_absorb(state, seed, CRHBYTES); + shake256_absorb(state, t, 2); + shake256_finalize(state); +} diff --git a/app/src/main/cpp/pq/mldsa65/symmetric.h b/app/src/main/cpp/pq/mldsa65/symmetric.h new file mode 100644 index 0000000..cba12d1 --- /dev/null +++ b/app/src/main/cpp/pq/mldsa65/symmetric.h @@ -0,0 +1,34 @@ +#ifndef SYMMETRIC_H +#define SYMMETRIC_H + +#include +#include "params.h" + +#include "fips202.h" + +typedef keccak_state stream128_state; +typedef keccak_state stream256_state; + +#define dilithium_shake128_stream_init DILITHIUM_NAMESPACE(dilithium_shake128_stream_init) +void dilithium_shake128_stream_init(keccak_state *state, + const uint8_t seed[SEEDBYTES], + uint16_t nonce); + +#define dilithium_shake256_stream_init DILITHIUM_NAMESPACE(dilithium_shake256_stream_init) +void dilithium_shake256_stream_init(keccak_state *state, + const uint8_t seed[CRHBYTES], + uint16_t nonce); + +#define STREAM128_BLOCKBYTES SHAKE128_RATE +#define STREAM256_BLOCKBYTES SHAKE256_RATE + +#define stream128_init(STATE, SEED, NONCE) \ + dilithium_shake128_stream_init(STATE, SEED, NONCE) +#define stream128_squeezeblocks(OUT, OUTBLOCKS, STATE) \ + shake128_squeezeblocks(OUT, OUTBLOCKS, STATE) +#define stream256_init(STATE, SEED, NONCE) \ + dilithium_shake256_stream_init(STATE, SEED, NONCE) +#define stream256_squeezeblocks(OUT, OUTBLOCKS, STATE) \ + shake256_squeezeblocks(OUT, OUTBLOCKS, STATE) + +#endif diff --git a/app/src/main/cpp/pq/pq.cpp b/app/src/main/cpp/pq/pq.cpp new file mode 100644 index 0000000..44e7250 --- /dev/null +++ b/app/src/main/cpp/pq/pq.cpp @@ -0,0 +1,37 @@ +// VEXTA: wrapper impl. Bridges to the namespaced C reference symbols. +#include "pq.h" +extern "C" { +// ML-DSA-65 (DILITHIUM_MODE=3 -> pqcrystals_dilithium3_ref_*) +int pqcrystals_dilithium3_ref_keypair(uint8_t* pk, uint8_t* sk); +int crypto_sign_keypair_from_seed(uint8_t* pk, uint8_t* sk, const uint8_t* seed); +int pqcrystals_dilithium3_ref_signature(uint8_t* sig, size_t* siglen, + const uint8_t* m, size_t mlen, const uint8_t* ctx, size_t ctxlen, const uint8_t* sk); +int pqcrystals_dilithium3_ref_verify(const uint8_t* sig, size_t siglen, + const uint8_t* m, size_t mlen, const uint8_t* ctx, size_t ctxlen, const uint8_t* pk); +// SPHINCS+ (bare crypto_sign_* in ref) +int crypto_sign_keypair(uint8_t* pk, uint8_t* sk); +int crypto_sign_seed_keypair(uint8_t* pk, uint8_t* sk, const uint8_t* seed); +int crypto_sign_signature(uint8_t* sig, size_t* siglen, const uint8_t* m, size_t mlen, const uint8_t* sk); +int crypto_sign_verify(const uint8_t* sig, size_t siglen, const uint8_t* m, size_t mlen, const uint8_t* pk); +} +namespace pq { +#ifndef VEXTA_PQ_VERIFY_ONLY +bool MLDSA65::keygen(uint8_t* pk, uint8_t* sk){ return pqcrystals_dilithium3_ref_keypair(pk,sk)==0; } +bool MLDSA65::keygen_from_seed(uint8_t* pk, uint8_t* sk, const uint8_t* seed){ return crypto_sign_keypair_from_seed(pk,sk,seed)==0; } +bool MLDSA65::sign(uint8_t* sig,size_t* sl,const uint8_t* m,size_t ml,const uint8_t* sk){ + return pqcrystals_dilithium3_ref_signature(sig,sl,m,ml,nullptr,0,sk)==0; } +#endif + +bool MLDSA65::verify(const uint8_t* sig,size_t sl,const uint8_t* m,size_t ml,const uint8_t* pk){ + return pqcrystals_dilithium3_ref_verify(sig,sl,m,ml,nullptr,0,pk)==0; } + +#ifndef VEXTA_PQ_VERIFY_ONLY +bool SPHINCS128s::keygen(uint8_t* pk,uint8_t* sk){ return crypto_sign_keypair(pk,sk)==0; } +bool SPHINCS128s::keygen_from_seed(uint8_t* pk,uint8_t* sk,const uint8_t* seed){ return crypto_sign_seed_keypair(pk,sk,seed)==0; } +bool SPHINCS128s::sign(uint8_t* sig,size_t* sl,const uint8_t* m,size_t ml,const uint8_t* sk){ + return crypto_sign_signature(sig,sl,m,ml,sk)==0; } +#endif + +bool SPHINCS128s::verify(const uint8_t* sig,size_t sl,const uint8_t* m,size_t ml,const uint8_t* pk){ + return crypto_sign_verify(sig,sl,m,ml,pk)==0; } +} diff --git a/app/src/main/cpp/pq/pq.h b/app/src/main/cpp/pq/pq.h new file mode 100644 index 0000000..96b7a5f --- /dev/null +++ b/app/src/main/cpp/pq/pq.h @@ -0,0 +1,30 @@ +// VEXTA: uniform post-quantum signature interface over the vendored C references. +#ifndef VEXTA_CRYPTO_PQ_H +#define VEXTA_CRYPTO_PQ_H +#include +#include +#include +namespace pq { + +struct MLDSA65 { + static constexpr size_t PUBKEY_BYTES = 1952; // ML-DSA-65 (FIPS 204) + static constexpr size_t SECKEY_BYTES = 4032; + static constexpr size_t SIG_BYTES = 3309; + static bool keygen(uint8_t* pk, uint8_t* sk); + static bool keygen_from_seed(uint8_t* pk, uint8_t* sk, const uint8_t* seed); + static bool sign(uint8_t* sig, size_t* siglen, const uint8_t* msg, size_t msglen, const uint8_t* sk); + static bool verify(const uint8_t* sig, size_t siglen, const uint8_t* msg, size_t msglen, const uint8_t* pk); +}; + +struct SPHINCS128s { + static constexpr size_t PUBKEY_BYTES = 32; // SLH-DSA sha2-128s (FIPS 205) + static constexpr size_t SECKEY_BYTES = 64; + static constexpr size_t SIG_BYTES = 7856; + static bool keygen(uint8_t* pk, uint8_t* sk); + static bool keygen_from_seed(uint8_t* pk, uint8_t* sk, const uint8_t* seed); + static bool sign(uint8_t* sig, size_t* siglen, const uint8_t* msg, size_t msglen, const uint8_t* sk); + static bool verify(const uint8_t* sig, size_t siglen, const uint8_t* msg, size_t msglen, const uint8_t* pk); +}; + +} // namespace pq +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/LICENSE b/app/src/main/cpp/pq/sphincsplus/LICENSE new file mode 100644 index 0000000..ca88ec4 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/LICENSE @@ -0,0 +1,2 @@ +SPDX-License-Identifier: (LicenseRef-SPHINCS-PLUS-Public-Domain OR CC0-1.0 OR 0BSD OR MIT-0) AND MIT + diff --git a/app/src/main/cpp/pq/sphincsplus/address.c b/app/src/main/cpp/pq/sphincsplus/address.c new file mode 100644 index 0000000..b136af8 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/address.c @@ -0,0 +1,104 @@ +#include +#include + +#include "address.h" +#include "params.h" +#include "utils.h" + +/* + * Specify which level of Merkle tree (the "layer") we're working on + */ +void set_layer_addr(uint32_t addr[8], uint32_t layer) +{ + ((unsigned char *)addr)[SPX_OFFSET_LAYER] = (unsigned char)layer; +} + +/* + * Specify which Merkle tree within the level (the "tree address") we're working on + */ +void set_tree_addr(uint32_t addr[8], uint64_t tree) +{ +#if (SPX_TREE_HEIGHT * (SPX_D - 1)) > 64 + #error Subtree addressing is currently limited to at most 2^64 trees +#endif + ull_to_bytes(&((unsigned char *)addr)[SPX_OFFSET_TREE], 8, tree ); +} + +/* + * Specify the reason we'll use this address structure for, that is, what + * hash will we compute with it. This is used so that unrelated types of + * hashes don't accidentally get the same address structure. The type will be + * one of the SPX_ADDR_TYPE constants + */ +void set_type(uint32_t addr[8], uint32_t type) +{ + ((unsigned char *)addr)[SPX_OFFSET_TYPE] = (unsigned char)type; +} + +/* + * Copy the layer and tree fields of the address structure. This is used + * when we're doing multiple types of hashes within the same Merkle tree + */ +void copy_subtree_addr(uint32_t out[8], const uint32_t in[8]) +{ + memcpy( out, in, SPX_OFFSET_TREE+8 ); +} + +/* These functions are used for OTS addresses. */ + +/* + * Specify which Merkle leaf we're working on; that is, which OTS keypair + * we're talking about. + */ +void set_keypair_addr(uint32_t addr[8], uint32_t keypair) +{ + u32_to_bytes(&((unsigned char *)addr)[SPX_OFFSET_KP_ADDR], keypair); +} + +/* + * Copy the layer, tree and keypair fields of the address structure. This is + * used when we're doing multiple things within the same OTS keypair + */ +void copy_keypair_addr(uint32_t out[8], const uint32_t in[8]) +{ + memcpy( out, in, SPX_OFFSET_TREE+8 ); + memcpy( (unsigned char *)out + SPX_OFFSET_KP_ADDR, (unsigned char *)in + SPX_OFFSET_KP_ADDR, 4); +} + +/* + * Specify which Merkle chain within the OTS we're working with + * (the chain address) + */ +void set_chain_addr(uint32_t addr[8], uint32_t chain) +{ + ((unsigned char *)addr)[SPX_OFFSET_CHAIN_ADDR] = (unsigned char)chain; +} + +/* + * Specify where in the Merkle chain we are +* (the hash address) + */ +void set_hash_addr(uint32_t addr[8], uint32_t hash) +{ + ((unsigned char *)addr)[SPX_OFFSET_HASH_ADDR] = (unsigned char)hash; +} + +/* These functions are used for all hash tree addresses (including FORS). */ + +/* + * Specify the height of the node in the Merkle/FORS tree we are in + * (the tree height) + */ +void set_tree_height(uint32_t addr[8], uint32_t tree_height) +{ + ((unsigned char *)addr)[SPX_OFFSET_TREE_HGT] = (unsigned char)tree_height; +} + +/* + * Specify the distance from the left edge of the node in the Merkle/FORS tree + * (the tree index) + */ +void set_tree_index(uint32_t addr[8], uint32_t tree_index) +{ + u32_to_bytes(&((unsigned char *)addr)[SPX_OFFSET_TREE_INDEX], tree_index ); +} diff --git a/app/src/main/cpp/pq/sphincsplus/address.h b/app/src/main/cpp/pq/sphincsplus/address.h new file mode 100644 index 0000000..49f8d66 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/address.h @@ -0,0 +1,51 @@ +#ifndef SPX_ADDRESS_H +#define SPX_ADDRESS_H + +#include +#include "params.h" + +/* The hash types that are passed to set_type */ +#define SPX_ADDR_TYPE_WOTS 0 +#define SPX_ADDR_TYPE_WOTSPK 1 +#define SPX_ADDR_TYPE_HASHTREE 2 +#define SPX_ADDR_TYPE_FORSTREE 3 +#define SPX_ADDR_TYPE_FORSPK 4 +#define SPX_ADDR_TYPE_WOTSPRF 5 +#define SPX_ADDR_TYPE_FORSPRF 6 + +#define set_layer_addr SPX_NAMESPACE(set_layer_addr) +void set_layer_addr(uint32_t addr[8], uint32_t layer); + +#define set_tree_addr SPX_NAMESPACE(set_tree_addr) +void set_tree_addr(uint32_t addr[8], uint64_t tree); + +#define set_type SPX_NAMESPACE(set_type) +void set_type(uint32_t addr[8], uint32_t type); + +/* Copies the layer and tree part of one address into the other */ +#define copy_subtree_addr SPX_NAMESPACE(copy_subtree_addr) +void copy_subtree_addr(uint32_t out[8], const uint32_t in[8]); + +/* These functions are used for WOTS and FORS addresses. */ + +#define set_keypair_addr SPX_NAMESPACE(set_keypair_addr) +void set_keypair_addr(uint32_t addr[8], uint32_t keypair); + +#define set_chain_addr SPX_NAMESPACE(set_chain_addr) +void set_chain_addr(uint32_t addr[8], uint32_t chain); + +#define set_hash_addr SPX_NAMESPACE(set_hash_addr) +void set_hash_addr(uint32_t addr[8], uint32_t hash); + +#define copy_keypair_addr SPX_NAMESPACE(copy_keypair_addr) +void copy_keypair_addr(uint32_t out[8], const uint32_t in[8]); + +/* These functions are used for all hash tree addresses (including FORS). */ + +#define set_tree_height SPX_NAMESPACE(set_tree_height) +void set_tree_height(uint32_t addr[8], uint32_t tree_height); + +#define set_tree_index SPX_NAMESPACE(set_tree_index) +void set_tree_index(uint32_t addr[8], uint32_t tree_index); + +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/api.h b/app/src/main/cpp/pq/sphincsplus/api.h new file mode 100644 index 0000000..d57a148 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/api.h @@ -0,0 +1,77 @@ +#ifndef SPX_API_H +#define SPX_API_H + +#include +#include + +#include "params.h" + +#define CRYPTO_ALGNAME "SPHINCS+" + +#define CRYPTO_SECRETKEYBYTES SPX_SK_BYTES +#define CRYPTO_PUBLICKEYBYTES SPX_PK_BYTES +#define CRYPTO_BYTES SPX_BYTES +#define CRYPTO_SEEDBYTES 3*SPX_N + +/* + * Returns the length of a secret key, in bytes + */ +unsigned long long crypto_sign_secretkeybytes(void); + +/* + * Returns the length of a public key, in bytes + */ +unsigned long long crypto_sign_publickeybytes(void); + +/* + * Returns the length of a signature, in bytes + */ +unsigned long long crypto_sign_bytes(void); + +/* + * Returns the length of the seed required to generate a key pair, in bytes + */ +unsigned long long crypto_sign_seedbytes(void); + +/* + * Generates a SPHINCS+ key pair given a seed. + * Format sk: [SK_SEED || SK_PRF || PUB_SEED || root] + * Format pk: [root || PUB_SEED] + */ +int crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk, + const unsigned char *seed); + +/* + * Generates a SPHINCS+ key pair. + * Format sk: [SK_SEED || SK_PRF || PUB_SEED || root] + * Format pk: [root || PUB_SEED] + */ +int crypto_sign_keypair(unsigned char *pk, unsigned char *sk); + +/** + * Returns an array containing a detached signature. + */ +int crypto_sign_signature(uint8_t *sig, size_t *siglen, + const uint8_t *m, size_t mlen, const uint8_t *sk); + +/** + * Verifies a detached signature and message under a given public key. + */ +int crypto_sign_verify(const uint8_t *sig, size_t siglen, + const uint8_t *m, size_t mlen, const uint8_t *pk); + +/** + * Returns an array containing the signature followed by the message. + */ +int crypto_sign(unsigned char *sm, unsigned long long *smlen, + const unsigned char *m, unsigned long long mlen, + const unsigned char *sk); + +/** + * Verifies a given signature-message pair under a given public key. + */ +int crypto_sign_open(unsigned char *m, unsigned long long *mlen, + const unsigned char *sm, unsigned long long smlen, + const unsigned char *pk); + +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/context.h b/app/src/main/cpp/pq/sphincsplus/context.h new file mode 100644 index 0000000..aded564 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/context.h @@ -0,0 +1,28 @@ +#ifndef SPX_CONTEXT_H +#define SPX_CONTEXT_H + +#include + +#include "params.h" + +typedef struct { + uint8_t pub_seed[SPX_N]; + uint8_t sk_seed[SPX_N]; + +#ifdef SPX_SHA2 + // sha256 state that absorbed pub_seed + uint8_t state_seeded[40]; + +# if SPX_SHA512 + // sha512 state that absorbed pub_seed + uint8_t state_seeded_512[72]; +# endif +#endif + +#ifdef SPX_HARAKA + uint64_t tweaked512_rc64[10][8]; + uint32_t tweaked256_rc32[10][8]; +#endif +} spx_ctx; + +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/fips202.c b/app/src/main/cpp/pq/sphincsplus/fips202.c new file mode 100644 index 0000000..8262d91 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/fips202.c @@ -0,0 +1,598 @@ +/* Based on the public domain implementation in + * crypto_hash/keccakc512/simple/ from http://bench.cr.yp.to/supercop.html + * by Ronny Van Keer + * and the public domain "TweetFips202" implementation + * from https://twitter.com/tweetfips202 + * by Gilles Van Assche, Daniel J. Bernstein, and Peter Schwabe */ + +#include +#include + +#include "fips202.h" + +#define NROUNDS 24 +#define ROL(a, offset) (((a) << (offset)) ^ ((a) >> (64 - (offset)))) + +/************************************************* + * Name: load64 + * + * Description: Load 8 bytes into uint64_t in little-endian order + * + * Arguments: - const uint8_t *x: pointer to input byte array + * + * Returns the loaded 64-bit unsigned integer + **************************************************/ +static uint64_t load64(const uint8_t *x) { + uint64_t r = 0; + for (size_t i = 0; i < 8; ++i) { + r |= (uint64_t)x[i] << 8 * i; + } + + return r; +} + +/************************************************* + * Name: store64 + * + * Description: Store a 64-bit integer to a byte array in little-endian order + * + * Arguments: - uint8_t *x: pointer to the output byte array + * - uint64_t u: input 64-bit unsigned integer + **************************************************/ +static void store64(uint8_t *x, uint64_t u) { + for (size_t i = 0; i < 8; ++i) { + x[i] = (uint8_t) (u >> 8 * i); + } +} + +/* Keccak round constants */ +static const uint64_t KeccakF_RoundConstants[NROUNDS] = { + 0x0000000000000001ULL, 0x0000000000008082ULL, + 0x800000000000808aULL, 0x8000000080008000ULL, + 0x000000000000808bULL, 0x0000000080000001ULL, + 0x8000000080008081ULL, 0x8000000000008009ULL, + 0x000000000000008aULL, 0x0000000000000088ULL, + 0x0000000080008009ULL, 0x000000008000000aULL, + 0x000000008000808bULL, 0x800000000000008bULL, + 0x8000000000008089ULL, 0x8000000000008003ULL, + 0x8000000000008002ULL, 0x8000000000000080ULL, + 0x000000000000800aULL, 0x800000008000000aULL, + 0x8000000080008081ULL, 0x8000000000008080ULL, + 0x0000000080000001ULL, 0x8000000080008008ULL +}; + +/************************************************* + * Name: KeccakF1600_StatePermute + * + * Description: The Keccak F1600 Permutation + * + * Arguments: - uint64_t *state: pointer to input/output Keccak state + **************************************************/ +static void KeccakF1600_StatePermute(uint64_t *state) { + int round; + + uint64_t Aba, Abe, Abi, Abo, Abu; + uint64_t Aga, Age, Agi, Ago, Agu; + uint64_t Aka, Ake, Aki, Ako, Aku; + uint64_t Ama, Ame, Ami, Amo, Amu; + uint64_t Asa, Ase, Asi, Aso, Asu; + uint64_t BCa, BCe, BCi, BCo, BCu; + uint64_t Da, De, Di, Do, Du; + uint64_t Eba, Ebe, Ebi, Ebo, Ebu; + uint64_t Ega, Ege, Egi, Ego, Egu; + uint64_t Eka, Eke, Eki, Eko, Eku; + uint64_t Ema, Eme, Emi, Emo, Emu; + uint64_t Esa, Ese, Esi, Eso, Esu; + + // copyFromState(A, state) + Aba = state[0]; + Abe = state[1]; + Abi = state[2]; + Abo = state[3]; + Abu = state[4]; + Aga = state[5]; + Age = state[6]; + Agi = state[7]; + Ago = state[8]; + Agu = state[9]; + Aka = state[10]; + Ake = state[11]; + Aki = state[12]; + Ako = state[13]; + Aku = state[14]; + Ama = state[15]; + Ame = state[16]; + Ami = state[17]; + Amo = state[18]; + Amu = state[19]; + Asa = state[20]; + Ase = state[21]; + Asi = state[22]; + Aso = state[23]; + Asu = state[24]; + + for (round = 0; round < NROUNDS; round += 2) { + // prepareTheta + BCa = Aba ^ Aga ^ Aka ^ Ama ^ Asa; + BCe = Abe ^ Age ^ Ake ^ Ame ^ Ase; + BCi = Abi ^ Agi ^ Aki ^ Ami ^ Asi; + BCo = Abo ^ Ago ^ Ako ^ Amo ^ Aso; + BCu = Abu ^ Agu ^ Aku ^ Amu ^ Asu; + + // thetaRhoPiChiIotaPrepareTheta(round , A, E) + Da = BCu ^ ROL(BCe, 1); + De = BCa ^ ROL(BCi, 1); + Di = BCe ^ ROL(BCo, 1); + Do = BCi ^ ROL(BCu, 1); + Du = BCo ^ ROL(BCa, 1); + + Aba ^= Da; + BCa = Aba; + Age ^= De; + BCe = ROL(Age, 44); + Aki ^= Di; + BCi = ROL(Aki, 43); + Amo ^= Do; + BCo = ROL(Amo, 21); + Asu ^= Du; + BCu = ROL(Asu, 14); + Eba = BCa ^ ((~BCe) & BCi); + Eba ^= KeccakF_RoundConstants[round]; + Ebe = BCe ^ ((~BCi) & BCo); + Ebi = BCi ^ ((~BCo) & BCu); + Ebo = BCo ^ ((~BCu) & BCa); + Ebu = BCu ^ ((~BCa) & BCe); + + Abo ^= Do; + BCa = ROL(Abo, 28); + Agu ^= Du; + BCe = ROL(Agu, 20); + Aka ^= Da; + BCi = ROL(Aka, 3); + Ame ^= De; + BCo = ROL(Ame, 45); + Asi ^= Di; + BCu = ROL(Asi, 61); + Ega = BCa ^ ((~BCe) & BCi); + Ege = BCe ^ ((~BCi) & BCo); + Egi = BCi ^ ((~BCo) & BCu); + Ego = BCo ^ ((~BCu) & BCa); + Egu = BCu ^ ((~BCa) & BCe); + + Abe ^= De; + BCa = ROL(Abe, 1); + Agi ^= Di; + BCe = ROL(Agi, 6); + Ako ^= Do; + BCi = ROL(Ako, 25); + Amu ^= Du; + BCo = ROL(Amu, 8); + Asa ^= Da; + BCu = ROL(Asa, 18); + Eka = BCa ^ ((~BCe) & BCi); + Eke = BCe ^ ((~BCi) & BCo); + Eki = BCi ^ ((~BCo) & BCu); + Eko = BCo ^ ((~BCu) & BCa); + Eku = BCu ^ ((~BCa) & BCe); + + Abu ^= Du; + BCa = ROL(Abu, 27); + Aga ^= Da; + BCe = ROL(Aga, 36); + Ake ^= De; + BCi = ROL(Ake, 10); + Ami ^= Di; + BCo = ROL(Ami, 15); + Aso ^= Do; + BCu = ROL(Aso, 56); + Ema = BCa ^ ((~BCe) & BCi); + Eme = BCe ^ ((~BCi) & BCo); + Emi = BCi ^ ((~BCo) & BCu); + Emo = BCo ^ ((~BCu) & BCa); + Emu = BCu ^ ((~BCa) & BCe); + + Abi ^= Di; + BCa = ROL(Abi, 62); + Ago ^= Do; + BCe = ROL(Ago, 55); + Aku ^= Du; + BCi = ROL(Aku, 39); + Ama ^= Da; + BCo = ROL(Ama, 41); + Ase ^= De; + BCu = ROL(Ase, 2); + Esa = BCa ^ ((~BCe) & BCi); + Ese = BCe ^ ((~BCi) & BCo); + Esi = BCi ^ ((~BCo) & BCu); + Eso = BCo ^ ((~BCu) & BCa); + Esu = BCu ^ ((~BCa) & BCe); + + // prepareTheta + BCa = Eba ^ Ega ^ Eka ^ Ema ^ Esa; + BCe = Ebe ^ Ege ^ Eke ^ Eme ^ Ese; + BCi = Ebi ^ Egi ^ Eki ^ Emi ^ Esi; + BCo = Ebo ^ Ego ^ Eko ^ Emo ^ Eso; + BCu = Ebu ^ Egu ^ Eku ^ Emu ^ Esu; + + // thetaRhoPiChiIotaPrepareTheta(round+1, E, A) + Da = BCu ^ ROL(BCe, 1); + De = BCa ^ ROL(BCi, 1); + Di = BCe ^ ROL(BCo, 1); + Do = BCi ^ ROL(BCu, 1); + Du = BCo ^ ROL(BCa, 1); + + Eba ^= Da; + BCa = Eba; + Ege ^= De; + BCe = ROL(Ege, 44); + Eki ^= Di; + BCi = ROL(Eki, 43); + Emo ^= Do; + BCo = ROL(Emo, 21); + Esu ^= Du; + BCu = ROL(Esu, 14); + Aba = BCa ^ ((~BCe) & BCi); + Aba ^= KeccakF_RoundConstants[round + 1]; + Abe = BCe ^ ((~BCi) & BCo); + Abi = BCi ^ ((~BCo) & BCu); + Abo = BCo ^ ((~BCu) & BCa); + Abu = BCu ^ ((~BCa) & BCe); + + Ebo ^= Do; + BCa = ROL(Ebo, 28); + Egu ^= Du; + BCe = ROL(Egu, 20); + Eka ^= Da; + BCi = ROL(Eka, 3); + Eme ^= De; + BCo = ROL(Eme, 45); + Esi ^= Di; + BCu = ROL(Esi, 61); + Aga = BCa ^ ((~BCe) & BCi); + Age = BCe ^ ((~BCi) & BCo); + Agi = BCi ^ ((~BCo) & BCu); + Ago = BCo ^ ((~BCu) & BCa); + Agu = BCu ^ ((~BCa) & BCe); + + Ebe ^= De; + BCa = ROL(Ebe, 1); + Egi ^= Di; + BCe = ROL(Egi, 6); + Eko ^= Do; + BCi = ROL(Eko, 25); + Emu ^= Du; + BCo = ROL(Emu, 8); + Esa ^= Da; + BCu = ROL(Esa, 18); + Aka = BCa ^ ((~BCe) & BCi); + Ake = BCe ^ ((~BCi) & BCo); + Aki = BCi ^ ((~BCo) & BCu); + Ako = BCo ^ ((~BCu) & BCa); + Aku = BCu ^ ((~BCa) & BCe); + + Ebu ^= Du; + BCa = ROL(Ebu, 27); + Ega ^= Da; + BCe = ROL(Ega, 36); + Eke ^= De; + BCi = ROL(Eke, 10); + Emi ^= Di; + BCo = ROL(Emi, 15); + Eso ^= Do; + BCu = ROL(Eso, 56); + Ama = BCa ^ ((~BCe) & BCi); + Ame = BCe ^ ((~BCi) & BCo); + Ami = BCi ^ ((~BCo) & BCu); + Amo = BCo ^ ((~BCu) & BCa); + Amu = BCu ^ ((~BCa) & BCe); + + Ebi ^= Di; + BCa = ROL(Ebi, 62); + Ego ^= Do; + BCe = ROL(Ego, 55); + Eku ^= Du; + BCi = ROL(Eku, 39); + Ema ^= Da; + BCo = ROL(Ema, 41); + Ese ^= De; + BCu = ROL(Ese, 2); + Asa = BCa ^ ((~BCe) & BCi); + Ase = BCe ^ ((~BCi) & BCo); + Asi = BCi ^ ((~BCo) & BCu); + Aso = BCo ^ ((~BCu) & BCa); + Asu = BCu ^ ((~BCa) & BCe); + } + + // copyToState(state, A) + state[0] = Aba; + state[1] = Abe; + state[2] = Abi; + state[3] = Abo; + state[4] = Abu; + state[5] = Aga; + state[6] = Age; + state[7] = Agi; + state[8] = Ago; + state[9] = Agu; + state[10] = Aka; + state[11] = Ake; + state[12] = Aki; + state[13] = Ako; + state[14] = Aku; + state[15] = Ama; + state[16] = Ame; + state[17] = Ami; + state[18] = Amo; + state[19] = Amu; + state[20] = Asa; + state[21] = Ase; + state[22] = Asi; + state[23] = Aso; + state[24] = Asu; +} + +/************************************************* + * Name: keccak_absorb + * + * Description: Absorb step of Keccak; + * non-incremental, starts by zeroeing the state. + * + * Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak state + * - uint32_t r: rate in bytes (e.g., 168 for SHAKE128) + * - const uint8_t *m: pointer to input to be absorbed into s + * - size_t mlen: length of input in bytes + * - uint8_t p: domain-separation byte for different + * Keccak-derived functions + **************************************************/ +static void keccak_absorb(uint64_t *s, uint32_t r, const uint8_t *m, + size_t mlen, uint8_t p) { + size_t i; + uint8_t t[200]; + + /* Zero state */ + for (i = 0; i < 25; ++i) { + s[i] = 0; + } + + while (mlen >= r) { + for (i = 0; i < r / 8; ++i) { + s[i] ^= load64(m + 8 * i); + } + + KeccakF1600_StatePermute(s); + mlen -= r; + m += r; + } + + for (i = 0; i < r; ++i) { + t[i] = 0; + } + for (i = 0; i < mlen; ++i) { + t[i] = m[i]; + } + t[i] = p; + t[r - 1] |= 128; + for (i = 0; i < r / 8; ++i) { + s[i] ^= load64(t + 8 * i); + } +} + +/************************************************* + * Name: keccak_squeezeblocks + * + * Description: Squeeze step of Keccak. Squeezes full blocks of r bytes each. + * Modifies the state. Can be called multiple times to keep + * squeezing, i.e., is incremental. + * + * Arguments: - uint8_t *h: pointer to output blocks + * - size_t nblocks: number of blocks to be + * squeezed (written to h) + * - uint64_t *s: pointer to input/output Keccak state + * - uint32_t r: rate in bytes (e.g., 168 for SHAKE128) + **************************************************/ +static void keccak_squeezeblocks(uint8_t *h, size_t nblocks, + uint64_t *s, uint32_t r) { + while (nblocks > 0) { + KeccakF1600_StatePermute(s); + for (size_t i = 0; i < (r >> 3); i++) { + store64(h + 8 * i, s[i]); + } + h += r; + nblocks--; + } +} + +/************************************************* + * Name: keccak_inc_init + * + * Description: Initializes the incremental Keccak state to zero. + * + * Arguments: - uint64_t *s_inc: pointer to input/output incremental state + * First 25 values represent Keccak state. + * 26th value represents either the number of absorbed bytes + * that have not been permuted, or not-yet-squeezed bytes. + **************************************************/ +static void keccak_inc_init(uint64_t *s_inc) { + size_t i; + + for (i = 0; i < 25; ++i) { + s_inc[i] = 0; + } + s_inc[25] = 0; +} + +/************************************************* + * Name: keccak_inc_absorb + * + * Description: Incremental keccak absorb + * Preceded by keccak_inc_init, succeeded by keccak_inc_finalize + * + * Arguments: - uint64_t *s_inc: pointer to input/output incremental state + * First 25 values represent Keccak state. + * 26th value represents either the number of absorbed bytes + * that have not been permuted, or not-yet-squeezed bytes. + * - uint32_t r: rate in bytes (e.g., 168 for SHAKE128) + * - const uint8_t *m: pointer to input to be absorbed into s + * - size_t mlen: length of input in bytes + **************************************************/ +static void keccak_inc_absorb(uint64_t *s_inc, uint32_t r, const uint8_t *m, + size_t mlen) { + size_t i; + + /* Recall that s_inc[25] is the non-absorbed bytes xored into the state */ + while (mlen + s_inc[25] >= r) { + for (i = 0; i < r - s_inc[25]; i++) { + /* Take the i'th byte from message + xor with the s_inc[25] + i'th byte of the state; little-endian */ + s_inc[(s_inc[25] + i) >> 3] ^= (uint64_t)m[i] << (8 * ((s_inc[25] + i) & 0x07)); + } + mlen -= (size_t)(r - s_inc[25]); + m += r - s_inc[25]; + s_inc[25] = 0; + + KeccakF1600_StatePermute(s_inc); + } + + for (i = 0; i < mlen; i++) { + s_inc[(s_inc[25] + i) >> 3] ^= (uint64_t)m[i] << (8 * ((s_inc[25] + i) & 0x07)); + } + s_inc[25] += mlen; +} + +/************************************************* + * Name: keccak_inc_finalize + * + * Description: Finalizes Keccak absorb phase, prepares for squeezing + * + * Arguments: - uint64_t *s_inc: pointer to input/output incremental state + * First 25 values represent Keccak state. + * 26th value represents either the number of absorbed bytes + * that have not been permuted, or not-yet-squeezed bytes. + * - uint32_t r: rate in bytes (e.g., 168 for SHAKE128) + * - uint8_t p: domain-separation byte for different + * Keccak-derived functions + **************************************************/ +static void keccak_inc_finalize(uint64_t *s_inc, uint32_t r, uint8_t p) { + /* After keccak_inc_absorb, we are guaranteed that s_inc[25] < r, + so we can always use one more byte for p in the current state. */ + s_inc[s_inc[25] >> 3] ^= (uint64_t)p << (8 * (s_inc[25] & 0x07)); + s_inc[(r - 1) >> 3] ^= (uint64_t)128 << (8 * ((r - 1) & 0x07)); + s_inc[25] = 0; +} + +/************************************************* + * Name: keccak_inc_squeeze + * + * Description: Incremental Keccak squeeze; can be called on byte-level + * + * Arguments: - uint8_t *h: pointer to output bytes + * - size_t outlen: number of bytes to be squeezed + * - uint64_t *s_inc: pointer to input/output incremental state + * First 25 values represent Keccak state. + * 26th value represents either the number of absorbed bytes + * that have not been permuted, or not-yet-squeezed bytes. + * - uint32_t r: rate in bytes (e.g., 168 for SHAKE128) + **************************************************/ +static void keccak_inc_squeeze(uint8_t *h, size_t outlen, + uint64_t *s_inc, uint32_t r) { + size_t i; + + /* First consume any bytes we still have sitting around */ + for (i = 0; i < outlen && i < s_inc[25]; i++) { + /* There are s_inc[25] bytes left, so r - s_inc[25] is the first + available byte. We consume from there, i.e., up to r. */ + h[i] = (uint8_t)(s_inc[(r - s_inc[25] + i) >> 3] >> (8 * ((r - s_inc[25] + i) & 0x07))); + } + h += i; + outlen -= i; + s_inc[25] -= i; + + /* Then squeeze the remaining necessary blocks */ + while (outlen > 0) { + KeccakF1600_StatePermute(s_inc); + + for (i = 0; i < outlen && i < r; i++) { + h[i] = (uint8_t)(s_inc[i >> 3] >> (8 * (i & 0x07))); + } + h += i; + outlen -= i; + s_inc[25] = r - i; + } +} + +void shake256_inc_init(uint64_t *s_inc) { + keccak_inc_init(s_inc); +} + +void shake256_inc_absorb(uint64_t *s_inc, const uint8_t *input, size_t inlen) { + keccak_inc_absorb(s_inc, SHAKE256_RATE, input, inlen); +} + +void shake256_inc_finalize(uint64_t *s_inc) { + keccak_inc_finalize(s_inc, SHAKE256_RATE, 0x1F); +} + +void shake256_inc_squeeze(uint8_t *output, size_t outlen, uint64_t *s_inc) { + keccak_inc_squeeze(output, outlen, s_inc, SHAKE256_RATE); +} + +/************************************************* + * Name: shake256_absorb + * + * Description: Absorb step of the SHAKE256 XOF. + * non-incremental, starts by zeroeing the state. + * + * Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak state + * - const uint8_t *input: pointer to input to be absorbed + * into s + * - size_t inlen: length of input in bytes + **************************************************/ +void shake256_absorb(uint64_t *s, const uint8_t *input, size_t inlen) { + keccak_absorb(s, SHAKE256_RATE, input, inlen, 0x1F); +} + +/************************************************* + * Name: shake256_squeezeblocks + * + * Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of + * SHAKE256_RATE bytes each. Modifies the state. Can be called + * multiple times to keep squeezing, i.e., is incremental. + * + * Arguments: - uint8_t *output: pointer to output blocks + * - size_t nblocks: number of blocks to be squeezed + * (written to output) + * - uint64_t *s: pointer to input/output Keccak state + **************************************************/ +void shake256_squeezeblocks(uint8_t *output, size_t nblocks, uint64_t *s) { + keccak_squeezeblocks(output, nblocks, s, SHAKE256_RATE); +} + +/************************************************* + * Name: shake256 + * + * Description: SHAKE256 XOF with non-incremental API + * + * Arguments: - uint8_t *output: pointer to output + * - size_t outlen: requested output length in bytes + * - const uint8_t *input: pointer to input + * - size_t inlen: length of input in bytes + **************************************************/ +void shake256(uint8_t *output, size_t outlen, + const uint8_t *input, size_t inlen) { + size_t nblocks = outlen / SHAKE256_RATE; + uint8_t t[SHAKE256_RATE]; + uint64_t s[25]; + + shake256_absorb(s, input, inlen); + shake256_squeezeblocks(output, nblocks, s); + + output += nblocks * SHAKE256_RATE; + outlen -= nblocks * SHAKE256_RATE; + + if (outlen) { + shake256_squeezeblocks(t, 1, s); + for (size_t i = 0; i < outlen; ++i) { + output[i] = t[i]; + } + } +} diff --git a/app/src/main/cpp/pq/sphincsplus/fips202.h b/app/src/main/cpp/pq/sphincsplus/fips202.h new file mode 100644 index 0000000..e11cb7f --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/fips202.h @@ -0,0 +1,47 @@ +#ifndef SPX_FIPS202_H +#define SPX_FIPS202_H + +#include +#include + +#define SHAKE128_RATE 168 +#define SHAKE256_RATE 136 +#define SHA3_256_RATE 136 +#define SHA3_512_RATE 72 + +void shake128_absorb(uint64_t *s, const uint8_t *input, size_t inlen); + +void shake128_squeezeblocks(uint8_t *output, size_t nblocks, uint64_t *s); + +void shake128_inc_init(uint64_t *s_inc); +void shake128_inc_absorb(uint64_t *s_inc, const uint8_t *input, size_t inlen); +void shake128_inc_finalize(uint64_t *s_inc); +void shake128_inc_squeeze(uint8_t *output, size_t outlen, uint64_t *s_inc); + +void shake256_absorb(uint64_t *s, const uint8_t *input, size_t inlen); +void shake256_squeezeblocks(uint8_t *output, size_t nblocks, uint64_t *s); + +void shake256_inc_init(uint64_t *s_inc); +void shake256_inc_absorb(uint64_t *s_inc, const uint8_t *input, size_t inlen); +void shake256_inc_finalize(uint64_t *s_inc); +void shake256_inc_squeeze(uint8_t *output, size_t outlen, uint64_t *s_inc); + +void shake128(uint8_t *output, size_t outlen, + const uint8_t *input, size_t inlen); + +void shake256(uint8_t *output, size_t outlen, + const uint8_t *input, size_t inlen); + +void sha3_256_inc_init(uint64_t *s_inc); +void sha3_256_inc_absorb(uint64_t *s_inc, const uint8_t *input, size_t inlen); +void sha3_256_inc_finalize(uint8_t *output, uint64_t *s_inc); + +void sha3_256(uint8_t *output, const uint8_t *input, size_t inlen); + +void sha3_512_inc_init(uint64_t *s_inc); +void sha3_512_inc_absorb(uint64_t *s_inc, const uint8_t *input, size_t inlen); +void sha3_512_inc_finalize(uint8_t *output, uint64_t *s_inc); + +void sha3_512(uint8_t *output, const uint8_t *input, size_t inlen); + +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/fors.c b/app/src/main/cpp/pq/sphincsplus/fors.c new file mode 100644 index 0000000..e6aa4b4 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/fors.c @@ -0,0 +1,161 @@ +#include +#include +#include + +#include "fors.h" +#include "utils.h" +#include "utilsx1.h" +#include "hash.h" +#include "thash.h" +#include "address.h" + +static void fors_gen_sk(unsigned char *sk, const spx_ctx *ctx, + uint32_t fors_leaf_addr[8]) +{ + prf_addr(sk, ctx, fors_leaf_addr); +} + +static void fors_sk_to_leaf(unsigned char *leaf, const unsigned char *sk, + const spx_ctx *ctx, + uint32_t fors_leaf_addr[8]) +{ + thash(leaf, sk, 1, ctx, fors_leaf_addr); +} + +struct fors_gen_leaf_info { + uint32_t leaf_addrx[8]; +}; + +static void fors_gen_leafx1(unsigned char *leaf, + const spx_ctx *ctx, + uint32_t addr_idx, void *info) +{ + struct fors_gen_leaf_info *fors_info = info; + uint32_t *fors_leaf_addr = fors_info->leaf_addrx; + + /* Only set the parts that the caller doesn't set */ + set_tree_index(fors_leaf_addr, addr_idx); + set_type(fors_leaf_addr, SPX_ADDR_TYPE_FORSPRF); + fors_gen_sk(leaf, ctx, fors_leaf_addr); + + set_type(fors_leaf_addr, SPX_ADDR_TYPE_FORSTREE); + fors_sk_to_leaf(leaf, leaf, + ctx, fors_leaf_addr); +} + +/** + * Interprets m as SPX_FORS_HEIGHT-bit unsigned integers. + * Assumes m contains at least SPX_FORS_HEIGHT * SPX_FORS_TREES bits. + * Assumes indices has space for SPX_FORS_TREES integers. + */ +static void message_to_indices(uint32_t *indices, const unsigned char *m) +{ + unsigned int i, j; + unsigned int offset = 0; + + for (i = 0; i < SPX_FORS_TREES; i++) { + indices[i] = 0; + for (j = 0; j < SPX_FORS_HEIGHT; j++) { + indices[i] ^= ((m[offset >> 3] >> (offset & 0x7)) & 1u) << j; + offset++; + } + } +} + +/** + * Signs a message m, deriving the secret key from sk_seed and the FTS address. + * Assumes m contains at least SPX_FORS_HEIGHT * SPX_FORS_TREES bits. + */ +void fors_sign(unsigned char *sig, unsigned char *pk, + const unsigned char *m, + const spx_ctx *ctx, + const uint32_t fors_addr[8]) +{ + uint32_t indices[SPX_FORS_TREES]; + unsigned char roots[SPX_FORS_TREES * SPX_N]; + uint32_t fors_tree_addr[8] = {0}; + struct fors_gen_leaf_info fors_info = {0}; + uint32_t *fors_leaf_addr = fors_info.leaf_addrx; + uint32_t fors_pk_addr[8] = {0}; + uint32_t idx_offset; + unsigned int i; + + copy_keypair_addr(fors_tree_addr, fors_addr); + copy_keypair_addr(fors_leaf_addr, fors_addr); + + copy_keypair_addr(fors_pk_addr, fors_addr); + set_type(fors_pk_addr, SPX_ADDR_TYPE_FORSPK); + + message_to_indices(indices, m); + + for (i = 0; i < SPX_FORS_TREES; i++) { + idx_offset = i * (1 << SPX_FORS_HEIGHT); + + set_tree_height(fors_tree_addr, 0); + set_tree_index(fors_tree_addr, indices[i] + idx_offset); + set_type(fors_tree_addr, SPX_ADDR_TYPE_FORSPRF); + + /* Include the secret key part that produces the selected leaf node. */ + fors_gen_sk(sig, ctx, fors_tree_addr); + set_type(fors_tree_addr, SPX_ADDR_TYPE_FORSTREE); + sig += SPX_N; + + /* Compute the authentication path for this leaf node. */ + treehashx1(roots + i*SPX_N, sig, ctx, + indices[i], idx_offset, SPX_FORS_HEIGHT, fors_gen_leafx1, + fors_tree_addr, &fors_info); + + sig += SPX_N * SPX_FORS_HEIGHT; + } + + /* Hash horizontally across all tree roots to derive the public key. */ + thash(pk, roots, SPX_FORS_TREES, ctx, fors_pk_addr); +} + +/** + * Derives the FORS public key from a signature. + * This can be used for verification by comparing to a known public key, or to + * subsequently verify a signature on the derived public key. The latter is the + * typical use-case when used as an FTS below an OTS in a hypertree. + * Assumes m contains at least SPX_FORS_HEIGHT * SPX_FORS_TREES bits. + */ +void fors_pk_from_sig(unsigned char *pk, + const unsigned char *sig, const unsigned char *m, + const spx_ctx* ctx, + const uint32_t fors_addr[8]) +{ + uint32_t indices[SPX_FORS_TREES]; + unsigned char roots[SPX_FORS_TREES * SPX_N]; + unsigned char leaf[SPX_N]; + uint32_t fors_tree_addr[8] = {0}; + uint32_t fors_pk_addr[8] = {0}; + uint32_t idx_offset; + unsigned int i; + + copy_keypair_addr(fors_tree_addr, fors_addr); + copy_keypair_addr(fors_pk_addr, fors_addr); + + set_type(fors_tree_addr, SPX_ADDR_TYPE_FORSTREE); + set_type(fors_pk_addr, SPX_ADDR_TYPE_FORSPK); + + message_to_indices(indices, m); + + for (i = 0; i < SPX_FORS_TREES; i++) { + idx_offset = i * (1 << SPX_FORS_HEIGHT); + + set_tree_height(fors_tree_addr, 0); + set_tree_index(fors_tree_addr, indices[i] + idx_offset); + + /* Derive the leaf from the included secret key part. */ + fors_sk_to_leaf(leaf, sig, ctx, fors_tree_addr); + sig += SPX_N; + + /* Derive the corresponding root node of this tree. */ + compute_root(roots + i*SPX_N, leaf, indices[i], idx_offset, + sig, SPX_FORS_HEIGHT, ctx, fors_tree_addr); + sig += SPX_N * SPX_FORS_HEIGHT; + } + + /* Hash horizontally across all tree roots to derive the public key. */ + thash(pk, roots, SPX_FORS_TREES, ctx, fors_pk_addr); +} diff --git a/app/src/main/cpp/pq/sphincsplus/fors.h b/app/src/main/cpp/pq/sphincsplus/fors.h new file mode 100644 index 0000000..8d98017 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/fors.h @@ -0,0 +1,32 @@ +#ifndef SPX_FORS_H +#define SPX_FORS_H + +#include + +#include "params.h" +#include "context.h" + +/** + * Signs a message m, deriving the secret key from sk_seed and the FTS address. + * Assumes m contains at least SPX_FORS_HEIGHT * SPX_FORS_TREES bits. + */ +#define fors_sign SPX_NAMESPACE(fors_sign) +void fors_sign(unsigned char *sig, unsigned char *pk, + const unsigned char *m, + const spx_ctx* ctx, + const uint32_t fors_addr[8]); + +/** + * Derives the FORS public key from a signature. + * This can be used for verification by comparing to a known public key, or to + * subsequently verify a signature on the derived public key. The latter is the + * typical use-case when used as an FTS below an OTS in a hypertree. + * Assumes m contains at least SPX_FORS_HEIGHT * SPX_FORS_TREES bits. + */ +#define fors_pk_from_sig SPX_NAMESPACE(fors_pk_from_sig) +void fors_pk_from_sig(unsigned char *pk, + const unsigned char *sig, const unsigned char *m, + const spx_ctx* ctx, + const uint32_t fors_addr[8]); + +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/hash.h b/app/src/main/cpp/pq/sphincsplus/hash.h new file mode 100644 index 0000000..b141f09 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/hash.h @@ -0,0 +1,27 @@ +#ifndef SPX_HASH_H +#define SPX_HASH_H + +#include +#include "context.h" +#include "params.h" + +#define initialize_hash_function SPX_NAMESPACE(initialize_hash_function) +void initialize_hash_function(spx_ctx *ctx); + +#define prf_addr SPX_NAMESPACE(prf_addr) +void prf_addr(unsigned char *out, const spx_ctx *ctx, + const uint32_t addr[8]); + +#define gen_message_random SPX_NAMESPACE(gen_message_random) +void gen_message_random(unsigned char *R, const unsigned char *sk_prf, + const unsigned char *optrand, + const unsigned char *m, unsigned long long mlen, + const spx_ctx *ctx); + +#define hash_message SPX_NAMESPACE(hash_message) +void hash_message(unsigned char *digest, uint64_t *tree, uint32_t *leaf_idx, + const unsigned char *R, const unsigned char *pk, + const unsigned char *m, unsigned long long mlen, + const spx_ctx *ctx); + +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/hash_sha2.c b/app/src/main/cpp/pq/sphincsplus/hash_sha2.c new file mode 100644 index 0000000..67098e6 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/hash_sha2.c @@ -0,0 +1,197 @@ +#include +#include + +#include "address.h" +#include "utils.h" +#include "params.h" +#include "hash.h" +#include "sha2.h" + +#if SPX_N >= 24 +#define SPX_SHAX_OUTPUT_BYTES SPX_SHA512_OUTPUT_BYTES +#define SPX_SHAX_BLOCK_BYTES SPX_SHA512_BLOCK_BYTES +#define shaX_inc_init sha512_inc_init +#define shaX_inc_blocks sha512_inc_blocks +#define shaX_inc_finalize sha512_inc_finalize +#define shaX sha512 +#define mgf1_X mgf1_512 +#else +#define SPX_SHAX_OUTPUT_BYTES SPX_SHA256_OUTPUT_BYTES +#define SPX_SHAX_BLOCK_BYTES SPX_SHA256_BLOCK_BYTES +#define shaX_inc_init sha256_inc_init +#define shaX_inc_blocks sha256_inc_blocks +#define shaX_inc_finalize sha256_inc_finalize +#define shaX sha256 +#define mgf1_X mgf1_256 +#endif + + +/* For SHA, there is no immediate reason to initialize at the start, + so this function is an empty operation. */ +void initialize_hash_function(spx_ctx *ctx) +{ + seed_state(ctx); +} + +/* + * Computes PRF(pk_seed, sk_seed, addr). + */ +void prf_addr(unsigned char *out, const spx_ctx *ctx, + const uint32_t addr[8]) +{ + uint8_t sha2_state[40]; + unsigned char buf[SPX_SHA256_ADDR_BYTES + SPX_N]; + unsigned char outbuf[SPX_SHA256_OUTPUT_BYTES]; + + /* Retrieve precomputed state containing pub_seed */ + memcpy(sha2_state, ctx->state_seeded, 40 * sizeof(uint8_t)); + + /* Remainder: ADDR^c ‖ SK.seed */ + memcpy(buf, addr, SPX_SHA256_ADDR_BYTES); + memcpy(buf + SPX_SHA256_ADDR_BYTES, ctx->sk_seed, SPX_N); + + sha256_inc_finalize(outbuf, sha2_state, buf, SPX_SHA256_ADDR_BYTES + SPX_N); + + memcpy(out, outbuf, SPX_N); +} + +/** + * Computes the message-dependent randomness R, using a secret seed as a key + * for HMAC, and an optional randomization value prefixed to the message. + * This requires m to have at least SPX_SHAX_BLOCK_BYTES + SPX_N space + * available in front of the pointer, i.e. before the message to use for the + * prefix. This is necessary to prevent having to move the message around (and + * allocate memory for it). + */ +void gen_message_random(unsigned char *R, const unsigned char *sk_prf, + const unsigned char *optrand, + const unsigned char *m, unsigned long long mlen, + const spx_ctx *ctx) +{ + (void)ctx; + + unsigned char buf[SPX_SHAX_BLOCK_BYTES + SPX_SHAX_OUTPUT_BYTES]; + uint8_t state[8 + SPX_SHAX_OUTPUT_BYTES]; + int i; + +#if SPX_N > SPX_SHAX_BLOCK_BYTES + #error "Currently only supports SPX_N of at most SPX_SHAX_BLOCK_BYTES" +#endif + + /* This implements HMAC-SHA */ + for (i = 0; i < SPX_N; i++) { + buf[i] = 0x36 ^ sk_prf[i]; + } + memset(buf + SPX_N, 0x36, SPX_SHAX_BLOCK_BYTES - SPX_N); + + shaX_inc_init(state); + shaX_inc_blocks(state, buf, 1); + + memcpy(buf, optrand, SPX_N); + + /* If optrand + message cannot fill up an entire block */ + if (SPX_N + mlen < SPX_SHAX_BLOCK_BYTES) { + memcpy(buf + SPX_N, m, mlen); + shaX_inc_finalize(buf + SPX_SHAX_BLOCK_BYTES, state, + buf, mlen + SPX_N); + } + /* Otherwise first fill a block, so that finalize only uses the message */ + else { + memcpy(buf + SPX_N, m, SPX_SHAX_BLOCK_BYTES - SPX_N); + shaX_inc_blocks(state, buf, 1); + + m += SPX_SHAX_BLOCK_BYTES - SPX_N; + mlen -= SPX_SHAX_BLOCK_BYTES - SPX_N; + shaX_inc_finalize(buf + SPX_SHAX_BLOCK_BYTES, state, m, mlen); + } + + for (i = 0; i < SPX_N; i++) { + buf[i] = 0x5c ^ sk_prf[i]; + } + memset(buf + SPX_N, 0x5c, SPX_SHAX_BLOCK_BYTES - SPX_N); + + shaX(buf, buf, SPX_SHAX_BLOCK_BYTES + SPX_SHAX_OUTPUT_BYTES); + memcpy(R, buf, SPX_N); +} + +/** + * Computes the message hash using R, the public key, and the message. + * Outputs the message digest and the index of the leaf. The index is split in + * the tree index and the leaf index, for convenient copying to an address. + */ +void hash_message(unsigned char *digest, uint64_t *tree, uint32_t *leaf_idx, + const unsigned char *R, const unsigned char *pk, + const unsigned char *m, unsigned long long mlen, + const spx_ctx *ctx) +{ + (void)ctx; +#define SPX_TREE_BITS (SPX_TREE_HEIGHT * (SPX_D - 1)) +#define SPX_TREE_BYTES ((SPX_TREE_BITS + 7) / 8) +#define SPX_LEAF_BITS SPX_TREE_HEIGHT +#define SPX_LEAF_BYTES ((SPX_LEAF_BITS + 7) / 8) +#define SPX_DGST_BYTES (SPX_FORS_MSG_BYTES + SPX_TREE_BYTES + SPX_LEAF_BYTES) + + unsigned char seed[2*SPX_N + SPX_SHAX_OUTPUT_BYTES]; + + /* Round to nearest multiple of SPX_SHAX_BLOCK_BYTES */ +#if (SPX_SHAX_BLOCK_BYTES & (SPX_SHAX_BLOCK_BYTES-1)) != 0 + #error "Assumes that SPX_SHAX_BLOCK_BYTES is a power of 2" +#endif +#define SPX_INBLOCKS (((SPX_N + SPX_PK_BYTES + SPX_SHAX_BLOCK_BYTES - 1) & \ + -SPX_SHAX_BLOCK_BYTES) / SPX_SHAX_BLOCK_BYTES) + unsigned char inbuf[SPX_INBLOCKS * SPX_SHAX_BLOCK_BYTES]; + + unsigned char buf[SPX_DGST_BYTES]; + unsigned char *bufp = buf; + uint8_t state[8 + SPX_SHAX_OUTPUT_BYTES]; + + shaX_inc_init(state); + + // seed: SHA-X(R ‖ PK.seed ‖ PK.root ‖ M) + memcpy(inbuf, R, SPX_N); + memcpy(inbuf + SPX_N, pk, SPX_PK_BYTES); + + /* If R + pk + message cannot fill up an entire block */ + if (SPX_N + SPX_PK_BYTES + mlen < SPX_INBLOCKS * SPX_SHAX_BLOCK_BYTES) { + memcpy(inbuf + SPX_N + SPX_PK_BYTES, m, mlen); + shaX_inc_finalize(seed + 2*SPX_N, state, inbuf, SPX_N + SPX_PK_BYTES + mlen); + } + /* Otherwise first fill a block, so that finalize only uses the message */ + else { + memcpy(inbuf + SPX_N + SPX_PK_BYTES, m, + SPX_INBLOCKS * SPX_SHAX_BLOCK_BYTES - SPX_N - SPX_PK_BYTES); + shaX_inc_blocks(state, inbuf, SPX_INBLOCKS); + + m += SPX_INBLOCKS * SPX_SHAX_BLOCK_BYTES - SPX_N - SPX_PK_BYTES; + mlen -= SPX_INBLOCKS * SPX_SHAX_BLOCK_BYTES - SPX_N - SPX_PK_BYTES; + shaX_inc_finalize(seed + 2*SPX_N, state, m, mlen); + } + + // H_msg: MGF1-SHA-X(R ‖ PK.seed ‖ seed) + memcpy(seed, R, SPX_N); + memcpy(seed + SPX_N, pk, SPX_N); + + /* By doing this in two steps, we prevent hashing the message twice; + otherwise each iteration in MGF1 would hash the message again. */ + mgf1_X(bufp, SPX_DGST_BYTES, seed, 2*SPX_N + SPX_SHAX_OUTPUT_BYTES); + + memcpy(digest, bufp, SPX_FORS_MSG_BYTES); + bufp += SPX_FORS_MSG_BYTES; + +#if SPX_TREE_BITS > 64 + #error For given height and depth, 64 bits cannot represent all subtrees +#endif + + if (SPX_D == 1) { + *tree = 0; + } else { + *tree = bytes_to_ull(bufp, SPX_TREE_BYTES); + *tree &= (~(uint64_t)0) >> (64 - SPX_TREE_BITS); + } + bufp += SPX_TREE_BYTES; + + *leaf_idx = (uint32_t)bytes_to_ull(bufp, SPX_LEAF_BYTES); + *leaf_idx &= (~(uint32_t)0) >> (32 - SPX_LEAF_BITS); +} + + diff --git a/app/src/main/cpp/pq/sphincsplus/merkle.c b/app/src/main/cpp/pq/sphincsplus/merkle.c new file mode 100644 index 0000000..414f468 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/merkle.c @@ -0,0 +1,61 @@ +#include +#include + +#include "utils.h" +#include "utilsx1.h" +#include "wots.h" +#include "wotsx1.h" +#include "merkle.h" +#include "address.h" +#include "params.h" + +/* + * This generates a Merkle signature (WOTS signature followed by the Merkle + * authentication path). This is in this file because most of the complexity + * is involved with the WOTS signature; the Merkle authentication path logic + * is mostly hidden in treehashx4 + */ +void merkle_sign(uint8_t *sig, unsigned char *root, + const spx_ctx *ctx, + uint32_t wots_addr[8], uint32_t tree_addr[8], + uint32_t idx_leaf) +{ + unsigned char *auth_path = sig + SPX_WOTS_BYTES; + struct leaf_info_x1 info = { 0 }; + unsigned steps[ SPX_WOTS_LEN ]; + + info.wots_sig = sig; + chain_lengths(steps, root); + info.wots_steps = steps; + + set_type(&tree_addr[0], SPX_ADDR_TYPE_HASHTREE); + set_type(&info.pk_addr[0], SPX_ADDR_TYPE_WOTSPK); + copy_subtree_addr(&info.leaf_addr[0], wots_addr); + copy_subtree_addr(&info.pk_addr[0], wots_addr); + + info.wots_sign_leaf = idx_leaf; + + treehashx1(root, auth_path, ctx, + idx_leaf, 0, + SPX_TREE_HEIGHT, + wots_gen_leafx1, + tree_addr, &info); +} + +/* Compute root node of the top-most subtree. */ +void merkle_gen_root(unsigned char *root, const spx_ctx *ctx) +{ + /* We do not need the auth path in key generation, but it simplifies the + code to have just one treehash routine that computes both root and path + in one function. */ + unsigned char auth_path[SPX_TREE_HEIGHT * SPX_N + SPX_WOTS_BYTES]; + uint32_t top_tree_addr[8] = {0}; + uint32_t wots_addr[8] = {0}; + + set_layer_addr(top_tree_addr, SPX_D - 1); + set_layer_addr(wots_addr, SPX_D - 1); + + merkle_sign(auth_path, root, ctx, + wots_addr, top_tree_addr, + (uint32_t)~0 /* ~0 means "don't bother generating an auth path */ ); +} diff --git a/app/src/main/cpp/pq/sphincsplus/merkle.h b/app/src/main/cpp/pq/sphincsplus/merkle.h new file mode 100644 index 0000000..9ac2759 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/merkle.h @@ -0,0 +1,18 @@ +#if !defined( MERKLE_H_ ) +#define MERKLE_H_ + +#include + +/* Generate a Merkle signature (WOTS signature followed by the Merkle */ +/* authentication path) */ +#define merkle_sign SPX_NAMESPACE(merkle_sign) +void merkle_sign(uint8_t *sig, unsigned char *root, + const spx_ctx* ctx, + uint32_t wots_addr[8], uint32_t tree_addr[8], + uint32_t idx_leaf); + +/* Compute the root node of the top-most subtree. */ +#define merkle_gen_root SPX_NAMESPACE(merkle_gen_root) +void merkle_gen_root(unsigned char *root, const spx_ctx* ctx); + +#endif /* MERKLE_H_ */ diff --git a/app/src/main/cpp/pq/sphincsplus/params.h b/app/src/main/cpp/pq/sphincsplus/params.h new file mode 100644 index 0000000..1d7f9c9 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/params.h @@ -0,0 +1,5 @@ +#define str(s) #s +#define xstr(s) str(s) + +#include xstr(params/params-PARAMS.h) + diff --git a/app/src/main/cpp/pq/sphincsplus/params/params-sphincs-sha2-128s.h b/app/src/main/cpp/pq/sphincsplus/params/params-sphincs-sha2-128s.h new file mode 100644 index 0000000..8ef8608 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/params/params-sphincs-sha2-128s.h @@ -0,0 +1,85 @@ +#ifndef SPX_PARAMS_H +#define SPX_PARAMS_H + +#define SPX_NAMESPACE(s) SPX_##s + +/* Hash output length in bytes. */ +#define SPX_N 16 +/* Height of the hypertree. */ +#define SPX_FULL_HEIGHT 63 +/* Number of subtree layer. */ +#define SPX_D 7 +/* FORS tree dimensions. */ +#define SPX_FORS_HEIGHT 12 +#define SPX_FORS_TREES 14 +/* Winternitz parameter, */ +#define SPX_WOTS_W 16 + +/* The hash function is defined by linking a different hash.c file, as opposed + to setting a #define constant. */ + +/* This is a SHA2-based parameter set, hence whether we use SHA-256 + * exclusively or we use both SHA-256 and SHA-512 is controlled by + * the following #define */ +#define SPX_SHA512 0 /* Use SHA-256 for all hashes */ + +/* For clarity */ +#define SPX_ADDR_BYTES 32 + +/* WOTS parameters. */ +#if SPX_WOTS_W == 256 + #define SPX_WOTS_LOGW 8 +#elif SPX_WOTS_W == 16 + #define SPX_WOTS_LOGW 4 +#else + #error SPX_WOTS_W assumed 16 or 256 +#endif + +#define SPX_WOTS_LEN1 (8 * SPX_N / SPX_WOTS_LOGW) + +/* SPX_WOTS_LEN2 is floor(log(len_1 * (w - 1)) / log(w)) + 1; we precompute */ +#if SPX_WOTS_W == 256 + #if SPX_N <= 1 + #define SPX_WOTS_LEN2 1 + #elif SPX_N <= 256 + #define SPX_WOTS_LEN2 2 + #else + #error Did not precompute SPX_WOTS_LEN2 for n outside {2, .., 256} + #endif +#elif SPX_WOTS_W == 16 + #if SPX_N <= 8 + #define SPX_WOTS_LEN2 2 + #elif SPX_N <= 136 + #define SPX_WOTS_LEN2 3 + #elif SPX_N <= 256 + #define SPX_WOTS_LEN2 4 + #else + #error Did not precompute SPX_WOTS_LEN2 for n outside {2, .., 256} + #endif +#endif + +#define SPX_WOTS_LEN (SPX_WOTS_LEN1 + SPX_WOTS_LEN2) +#define SPX_WOTS_BYTES (SPX_WOTS_LEN * SPX_N) +#define SPX_WOTS_PK_BYTES SPX_WOTS_BYTES + +/* Subtree size. */ +#define SPX_TREE_HEIGHT (SPX_FULL_HEIGHT / SPX_D) + +#if SPX_TREE_HEIGHT * SPX_D != SPX_FULL_HEIGHT + #error SPX_D should always divide SPX_FULL_HEIGHT +#endif + +/* FORS parameters. */ +#define SPX_FORS_MSG_BYTES ((SPX_FORS_HEIGHT * SPX_FORS_TREES + 7) / 8) +#define SPX_FORS_BYTES ((SPX_FORS_HEIGHT + 1) * SPX_FORS_TREES * SPX_N) +#define SPX_FORS_PK_BYTES SPX_N + +/* Resulting SPX sizes. */ +#define SPX_BYTES (SPX_N + SPX_FORS_BYTES + SPX_D * SPX_WOTS_BYTES +\ + SPX_FULL_HEIGHT * SPX_N) +#define SPX_PK_BYTES (2 * SPX_N) +#define SPX_SK_BYTES (2 * SPX_N + SPX_PK_BYTES) + +#include "../sha2_offsets.h" + +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/randombytes.h b/app/src/main/cpp/pq/sphincsplus/randombytes.h new file mode 100644 index 0000000..3308bdc --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/randombytes.h @@ -0,0 +1,15 @@ +#ifndef VEXTA_PQ_RANDOMBYTES_H +#define VEXTA_PQ_RANDOMBYTES_H +#include +#include +#ifdef __cplusplus +extern "C" { +#endif +/* VEXTA: unified prototype. Definition lives in pq_randombytes.cpp and is + backed by Bitcoin's CSPRNG (GetStrongRandBytes). The upstream test-only + randombytes.c files were deleted — they must never ship in a wallet. */ +void randombytes(uint8_t *out, size_t outlen); +#ifdef __cplusplus +} +#endif +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/sha2.c b/app/src/main/cpp/pq/sphincsplus/sha2.c new file mode 100644 index 0000000..ef73047 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/sha2.c @@ -0,0 +1,700 @@ +/* Based on the public domain implementation in + * crypto_hash/sha512/ref/ from http://bench.cr.yp.to/supercop.html + * by D. J. Bernstein */ + +#include +#include +#include + +#include "utils.h" +#include "sha2.h" + +static uint32_t load_bigendian_32(const uint8_t *x) { + return (uint32_t)(x[3]) | (((uint32_t)(x[2])) << 8) | + (((uint32_t)(x[1])) << 16) | (((uint32_t)(x[0])) << 24); +} + +static uint64_t load_bigendian_64(const uint8_t *x) { + return (uint64_t)(x[7]) | (((uint64_t)(x[6])) << 8) | + (((uint64_t)(x[5])) << 16) | (((uint64_t)(x[4])) << 24) | + (((uint64_t)(x[3])) << 32) | (((uint64_t)(x[2])) << 40) | + (((uint64_t)(x[1])) << 48) | (((uint64_t)(x[0])) << 56); +} + +static void store_bigendian_32(uint8_t *x, uint64_t u) { + x[3] = (uint8_t) u; + u >>= 8; + x[2] = (uint8_t) u; + u >>= 8; + x[1] = (uint8_t) u; + u >>= 8; + x[0] = (uint8_t) u; +} + +static void store_bigendian_64(uint8_t *x, uint64_t u) { + x[7] = (uint8_t) u; + u >>= 8; + x[6] = (uint8_t) u; + u >>= 8; + x[5] = (uint8_t) u; + u >>= 8; + x[4] = (uint8_t) u; + u >>= 8; + x[3] = (uint8_t) u; + u >>= 8; + x[2] = (uint8_t) u; + u >>= 8; + x[1] = (uint8_t) u; + u >>= 8; + x[0] = (uint8_t) u; +} + +#define SHR(x, c) ((x) >> (c)) +#define ROTR_32(x, c) (((x) >> (c)) | ((x) << (32 - (c)))) +#define ROTR_64(x,c) (((x) >> (c)) | ((x) << (64 - (c)))) + +#define Ch(x, y, z) (((x) & (y)) ^ (~(x) & (z))) +#define Maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) + +#define Sigma0_32(x) (ROTR_32(x, 2) ^ ROTR_32(x,13) ^ ROTR_32(x,22)) +#define Sigma1_32(x) (ROTR_32(x, 6) ^ ROTR_32(x,11) ^ ROTR_32(x,25)) +#define sigma0_32(x) (ROTR_32(x, 7) ^ ROTR_32(x,18) ^ SHR(x, 3)) +#define sigma1_32(x) (ROTR_32(x,17) ^ ROTR_32(x,19) ^ SHR(x,10)) + +#define Sigma0_64(x) (ROTR_64(x,28) ^ ROTR_64(x,34) ^ ROTR_64(x,39)) +#define Sigma1_64(x) (ROTR_64(x,14) ^ ROTR_64(x,18) ^ ROTR_64(x,41)) +#define sigma0_64(x) (ROTR_64(x, 1) ^ ROTR_64(x, 8) ^ SHR(x,7)) +#define sigma1_64(x) (ROTR_64(x,19) ^ ROTR_64(x,61) ^ SHR(x,6)) + +#define M_32(w0, w14, w9, w1) w0 = sigma1_32(w14) + (w9) + sigma0_32(w1) + (w0); +#define M_64(w0, w14, w9, w1) w0 = sigma1_64(w14) + (w9) + sigma0_64(w1) + (w0); + +#define EXPAND_32 \ + M_32(w0, w14, w9, w1) \ + M_32(w1, w15, w10, w2) \ + M_32(w2, w0, w11, w3) \ + M_32(w3, w1, w12, w4) \ + M_32(w4, w2, w13, w5) \ + M_32(w5, w3, w14, w6) \ + M_32(w6, w4, w15, w7) \ + M_32(w7, w5, w0, w8) \ + M_32(w8, w6, w1, w9) \ + M_32(w9, w7, w2, w10) \ + M_32(w10, w8, w3, w11) \ + M_32(w11, w9, w4, w12) \ + M_32(w12, w10, w5, w13) \ + M_32(w13, w11, w6, w14) \ + M_32(w14, w12, w7, w15) \ + M_32(w15, w13, w8, w0) + +#define EXPAND_64 \ + M_64(w0 ,w14,w9 ,w1 ) \ + M_64(w1 ,w15,w10,w2 ) \ + M_64(w2 ,w0 ,w11,w3 ) \ + M_64(w3 ,w1 ,w12,w4 ) \ + M_64(w4 ,w2 ,w13,w5 ) \ + M_64(w5 ,w3 ,w14,w6 ) \ + M_64(w6 ,w4 ,w15,w7 ) \ + M_64(w7 ,w5 ,w0 ,w8 ) \ + M_64(w8 ,w6 ,w1 ,w9 ) \ + M_64(w9 ,w7 ,w2 ,w10) \ + M_64(w10,w8 ,w3 ,w11) \ + M_64(w11,w9 ,w4 ,w12) \ + M_64(w12,w10,w5 ,w13) \ + M_64(w13,w11,w6 ,w14) \ + M_64(w14,w12,w7 ,w15) \ + M_64(w15,w13,w8 ,w0 ) + +#define F_32(w, k) \ + T1 = h + Sigma1_32(e) + Ch(e, f, g) + (k) + (w); \ + T2 = Sigma0_32(a) + Maj(a, b, c); \ + h = g; \ + g = f; \ + f = e; \ + e = d + T1; \ + d = c; \ + c = b; \ + b = a; \ + a = T1 + T2; + +#define F_64(w,k) \ + T1 = h + Sigma1_64(e) + Ch(e,f,g) + k + w; \ + T2 = Sigma0_64(a) + Maj(a,b,c); \ + h = g; \ + g = f; \ + f = e; \ + e = d + T1; \ + d = c; \ + c = b; \ + b = a; \ + a = T1 + T2; + +static size_t crypto_hashblocks_sha256(uint8_t *statebytes, + const uint8_t *in, size_t inlen) { + uint32_t state[8]; + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + uint32_t e; + uint32_t f; + uint32_t g; + uint32_t h; + uint32_t T1; + uint32_t T2; + + a = load_bigendian_32(statebytes + 0); + state[0] = a; + b = load_bigendian_32(statebytes + 4); + state[1] = b; + c = load_bigendian_32(statebytes + 8); + state[2] = c; + d = load_bigendian_32(statebytes + 12); + state[3] = d; + e = load_bigendian_32(statebytes + 16); + state[4] = e; + f = load_bigendian_32(statebytes + 20); + state[5] = f; + g = load_bigendian_32(statebytes + 24); + state[6] = g; + h = load_bigendian_32(statebytes + 28); + state[7] = h; + + while (inlen >= 64) { + uint32_t w0 = load_bigendian_32(in + 0); + uint32_t w1 = load_bigendian_32(in + 4); + uint32_t w2 = load_bigendian_32(in + 8); + uint32_t w3 = load_bigendian_32(in + 12); + uint32_t w4 = load_bigendian_32(in + 16); + uint32_t w5 = load_bigendian_32(in + 20); + uint32_t w6 = load_bigendian_32(in + 24); + uint32_t w7 = load_bigendian_32(in + 28); + uint32_t w8 = load_bigendian_32(in + 32); + uint32_t w9 = load_bigendian_32(in + 36); + uint32_t w10 = load_bigendian_32(in + 40); + uint32_t w11 = load_bigendian_32(in + 44); + uint32_t w12 = load_bigendian_32(in + 48); + uint32_t w13 = load_bigendian_32(in + 52); + uint32_t w14 = load_bigendian_32(in + 56); + uint32_t w15 = load_bigendian_32(in + 60); + + F_32(w0, 0x428a2f98) + F_32(w1, 0x71374491) + F_32(w2, 0xb5c0fbcf) + F_32(w3, 0xe9b5dba5) + F_32(w4, 0x3956c25b) + F_32(w5, 0x59f111f1) + F_32(w6, 0x923f82a4) + F_32(w7, 0xab1c5ed5) + F_32(w8, 0xd807aa98) + F_32(w9, 0x12835b01) + F_32(w10, 0x243185be) + F_32(w11, 0x550c7dc3) + F_32(w12, 0x72be5d74) + F_32(w13, 0x80deb1fe) + F_32(w14, 0x9bdc06a7) + F_32(w15, 0xc19bf174) + + EXPAND_32 + + F_32(w0, 0xe49b69c1) + F_32(w1, 0xefbe4786) + F_32(w2, 0x0fc19dc6) + F_32(w3, 0x240ca1cc) + F_32(w4, 0x2de92c6f) + F_32(w5, 0x4a7484aa) + F_32(w6, 0x5cb0a9dc) + F_32(w7, 0x76f988da) + F_32(w8, 0x983e5152) + F_32(w9, 0xa831c66d) + F_32(w10, 0xb00327c8) + F_32(w11, 0xbf597fc7) + F_32(w12, 0xc6e00bf3) + F_32(w13, 0xd5a79147) + F_32(w14, 0x06ca6351) + F_32(w15, 0x14292967) + + EXPAND_32 + + F_32(w0, 0x27b70a85) + F_32(w1, 0x2e1b2138) + F_32(w2, 0x4d2c6dfc) + F_32(w3, 0x53380d13) + F_32(w4, 0x650a7354) + F_32(w5, 0x766a0abb) + F_32(w6, 0x81c2c92e) + F_32(w7, 0x92722c85) + F_32(w8, 0xa2bfe8a1) + F_32(w9, 0xa81a664b) + F_32(w10, 0xc24b8b70) + F_32(w11, 0xc76c51a3) + F_32(w12, 0xd192e819) + F_32(w13, 0xd6990624) + F_32(w14, 0xf40e3585) + F_32(w15, 0x106aa070) + + EXPAND_32 + + F_32(w0, 0x19a4c116) + F_32(w1, 0x1e376c08) + F_32(w2, 0x2748774c) + F_32(w3, 0x34b0bcb5) + F_32(w4, 0x391c0cb3) + F_32(w5, 0x4ed8aa4a) + F_32(w6, 0x5b9cca4f) + F_32(w7, 0x682e6ff3) + F_32(w8, 0x748f82ee) + F_32(w9, 0x78a5636f) + F_32(w10, 0x84c87814) + F_32(w11, 0x8cc70208) + F_32(w12, 0x90befffa) + F_32(w13, 0xa4506ceb) + F_32(w14, 0xbef9a3f7) + F_32(w15, 0xc67178f2) + + a += state[0]; + b += state[1]; + c += state[2]; + d += state[3]; + e += state[4]; + f += state[5]; + g += state[6]; + h += state[7]; + + state[0] = a; + state[1] = b; + state[2] = c; + state[3] = d; + state[4] = e; + state[5] = f; + state[6] = g; + state[7] = h; + + in += 64; + inlen -= 64; + } + + store_bigendian_32(statebytes + 0, state[0]); + store_bigendian_32(statebytes + 4, state[1]); + store_bigendian_32(statebytes + 8, state[2]); + store_bigendian_32(statebytes + 12, state[3]); + store_bigendian_32(statebytes + 16, state[4]); + store_bigendian_32(statebytes + 20, state[5]); + store_bigendian_32(statebytes + 24, state[6]); + store_bigendian_32(statebytes + 28, state[7]); + + return inlen; +} + +static int crypto_hashblocks_sha512(unsigned char *statebytes,const unsigned char *in,unsigned long long inlen) +{ + uint64_t state[8]; + uint64_t a; + uint64_t b; + uint64_t c; + uint64_t d; + uint64_t e; + uint64_t f; + uint64_t g; + uint64_t h; + uint64_t T1; + uint64_t T2; + + a = load_bigendian_64(statebytes + 0); state[0] = a; + b = load_bigendian_64(statebytes + 8); state[1] = b; + c = load_bigendian_64(statebytes + 16); state[2] = c; + d = load_bigendian_64(statebytes + 24); state[3] = d; + e = load_bigendian_64(statebytes + 32); state[4] = e; + f = load_bigendian_64(statebytes + 40); state[5] = f; + g = load_bigendian_64(statebytes + 48); state[6] = g; + h = load_bigendian_64(statebytes + 56); state[7] = h; + + while (inlen >= 128) { + uint64_t w0 = load_bigendian_64(in + 0); + uint64_t w1 = load_bigendian_64(in + 8); + uint64_t w2 = load_bigendian_64(in + 16); + uint64_t w3 = load_bigendian_64(in + 24); + uint64_t w4 = load_bigendian_64(in + 32); + uint64_t w5 = load_bigendian_64(in + 40); + uint64_t w6 = load_bigendian_64(in + 48); + uint64_t w7 = load_bigendian_64(in + 56); + uint64_t w8 = load_bigendian_64(in + 64); + uint64_t w9 = load_bigendian_64(in + 72); + uint64_t w10 = load_bigendian_64(in + 80); + uint64_t w11 = load_bigendian_64(in + 88); + uint64_t w12 = load_bigendian_64(in + 96); + uint64_t w13 = load_bigendian_64(in + 104); + uint64_t w14 = load_bigendian_64(in + 112); + uint64_t w15 = load_bigendian_64(in + 120); + + F_64(w0 ,0x428a2f98d728ae22ULL) + F_64(w1 ,0x7137449123ef65cdULL) + F_64(w2 ,0xb5c0fbcfec4d3b2fULL) + F_64(w3 ,0xe9b5dba58189dbbcULL) + F_64(w4 ,0x3956c25bf348b538ULL) + F_64(w5 ,0x59f111f1b605d019ULL) + F_64(w6 ,0x923f82a4af194f9bULL) + F_64(w7 ,0xab1c5ed5da6d8118ULL) + F_64(w8 ,0xd807aa98a3030242ULL) + F_64(w9 ,0x12835b0145706fbeULL) + F_64(w10,0x243185be4ee4b28cULL) + F_64(w11,0x550c7dc3d5ffb4e2ULL) + F_64(w12,0x72be5d74f27b896fULL) + F_64(w13,0x80deb1fe3b1696b1ULL) + F_64(w14,0x9bdc06a725c71235ULL) + F_64(w15,0xc19bf174cf692694ULL) + + EXPAND_64 + + F_64(w0 ,0xe49b69c19ef14ad2ULL) + F_64(w1 ,0xefbe4786384f25e3ULL) + F_64(w2 ,0x0fc19dc68b8cd5b5ULL) + F_64(w3 ,0x240ca1cc77ac9c65ULL) + F_64(w4 ,0x2de92c6f592b0275ULL) + F_64(w5 ,0x4a7484aa6ea6e483ULL) + F_64(w6 ,0x5cb0a9dcbd41fbd4ULL) + F_64(w7 ,0x76f988da831153b5ULL) + F_64(w8 ,0x983e5152ee66dfabULL) + F_64(w9 ,0xa831c66d2db43210ULL) + F_64(w10,0xb00327c898fb213fULL) + F_64(w11,0xbf597fc7beef0ee4ULL) + F_64(w12,0xc6e00bf33da88fc2ULL) + F_64(w13,0xd5a79147930aa725ULL) + F_64(w14,0x06ca6351e003826fULL) + F_64(w15,0x142929670a0e6e70ULL) + + EXPAND_64 + + F_64(w0 ,0x27b70a8546d22ffcULL) + F_64(w1 ,0x2e1b21385c26c926ULL) + F_64(w2 ,0x4d2c6dfc5ac42aedULL) + F_64(w3 ,0x53380d139d95b3dfULL) + F_64(w4 ,0x650a73548baf63deULL) + F_64(w5 ,0x766a0abb3c77b2a8ULL) + F_64(w6 ,0x81c2c92e47edaee6ULL) + F_64(w7 ,0x92722c851482353bULL) + F_64(w8 ,0xa2bfe8a14cf10364ULL) + F_64(w9 ,0xa81a664bbc423001ULL) + F_64(w10,0xc24b8b70d0f89791ULL) + F_64(w11,0xc76c51a30654be30ULL) + F_64(w12,0xd192e819d6ef5218ULL) + F_64(w13,0xd69906245565a910ULL) + F_64(w14,0xf40e35855771202aULL) + F_64(w15,0x106aa07032bbd1b8ULL) + + EXPAND_64 + + F_64(w0 ,0x19a4c116b8d2d0c8ULL) + F_64(w1 ,0x1e376c085141ab53ULL) + F_64(w2 ,0x2748774cdf8eeb99ULL) + F_64(w3 ,0x34b0bcb5e19b48a8ULL) + F_64(w4 ,0x391c0cb3c5c95a63ULL) + F_64(w5 ,0x4ed8aa4ae3418acbULL) + F_64(w6 ,0x5b9cca4f7763e373ULL) + F_64(w7 ,0x682e6ff3d6b2b8a3ULL) + F_64(w8 ,0x748f82ee5defb2fcULL) + F_64(w9 ,0x78a5636f43172f60ULL) + F_64(w10,0x84c87814a1f0ab72ULL) + F_64(w11,0x8cc702081a6439ecULL) + F_64(w12,0x90befffa23631e28ULL) + F_64(w13,0xa4506cebde82bde9ULL) + F_64(w14,0xbef9a3f7b2c67915ULL) + F_64(w15,0xc67178f2e372532bULL) + + EXPAND_64 + + F_64(w0 ,0xca273eceea26619cULL) + F_64(w1 ,0xd186b8c721c0c207ULL) + F_64(w2 ,0xeada7dd6cde0eb1eULL) + F_64(w3 ,0xf57d4f7fee6ed178ULL) + F_64(w4 ,0x06f067aa72176fbaULL) + F_64(w5 ,0x0a637dc5a2c898a6ULL) + F_64(w6 ,0x113f9804bef90daeULL) + F_64(w7 ,0x1b710b35131c471bULL) + F_64(w8 ,0x28db77f523047d84ULL) + F_64(w9 ,0x32caab7b40c72493ULL) + F_64(w10,0x3c9ebe0a15c9bebcULL) + F_64(w11,0x431d67c49c100d4cULL) + F_64(w12,0x4cc5d4becb3e42b6ULL) + F_64(w13,0x597f299cfc657e2aULL) + F_64(w14,0x5fcb6fab3ad6faecULL) + F_64(w15,0x6c44198c4a475817ULL) + + a += state[0]; + b += state[1]; + c += state[2]; + d += state[3]; + e += state[4]; + f += state[5]; + g += state[6]; + h += state[7]; + + state[0] = a; + state[1] = b; + state[2] = c; + state[3] = d; + state[4] = e; + state[5] = f; + state[6] = g; + state[7] = h; + + in += 128; + inlen -= 128; + } + + store_bigendian_64(statebytes + 0,state[0]); + store_bigendian_64(statebytes + 8,state[1]); + store_bigendian_64(statebytes + 16,state[2]); + store_bigendian_64(statebytes + 24,state[3]); + store_bigendian_64(statebytes + 32,state[4]); + store_bigendian_64(statebytes + 40,state[5]); + store_bigendian_64(statebytes + 48,state[6]); + store_bigendian_64(statebytes + 56,state[7]); + + return inlen; +} + + +static const uint8_t iv_256[32] = { + 0x6a, 0x09, 0xe6, 0x67, 0xbb, 0x67, 0xae, 0x85, + 0x3c, 0x6e, 0xf3, 0x72, 0xa5, 0x4f, 0xf5, 0x3a, + 0x51, 0x0e, 0x52, 0x7f, 0x9b, 0x05, 0x68, 0x8c, + 0x1f, 0x83, 0xd9, 0xab, 0x5b, 0xe0, 0xcd, 0x19 +}; + +static const uint8_t iv_512[64] = { + 0x6a, 0x09, 0xe6, 0x67, 0xf3, 0xbc, 0xc9, 0x08, 0xbb, 0x67, 0xae, + 0x85, 0x84, 0xca, 0xa7, 0x3b, 0x3c, 0x6e, 0xf3, 0x72, 0xfe, 0x94, + 0xf8, 0x2b, 0xa5, 0x4f, 0xf5, 0x3a, 0x5f, 0x1d, 0x36, 0xf1, 0x51, + 0x0e, 0x52, 0x7f, 0xad, 0xe6, 0x82, 0xd1, 0x9b, 0x05, 0x68, 0x8c, + 0x2b, 0x3e, 0x6c, 0x1f, 0x1f, 0x83, 0xd9, 0xab, 0xfb, 0x41, 0xbd, + 0x6b, 0x5b, 0xe0, 0xcd, 0x19, 0x13, 0x7e, 0x21, 0x79 +}; + +void sha256_inc_init(uint8_t *state) { + for (size_t i = 0; i < 32; ++i) { + state[i] = iv_256[i]; + } + for (size_t i = 32; i < 40; ++i) { + state[i] = 0; + } +} + +void sha512_inc_init(uint8_t *state) { + for (size_t i = 0; i < 64; ++i) { + state[i] = iv_512[i]; + } + for (size_t i = 64; i < 72; ++i) { + state[i] = 0; + } +} + +void sha256_inc_blocks(uint8_t *state, const uint8_t *in, size_t inblocks) { + uint64_t bytes = load_bigendian_64(state + 32); + + crypto_hashblocks_sha256(state, in, 64 * inblocks); + bytes += 64 * inblocks; + + store_bigendian_64(state + 32, bytes); +} + +void sha512_inc_blocks(uint8_t *state, const uint8_t *in, size_t inblocks) { + uint64_t bytes = load_bigendian_64(state + 64); + + crypto_hashblocks_sha512(state, in, 128 * inblocks); + bytes += 128 * inblocks; + + store_bigendian_64(state + 64, bytes); +} + +void sha256_inc_finalize(uint8_t *out, uint8_t *state, const uint8_t *in, size_t inlen) { + uint8_t padded[128]; + uint64_t bytes = load_bigendian_64(state + 32) + inlen; + + crypto_hashblocks_sha256(state, in, inlen); + in += inlen; + inlen &= 63; + in -= inlen; + + for (size_t i = 0; i < inlen; ++i) { + padded[i] = in[i]; + } + padded[inlen] = 0x80; + + if (inlen < 56) { + for (size_t i = inlen + 1; i < 56; ++i) { + padded[i] = 0; + } + padded[56] = (uint8_t) (bytes >> 53); + padded[57] = (uint8_t) (bytes >> 45); + padded[58] = (uint8_t) (bytes >> 37); + padded[59] = (uint8_t) (bytes >> 29); + padded[60] = (uint8_t) (bytes >> 21); + padded[61] = (uint8_t) (bytes >> 13); + padded[62] = (uint8_t) (bytes >> 5); + padded[63] = (uint8_t) (bytes << 3); + crypto_hashblocks_sha256(state, padded, 64); + } else { + for (size_t i = inlen + 1; i < 120; ++i) { + padded[i] = 0; + } + padded[120] = (uint8_t) (bytes >> 53); + padded[121] = (uint8_t) (bytes >> 45); + padded[122] = (uint8_t) (bytes >> 37); + padded[123] = (uint8_t) (bytes >> 29); + padded[124] = (uint8_t) (bytes >> 21); + padded[125] = (uint8_t) (bytes >> 13); + padded[126] = (uint8_t) (bytes >> 5); + padded[127] = (uint8_t) (bytes << 3); + crypto_hashblocks_sha256(state, padded, 128); + } + + for (size_t i = 0; i < 32; ++i) { + out[i] = state[i]; + } + +} + +void sha512_inc_finalize(uint8_t *out, uint8_t *state, const uint8_t *in, size_t inlen) { + uint8_t padded[256]; + uint64_t bytes = load_bigendian_64(state + 64) + inlen; + + crypto_hashblocks_sha512(state, in, inlen); + in += inlen; + inlen &= 127; + in -= inlen; + + for (size_t i = 0; i < inlen; ++i) { + padded[i] = in[i]; + } + padded[inlen] = 0x80; + + if (inlen < 112) { + for (size_t i = inlen + 1; i < 119; ++i) { + padded[i] = 0; + } + padded[119] = (uint8_t) (bytes >> 61); + padded[120] = (uint8_t) (bytes >> 53); + padded[121] = (uint8_t) (bytes >> 45); + padded[122] = (uint8_t) (bytes >> 37); + padded[123] = (uint8_t) (bytes >> 29); + padded[124] = (uint8_t) (bytes >> 21); + padded[125] = (uint8_t) (bytes >> 13); + padded[126] = (uint8_t) (bytes >> 5); + padded[127] = (uint8_t) (bytes << 3); + crypto_hashblocks_sha512(state, padded, 128); + } else { + for (size_t i = inlen + 1; i < 247; ++i) { + padded[i] = 0; + } + padded[247] = (uint8_t) (bytes >> 61); + padded[248] = (uint8_t) (bytes >> 53); + padded[249] = (uint8_t) (bytes >> 45); + padded[250] = (uint8_t) (bytes >> 37); + padded[251] = (uint8_t) (bytes >> 29); + padded[252] = (uint8_t) (bytes >> 21); + padded[253] = (uint8_t) (bytes >> 13); + padded[254] = (uint8_t) (bytes >> 5); + padded[255] = (uint8_t) (bytes << 3); + crypto_hashblocks_sha512(state, padded, 256); + } + + for (size_t i = 0; i < 64; ++i) { + out[i] = state[i]; + } +} + +void sha256(uint8_t *out, const uint8_t *in, size_t inlen) { + uint8_t state[40]; + + sha256_inc_init(state); + sha256_inc_finalize(out, state, in, inlen); +} + +void sha512(uint8_t *out, const uint8_t *in, size_t inlen) { + uint8_t state[72]; + + sha512_inc_init(state); + sha512_inc_finalize(out, state, in, inlen); +} + +/** + * mgf1 function based on the SHA-256 hash function + * Note that inlen should be sufficiently small that it still allows for + * an array to be allocated on the stack. Typically 'in' is merely a seed. + * Outputs outlen number of bytes + */ +void mgf1_256(unsigned char *out, unsigned long outlen, + const unsigned char *in, unsigned long inlen) +{ + SPX_VLA(uint8_t, inbuf, inlen+4); + unsigned char outbuf[SPX_SHA256_OUTPUT_BYTES]; + unsigned long i; + + memcpy(inbuf, in, inlen); + + /* While we can fit in at least another full block of SHA256 output.. */ + for (i = 0; (i+1)*SPX_SHA256_OUTPUT_BYTES <= outlen; i++) { + u32_to_bytes(inbuf + inlen, i); + sha256(out, inbuf, inlen + 4); + out += SPX_SHA256_OUTPUT_BYTES; + } + /* Until we cannot anymore, and we fill the remainder. */ + if (outlen > i*SPX_SHA256_OUTPUT_BYTES) { + u32_to_bytes(inbuf + inlen, i); + sha256(outbuf, inbuf, inlen + 4); + memcpy(out, outbuf, outlen - i*SPX_SHA256_OUTPUT_BYTES); + } +} + +/* + * mgf1 function based on the SHA-512 hash function + */ +void mgf1_512(unsigned char *out, unsigned long outlen, + const unsigned char *in, unsigned long inlen) +{ + SPX_VLA(uint8_t, inbuf, inlen+4); + unsigned char outbuf[SPX_SHA512_OUTPUT_BYTES]; + unsigned long i; + + memcpy(inbuf, in, inlen); + + /* While we can fit in at least another full block of SHA512 output.. */ + for (i = 0; (i+1)*SPX_SHA512_OUTPUT_BYTES <= outlen; i++) { + u32_to_bytes(inbuf + inlen, i); + sha512(out, inbuf, inlen + 4); + out += SPX_SHA512_OUTPUT_BYTES; + } + /* Until we cannot anymore, and we fill the remainder. */ + if (outlen > i*SPX_SHA512_OUTPUT_BYTES) { + u32_to_bytes(inbuf + inlen, i); + sha512(outbuf, inbuf, inlen + 4); + memcpy(out, outbuf, outlen - i*SPX_SHA512_OUTPUT_BYTES); + } +} + + +/** + * Absorb the constant pub_seed using one round of the compression function + * This initializes state_seeded and state_seeded_512, which can then be + * reused in thash + **/ +void seed_state(spx_ctx *ctx) { + uint8_t block[SPX_SHA512_BLOCK_BYTES]; + size_t i; + + for (i = 0; i < SPX_N; ++i) { + block[i] = ctx->pub_seed[i]; + } + for (i = SPX_N; i < SPX_SHA512_BLOCK_BYTES; ++i) { + block[i] = 0; + } + /* block has been properly initialized for both SHA-256 and SHA-512 */ + + sha256_inc_init(ctx->state_seeded); + sha256_inc_blocks(ctx->state_seeded, block, 1); +#if SPX_SHA512 + sha512_inc_init(ctx->state_seeded_512); + sha512_inc_blocks(ctx->state_seeded_512, block, 1); +#endif +} diff --git a/app/src/main/cpp/pq/sphincsplus/sha2.h b/app/src/main/cpp/pq/sphincsplus/sha2.h new file mode 100644 index 0000000..732ab4b --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/sha2.h @@ -0,0 +1,43 @@ +#ifndef SPX_SHA2_H +#define SPX_SHA2_H + +#include "params.h" + +#define SPX_SHA256_BLOCK_BYTES 64 +#define SPX_SHA256_OUTPUT_BYTES 32 /* This does not necessarily equal SPX_N */ + +#define SPX_SHA512_BLOCK_BYTES 128 +#define SPX_SHA512_OUTPUT_BYTES 64 + +#if SPX_SHA256_OUTPUT_BYTES < SPX_N + #error Linking against SHA-256 with N larger than 32 bytes is not supported +#endif + +#define SPX_SHA256_ADDR_BYTES 22 + +#include +#include + +void sha256_inc_init(uint8_t *state); +void sha256_inc_blocks(uint8_t *state, const uint8_t *in, size_t inblocks); +void sha256_inc_finalize(uint8_t *out, uint8_t *state, const uint8_t *in, size_t inlen); +void sha256(uint8_t *out, const uint8_t *in, size_t inlen); + +void sha512_inc_init(uint8_t *state); +void sha512_inc_blocks(uint8_t *state, const uint8_t *in, size_t inblocks); +void sha512_inc_finalize(uint8_t *out, uint8_t *state, const uint8_t *in, size_t inlen); +void sha512(uint8_t *out, const uint8_t *in, size_t inlen); + +#define mgf1_256 SPX_NAMESPACE(mgf1_256) +void mgf1_256(unsigned char *out, unsigned long outlen, + const unsigned char *in, unsigned long inlen); + +#define mgf1_512 SPX_NAMESPACE(mgf1_512) +void mgf1_512(unsigned char *out, unsigned long outlen, + const unsigned char *in, unsigned long inlen); + +#define seed_state SPX_NAMESPACE(seed_state) +void seed_state(spx_ctx *ctx); + + +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/sha2_offsets.h b/app/src/main/cpp/pq/sphincsplus/sha2_offsets.h new file mode 100644 index 0000000..49f7e85 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/sha2_offsets.h @@ -0,0 +1,20 @@ +#ifndef SHA2_OFFSETS_H_ +#define SHA2_OFFSETS_H_ + +/* + * Offsets of various fields in the address structure when we use SHA2 as + * the Sphincs+ hash function + */ + +#define SPX_OFFSET_LAYER 0 /* The byte used to specify the Merkle tree layer */ +#define SPX_OFFSET_TREE 1 /* The start of the 8 byte field used to specify the tree */ +#define SPX_OFFSET_TYPE 9 /* The byte used to specify the hash type (reason) */ +#define SPX_OFFSET_KP_ADDR 10 /* The start of the 4 byte field used to specify the key pair address */ +#define SPX_OFFSET_CHAIN_ADDR 17 /* The byte used to specify the chain address (which Winternitz chain) */ +#define SPX_OFFSET_HASH_ADDR 21 /* The byte used to specify the hash address (where in the Winternitz chain) */ +#define SPX_OFFSET_TREE_HGT 17 /* The byte used to specify the height of this node in the FORS or Merkle tree */ +#define SPX_OFFSET_TREE_INDEX 18 /* The start of the 4 byte field used to specify the node in the FORS or Merkle tree */ + +#define SPX_SHA2 1 + +#endif /* SHA2_OFFSETS_H_ */ diff --git a/app/src/main/cpp/pq/sphincsplus/sign.c b/app/src/main/cpp/pq/sphincsplus/sign.c new file mode 100644 index 0000000..a1ee78a --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/sign.c @@ -0,0 +1,293 @@ +#include +#include +#include + +#include "api.h" +#include "params.h" +#include "wots.h" +#include "fors.h" +#include "hash.h" +#include "thash.h" +#include "address.h" +#include "randombytes.h" +#include "utils.h" +#include "merkle.h" + +#ifndef VEXTA_PQ_VERIFY_ONLY +/* + * Returns the length of a secret key, in bytes + */ +unsigned long long crypto_sign_secretkeybytes(void) +{ + return CRYPTO_SECRETKEYBYTES; +} + +/* + * Returns the length of a public key, in bytes + */ +unsigned long long crypto_sign_publickeybytes(void) +{ + return CRYPTO_PUBLICKEYBYTES; +} + +/* + * Returns the length of a signature, in bytes + */ +unsigned long long crypto_sign_bytes(void) +{ + return CRYPTO_BYTES; +} + +/* + * Returns the length of the seed required to generate a key pair, in bytes + */ +unsigned long long crypto_sign_seedbytes(void) +{ + return CRYPTO_SEEDBYTES; +} + +/* + * Generates an SPX key pair given a seed of length + * Format sk: [SK_SEED || SK_PRF || PUB_SEED || root] + * Format pk: [PUB_SEED || root] + */ +int crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk, + const unsigned char *seed) +{ + spx_ctx ctx; + + /* Initialize SK_SEED, SK_PRF and PUB_SEED from seed. */ + memcpy(sk, seed, CRYPTO_SEEDBYTES); + + memcpy(pk, sk + 2*SPX_N, SPX_N); + + memcpy(ctx.pub_seed, pk, SPX_N); + memcpy(ctx.sk_seed, sk, SPX_N); + + /* This hook allows the hash function instantiation to do whatever + preparation or computation it needs, based on the public seed. */ + initialize_hash_function(&ctx); + + /* Compute root node of the top-most subtree. */ + merkle_gen_root(sk + 3*SPX_N, &ctx); + + memcpy(pk + SPX_N, sk + 3*SPX_N, SPX_N); + + return 0; +} + +/* + * Generates an SPX key pair. + * Format sk: [SK_SEED || SK_PRF || PUB_SEED || root] + * Format pk: [PUB_SEED || root] + */ +int crypto_sign_keypair(unsigned char *pk, unsigned char *sk) +{ + unsigned char seed[CRYPTO_SEEDBYTES]; + randombytes(seed, CRYPTO_SEEDBYTES); + crypto_sign_seed_keypair(pk, sk, seed); + + return 0; +} + +/** + * Returns an array containing a detached signature. + */ +int crypto_sign_signature(uint8_t *sig, size_t *siglen, + const uint8_t *m, size_t mlen, const uint8_t *sk) +{ + spx_ctx ctx; + + const unsigned char *sk_prf = sk + SPX_N; + const unsigned char *pk = sk + 2*SPX_N; + + unsigned char optrand[SPX_N]; + unsigned char mhash[SPX_FORS_MSG_BYTES]; + unsigned char root[SPX_N]; + uint32_t i; + uint64_t tree; + uint32_t idx_leaf; + uint32_t wots_addr[8] = {0}; + uint32_t tree_addr[8] = {0}; + + memcpy(ctx.sk_seed, sk, SPX_N); + memcpy(ctx.pub_seed, pk, SPX_N); + + /* This hook allows the hash function instantiation to do whatever + preparation or computation it needs, based on the public seed. */ + initialize_hash_function(&ctx); + + set_type(wots_addr, SPX_ADDR_TYPE_WOTS); + set_type(tree_addr, SPX_ADDR_TYPE_HASHTREE); + + /* Optionally, signing can be made non-deterministic using optrand. + This can help counter side-channel attacks that would benefit from + getting a large number of traces when the signer uses the same nodes. */ + randombytes(optrand, SPX_N); + /* Compute the digest randomization value. */ + gen_message_random(sig, sk_prf, optrand, m, mlen, &ctx); + + /* Derive the message digest and leaf index from R, PK and M. */ + hash_message(mhash, &tree, &idx_leaf, sig, pk, m, mlen, &ctx); + sig += SPX_N; + + set_tree_addr(wots_addr, tree); + set_keypair_addr(wots_addr, idx_leaf); + + /* Sign the message hash using FORS. */ + fors_sign(sig, root, mhash, &ctx, wots_addr); + sig += SPX_FORS_BYTES; + + for (i = 0; i < SPX_D; i++) { + set_layer_addr(tree_addr, i); + set_tree_addr(tree_addr, tree); + + copy_subtree_addr(wots_addr, tree_addr); + set_keypair_addr(wots_addr, idx_leaf); + + merkle_sign(sig, root, &ctx, wots_addr, tree_addr, idx_leaf); + sig += SPX_WOTS_BYTES + SPX_TREE_HEIGHT * SPX_N; + + /* Update the indices for the next layer. */ + idx_leaf = (tree & ((1 << SPX_TREE_HEIGHT)-1)); + tree = tree >> SPX_TREE_HEIGHT; + } + + *siglen = SPX_BYTES; + + return 0; +} + +#endif // !VEXTA_PQ_VERIFY_ONLY + +/** + * Verifies a detached signature and message under a given public key. + */ +int crypto_sign_verify(const uint8_t *sig, size_t siglen, + const uint8_t *m, size_t mlen, const uint8_t *pk) +{ + spx_ctx ctx; + const unsigned char *pub_root = pk + SPX_N; + unsigned char mhash[SPX_FORS_MSG_BYTES]; + unsigned char wots_pk[SPX_WOTS_BYTES]; + unsigned char root[SPX_N]; + unsigned char leaf[SPX_N]; + unsigned int i; + uint64_t tree; + uint32_t idx_leaf; + uint32_t wots_addr[8] = {0}; + uint32_t tree_addr[8] = {0}; + uint32_t wots_pk_addr[8] = {0}; + + if (siglen != SPX_BYTES) { + return -1; + } + + memcpy(ctx.pub_seed, pk, SPX_N); + + /* This hook allows the hash function instantiation to do whatever + preparation or computation it needs, based on the public seed. */ + initialize_hash_function(&ctx); + + set_type(wots_addr, SPX_ADDR_TYPE_WOTS); + set_type(tree_addr, SPX_ADDR_TYPE_HASHTREE); + set_type(wots_pk_addr, SPX_ADDR_TYPE_WOTSPK); + + /* Derive the message digest and leaf index from R || PK || M. */ + /* The additional SPX_N is a result of the hash domain separator. */ + hash_message(mhash, &tree, &idx_leaf, sig, pk, m, mlen, &ctx); + sig += SPX_N; + + /* Layer correctly defaults to 0, so no need to set_layer_addr */ + set_tree_addr(wots_addr, tree); + set_keypair_addr(wots_addr, idx_leaf); + + fors_pk_from_sig(root, sig, mhash, &ctx, wots_addr); + sig += SPX_FORS_BYTES; + + /* For each subtree.. */ + for (i = 0; i < SPX_D; i++) { + set_layer_addr(tree_addr, i); + set_tree_addr(tree_addr, tree); + + copy_subtree_addr(wots_addr, tree_addr); + set_keypair_addr(wots_addr, idx_leaf); + + copy_keypair_addr(wots_pk_addr, wots_addr); + + /* The WOTS public key is only correct if the signature was correct. */ + /* Initially, root is the FORS pk, but on subsequent iterations it is + the root of the subtree below the currently processed subtree. */ + wots_pk_from_sig(wots_pk, sig, root, &ctx, wots_addr); + sig += SPX_WOTS_BYTES; + + /* Compute the leaf node using the WOTS public key. */ + thash(leaf, wots_pk, SPX_WOTS_LEN, &ctx, wots_pk_addr); + + /* Compute the root node of this subtree. */ + compute_root(root, leaf, idx_leaf, 0, sig, SPX_TREE_HEIGHT, + &ctx, tree_addr); + sig += SPX_TREE_HEIGHT * SPX_N; + + /* Update the indices for the next layer. */ + idx_leaf = (tree & ((1 << SPX_TREE_HEIGHT)-1)); + tree = tree >> SPX_TREE_HEIGHT; + } + + /* Check if the root node equals the root node in the public key. */ + if (memcmp(root, pub_root, SPX_N)) { + return -1; + } + + return 0; +} + + +#ifndef VEXTA_PQ_VERIFY_ONLY +/** + * Returns an array containing the signature followed by the message. + */ +int crypto_sign(unsigned char *sm, unsigned long long *smlen, + const unsigned char *m, unsigned long long mlen, + const unsigned char *sk) +{ + size_t siglen; + + crypto_sign_signature(sm, &siglen, m, (size_t)mlen, sk); + + memmove(sm + SPX_BYTES, m, mlen); + *smlen = siglen + mlen; + + return 0; +} + +#endif // !VEXTA_PQ_VERIFY_ONLY + +/** + * Verifies a given signature-message pair under a given public key. + */ +int crypto_sign_open(unsigned char *m, unsigned long long *mlen, + const unsigned char *sm, unsigned long long smlen, + const unsigned char *pk) +{ + /* The API caller does not necessarily know what size a signature should be + but SPHINCS+ signatures are always exactly SPX_BYTES. */ + if (smlen < SPX_BYTES) { + memset(m, 0, smlen); + *mlen = 0; + return -1; + } + + *mlen = smlen - SPX_BYTES; + + if (crypto_sign_verify(sm, SPX_BYTES, sm + SPX_BYTES, (size_t)*mlen, pk)) { + memset(m, 0, smlen); + *mlen = 0; + return -1; + } + + /* If verification was successful, move the message to the right place. */ + memmove(m, sm + SPX_BYTES, *mlen); + + return 0; +} diff --git a/app/src/main/cpp/pq/sphincsplus/thash.h b/app/src/main/cpp/pq/sphincsplus/thash.h new file mode 100644 index 0000000..8687ccf --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/thash.h @@ -0,0 +1,13 @@ +#ifndef SPX_THASH_H +#define SPX_THASH_H + +#include "context.h" +#include "params.h" + +#include + +#define thash SPX_NAMESPACE(thash) +void thash(unsigned char *out, const unsigned char *in, unsigned int inblocks, + const spx_ctx *ctx, uint32_t addr[8]); + +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/thash_sha2_simple.c b/app/src/main/cpp/pq/sphincsplus/thash_sha2_simple.c new file mode 100644 index 0000000..da58896 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/thash_sha2_simple.c @@ -0,0 +1,59 @@ +#include +#include + +#include "thash.h" +#include "address.h" +#include "params.h" +#include "utils.h" +#include "sha2.h" + +#if SPX_SHA512 +static void thash_512(unsigned char *out, const unsigned char *in, unsigned int inblocks, + const spx_ctx *ctx, uint32_t addr[8]); +#endif + +/** + * Takes an array of inblocks concatenated arrays of SPX_N bytes. + */ +void thash(unsigned char *out, const unsigned char *in, unsigned int inblocks, + const spx_ctx *ctx, uint32_t addr[8]) +{ +#if SPX_SHA512 + if (inblocks > 1) { + thash_512(out, in, inblocks, ctx, addr); + return; + } +#endif + + unsigned char outbuf[SPX_SHA256_OUTPUT_BYTES]; + uint8_t sha2_state[40]; + SPX_VLA(uint8_t, buf, SPX_SHA256_ADDR_BYTES + inblocks*SPX_N); + + /* Retrieve precomputed state containing pub_seed */ + memcpy(sha2_state, ctx->state_seeded, 40 * sizeof(uint8_t)); + + memcpy(buf, addr, SPX_SHA256_ADDR_BYTES); + memcpy(buf + SPX_SHA256_ADDR_BYTES, in, inblocks * SPX_N); + + sha256_inc_finalize(outbuf, sha2_state, buf, SPX_SHA256_ADDR_BYTES + inblocks*SPX_N); + memcpy(out, outbuf, SPX_N); +} + +#if SPX_SHA512 +static void thash_512(unsigned char *out, const unsigned char *in, unsigned int inblocks, + const spx_ctx *ctx, uint32_t addr[8]) +{ + unsigned char outbuf[SPX_SHA512_OUTPUT_BYTES]; + uint8_t sha2_state[72]; + SPX_VLA(uint8_t, buf, SPX_SHA256_ADDR_BYTES + inblocks*SPX_N); + + /* Retrieve precomputed state containing pub_seed */ + memcpy(sha2_state, ctx->state_seeded_512, 72 * sizeof(uint8_t)); + + memcpy(buf, addr, SPX_SHA256_ADDR_BYTES); + memcpy(buf + SPX_SHA256_ADDR_BYTES, in, inblocks * SPX_N); + + sha512_inc_finalize(outbuf, sha2_state, buf, SPX_SHA256_ADDR_BYTES + inblocks*SPX_N); + memcpy(out, outbuf, SPX_N); +} +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/utils.c b/app/src/main/cpp/pq/sphincsplus/utils.c new file mode 100644 index 0000000..63d52ee --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/utils.c @@ -0,0 +1,154 @@ +#include + +#include "utils.h" +#include "params.h" +#include "hash.h" +#include "thash.h" +#include "address.h" + +/** + * Converts the value of 'in' to 'outlen' bytes in big-endian byte order. + */ +void ull_to_bytes(unsigned char *out, unsigned int outlen, + unsigned long long in) +{ + int i; + + /* Iterate over out in decreasing order, for big-endianness. */ + for (i = (signed int)outlen - 1; i >= 0; i--) { + out[i] = in & 0xff; + in = in >> 8; + } +} + +void u32_to_bytes(unsigned char *out, uint32_t in) +{ + out[0] = (unsigned char)(in >> 24); + out[1] = (unsigned char)(in >> 16); + out[2] = (unsigned char)(in >> 8); + out[3] = (unsigned char)in; +} + +/** + * Converts the inlen bytes in 'in' from big-endian byte order to an integer. + */ +unsigned long long bytes_to_ull(const unsigned char *in, unsigned int inlen) +{ + unsigned long long retval = 0; + unsigned int i; + + for (i = 0; i < inlen; i++) { + retval |= ((unsigned long long)in[i]) << (8*(inlen - 1 - i)); + } + return retval; +} + +/** + * Computes a root node given a leaf and an auth path. + * Expects address to be complete other than the tree_height and tree_index. + */ +void compute_root(unsigned char *root, const unsigned char *leaf, + uint32_t leaf_idx, uint32_t idx_offset, + const unsigned char *auth_path, uint32_t tree_height, + const spx_ctx *ctx, uint32_t addr[8]) +{ + uint32_t i; + unsigned char buffer[2 * SPX_N]; + + /* If leaf_idx is odd (last bit = 1), current path element is a right child + and auth_path has to go left. Otherwise it is the other way around. */ + if (leaf_idx & 1) { + memcpy(buffer + SPX_N, leaf, SPX_N); + memcpy(buffer, auth_path, SPX_N); + } + else { + memcpy(buffer, leaf, SPX_N); + memcpy(buffer + SPX_N, auth_path, SPX_N); + } + auth_path += SPX_N; + + for (i = 0; i < tree_height - 1; i++) { + leaf_idx >>= 1; + idx_offset >>= 1; + /* Set the address of the node we're creating. */ + set_tree_height(addr, i + 1); + set_tree_index(addr, leaf_idx + idx_offset); + + /* Pick the right or left neighbor, depending on parity of the node. */ + if (leaf_idx & 1) { + thash(buffer + SPX_N, buffer, 2, ctx, addr); + memcpy(buffer, auth_path, SPX_N); + } + else { + thash(buffer, buffer, 2, ctx, addr); + memcpy(buffer + SPX_N, auth_path, SPX_N); + } + auth_path += SPX_N; + } + + /* The last iteration is exceptional; we do not copy an auth_path node. */ + leaf_idx >>= 1; + idx_offset >>= 1; + set_tree_height(addr, tree_height); + set_tree_index(addr, leaf_idx + idx_offset); + thash(root, buffer, 2, ctx, addr); +} + +/** + * For a given leaf index, computes the authentication path and the resulting + * root node using Merkle's TreeHash algorithm. + * Expects the layer and tree parts of the tree_addr to be set, as well as the + * tree type (i.e. SPX_ADDR_TYPE_HASHTREE or SPX_ADDR_TYPE_FORSTREE). + * Applies the offset idx_offset to indices before building addresses, so that + * it is possible to continue counting indices across trees. + */ +void treehash(unsigned char *root, unsigned char *auth_path, const spx_ctx* ctx, + uint32_t leaf_idx, uint32_t idx_offset, uint32_t tree_height, + void (*gen_leaf)( + unsigned char* /* leaf */, + const spx_ctx* /* ctx */, + uint32_t /* addr_idx */, const uint32_t[8] /* tree_addr */), + uint32_t tree_addr[8]) +{ + SPX_VLA(uint8_t, stack, (tree_height+1)*SPX_N); + SPX_VLA(unsigned int, heights, tree_height+1); + unsigned int offset = 0; + uint32_t idx; + uint32_t tree_idx; + + for (idx = 0; idx < (uint32_t)(1 << tree_height); idx++) { + /* Add the next leaf node to the stack. */ + gen_leaf(stack + offset*SPX_N, ctx, idx + idx_offset, tree_addr); + offset++; + heights[offset - 1] = 0; + + /* If this is a node we need for the auth path.. */ + if ((leaf_idx ^ 0x1) == idx) { + memcpy(auth_path, stack + (offset - 1)*SPX_N, SPX_N); + } + + /* While the top-most nodes are of equal height.. */ + while (offset >= 2 && heights[offset - 1] == heights[offset - 2]) { + /* Compute index of the new node, in the next layer. */ + tree_idx = (idx >> (heights[offset - 1] + 1)); + + /* Set the address of the node we're creating. */ + set_tree_height(tree_addr, heights[offset - 1] + 1); + set_tree_index(tree_addr, + tree_idx + (idx_offset >> (heights[offset-1] + 1))); + /* Hash the top-most nodes from the stack together. */ + thash(stack + (offset - 2)*SPX_N, + stack + (offset - 2)*SPX_N, 2, ctx, tree_addr); + offset--; + /* Note that the top-most node is now one layer higher. */ + heights[offset - 1]++; + + /* If this is a node we need for the auth path.. */ + if (((leaf_idx >> heights[offset - 1]) ^ 0x1) == tree_idx) { + memcpy(auth_path + heights[offset - 1]*SPX_N, + stack + (offset - 1)*SPX_N, SPX_N); + } + } + } + memcpy(root, stack, SPX_N); +} diff --git a/app/src/main/cpp/pq/sphincsplus/utils.h b/app/src/main/cpp/pq/sphincsplus/utils.h new file mode 100644 index 0000000..b13502c --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/utils.h @@ -0,0 +1,64 @@ +#ifndef SPX_UTILS_H +#define SPX_UTILS_H + +#include +#include "params.h" +#include "context.h" + + +/* To support MSVC use alloca() instead of VLAs. See #20. */ +#ifdef _MSC_VER +/* MSVC defines _alloca in malloc.h */ +# include +/* Note: _malloca(), which is recommended over deprecated _alloca, + requires that you call _freea(). So we stick with _alloca */ +# define SPX_VLA(__t,__x,__s) __t *__x = (__t*)_alloca((__s)*sizeof(__t)) +#else +# define SPX_VLA(__t,__x,__s) __t __x[__s] +#endif + +/** + * Converts the value of 'in' to 'outlen' bytes in big-endian byte order. + */ +#define ull_to_bytes SPX_NAMESPACE(ull_to_bytes) +void ull_to_bytes(unsigned char *out, unsigned int outlen, + unsigned long long in); +#define u32_to_bytes SPX_NAMESPACE(u32_to_bytes) +void u32_to_bytes(unsigned char *out, uint32_t in); + +/** + * Converts the inlen bytes in 'in' from big-endian byte order to an integer. + */ +#define bytes_to_ull SPX_NAMESPACE(bytes_to_ull) +unsigned long long bytes_to_ull(const unsigned char *in, unsigned int inlen); + +/** + * Computes a root node given a leaf and an auth path. + * Expects address to be complete other than the tree_height and tree_index. + */ +#define compute_root SPX_NAMESPACE(compute_root) +void compute_root(unsigned char *root, const unsigned char *leaf, + uint32_t leaf_idx, uint32_t idx_offset, + const unsigned char *auth_path, uint32_t tree_height, + const spx_ctx *ctx, uint32_t addr[8]); + +/** + * For a given leaf index, computes the authentication path and the resulting + * root node using Merkle's TreeHash algorithm. + * Expects the layer and tree parts of the tree_addr to be set, as well as the + * tree type (i.e. SPX_ADDR_TYPE_HASHTREE or SPX_ADDR_TYPE_FORSTREE). + * Applies the offset idx_offset to indices before building addresses, so that + * it is possible to continue counting indices across trees. + */ +#define treehash SPX_NAMESPACE(treehash) +void treehash(unsigned char *root, unsigned char *auth_path, + const spx_ctx* ctx, + uint32_t leaf_idx, uint32_t idx_offset, uint32_t tree_height, + void (*gen_leaf)( + unsigned char* /* leaf */, + const spx_ctx* ctx /* ctx */, + uint32_t /* addr_idx */, const uint32_t[8] /* tree_addr */), + uint32_t tree_addr[8]); + + +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/utilsx1.c b/app/src/main/cpp/pq/sphincsplus/utilsx1.c new file mode 100644 index 0000000..f6a6700 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/utilsx1.c @@ -0,0 +1,100 @@ +#include + +#include "utils.h" +#include "utilsx1.h" +#include "params.h" +#include "thash.h" +#include "address.h" + +/* + * Generate the entire Merkle tree, computing the authentication path for + * leaf_idx, and the resulting root node using Merkle's TreeHash algorithm. + * Expects the layer and tree parts of the tree_addr to be set, as well as the + * tree type (i.e. SPX_ADDR_TYPE_HASHTREE or SPX_ADDR_TYPE_FORSTREE) + * + * This expects tree_addr to be initialized to the addr structures for the + * Merkle tree nodes + * + * Applies the offset idx_offset to indices before building addresses, so that + * it is possible to continue counting indices across trees. + * + * This works by using the standard Merkle tree building algorithm, + */ +void treehashx1(unsigned char *root, unsigned char *auth_path, + const spx_ctx* ctx, + uint32_t leaf_idx, uint32_t idx_offset, + uint32_t tree_height, + void (*gen_leaf)( + unsigned char* /* Where to write the leaves */, + const spx_ctx* /* ctx */, + uint32_t idx, void *info), + uint32_t tree_addr[8], + void *info) +{ + /* This is where we keep the intermediate nodes */ + SPX_VLA(uint8_t, stack, tree_height*SPX_N); + + uint32_t idx; + uint32_t max_idx = (uint32_t)((1 << tree_height) - 1); + for (idx = 0;; idx++) { + unsigned char current[2*SPX_N]; /* Current logical node is at */ + /* index[SPX_N]. We do this to minimize the number of copies */ + /* needed during a thash */ + gen_leaf( ¤t[SPX_N], ctx, idx + idx_offset, + info ); + + /* Now combine the freshly generated right node with previously */ + /* generated left ones */ + uint32_t internal_idx_offset = idx_offset; + uint32_t internal_idx = idx; + uint32_t internal_leaf = leaf_idx; + uint32_t h; /* The height we are in the Merkle tree */ + for (h=0;; h++, internal_idx >>= 1, internal_leaf >>= 1) { + + /* Check if we hit the top of the tree */ + if (h == tree_height) { + /* We hit the root; return it */ + memcpy( root, ¤t[SPX_N], SPX_N ); + return; + } + + /* + * Check if the node we have is a part of the + * authentication path; if it is, write it out + */ + if ((internal_idx ^ internal_leaf) == 0x01) { + memcpy( &auth_path[ h * SPX_N ], + ¤t[SPX_N], + SPX_N ); + } + + /* + * Check if we're at a left child; if so, stop going up the stack + * Exception: if we've reached the end of the tree, keep on going + * (so we combine the last 4 nodes into the one root node in two + * more iterations) + */ + if ((internal_idx & 1) == 0 && idx < max_idx) { + break; + } + + /* Ok, we're at a right node */ + /* Now combine the left and right logical nodes together */ + + /* Set the address of the node we're creating. */ + internal_idx_offset >>= 1; + set_tree_height(tree_addr, h + 1); + set_tree_index(tree_addr, internal_idx/2 + internal_idx_offset ); + + unsigned char *left = &stack[h * SPX_N]; + memcpy( ¤t[0], left, SPX_N ); + thash( ¤t[1 * SPX_N], + ¤t[0 * SPX_N], + 2, ctx, tree_addr); + } + + /* We've hit a left child; save the current for when we get the */ + /* corresponding right right */ + memcpy( &stack[h * SPX_N], ¤t[SPX_N], SPX_N); + } +} diff --git a/app/src/main/cpp/pq/sphincsplus/utilsx1.h b/app/src/main/cpp/pq/sphincsplus/utilsx1.h new file mode 100644 index 0000000..a7fcf15 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/utilsx1.h @@ -0,0 +1,26 @@ +#ifndef SPX_UTILSX4_H +#define SPX_UTILSX4_H + +#include +#include "params.h" +#include "context.h" + +/** + * For a given leaf index, computes the authentication path and the resulting + * root node using Merkle's TreeHash algorithm. + * Expects the layer and tree parts of the tree_addr to be set, as well as the + * tree type (i.e. SPX_ADDR_TYPE_HASHTREE or SPX_ADDR_TYPE_FORSTREE). + * Applies the offset idx_offset to indices before building addresses, so that + * it is possible to continue counting indices across trees. + */ +#define treehashx1 SPX_NAMESPACE(treehashx1) +void treehashx1(unsigned char *root, unsigned char *auth_path, + const spx_ctx* ctx, + uint32_t leaf_idx, uint32_t idx_offset, uint32_t tree_height, + void (*gen_leaf)( + unsigned char* /* Where to write the leaf */, + const spx_ctx* /* ctx */, + uint32_t addr_idx, void *info), + uint32_t tree_addrx4[8], void *info); + +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/wots.c b/app/src/main/cpp/pq/sphincsplus/wots.c new file mode 100644 index 0000000..df83278 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/wots.c @@ -0,0 +1,112 @@ +#include +#include + +#include "utils.h" +#include "utilsx1.h" +#include "hash.h" +#include "thash.h" +#include "wots.h" +#include "wotsx1.h" +#include "address.h" +#include "params.h" + +// TODO clarify address expectations, and make them more uniform. +// TODO i.e. do we expect types to be set already? +// TODO and do we expect modifications or copies? + +/** + * Computes the chaining function. + * out and in have to be n-byte arrays. + * + * Interprets in as start-th value of the chain. + * addr has to contain the address of the chain. + */ +static void gen_chain(unsigned char *out, const unsigned char *in, + unsigned int start, unsigned int steps, + const spx_ctx *ctx, uint32_t addr[8]) +{ + uint32_t i; + + /* Initialize out with the value at position 'start'. */ + memcpy(out, in, SPX_N); + + /* Iterate 'steps' calls to the hash function. */ + for (i = start; i < (start+steps) && i < SPX_WOTS_W; i++) { + set_hash_addr(addr, i); + thash(out, out, 1, ctx, addr); + } +} + +/** + * base_w algorithm as described in draft. + * Interprets an array of bytes as integers in base w. + * This only works when log_w is a divisor of 8. + */ +static void base_w(unsigned int *output, const int out_len, + const unsigned char *input) +{ + int in = 0; + int out = 0; + unsigned char total; + int bits = 0; + int consumed; + + for (consumed = 0; consumed < out_len; consumed++) { + if (bits == 0) { + total = input[in]; + in++; + bits += 8; + } + bits -= SPX_WOTS_LOGW; + output[out] = (total >> bits) & (SPX_WOTS_W - 1); + out++; + } +} + +/* Computes the WOTS+ checksum over a message (in base_w). */ +static void wots_checksum(unsigned int *csum_base_w, + const unsigned int *msg_base_w) +{ + unsigned int csum = 0; + unsigned char csum_bytes[(SPX_WOTS_LEN2 * SPX_WOTS_LOGW + 7) / 8]; + unsigned int i; + + /* Compute checksum. */ + for (i = 0; i < SPX_WOTS_LEN1; i++) { + csum += SPX_WOTS_W - 1 - msg_base_w[i]; + } + + /* Convert checksum to base_w. */ + /* Make sure expected empty zero bits are the least significant bits. */ + csum = csum << ((8 - ((SPX_WOTS_LEN2 * SPX_WOTS_LOGW) % 8)) % 8); + ull_to_bytes(csum_bytes, sizeof(csum_bytes), csum); + base_w(csum_base_w, SPX_WOTS_LEN2, csum_bytes); +} + +/* Takes a message and derives the matching chain lengths. */ +void chain_lengths(unsigned int *lengths, const unsigned char *msg) +{ + base_w(lengths, SPX_WOTS_LEN1, msg); + wots_checksum(lengths + SPX_WOTS_LEN1, lengths); +} + +/** + * Takes a WOTS signature and an n-byte message, computes a WOTS public key. + * + * Writes the computed public key to 'pk'. + */ +void wots_pk_from_sig(unsigned char *pk, + const unsigned char *sig, const unsigned char *msg, + const spx_ctx *ctx, uint32_t addr[8]) +{ + unsigned int lengths[SPX_WOTS_LEN]; + uint32_t i; + + chain_lengths(lengths, msg); + + for (i = 0; i < SPX_WOTS_LEN; i++) { + set_chain_addr(addr, i); + gen_chain(pk + i*SPX_N, sig + i*SPX_N, + lengths[i], SPX_WOTS_W - 1 - lengths[i], ctx, addr); + } +} diff --git a/app/src/main/cpp/pq/sphincsplus/wots.h b/app/src/main/cpp/pq/sphincsplus/wots.h new file mode 100644 index 0000000..7e77056 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/wots.h @@ -0,0 +1,25 @@ +#ifndef SPX_WOTS_H +#define SPX_WOTS_H + +#include + +#include "params.h" +#include "context.h" + +/** + * Takes a WOTS signature and an n-byte message, computes a WOTS public key. + * + * Writes the computed public key to 'pk'. + */ +#define wots_pk_from_sig SPX_NAMESPACE(wots_pk_from_sig) +void wots_pk_from_sig(unsigned char *pk, + const unsigned char *sig, const unsigned char *msg, + const spx_ctx *ctx, uint32_t addr[8]); + +/* + * Compute the chain lengths needed for a given message hash + */ +#define chain_lengths SPX_NAMESPACE(chain_lengths) +void chain_lengths(unsigned int *lengths, const unsigned char *msg); + +#endif diff --git a/app/src/main/cpp/pq/sphincsplus/wotsx1.c b/app/src/main/cpp/pq/sphincsplus/wotsx1.c new file mode 100644 index 0000000..dfb3780 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/wotsx1.c @@ -0,0 +1,73 @@ +#include +#include + +#include "utils.h" +#include "hash.h" +#include "thash.h" +#include "wots.h" +#include "wotsx1.h" +#include "address.h" +#include "params.h" + +/* + * This generates a WOTS public key + * It also generates the WOTS signature if leaf_info indicates + * that we're signing with this WOTS key + */ +void wots_gen_leafx1(unsigned char *dest, + const spx_ctx *ctx, + uint32_t leaf_idx, void *v_info) { + struct leaf_info_x1 *info = v_info; + uint32_t *leaf_addr = info->leaf_addr; + uint32_t *pk_addr = info->pk_addr; + unsigned int i, k; + unsigned char pk_buffer[ SPX_WOTS_BYTES ]; + unsigned char *buffer; + uint32_t wots_k_mask; + + if (leaf_idx == info->wots_sign_leaf) { + /* We're traversing the leaf that's signing; generate the WOTS */ + /* signature */ + wots_k_mask = 0; + } else { + /* Nope, we're just generating pk's; turn off the signature logic */ + wots_k_mask = (uint32_t)~0; + } + + set_keypair_addr( leaf_addr, leaf_idx ); + set_keypair_addr( pk_addr, leaf_idx ); + + for (i = 0, buffer = pk_buffer; i < SPX_WOTS_LEN; i++, buffer += SPX_N) { + uint32_t wots_k = info->wots_steps[i] | wots_k_mask; /* Set wots_k to */ + /* the step if we're generating a signature, ~0 if we're not */ + + /* Start with the secret seed */ + set_chain_addr(leaf_addr, i); + set_hash_addr(leaf_addr, 0); + set_type(leaf_addr, SPX_ADDR_TYPE_WOTSPRF); + + prf_addr(buffer, ctx, leaf_addr); + + set_type(leaf_addr, SPX_ADDR_TYPE_WOTS); + + /* Iterate down the WOTS chain */ + for (k=0;; k++) { + /* Check if this is the value that needs to be saved as a */ + /* part of the WOTS signature */ + if (k == wots_k) { + memcpy( info->wots_sig + i * SPX_N, buffer, SPX_N ); + } + + /* Check if we hit the top of the chain */ + if (k == SPX_WOTS_W - 1) break; + + /* Iterate one step on the chain */ + set_hash_addr(leaf_addr, k); + + thash(buffer, buffer, 1, ctx, leaf_addr); + } + } + + /* Do the final thash to generate the public keys */ + thash(dest, pk_buffer, SPX_WOTS_LEN, ctx, pk_addr); +} diff --git a/app/src/main/cpp/pq/sphincsplus/wotsx1.h b/app/src/main/cpp/pq/sphincsplus/wotsx1.h new file mode 100644 index 0000000..1257f81 --- /dev/null +++ b/app/src/main/cpp/pq/sphincsplus/wotsx1.h @@ -0,0 +1,36 @@ +#if !defined( WOTSX1_H_ ) +#define WOTSX1_H_ + +#include + +/* + * This is here to provide an interface to the internal wots_gen_leafx1 + * routine. While this routine is not referenced in the package outside of + * wots.c, it is called from the stand-alone benchmark code to characterize + * the performance + */ +struct leaf_info_x1 { + unsigned char *wots_sig; + uint32_t wots_sign_leaf; /* The index of the WOTS we're using to sign */ + uint32_t *wots_steps; + uint32_t leaf_addr[8]; + uint32_t pk_addr[8]; +}; + +/* Macro to set the leaf_info to something 'benign', that is, it would */ +/* run with the same time as it does during the real signing process */ +/* Used only by the benchmark code */ +#define INITIALIZE_LEAF_INFO_X1(info, addr, step_buffer) { \ + info.wots_sig = 0; \ + info.wots_sign_leaf = ~0u; \ + info.wots_steps = step_buffer; \ + memcpy( &info.leaf_addr[0], addr, 32 ); \ + memcpy( &info.pk_addr[0], addr, 32 ); \ +} + +#define wots_gen_leafx1 SPX_NAMESPACE(wots_gen_leafx1) +void wots_gen_leafx1(unsigned char *dest, + const spx_ctx *ctx, + uint32_t leaf_idx, void *v_info); + +#endif /* WOTSX1_H_ */ diff --git a/app/src/main/cpp/vexta_pq_jni.cpp b/app/src/main/cpp/vexta_pq_jni.cpp new file mode 100644 index 0000000..ca634be --- /dev/null +++ b/app/src/main/cpp/vexta_pq_jni.cpp @@ -0,0 +1,345 @@ +#include + +#include +#include +#include + +#include "pq/pq.h" + +namespace { + +jbyteArray to_jbyte_array( + JNIEnv* env, + const uint8_t* data, + size_t size) +{ + jbyteArray result = + env->NewByteArray(static_cast(size)); + + if (result == nullptr) { + return nullptr; + } + + env->SetByteArrayRegion( + result, + 0, + static_cast(size), + reinterpret_cast(data) + ); + + return result; +} + +bool copy_seed( + JNIEnv* env, + jbyteArray seed_array, + std::array& seed) +{ + if (seed_array == nullptr || + env->GetArrayLength(seed_array) != 64) { + return false; + } + + env->GetByteArrayRegion( + seed_array, + 0, + 64, + reinterpret_cast(seed.data()) + ); + + return !env->ExceptionCheck(); +} + +} // namespace + +extern "C" +JNIEXPORT jstring JNICALL +Java_org_vextaproject_wallet_VextaPQ_nativeVersion( + JNIEnv* env, + jobject) +{ + return env->NewStringUTF("Vexta PQ JNI 0.2"); +} + +extern "C" +JNIEXPORT jobjectArray JNICALL +Java_org_vextaproject_wallet_VextaPQ_mldsaKeypairFromSeed( + JNIEnv* env, + jobject, + jbyteArray seed_array) +{ + std::array seed{}; + if (!copy_seed(env, seed_array, seed)) { + return nullptr; + } + + std::vector public_key( + pq::MLDSA65::PUBKEY_BYTES + ); + std::vector secret_key( + pq::MLDSA65::SECKEY_BYTES + ); + + if (!pq::MLDSA65::keygen_from_seed( + public_key.data(), + secret_key.data(), + seed.data())) { + return nullptr; + } + + jclass byte_array_class = env->FindClass("[B"); + jobjectArray result = + env->NewObjectArray(2, byte_array_class, nullptr); + + if (result == nullptr) { + return nullptr; + } + + env->SetObjectArrayElement( + result, + 0, + to_jbyte_array( + env, + public_key.data(), + public_key.size() + ) + ); + + env->SetObjectArrayElement( + result, + 1, + to_jbyte_array( + env, + secret_key.data(), + secret_key.size() + ) + ); + + return result; +} + +extern "C" +JNIEXPORT jobjectArray JNICALL +Java_org_vextaproject_wallet_VextaPQ_sphincsKeypairFromSeed( + JNIEnv* env, + jobject, + jbyteArray seed_array) +{ + std::array seed{}; + if (!copy_seed(env, seed_array, seed)) { + return nullptr; + } + + std::vector public_key( + pq::SPHINCS128s::PUBKEY_BYTES + ); + std::vector secret_key( + pq::SPHINCS128s::SECKEY_BYTES + ); + + if (!pq::SPHINCS128s::keygen_from_seed( + public_key.data(), + secret_key.data(), + seed.data())) { + return nullptr; + } + + jclass byte_array_class = env->FindClass("[B"); + jobjectArray result = + env->NewObjectArray(2, byte_array_class, nullptr); + + if (result == nullptr) { + return nullptr; + } + + env->SetObjectArrayElement( + result, + 0, + to_jbyte_array( + env, + public_key.data(), + public_key.size() + ) + ); + + env->SetObjectArrayElement( + result, + 1, + to_jbyte_array( + env, + secret_key.data(), + secret_key.size() + ) + ); + + return result; +} + +namespace { + +bool copy_byte_array( + JNIEnv* env, + jbyteArray input, + std::vector& output) +{ + if (input == nullptr) { + return false; + } + + const jsize length = env->GetArrayLength(input); + + output.resize(static_cast(length)); + + if (length > 0) { + env->GetByteArrayRegion( + input, + 0, + length, + reinterpret_cast(output.data()) + ); + } + + return !env->ExceptionCheck(); +} + +} // namespace + +extern "C" +JNIEXPORT jbyteArray JNICALL +Java_org_vextaproject_wallet_VextaPQ_mldsaSign( + JNIEnv* env, + jobject, + jbyteArray message_array, + jbyteArray secret_key_array) +{ + std::vector message; + std::vector secret_key; + + if (!copy_byte_array(env, message_array, message) || + !copy_byte_array(env, secret_key_array, secret_key) || + secret_key.size() != pq::MLDSA65::SECKEY_BYTES) { + return nullptr; + } + + std::vector signature( + pq::MLDSA65::SIG_BYTES + ); + + size_t signature_length = signature.size(); + + if (!pq::MLDSA65::sign( + signature.data(), + &signature_length, + message.data(), + message.size(), + secret_key.data())) { + return nullptr; + } + + signature.resize(signature_length); + + return to_jbyte_array( + env, + signature.data(), + signature.size() + ); +} + +extern "C" +JNIEXPORT jboolean JNICALL +Java_org_vextaproject_wallet_VextaPQ_mldsaVerify( + JNIEnv* env, + jobject, + jbyteArray signature_array, + jbyteArray message_array, + jbyteArray public_key_array) +{ + std::vector signature; + std::vector message; + std::vector public_key; + + if (!copy_byte_array(env, signature_array, signature) || + !copy_byte_array(env, message_array, message) || + !copy_byte_array(env, public_key_array, public_key) || + public_key.size() != pq::MLDSA65::PUBKEY_BYTES) { + return JNI_FALSE; + } + + return pq::MLDSA65::verify( + signature.data(), + signature.size(), + message.data(), + message.size(), + public_key.data() + ) ? JNI_TRUE : JNI_FALSE; +} + +extern "C" +JNIEXPORT jbyteArray JNICALL +Java_org_vextaproject_wallet_VextaPQ_sphincsSign( + JNIEnv* env, + jobject, + jbyteArray message_array, + jbyteArray secret_key_array) +{ + std::vector message; + std::vector secret_key; + + if (!copy_byte_array(env, message_array, message) || + !copy_byte_array(env, secret_key_array, secret_key) || + secret_key.size() != pq::SPHINCS128s::SECKEY_BYTES) { + return nullptr; + } + + std::vector signature( + pq::SPHINCS128s::SIG_BYTES + ); + + size_t signature_length = signature.size(); + + if (!pq::SPHINCS128s::sign( + signature.data(), + &signature_length, + message.data(), + message.size(), + secret_key.data())) { + return nullptr; + } + + signature.resize(signature_length); + + return to_jbyte_array( + env, + signature.data(), + signature.size() + ); +} + +extern "C" +JNIEXPORT jboolean JNICALL +Java_org_vextaproject_wallet_VextaPQ_sphincsVerify( + JNIEnv* env, + jobject, + jbyteArray signature_array, + jbyteArray message_array, + jbyteArray public_key_array) +{ + std::vector signature; + std::vector message; + std::vector public_key; + + if (!copy_byte_array(env, signature_array, signature) || + !copy_byte_array(env, message_array, message) || + !copy_byte_array(env, public_key_array, public_key) || + public_key.size() != pq::SPHINCS128s::PUBKEY_BYTES) { + return JNI_FALSE; + } + + return pq::SPHINCS128s::verify( + signature.data(), + signature.size(), + message.data(), + message.size(), + public_key.data() + ) ? JNI_TRUE : JNI_FALSE; +} diff --git a/app/src/main/cpp/vexta_pq_randombytes.cpp b/app/src/main/cpp/vexta_pq_randombytes.cpp new file mode 100644 index 0000000..20ef6d0 --- /dev/null +++ b/app/src/main/cpp/vexta_pq_randombytes.cpp @@ -0,0 +1,93 @@ +#include +#include +#include +#include + +#include +#include +#include + +namespace { + +bool fill_from_getrandom(uint8_t* out, size_t outlen) +{ +#ifdef SYS_getrandom + while (outlen > 0) { + const long result = syscall( + SYS_getrandom, + out, + outlen, + 0 + ); + + if (result > 0) { + out += static_cast(result); + outlen -= static_cast(result); + continue; + } + + if (result < 0 && errno == EINTR) { + continue; + } + + return false; + } + + return true; +#else + (void)out; + (void)outlen; + return false; +#endif +} + +bool fill_from_urandom(uint8_t* out, size_t outlen) +{ + const int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); + if (fd < 0) { + return false; + } + + while (outlen > 0) { + const ssize_t result = read(fd, out, outlen); + + if (result > 0) { + out += static_cast(result); + outlen -= static_cast(result); + continue; + } + + if (result < 0 && errno == EINTR) { + continue; + } + + close(fd); + return false; + } + + close(fd); + return true; +} + +} // namespace + +extern "C" void randombytes(uint8_t* out, size_t outlen) +{ + if (outlen == 0) { + return; + } + + if (out == nullptr) { + std::abort(); + } + + if (fill_from_getrandom(out, outlen)) { + return; + } + + if (fill_from_urandom(out, outlen)) { + return; + } + + std::abort(); +} diff --git a/app/src/main/java/org/vextaproject/wallet/BlockScanner.kt b/app/src/main/java/org/vextaproject/wallet/BlockScanner.kt index 8759fe4..8ebc7b2 100644 --- a/app/src/main/java/org/vextaproject/wallet/BlockScanner.kt +++ b/app/src/main/java/org/vextaproject/wallet/BlockScanner.kt @@ -23,13 +23,20 @@ object BlockScanner { private const val genesisHash = "0000027626959d894ae1c8a6f9050cdcbdf58eb0037ab06345cb5502955370f1" + enum class AddressType { + STANDARD, + MLDSA, + SLHDSA + } + data class SpendableUtxo( val txid: String, val outputIndex: Long, val value: Long, val height: Int, val addressIndex: Int, - val isCoinbase: Boolean + val isCoinbase: Boolean, + val addressType: AddressType = AddressType.STANDARD ) data class WalletTransaction( @@ -59,7 +66,8 @@ object BlockScanner { val value: Long, val height: Int, val addressIndex: Int, - val isCoinbase: Boolean + val isCoinbase: Boolean, + val addressType: AddressType ) private data class ParsedTransaction( @@ -86,13 +94,17 @@ object BlockScanner { matchingHeights: List, walletScript: ByteArray, addressIndex: Int, + addressType: AddressType = AddressType.STANDARD, initialUtxos: List = emptyList(), progress: (Int, Int) -> Unit ): Result { if (matchingHeights.isEmpty()) { val existing = initialUtxos - .filter { it.addressIndex == addressIndex } + .filter { + it.addressIndex == addressIndex && + it.addressType == addressType + } .sortedWith( compareBy { it.height } .thenBy { it.txid } @@ -128,7 +140,10 @@ object BlockScanner { val utxos = linkedMapOf() initialUtxos - .filter { it.addressIndex == addressIndex } + .filter { + it.addressIndex == addressIndex && + it.addressType == addressType + } .forEach { existing -> val txidWire = existing.txid @@ -144,7 +159,8 @@ object BlockScanner { value = existing.value, height = existing.height, addressIndex = existing.addressIndex, - isCoinbase = existing.isCoinbase + isCoinbase = existing.isCoinbase, + addressType = existing.addressType ) } var receivedTransactions = 0 @@ -241,7 +257,8 @@ object BlockScanner { value = transactionOutput.value, height = height, addressIndex = addressIndex, - isCoinbase = transaction.isCoinbase + isCoinbase = transaction.isCoinbase, + addressType = addressType ) receivedWalletValue += @@ -290,7 +307,8 @@ object BlockScanner { value = utxo.value, height = utxo.height, addressIndex = utxo.addressIndex, - isCoinbase = utxo.isCoinbase + isCoinbase = utxo.isCoinbase, + addressType = utxo.addressType ) } diff --git a/app/src/main/java/org/vextaproject/wallet/VextaPQ.kt b/app/src/main/java/org/vextaproject/wallet/VextaPQ.kt new file mode 100644 index 0000000..04d2d2d --- /dev/null +++ b/app/src/main/java/org/vextaproject/wallet/VextaPQ.kt @@ -0,0 +1,39 @@ +package org.vextaproject.wallet + +object VextaPQ { + init { + System.loadLibrary("vextapq") + } + + external fun nativeVersion(): String + + external fun mldsaKeypairFromSeed( + seed: ByteArray + ): Array? + + external fun sphincsKeypairFromSeed( + seed: ByteArray + ): Array? + + external fun mldsaSign( + message: ByteArray, + secretKey: ByteArray + ): ByteArray? + + external fun mldsaVerify( + signature: ByteArray, + message: ByteArray, + publicKey: ByteArray + ): Boolean + + external fun sphincsSign( + message: ByteArray, + secretKey: ByteArray + ): ByteArray? + + external fun sphincsVerify( + signature: ByteArray, + message: ByteArray, + publicKey: ByteArray + ): Boolean +} diff --git a/app/src/main/java/org/vextaproject/wallet/VextaTransactionSender.kt b/app/src/main/java/org/vextaproject/wallet/VextaTransactionSender.kt index 108eca0..24edd58 100644 --- a/app/src/main/java/org/vextaproject/wallet/VextaTransactionSender.kt +++ b/app/src/main/java/org/vextaproject/wallet/VextaTransactionSender.kt @@ -48,6 +48,13 @@ object VextaTransactionSender { val fee: Long ) + data class PqSigningKey( + val addressType: BlockScanner.AddressType, + val publicKey: ByteArray, + val secretKey: ByteArray + ) + + private const val COINBASE_MATURITY = 100 private fun validateCoinbaseMaturity( @@ -90,9 +97,9 @@ object VextaTransactionSender { Math.addExact(sum, utxo.value) } - val fee = estimateFee( - inputCount = spendableUtxos.size, - outputCount = 1 + val fee = estimateFeeFromOutputVirtualBytes( + inputs = spendableUtxos, + outputVirtualBytes = listOf(43L) ) val amount = total - fee @@ -112,6 +119,8 @@ object VextaTransactionSender { recipientAddress: String, amountSatoshis: Long, privateKeysByIndex: Map, + pqSigningKeys: + Map, PqSigningKey>, changePubKeyHash: ByteArray, chainHeight: Int ): CreatedTransaction { @@ -127,19 +136,32 @@ object VextaTransactionSender { "Invalid change public-key hash" } - require(privateKeysByIndex.isNotEmpty()) { - "No wallet private keys supplied" + require( + privateKeysByIndex.isNotEmpty() || + pqSigningKeys.isNotEmpty() + ) { + "No wallet signing keys supplied" } val recipientScript = addressToScript(recipientAddress) val changeScript = byteArrayOf(0x00, 0x14) + changePubKeyHash - val orderedUtxos = spendableUtxos.sortedWith( - compareBy { it.height } - .thenBy { it.txid } - .thenBy { it.outputIndex } - ) + val orderedUtxos = + spendableUtxos + .sortedWith( + compareBy { + it.height + } + .thenBy { it.txid } + .thenBy { it.outputIndex } + ) + + if (orderedUtxos.isEmpty()) { + throw IllegalStateException( + "No spendable outputs available" + ) + } val maxSpend = calculateMaxSpend( @@ -172,8 +194,11 @@ object VextaTransactionSender { Math.addExact(selectedValue, utxo.value) val estimatedFee = estimateFee( - inputCount = selected.size, - outputCount = 2 + inputs = selected, + outputScripts = listOf( + recipientScript, + changeScript + ) ) if ( @@ -191,8 +216,11 @@ object VextaTransactionSender { } fee = estimateFee( - inputCount = selected.size, - outputCount = 2 + inputs = selected, + outputScripts = listOf( + recipientScript, + changeScript + ) ) if (selectedValue < amountSatoshis + fee) { @@ -267,37 +295,127 @@ object VextaTransactionSender { } selected.forEachIndexed { index, utxo -> - val privateKey = - privateKeysByIndex[utxo.addressIndex] - ?: throw IllegalStateException( - "Missing private key for address index " + - utxo.addressIndex - ) + when (utxo.addressType) { + BlockScanner.AddressType.STANDARD -> { + val privateKey = + privateKeysByIndex[utxo.addressIndex] + ?: throw IllegalStateException( + "Missing private key for address index " + + utxo.addressIndex + ) - val walletPubKeyHash = - Utils.sha256hash160(privateKey.pubKey) + val walletPubKeyHash = + Utils.sha256hash160(privateKey.pubKey) - val scriptCode = - ScriptBuilder.createP2PKHOutputScript( - walletPubKeyHash - ) + val scriptCode = + ScriptBuilder.createP2PKHOutputScript( + walletPubKeyHash + ) - val signature = - transaction.calculateWitnessSignature( - index, - privateKey, - scriptCode, - Coin.valueOf(utxo.value), - Transaction.SigHash.ALL, - false - ) + val signature = + transaction.calculateWitnessSignature( + index, + privateKey, + scriptCode, + Coin.valueOf(utxo.value), + Transaction.SigHash.ALL, + false + ) - transaction.getInput(index.toLong()).setWitness( - TransactionWitness.redeemP2WPKH( - signature, - privateKey - ) - ) + transaction + .getInput(index.toLong()) + .setWitness( + TransactionWitness.redeemP2WPKH( + signature, + privateKey + ) + ) + } + + BlockScanner.AddressType.MLDSA, + BlockScanner.AddressType.SLHDSA -> { + val pqKey = + pqSigningKeys[ + utxo.addressType to + utxo.addressIndex + ] ?: throw IllegalStateException( + "Missing PQ signing key for " + + "${utxo.addressType} address index " + + utxo.addressIndex + ) + + require( + pqKey.addressType == utxo.addressType + ) { + "PQ signing key type mismatch" + } + + val witnessVersion = + when (utxo.addressType) { + BlockScanner.AddressType.MLDSA -> 2 + BlockScanner.AddressType.SLHDSA -> 3 + BlockScanner.AddressType.STANDARD -> + error("Standard input is not PQ") + } + + val witnessProgram = + MessageDigest + .getInstance("SHA-256") + .digest(pqKey.publicKey) + + val signatureHash = + calculatePqrSignatureHash( + selected = selected, + outputs = outputs, + inputIndex = index, + witnessVersion = witnessVersion, + witnessProgram = witnessProgram, + amount = utxo.value, + version = 2, + lockTime = 0L, + inputSequence = sequence + ) + + val signature = + try { + when (utxo.addressType) { + BlockScanner.AddressType.MLDSA -> + requireNotNull( + VextaPQ.mldsaSign( + signatureHash, + pqKey.secretKey + ) + ) { + "ML-DSA signing failed" + } + + BlockScanner.AddressType.SLHDSA -> + requireNotNull( + VextaPQ.sphincsSign( + signatureHash, + pqKey.secretKey + ) + ) { + "SLH-DSA signing failed" + } + + BlockScanner.AddressType.STANDARD -> + error("Standard input is not PQ") + } + } finally { + pqKey.secretKey.fill(0) + } + + transaction + .getInput(index.toLong()) + .setWitness( + createPqWitness( + signature, + pqKey.publicKey + ) + ) + } + } } val rawTransaction = @@ -340,6 +458,468 @@ object VextaTransactionSender { return successfulPeers } + private fun calculatePqrSignatureHash( + selected: List, + outputs: List, + inputIndex: Int, + witnessVersion: Int, + witnessProgram: ByteArray, + amount: Long, + version: Int = 2, + lockTime: Long = 0L, + inputSequence: Long = sequence + ): ByteArray { + require(inputIndex in selected.indices) { + "Invalid PQR input index" + } + + require(witnessVersion == 2 || witnessVersion == 3) { + "Invalid PQR witness version" + } + + require(witnessProgram.size == 32) { + "Invalid PQR witness program" + } + + val hashPrevouts = doubleSha256( + ByteArrayOutputStream().apply { + selected.forEach { utxo -> + write(hashDisplayToWire(utxo.txid)) + writeUInt32LE(this, utxo.outputIndex) + } + }.toByteArray() + ) + + val hashSequence = doubleSha256( + ByteArrayOutputStream().apply { + selected.forEach { + writeUInt32LE(this, inputSequence) + } + }.toByteArray() + ) + + val hashOutputs = doubleSha256( + ByteArrayOutputStream().apply { + outputs.forEach { transactionOutput -> + writeInt64LE(this, transactionOutput.value) + writeCompactSize( + this, + transactionOutput.script.size.toLong() + ) + write(transactionOutput.script) + } + }.toByteArray() + ) + + val currentInput = selected[inputIndex] + + val scriptCode = + byteArrayOf( + if (witnessVersion == 2) { + 0x52 + } else { + 0x53 + }, + 0x20 + ) + witnessProgram + + val preimage = ByteArrayOutputStream().apply { + write(0x00) + writeInt32LE(this, version) + + write(hashPrevouts) + write(hashSequence) + + write(hashDisplayToWire(currentInput.txid)) + writeUInt32LE(this, currentInput.outputIndex) + + writeCompactSize(this, scriptCode.size.toLong()) + write(scriptCode) + + writeInt64LE(this, amount) + writeUInt32LE(this, inputSequence) + + write(hashOutputs) + + writeUInt32LE(this, lockTime) + writeUInt32LE(this, 1L) + }.toByteArray() + + return doubleSha256(preimage) + } + + fun runPqrSignatureHashSelfTest(): Pair { + val selected = listOf( + BlockScanner.SpendableUtxo( + txid = + "0000000000000000000000000000000000000000000000000000000000000001", + outputIndex = 3L, + value = 987654L, + height = 1, + addressIndex = 0, + isCoinbase = false, + addressType = BlockScanner.AddressType.MLDSA + ) + ) + + val outputs = listOf( + TransactionOutputData( + value = 123456L, + script = byteArrayOf(0x51) + ) + ) + + val program = + ByteArray(32) { index -> + index.toByte() + } + + val v2 = calculatePqrSignatureHash( + selected = selected, + outputs = outputs, + inputIndex = 0, + witnessVersion = 2, + witnessProgram = program, + amount = 987654L, + version = 2, + lockTime = 500L, + inputSequence = 0xfffffffeL + ) + + val v3 = calculatePqrSignatureHash( + selected = selected, + outputs = outputs, + inputIndex = 0, + witnessVersion = 3, + witnessProgram = program, + amount = 987654L, + version = 2, + lockTime = 500L, + inputSequence = 0xfffffffeL + ) + + val v2CoreHex = v2.reversedArray().toHex() + val v3CoreHex = v3.reversedArray().toHex() + + check( + v2CoreHex == + "ba39c447b4577db974473a1e7ed2182686672f2900045a40aee6b4dc5811995b" + ) { + "PQR V2 signature hash self-test failed: $v2CoreHex" + } + + check( + v3CoreHex == + "dab3188530f1094ef87087451da77bfba77dfc6e63d1730bb9d0475cebe76618" + ) { + "PQR V3 signature hash self-test failed: $v3CoreHex" + } + + return v2CoreHex to v3CoreHex + } + + private fun createPqWitness( + signature: ByteArray, + publicKey: ByteArray + ): TransactionWitness { + require(signature.isNotEmpty()) { + "PQ signature is empty" + } + + require(publicKey.isNotEmpty()) { + "PQ public key is empty" + } + + return TransactionWitness(2).apply { + setPush(0, signature) + setPush(1, publicKey) + } + } + + fun runPqWitnessSelfTest(): List { + val seed = + ByteArray(64) { index -> + index.toByte() + } + + val mldsa = + requireNotNull( + VextaPQ.mldsaKeypairFromSeed(seed) + ) + + val sphincs = + requireNotNull( + VextaPQ.sphincsKeypairFromSeed(seed) + ) + + val message = + "Vexta PQ witness self-test" + .toByteArray(Charsets.UTF_8) + + val mldsaSignature = + requireNotNull( + VextaPQ.mldsaSign( + message, + mldsa[1] + ) + ) + + val sphincsSignature = + requireNotNull( + VextaPQ.sphincsSign( + message, + sphincs[1] + ) + ) + + val mldsaWitness = + createPqWitness( + mldsaSignature, + mldsa[0] + ) + + val sphincsWitness = + createPqWitness( + sphincsSignature, + sphincs[0] + ) + + check(mldsaWitness.pushCount == 2) + check(mldsaWitness.getPush(0).size == 3309) + check(mldsaWitness.getPush(1).size == 1952) + + check(sphincsWitness.pushCount == 2) + check(sphincsWitness.getPush(0).size == 7856) + check(sphincsWitness.getPush(1).size == 32) + + mldsa[1].fill(0) + sphincs[1].fill(0) + + return listOf( + "MLDSA_WITNESS_PUSHES=${mldsaWitness.pushCount}", + "MLDSA_SIG_BYTES=${mldsaWitness.getPush(0).size}", + "MLDSA_PUBKEY_BYTES=${mldsaWitness.getPush(1).size}", + "SLHDSA_WITNESS_PUSHES=${sphincsWitness.pushCount}", + "SLHDSA_SIG_BYTES=${sphincsWitness.getPush(0).size}", + "SLHDSA_PUBKEY_BYTES=${sphincsWitness.getPush(1).size}", + "PQ_WITNESS_RESULT=PASS" + ) + } + + fun runPqSignedTransactionSelfTest(): List { + val recipientAddress = + "vtx1z6engqms3emse5lycna6yt7gd6svu7nfd28dccr7mfs8s54pz8rysd6lu5u" + + val recipientScript = + addressToScript(recipientAddress) + + val changePubKeyHash = + ByteArray(20) { index -> + (0x40 + index).toByte() + } + + val changeScript = + byteArrayOf(0x00, 0x14) + changePubKeyHash + + val amountSatoshis = 100_000L + val utxoValue = 10_000_000L + + fun testType( + addressType: BlockScanner.AddressType, + witnessVersion: Int, + expectedSignatureBytes: Int, + expectedPublicKeyBytes: Int, + txidSuffix: String + ): List { + val seed = + ByteArray(64) { index -> + index.toByte() + } + + val keyPair = + when (addressType) { + BlockScanner.AddressType.MLDSA -> + requireNotNull( + VextaPQ.mldsaKeypairFromSeed(seed) + ) + + BlockScanner.AddressType.SLHDSA -> + requireNotNull( + VextaPQ.sphincsKeypairFromSeed(seed) + ) + + BlockScanner.AddressType.STANDARD -> + error("Standard is not a PQ self-test type") + } + + require(keyPair.size == 2) + + val publicKey = keyPair[0] + val secretKey = keyPair[1] + + val syntheticTxid = + txidSuffix.padStart(64, '0') + + val utxo = + BlockScanner.SpendableUtxo( + txid = syntheticTxid, + outputIndex = 0L, + value = utxoValue, + height = 1, + addressIndex = 0, + isCoinbase = false, + addressType = addressType + ) + + val pqSigningKeys = + mapOf( + (addressType to 0) to + PqSigningKey( + addressType = addressType, + publicKey = publicKey, + secretKey = secretKey + ) + ) + + val created = + createAndSign( + spendableUtxos = listOf(utxo), + recipientAddress = recipientAddress, + amountSatoshis = amountSatoshis, + privateKeysByIndex = emptyMap(), + pqSigningKeys = pqSigningKeys, + changePubKeyHash = changePubKeyHash, + chainHeight = 10_000 + ) + + val parsed = + Transaction( + MainNetParams.get(), + created.rawTransaction + ) + + check(parsed.inputs.size == 1) + + val witness = + parsed.getInput(0L).getWitness() + + check(witness.pushCount == 2) + + val signature = + witness.getPush(0) + + val parsedPublicKey = + witness.getPush(1) + + check(signature.size == expectedSignatureBytes) + check(parsedPublicKey.size == expectedPublicKeyBytes) + check(parsedPublicKey.contentEquals(publicKey)) + + check( + parsed.txId.toString() == + created.txid + ) + + val witnessProgram = + MessageDigest + .getInstance("SHA-256") + .digest(publicKey) + + val outputs = + mutableListOf( + TransactionOutputData( + value = created.amount, + script = recipientScript + ) + ) + + if (created.change > 0L) { + outputs.add( + TransactionOutputData( + value = created.change, + script = changeScript + ) + ) + } + + val signatureHash = + calculatePqrSignatureHash( + selected = listOf(utxo), + outputs = outputs, + inputIndex = 0, + witnessVersion = witnessVersion, + witnessProgram = witnessProgram, + amount = utxo.value, + version = 2, + lockTime = 0L, + inputSequence = sequence + ) + + val verified = + when (addressType) { + BlockScanner.AddressType.MLDSA -> + VextaPQ.mldsaVerify( + signature, + signatureHash, + publicKey + ) + + BlockScanner.AddressType.SLHDSA -> + VextaPQ.sphincsVerify( + signature, + signatureHash, + publicKey + ) + + BlockScanner.AddressType.STANDARD -> + false + } + + check(verified) + + seed.fill(0) + signatureHash.fill(0) + + return listOf( + "${addressType.name}_SIGNED_TX_BYTES=" + + created.rawTransaction.size, + "${addressType.name}_SIGNED_TX_WITNESS_PUSHES=" + + witness.pushCount, + "${addressType.name}_SIGNED_TX_SIG_BYTES=" + + signature.size, + "${addressType.name}_SIGNED_TX_PUBKEY_BYTES=" + + parsedPublicKey.size, + "${addressType.name}_SIGNED_TX_VERIFY=$verified", + "${addressType.name}_SIGNED_TX_TXID_MATCH=true" + ) + } + + val results = + mutableListOf() + + results += + testType( + addressType = BlockScanner.AddressType.MLDSA, + witnessVersion = 2, + expectedSignatureBytes = 3309, + expectedPublicKeyBytes = 1952, + txidSuffix = "01" + ) + + results += + testType( + addressType = BlockScanner.AddressType.SLHDSA, + witnessVersion = 3, + expectedSignatureBytes = 7856, + expectedPublicKeyBytes = 32, + txidSuffix = "02" + ) + + results += "PQ_SIGNED_TX_RESULT=PASS" + + return results + } + private fun serializeTransaction( selected: List, outputs: List, @@ -401,14 +981,64 @@ object VextaTransactionSender { }.toByteArray() } - private fun estimateFee( - inputCount: Int, - outputCount: Int + private fun estimatedInputVirtualBytes( + addressType: BlockScanner.AddressType ): Long { + return when (addressType) { + BlockScanner.AddressType.STANDARD -> + 69L + + BlockScanner.AddressType.MLDSA -> + 1358L + + BlockScanner.AddressType.SLHDSA -> + 2015L + } + } + + private fun estimatedOutputVirtualBytes( + script: ByteArray + ): Long { + val scriptLengthBytes = + when { + script.size < 253 -> 1L + script.size <= 0xffff -> 3L + else -> 5L + } + + return 8L + + scriptLengthBytes + + script.size.toLong() + } + + private fun estimateFee( + inputs: List, + outputScripts: List + ): Long { + return estimateFeeFromOutputVirtualBytes( + inputs = inputs, + outputVirtualBytes = + outputScripts.map { + estimatedOutputVirtualBytes(it) + } + ) + } + + private fun estimateFeeFromOutputVirtualBytes( + inputs: List, + outputVirtualBytes: List + ): Long { + val inputVirtualBytes = + inputs.sumOf { + estimatedInputVirtualBytes( + it.addressType + ) + } + val virtualBytes = 11L + - inputCount * 69L + - outputCount * 31L + inputVirtualBytes + + outputVirtualBytes.sum() return ( virtualBytes * defaultFeeSatoshisPerKb + 999L @@ -419,13 +1049,33 @@ object VextaTransactionSender { val value = address.trim() if (value.lowercase().startsWith("vtx1")) { - val witnessProgram = decodeBech32Witness(value) + val (witnessVersion, witnessProgram) = + decodeBech32Witness(value) - require(witnessProgram.size == 20) { - "Only Vexta P2WPKH addresses are supported" + return when (witnessVersion) { + 0 -> { + require(witnessProgram.size == 20) { + "Only Vexta P2WPKH witness v0 addresses are supported" + } + byteArrayOf(0x00, 0x14) + witnessProgram + } + + 2 -> { + require(witnessProgram.size == 32) { + "Invalid ML-DSA witness program" + } + byteArrayOf(0x52, 0x20) + witnessProgram + } + + 3 -> { + require(witnessProgram.size == 32) { + "Invalid SLH-DSA witness program" + } + byteArrayOf(0x53, 0x20) + witnessProgram + } + + else -> error("Unsupported witness version") } - - return byteArrayOf(0x00, 0x14) + witnessProgram } val decoded = decodeBase58Check(value) @@ -452,7 +1102,9 @@ object VextaTransactionSender { ) } - private fun decodeBech32Witness(address: String): ByteArray { + private fun decodeBech32Witness( + address: String + ): Pair { require(address == address.lowercase()) { "Mixed-case Bech32 address" } @@ -477,21 +1129,38 @@ object VextaTransactionSender { index } - require(bech32Polymod(expandHrp(hrp) + values) == 1) { - "Invalid Bech32 checksum" + val data = values.dropLast(6) + require(data.isNotEmpty()) { + "Missing witness version" } - val data = values.dropLast(6) - require(data.isNotEmpty() && data[0] == 0) { + val witnessVersion = data[0] + require(witnessVersion in 0..16) { "Unsupported witness version" } - return convertBits( + val expectedChecksum = + if (witnessVersion == 0) 1 else 0x2bc830a3 + + require( + bech32Polymod(expandHrp(hrp) + values) == + expectedChecksum + ) { + if (witnessVersion == 0) { + "Invalid Bech32 checksum" + } else { + "Invalid Bech32m checksum" + } + } + + val witnessProgram = convertBits( data.drop(1), 5, 8, false ).map { it.toByte() }.toByteArray() + + return witnessVersion to witnessProgram } private fun convertBits( diff --git a/app/src/main/java/org/vextaproject/wallet/WalletActivity.kt b/app/src/main/java/org/vextaproject/wallet/WalletActivity.kt index 1735cb2..68a7557 100644 --- a/app/src/main/java/org/vextaproject/wallet/WalletActivity.kt +++ b/app/src/main/java/org/vextaproject/wallet/WalletActivity.kt @@ -97,6 +97,16 @@ class WalletActivity : FragmentActivity() { "tx_notifications_initialized" private const val PREF_RECEIVE_ADDRESS_INDEX = "receive_address_index" + private const val PREF_MLDSA_ADDRESS_INDEX = + "mldsa_address_index" + private const val PREF_SLHDSA_ADDRESS_INDEX = + "slhdsa_address_index" + private const val PREF_RECEIVE_ADDRESS_TYPE = + "receive_address_type" + private const val PREF_MLDSA_ADDRESS_CREATED = + "mldsa_address_created" + private const val PREF_SLHDSA_ADDRESS_CREATED = + "slhdsa_address_created" private const val PREF_ADDRESS_LABEL_PREFIX = "receive_address_label_" private const val PREF_RESTORE_ADDRESS_DISCOVERY = @@ -342,6 +352,13 @@ class WalletActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if ( + applicationInfo.flags and + android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE != 0 + ) { + runPqCompatibilitySelfTest() + } + onBackPressedDispatcher.addCallback( this, object : OnBackPressedCallback(true) { @@ -415,6 +432,450 @@ class WalletActivity : FragmentActivity() { checkForUpdates() } + private fun hexToBytes( + value: String + ): ByteArray { + require(value.length % 2 == 0) + + return ByteArray(value.length / 2) { index -> + value.substring( + index * 2, + index * 2 + 2 + ).toInt(16).toByte() + } + } + + private fun runPqCompatibilitySelfTest() { + Thread { + try { + val seed = ByteArray(64) { index -> + index.toByte() + } + + val mldsa = + requireNotNull( + VextaPQ.mldsaKeypairFromSeed(seed) + ) + + val sphincs = + requireNotNull( + VextaPQ.sphincsKeypairFromSeed(seed) + ) + + require(mldsa.size == 2) + require(sphincs.size == 2) + + fun sha256Hex(data: ByteArray): String = + MessageDigest + .getInstance("SHA-256") + .digest(data) + .joinToString("") { byte -> + "%02x".format(byte.toInt() and 0xff) + } + + val results = listOf( + "MLDSA_PK" to sha256Hex(mldsa[0]), + "MLDSA_SK" to sha256Hex(mldsa[1]), + "SPHINCS_PK" to sha256Hex(sphincs[0]), + "SPHINCS_SK" to sha256Hex(sphincs[1]) + ) + + results.forEach { (name, hash) -> + android.util.Log.i( + "VextaPQTest", + "$name=$hash" + ) + } + + val passed = + results[0].second == + "d666806e11cee19a7c989f7445f90dd419cf4d2d51db8c0fdb4c0f0a542238c9" && + results[1].second == + "9f1e24f47795fe50040384e3d6183988047170fa2d866406b70fe0a3f8216063" && + results[2].second == + "9d5b69aa74c4939468bae5108568a4d0926f2532c96b0742eda0a7eff4c99c3e" && + results[3].second == + "c6222658caa895a591af74d9f8c85d160962d253a782edf95875bbb3acee0a07" + + android.util.Log.i( + "VextaPQTest", + if (passed) { + "KEYGEN_RESULT=PASS" + } else { + "KEYGEN_RESULT=FAIL" + } + ) + + + val mldsaKeyId = + MessageDigest + .getInstance("SHA-256") + .digest(mldsa[0]) + + val sphincsKeyId = + MessageDigest + .getInstance("SHA-256") + .digest(sphincs[0]) + + val mldsaAddress = + encodeSegwitAddress( + "vtx", + 2, + mldsaKeyId + ) + + val sphincsAddress = + encodeSegwitAddress( + "vtx", + 3, + sphincsKeyId + ) + + android.util.Log.i( + "VextaPQTest", + "MLDSA_ADDRESS=$mldsaAddress" + ) + + android.util.Log.i( + "VextaPQTest", + "SLHDSA_ADDRESS=$sphincsAddress" + ) + + val addressPassed = + mldsaAddress == + "vtx1z6engqms3emse5lycna6yt7gd6svu7nfd28dccr7mfs8s54pz8rysd6lu5u" && + sphincsAddress == + "vtx1rn4dkn2n5cjfeg696u5gg269y6zfx7ffje94swshd5zn7laxfnslqfc63ay" + + android.util.Log.i( + "VextaPQTest", + "ADDRESS_RESULT=" + + if (addressPassed) "PASS" + else "FAIL" + ) + + fun derivePqSeedMaterial( + walletSeed: ByteArray, + algorithm: Int, + counter: Int + ): ByteArray { + val domain = + "Vexta-PQ-HD-v1" + .toByteArray(Charsets.US_ASCII) + + val data = ByteArray( + domain.size + 1 + 4 + ) + + System.arraycopy( + domain, + 0, + data, + 0, + domain.size + ) + + var offset = domain.size + + data[offset++] = + algorithm.toByte() + + data[offset++] = + ((counter ushr 24) and 0xff).toByte() + data[offset++] = + ((counter ushr 16) and 0xff).toByte() + data[offset++] = + ((counter ushr 8) and 0xff).toByte() + data[offset] = + (counter and 0xff).toByte() + + val mac = + javax.crypto.Mac.getInstance( + "HmacSHA512" + ) + + mac.init( + javax.crypto.spec.SecretKeySpec( + walletSeed, + "HmacSHA512" + ) + ) + + return mac.doFinal(data) + } + + val pqHdWalletSeed = + ByteArray(32) { index -> + index.toByte() + } + + val pqHdVectors = listOf( + "MLDSA_COUNTER_0" to + derivePqSeedMaterial( + pqHdWalletSeed, + 2, + 0 + ), + "MLDSA_COUNTER_1" to + derivePqSeedMaterial( + pqHdWalletSeed, + 2, + 1 + ), + "SLHDSA_COUNTER_0" to + derivePqSeedMaterial( + pqHdWalletSeed, + 3, + 0 + ), + "SLHDSA_COUNTER_1" to + derivePqSeedMaterial( + pqHdWalletSeed, + 3, + 1 + ) + ) + + pqHdVectors.forEach { (name, value) -> + android.util.Log.i( + "VextaPQTest", + "$name=${value.joinToString("") { + "%02x".format( + it.toInt() and 0xff + ) + }}" + ) + } + + val pqHdPassed = + pqHdVectors[0].second.contentEquals( + hexToBytes( + "0b5d81161cee1dd59cba10551b48069b2d38deabaec25ce2553777f2ee1574d8a9b5dd1cc72b84f5a6f97a943a060484b048f7ae06433ef73519c9c47136a8e6" + ) + ) && + pqHdVectors[1].second.contentEquals( + hexToBytes( + "96afee1e425cdc5cef6f2a5e565669e059f77538d432b00598c2ce4dae777de58a9ca26ea8c8f63d54a3b9b25554c9db4f4b2f3ff8a83954db44f94c46c7919b" + ) + ) && + pqHdVectors[2].second.contentEquals( + hexToBytes( + "62789e270793f5ca638ceba587a63c8931e1fa8f6ae300d79219b56351d71899905391fd9739652b11db87176ecba328cbac551b6b685961afe9c35346ec0d34" + ) + ) && + pqHdVectors[3].second.contentEquals( + hexToBytes( + "8e445ae4bb057ce26190c032115adeebbda64f6768904dd56147f4832981fb762e7d683b639c2bff6034c93223f71d3ca3d12bad887cb139d3c62da1f8487fa2" + ) + ) + + android.util.Log.i( + "VextaPQTest", + "PQ_HD_RESULT=" + + if (pqHdPassed) "PASS" + else "FAIL" + ) + + val message = + "Vexta PQ Android signature test" + .toByteArray(Charsets.UTF_8) + + val mldsaSignature = + requireNotNull( + VextaPQ.mldsaSign( + message, + mldsa[1] + ) + ) + + val mldsaVerified = + VextaPQ.mldsaVerify( + mldsaSignature, + message, + mldsa[0] + ) + + android.util.Log.i( + "VextaPQTest", + "MLDSA_SIG_BYTES=${mldsaSignature.size}" + ) + + android.util.Log.i( + "VextaPQTest", + "MLDSA_VERIFY=$mldsaVerified" + ) + + val sphincsSignature = + requireNotNull( + VextaPQ.sphincsSign( + message, + sphincs[1] + ) + ) + + val sphincsVerified = + VextaPQ.sphincsVerify( + sphincsSignature, + message, + sphincs[0] + ) + + android.util.Log.i( + "VextaPQTest", + "SPHINCS_SIG_BYTES=${sphincsSignature.size}" + ) + + android.util.Log.i( + "VextaPQTest", + "SPHINCS_VERIFY=$sphincsVerified" + ) + + java.io.File( + filesDir, + "pq-test-message.bin" + ).writeBytes(message) + + java.io.File( + filesDir, + "pq-mldsa.sig" + ).writeBytes(mldsaSignature) + + java.io.File( + filesDir, + "pq-sphincs.sig" + ).writeBytes(sphincsSignature) + + android.util.Log.i( + "VextaPQTest", + "SIGNATURE_FILES_WRITTEN=true" + ) + + val signatureTestPassed = + passed && + mldsaSignature.size == 3309 && + mldsaVerified && + sphincsSignature.size == 7856 && + sphincsVerified + + val coreMldsaSignature = + assets.open("pq-core-mldsa.sig").use { + it.readBytes() + } + + val coreSphincsSignature = + assets.open("pq-core-sphincs.sig").use { + it.readBytes() + } + + val coreMldsaVerified = + VextaPQ.mldsaVerify( + coreMldsaSignature, + message, + mldsa[0] + ) + + val coreSphincsVerified = + VextaPQ.sphincsVerify( + coreSphincsSignature, + message, + sphincs[0] + ) + + android.util.Log.i( + "VextaPQTest", + "CORE_TO_ANDROID_MLDSA=$coreMldsaVerified" + ) + + android.util.Log.i( + "VextaPQTest", + "CORE_TO_ANDROID_SPHINCS=$coreSphincsVerified" + ) + + val (pqrV2, pqrV3) = + VextaTransactionSender + .runPqrSignatureHashSelfTest() + + android.util.Log.i( + "VextaPQTest", + "PQR_V2=$pqrV2" + ) + + android.util.Log.i( + "VextaPQTest", + "PQR_V3=$pqrV3" + ) + + val pqrPassed = + pqrV2 == + "ba39c447b4577db974473a1e7ed2182686672f2900045a40aee6b4dc5811995b" && + pqrV3 == + "dab3188530f1094ef87087451da77bfba77dfc6e63d1730bb9d0475cebe76618" + + android.util.Log.i( + "VextaPQTest", + "PQR_HASH_RESULT=" + + if (pqrPassed) "PASS" + else "FAIL" + ) + + val pqWitnessResults = + VextaTransactionSender + .runPqWitnessSelfTest() + + pqWitnessResults.forEach { result -> + android.util.Log.i( + "VextaPQTest", + result + ) + } + + val pqWitnessPassed = + pqWitnessResults.contains( + "PQ_WITNESS_RESULT=PASS" + ) + + val pqSignedTxResults = + VextaTransactionSender + .runPqSignedTransactionSelfTest() + + pqSignedTxResults.forEach { result -> + android.util.Log.i( + "VextaPQTest", + result + ) + } + + val pqSignedTxPassed = + pqSignedTxResults.contains( + "PQ_SIGNED_TX_RESULT=PASS" + ) + + val finalPassed = + signatureTestPassed && + coreMldsaVerified && + coreSphincsVerified && + pqrPassed && + pqWitnessPassed && + pqSignedTxPassed + + android.util.Log.i( + "VextaPQTest", + if (finalPassed) { + "RESULT=PASS" + } else { + "RESULT=FAIL" + } + ) + } catch (error: Throwable) { + android.util.Log.e( + "VextaPQTest", + "RESULT=ERROR", + error + ) + } + }.start() + } + override fun onResume() { super.onResume() @@ -1053,38 +1514,122 @@ class WalletActivity : FragmentActivity() { editor.apply() } + private fun backgroundUtxoKey( + addressType: BlockScanner.AddressType, + addressIndex: Int + ): String { + return PREF_BACKGROUND_UTXO_PREFIX + + addressType.name + + "_" + + addressIndex + } + private fun loadCachedWalletUtxos(): List { val preferences = getSharedPreferences(PREFS, MODE_PRIVATE) - val highestIndex = currentReceiveAddressIndex() + val addressRanges = + buildList { + add( + BlockScanner.AddressType.STANDARD to + (0..currentReceiveAddressIndex()) + ) - return (0..highestIndex) - .flatMap { addressIndex -> - val values = - preferences.getStringSet( - PREF_BACKGROUND_UTXO_PREFIX + addressIndex, - emptySet() - ) ?: emptySet() + if ( + pqAddressCreated( + ReceiveAddressType.MLDSA + ) + ) { + add( + BlockScanner.AddressType.MLDSA to + ( + 0..currentPqAddressIndex( + ReceiveAddressType.MLDSA + ) + ) + ) + } - values.mapNotNull { value -> - try { - val parts = value.split("|") + if ( + pqAddressCreated( + ReceiveAddressType.SLHDSA + ) + ) { + add( + BlockScanner.AddressType.SLHDSA to + ( + 0..currentPqAddressIndex( + ReceiveAddressType.SLHDSA + ) + ) + ) + } + } - if (parts.size != 6) { - return@mapNotNull null + return addressRanges + .flatMap { (addressType, indices) -> + indices.flatMap { addressIndex -> + val newKey = + backgroundUtxoKey( + addressType, + addressIndex + ) + + val values = + if ( + addressType == + BlockScanner.AddressType.STANDARD && + !preferences.contains(newKey) + ) { + val legacyKey = + PREF_BACKGROUND_UTXO_PREFIX + + addressIndex + + preferences.getStringSet( + legacyKey, + emptySet() + ) ?: emptySet() + } else { + preferences.getStringSet( + newKey, + emptySet() + ) ?: emptySet() } - BlockScanner.SpendableUtxo( - txid = parts[0], - outputIndex = parts[1].toLong(), - value = parts[2].toLong(), - height = parts[3].toInt(), - addressIndex = parts[4].toInt(), - isCoinbase = parts[5].toBooleanStrict() - ) - } catch (_: Exception) { - null + values.mapNotNull { value -> + try { + val parts = value.split("|") + + if ( + parts.size != 6 && + parts.size != 7 + ) { + return@mapNotNull null + } + + BlockScanner.SpendableUtxo( + txid = parts[0], + outputIndex = + parts[1].toLong(), + value = + parts[2].toLong(), + height = + parts[3].toInt(), + addressIndex = + parts[4].toInt(), + isCoinbase = + parts[5].toBooleanStrict(), + addressType = + if (parts.size == 7) { + BlockScanner.AddressType + .valueOf(parts[6]) + } else { + BlockScanner.AddressType.STANDARD + } + ) + } catch (_: Exception) { + null + } } } } @@ -1299,6 +1844,307 @@ class WalletActivity : FragmentActivity() { ) } + private enum class ReceiveAddressType { + STANDARD, + MLDSA, + SLHDSA + } + + private data class PqDerivedKey( + val address: String, + val scriptPubKey: ByteArray, + val publicKey: ByteArray, + val secretKey: ByteArray + ) + + private fun derivePqSeedMaterial( + words: List, + type: ReceiveAddressType, + counter: Int + ): ByteArray { + require( + type == ReceiveAddressType.MLDSA || + type == ReceiveAddressType.SLHDSA + ) + require(counter >= 0) + + MnemonicCode.INSTANCE.check(words) + + val bip39Seed = + MnemonicCode.toSeed(words, "") + + val masterKey = + HDKeyDerivation.createMasterPrivateKey( + bip39Seed + ) + + val walletSeed = + masterKey.privKeyBytes + + val domain = + "Vexta-PQ-HD-v1" + .toByteArray(Charsets.US_ASCII) + + val data = + ByteArray(domain.size + 1 + 4) + + System.arraycopy( + domain, + 0, + data, + 0, + domain.size + ) + + var offset = domain.size + + data[offset++] = + when (type) { + ReceiveAddressType.MLDSA -> 2 + ReceiveAddressType.SLHDSA -> 3 + ReceiveAddressType.STANDARD -> + error("Standard address is not PQ") + }.toByte() + + data[offset++] = + ((counter ushr 24) and 0xff).toByte() + data[offset++] = + ((counter ushr 16) and 0xff).toByte() + data[offset++] = + ((counter ushr 8) and 0xff).toByte() + data[offset] = + (counter and 0xff).toByte() + + return try { + val mac = + javax.crypto.Mac.getInstance( + "HmacSHA512" + ) + + mac.init( + javax.crypto.spec.SecretKeySpec( + walletSeed, + "HmacSHA512" + ) + ) + + mac.doFinal(data) + } finally { + data.fill(0) + walletSeed.fill(0) + bip39Seed.fill(0) + } + } + + private fun derivePqKey( + words: List, + type: ReceiveAddressType, + counter: Int + ): PqDerivedKey { + val seedMaterial = + derivePqSeedMaterial( + words, + type, + counter + ) + + return try { + val keyPair = + when (type) { + ReceiveAddressType.MLDSA -> + requireNotNull( + VextaPQ.mldsaKeypairFromSeed( + seedMaterial + ) + ) + + ReceiveAddressType.SLHDSA -> + requireNotNull( + VextaPQ.sphincsKeypairFromSeed( + seedMaterial + ) + ) + + ReceiveAddressType.STANDARD -> + error("Standard address is not PQ") + } + + require(keyPair.size == 2) + + val publicKey = keyPair[0] + val secretKey = keyPair[1] + + val keyId = + MessageDigest + .getInstance("SHA-256") + .digest(publicKey) + + val witnessVersion = + when (type) { + ReceiveAddressType.MLDSA -> 2 + ReceiveAddressType.SLHDSA -> 3 + ReceiveAddressType.STANDARD -> + error("Standard address is not PQ") + } + + val address = + encodeSegwitAddress( + "vtx", + witnessVersion, + keyId + ) + + val scriptPubKey = + byteArrayOf( + if (witnessVersion == 2) { + 0x52.toByte() + } else { + 0x53.toByte() + }, + 0x20 + ) + keyId + + PqDerivedKey( + address = address, + scriptPubKey = scriptPubKey, + publicKey = publicKey, + secretKey = secretKey + ) + } finally { + seedMaterial.fill(0) + } + } + + private fun currentReceiveAddressType(): ReceiveAddressType { + val stored = + getSharedPreferences( + PREFS, + MODE_PRIVATE + ).getString( + PREF_RECEIVE_ADDRESS_TYPE, + ReceiveAddressType.STANDARD.name + ) + + return try { + ReceiveAddressType.valueOf( + stored ?: ReceiveAddressType.STANDARD.name + ) + } catch (_: Exception) { + ReceiveAddressType.STANDARD + } + } + + private fun setCurrentReceiveAddressType( + type: ReceiveAddressType + ) { + getSharedPreferences( + PREFS, + MODE_PRIVATE + ) + .edit() + .putString( + PREF_RECEIVE_ADDRESS_TYPE, + type.name + ) + .apply() + } + + private fun pqAddressCreated( + type: ReceiveAddressType + ): Boolean { + val key = + when (type) { + ReceiveAddressType.MLDSA -> + PREF_MLDSA_ADDRESS_CREATED + + ReceiveAddressType.SLHDSA -> + PREF_SLHDSA_ADDRESS_CREATED + + ReceiveAddressType.STANDARD -> + return true + } + + return getSharedPreferences( + PREFS, + MODE_PRIVATE + ).getBoolean(key, false) + } + + private fun setPqAddressCreated( + type: ReceiveAddressType + ) { + val key = + when (type) { + ReceiveAddressType.MLDSA -> + PREF_MLDSA_ADDRESS_CREATED + + ReceiveAddressType.SLHDSA -> + PREF_SLHDSA_ADDRESS_CREATED + + ReceiveAddressType.STANDARD -> + return + } + + getSharedPreferences( + PREFS, + MODE_PRIVATE + ) + .edit() + .putBoolean(key, true) + .apply() + } + + private fun currentPqAddressIndex( + type: ReceiveAddressType + ): Int { + val key = + when (type) { + ReceiveAddressType.MLDSA -> + PREF_MLDSA_ADDRESS_INDEX + + ReceiveAddressType.SLHDSA -> + PREF_SLHDSA_ADDRESS_INDEX + + ReceiveAddressType.STANDARD -> + error("Standard address has its own index") + } + + return getSharedPreferences( + PREFS, + MODE_PRIVATE + ) + .getInt(key, 0) + .coerceAtLeast(0) + } + + private fun setCurrentPqAddressIndex( + type: ReceiveAddressType, + index: Int + ) { + val key = + when (type) { + ReceiveAddressType.MLDSA -> + PREF_MLDSA_ADDRESS_INDEX + + ReceiveAddressType.SLHDSA -> + PREF_SLHDSA_ADDRESS_INDEX + + ReceiveAddressType.STANDARD -> + error("Standard address has its own index") + } + + getSharedPreferences( + PREFS, + MODE_PRIVATE + ) + .edit() + .putInt( + key, + index.coerceAtLeast(0) + ) + .apply() + } + private fun currentReceiveAddressIndex(): Int { return getSharedPreferences(PREFS, MODE_PRIVATE) .getInt(PREF_RECEIVE_ADDRESS_INDEX, 0) @@ -1326,7 +2172,7 @@ class WalletActivity : FragmentActivity() { PREF_RESTORE_ADDRESS_DISCOVERY, false ) - val highestAddressIndex = + val highestStandardAddressIndex = if (restoreAddressDiscovery) { maxOf( currentReceiveAddressIndex(), @@ -1336,7 +2182,37 @@ class WalletActivity : FragmentActivity() { currentReceiveAddressIndex() } - var highestUsedAddressIndex = -1 + val highestMldsaAddressIndex = + if (restoreAddressDiscovery) { + maxOf( + currentPqAddressIndex( + ReceiveAddressType.MLDSA + ), + RESTORE_ADDRESS_LOOKAHEAD + ) + } else { + currentPqAddressIndex( + ReceiveAddressType.MLDSA + ) + } + + val highestSlhdsaAddressIndex = + if (restoreAddressDiscovery) { + maxOf( + currentPqAddressIndex( + ReceiveAddressType.SLHDSA + ), + RESTORE_ADDRESS_LOOKAHEAD + ) + } else { + currentPqAddressIndex( + ReceiveAddressType.SLHDSA + ) + } + + var highestUsedStandardAddressIndex = -1 + var highestUsedMldsaAddressIndex = -1 + var highestUsedSlhdsaAddressIndex = -1 val allUtxos = mutableListOf() @@ -1348,30 +2224,124 @@ class WalletActivity : FragmentActivity() { var totalReceivedTransactions = 0 var totalSpentTransactions = 0 - for (addressIndex in 0..highestAddressIndex) { - val scriptPubKey = - deriveWitnessScript(words, addressIndex) + val scanTargets = + buildList { + for (addressIndex in 0..highestStandardAddressIndex) { + add( + Triple( + BlockScanner.AddressType.STANDARD, + addressIndex, + deriveWitnessScript( + words, + addressIndex + ) + ) + ) + } + + if ( + restoreAddressDiscovery || + pqAddressCreated( + ReceiveAddressType.MLDSA + ) + ) { + for ( + addressIndex in + 0..highestMldsaAddressIndex + ) { + val key = + derivePqKey( + words, + ReceiveAddressType.MLDSA, + addressIndex + ) + + val script = key.scriptPubKey + key.secretKey.fill(0) + + add( + Triple( + BlockScanner.AddressType.MLDSA, + addressIndex, + script + ) + ) + } + } + + if ( + restoreAddressDiscovery || + pqAddressCreated( + ReceiveAddressType.SLHDSA + ) + ) { + for ( + addressIndex in + 0..highestSlhdsaAddressIndex + ) { + val key = + derivePqKey( + words, + ReceiveAddressType.SLHDSA, + addressIndex + ) + + val script = key.scriptPubKey + key.secretKey.fill(0) + + add( + Triple( + BlockScanner.AddressType.SLHDSA, + addressIndex, + script + ) + ) + } + } + } + + val totalAddressesToScan = + scanTargets.size + + scanTargets.forEachIndexed { + targetPosition, + (addressType, addressIndex, scriptPubKey) -> + + val typeLabel = + when (addressType) { + BlockScanner.AddressType.STANDARD -> + "Standard" + + BlockScanner.AddressType.MLDSA -> + "ML-DSA" + + BlockScanner.AddressType.SLHDSA -> + "SLH-DSA" + } runOnUiThread { blockchainScanStatus?.text = - "Scanning address " + - "${addressIndex + 1} of " + - "${highestAddressIndex + 1}..." + "Scanning $typeLabel address " + + "${addressIndex + 1}\n" + + "Wallet address " + + "${targetPosition + 1} of " + + "$totalAddressesToScan..." } - val filterResult = CompactFilterClient.scan( - this@WalletActivity, - "87.106.99.23", - scriptPubKey - ) { scanned, total -> - runOnUiThread { - blockchainScanStatus?.text = - "Address ${addressIndex + 1}/" + - "${highestAddressIndex + 1}\n" + - "Scanning compact filters: " + - "$scanned / $total" + val filterResult = + CompactFilterClient.scan( + this@WalletActivity, + "87.106.99.23", + scriptPubKey + ) { scanned, total -> + runOnUiThread { + blockchainScanStatus?.text = + "$typeLabel address " + + "${addressIndex + 1}\n" + + "Scanning compact filters: " + + "$scanned / $total" + } } - } totalFiltersScanned += filterResult.scannedFilters @@ -1382,33 +2352,57 @@ class WalletActivity : FragmentActivity() { filterResult.localHeaderHeight ) - val blockResult = BlockScanner.scan( - this@WalletActivity, - "87.106.99.23", - filterResult.matchingHeights, - scriptPubKey, - addressIndex - ) { downloaded, total -> - runOnUiThread { - blockchainScanStatus?.text = - "Address ${addressIndex + 1}/" + - "${highestAddressIndex + 1}\n" + - "Relevant blocks downloaded: " + - "$downloaded / $total" + val blockResult = + BlockScanner.scan( + this@WalletActivity, + "87.106.99.23", + filterResult.matchingHeights, + scriptPubKey, + addressIndex, + addressType = addressType + ) { downloaded, total -> + runOnUiThread { + blockchainScanStatus?.text = + "$typeLabel address " + + "${addressIndex + 1}\n" + + "Relevant blocks downloaded: " + + "$downloaded / $total" + } } - } allUtxos.addAll(blockResult.utxos) allTransactions.addAll( blockResult.transactions ) - if ( + val addressWasUsed = blockResult.receivedTransactions > 0 || - blockResult.spentTransactions > 0 || - blockResult.transactions.isNotEmpty() - ) { - highestUsedAddressIndex = addressIndex + blockResult.spentTransactions > 0 || + blockResult.transactions.isNotEmpty() + + if (addressWasUsed) { + when (addressType) { + BlockScanner.AddressType.STANDARD -> + highestUsedStandardAddressIndex = + maxOf( + highestUsedStandardAddressIndex, + addressIndex + ) + + BlockScanner.AddressType.MLDSA -> + highestUsedMldsaAddressIndex = + maxOf( + highestUsedMldsaAddressIndex, + addressIndex + ) + + BlockScanner.AddressType.SLHDSA -> + highestUsedSlhdsaAddressIndex = + maxOf( + highestUsedSlhdsaAddressIndex, + addressIndex + ) + } } totalRelevantBlocks += @@ -1528,9 +2522,52 @@ class WalletActivity : FragmentActivity() { if (restoreAddressDiscovery) { setCurrentReceiveAddressIndex( - highestUsedAddressIndex.coerceAtLeast(0) + highestUsedStandardAddressIndex.coerceAtLeast(0) ) + + if (highestUsedMldsaAddressIndex >= 0) { + setCurrentPqAddressIndex( + ReceiveAddressType.MLDSA, + highestUsedMldsaAddressIndex + ) + setPqAddressCreated( + ReceiveAddressType.MLDSA + ) + } else { + setCurrentPqAddressIndex( + ReceiveAddressType.MLDSA, + 0 + ) + } + + if (highestUsedSlhdsaAddressIndex >= 0) { + setCurrentPqAddressIndex( + ReceiveAddressType.SLHDSA, + highestUsedSlhdsaAddressIndex + ) + setPqAddressCreated( + ReceiveAddressType.SLHDSA + ) + } else { + setCurrentPqAddressIndex( + ReceiveAddressType.SLHDSA, + 0 + ) + } + + setCurrentReceiveAddressType( + ReceiveAddressType.STANDARD + ) + preferences.edit() + .putBoolean( + PREF_MLDSA_ADDRESS_CREATED, + highestUsedMldsaAddressIndex >= 0 + ) + .putBoolean( + PREF_SLHDSA_ADDRESS_CREATED, + highestUsedSlhdsaAddressIndex >= 0 + ) .putBoolean( PREF_RESTORE_ADDRESS_DISCOVERY, false @@ -1562,7 +2599,7 @@ class WalletActivity : FragmentActivity() { blockchainScanStatus?.text = "Addresses scanned: " + - "${highestAddressIndex + 1}\n" + + "$totalAddressesToScan\n" + "Filters scanned: $totalFiltersScanned\n" + "Relevant blocks: $totalRelevantBlocks\n" + "Received transactions: " + @@ -2105,6 +3142,8 @@ class WalletActivity : FragmentActivity() { } private fun showSeedLengthDialog() { + lateinit var dialog: AlertDialog + val content = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL setPadding( @@ -2116,6 +3155,7 @@ class WalletActivity : FragmentActivity() { addView( primaryButton("12-word recovery phrase") { + dialog.dismiss() createNewWallet(12) } ) @@ -2124,19 +3164,23 @@ class WalletActivity : FragmentActivity() { addView( primaryButton("24-word recovery phrase") { + dialog.dismiss() createNewWallet(24) } ) } - AlertDialog.Builder(this) - .setTitle("Recovery phrase length") - .setMessage( - "Choose the recovery phrase length for your new wallet." - ) - .setView(content) - .setNegativeButton("Cancel", null) - .show() + dialog = + AlertDialog.Builder(this) + .setTitle("Recovery phrase length") + .setMessage( + "Choose the recovery phrase length for your new wallet." + ) + .setView(content) + .setNegativeButton("Cancel", null) + .create() + + dialog.show() } private fun createNewWallet(wordCount: Int) { @@ -2159,9 +3203,40 @@ class WalletActivity : FragmentActivity() { saveMnemonic(words.joinToString(" ")) - getSharedPreferences(PREFS, MODE_PRIVATE) - .edit() + val preferences = + getSharedPreferences(PREFS, MODE_PRIVATE) + + val editor = preferences.edit() + + preferences.all.keys + .filter { + it.startsWith( + PREF_BACKGROUND_UTXO_PREFIX + ) + } + .forEach { + editor.remove(it) + } + + editor + .remove(PREF_BACKGROUND_TX_HISTORY) + .remove(PREF_LAST_BACKGROUND_SCAN_HEIGHT) + .remove(PREF_KNOWN_INCOMING_TXIDS) + .remove(PREF_OUTGOING_TRANSACTIONS) .putBoolean(PREF_BACKUP_CONFIRMED, false) + .putInt(PREF_RECEIVE_ADDRESS_INDEX, 0) + .putInt(PREF_MLDSA_ADDRESS_INDEX, 0) + .putInt(PREF_SLHDSA_ADDRESS_INDEX, 0) + .putBoolean(PREF_MLDSA_ADDRESS_CREATED, false) + .putBoolean(PREF_SLHDSA_ADDRESS_CREATED, false) + .putString( + PREF_RECEIVE_ADDRESS_TYPE, + ReceiveAddressType.STANDARD.name + ) + .putBoolean( + PREF_RESTORE_ADDRESS_DISCOVERY, + false + ) .apply() showSeedBackup(words) @@ -2831,8 +3906,65 @@ class WalletActivity : FragmentActivity() { return } - val addressIndex = currentReceiveAddressIndex() - val address = deriveAddress(words, addressIndex) + val receiveType = + currentReceiveAddressType() + + val addressIndex: Int + val address: String + val typeLabel: String + + when (receiveType) { + ReceiveAddressType.STANDARD -> { + addressIndex = + currentReceiveAddressIndex() + + address = + deriveAddress( + words, + addressIndex + ) + + typeLabel = "Standard" + } + + ReceiveAddressType.MLDSA -> { + addressIndex = + currentPqAddressIndex( + ReceiveAddressType.MLDSA + ) + + val key = + derivePqKey( + words, + ReceiveAddressType.MLDSA, + addressIndex + ) + + address = key.address + key.secretKey.fill(0) + + typeLabel = "ML-DSA" + } + + ReceiveAddressType.SLHDSA -> { + addressIndex = + currentPqAddressIndex( + ReceiveAddressType.SLHDSA + ) + + val key = + derivePqKey( + words, + ReceiveAddressType.SLHDSA, + addressIndex + ) + + address = key.address + key.secretKey.fill(0) + + typeLabel = "SPHINCS+" + } + } val content = baseLayout() content.addView(space(10)) @@ -2850,19 +3982,22 @@ class WalletActivity : FragmentActivity() { card { addView( sectionTitle( - "Receive address ${addressIndex + 1}" + "$typeLabel receive address ${addressIndex + 1}" ) ) - val currentLabel = addressLabel(addressIndex) + if (receiveType == ReceiveAddressType.STANDARD) { + val currentLabel = + addressLabel(addressIndex) - if (currentLabel.isNotBlank()) { - addView(space(8)) - addView( - smallStatus( - "Label: $currentLabel" + if (currentLabel.isNotBlank()) { + addView(space(8)) + addView( + smallStatus( + "Label: $currentLabel" + ) ) - ) + } } addView(space(14)) @@ -2903,9 +4038,10 @@ class WalletActivity : FragmentActivity() { isFocusable = true setOnClickListener { - val clipboard = getSystemService( - Context.CLIPBOARD_SERVICE - ) as ClipboardManager + val clipboard = + getSystemService( + Context.CLIPBOARD_SERVICE + ) as ClipboardManager clipboard.setPrimaryClip( ClipData.newPlainText( @@ -2929,36 +4065,58 @@ class WalletActivity : FragmentActivity() { "Tap the address to copy it" ) ) - addView(space(12)) - addView( - secondaryButton("Edit address label") { - val input = - EditText(this@WalletActivity).apply { - hint = "e.g. Mining, Pool payout, Personal" - setText(addressLabel(addressIndex)) - setSelection(text.length) - } - AlertDialog.Builder(this@WalletActivity) - .setTitle( - "Address ${addressIndex + 1} label" + if (receiveType == ReceiveAddressType.STANDARD) { + addView(space(12)) + addView( + secondaryButton("Edit address label") { + val input = + EditText( + this@WalletActivity + ).apply { + hint = + "e.g. Mining, Pool payout, Personal" + setText( + addressLabel( + addressIndex + ) + ) + setSelection(text.length) + } + + AlertDialog.Builder( + this@WalletActivity ) - .setView(input) - .setNegativeButton("Cancel", null) - .setNeutralButton("Remove") { _, _ -> - setAddressLabel(addressIndex, "") - showReceive() - } - .setPositiveButton("Save") { _, _ -> - setAddressLabel( - addressIndex, - input.text.toString() + .setTitle( + "Address ${addressIndex + 1} label" ) - showReceive() - } - .show() - } - ) + .setView(input) + .setNegativeButton( + "Cancel", + null + ) + .setNeutralButton( + "Remove" + ) { _, _ -> + setAddressLabel( + addressIndex, + "" + ) + showReceive() + } + .setPositiveButton( + "Save" + ) { _, _ -> + setAddressLabel( + addressIndex, + input.text.toString() + ) + showReceive() + } + .show() + } + ) + } } ) @@ -2966,38 +4124,92 @@ class WalletActivity : FragmentActivity() { content.addView( card { - addView(sectionTitle("Address management")) - addView(space(12)) addView( - secondaryButton("Manage address labels") { - showAddressLabelManager() - } + sectionTitle( + "Address management" + ) ) - addView(space(10)) + addView(space(12)) + addView( - secondaryButton("Generate new address") { + secondaryButton( + "Generate new address" + ) { + val options = + arrayOf( + "Standard", + "ML-DSA", + "SPHINCS+" + ) + AlertDialog.Builder( this@WalletActivity ) - .setTitle("Generate new address?") - .setMessage( - "The current address will remain " + - "valid and the wallet will continue " + - "scanning it. A new address will be " + - "generated from the same recovery phrase." + .setTitle( + "Choose address type" ) - .setNegativeButton("Cancel", null) - .setPositiveButton("Generate") { - _, _ -> - setCurrentReceiveAddressIndex( - addressIndex + 1 + .setItems( + options + ) { _, which -> + val selectedType = + when (which) { + 0 -> + ReceiveAddressType.STANDARD + + 1 -> + ReceiveAddressType.MLDSA + + 2 -> + ReceiveAddressType.SLHDSA + + else -> + return@setItems + } + + when (selectedType) { + ReceiveAddressType.STANDARD -> { + setCurrentReceiveAddressIndex( + currentReceiveAddressIndex() + 1 + ) + } + + ReceiveAddressType.MLDSA, + ReceiveAddressType.SLHDSA -> { + if ( + pqAddressCreated( + selectedType + ) + ) { + setCurrentPqAddressIndex( + selectedType, + currentPqAddressIndex( + selectedType + ) + 1 + ) + } else { + setPqAddressCreated( + selectedType + ) + } + } + } + + setCurrentReceiveAddressType( + selectedType ) + showReceive() } + .setNegativeButton( + "Cancel", + null + ) .show() } ) + addView(space(10)) + addView( helpText( "All previously generated addresses remain " + @@ -3797,6 +5009,10 @@ class WalletActivity : FragmentActivity() { val privateKeysByIndex = latestSpendableUtxos + .filter { + it.addressType == + BlockScanner.AddressType.STANDARD + } .map { it.addressIndex } .distinct() .associateWith { index -> @@ -3806,30 +5022,88 @@ class WalletActivity : FragmentActivity() { ) } - val changePrivateKey = - deriveWalletPrivateKey( - words, - currentReceiveAddressIndex() - ) - - val changePubKeyHash = - Utils.sha256hash160( - changePrivateKey.pubKey - ) + val pqSigningKeys = + linkedMapOf< + Pair, + VextaTransactionSender.PqSigningKey + >() val transaction = - VextaTransactionSender.createAndSign( - spendableUtxos = - latestSpendableUtxos, - recipientAddress = recipient, - amountSatoshis = amountSatoshis, - privateKeysByIndex = - privateKeysByIndex, - changePubKeyHash = - changePubKeyHash, - chainHeight = - latestChainHeight - ) + try { + latestSpendableUtxos + .filter { + it.addressType != + BlockScanner.AddressType.STANDARD + } + .distinctBy { + it.addressType to it.addressIndex + } + .forEach { utxo -> + val receiveType = + when (utxo.addressType) { + BlockScanner.AddressType.MLDSA -> + ReceiveAddressType.MLDSA + + BlockScanner.AddressType.SLHDSA -> + ReceiveAddressType.SLHDSA + + BlockScanner.AddressType.STANDARD -> + error( + "Standard UTXO is not PQ" + ) + } + + val key = + derivePqKey( + words, + receiveType, + utxo.addressIndex + ) + + pqSigningKeys[ + utxo.addressType to + utxo.addressIndex + ] = + VextaTransactionSender.PqSigningKey( + addressType = + utxo.addressType, + publicKey = + key.publicKey, + secretKey = + key.secretKey + ) + } + + val changePrivateKey = + deriveWalletPrivateKey( + words, + currentReceiveAddressIndex() + ) + + val changePubKeyHash = + Utils.sha256hash160( + changePrivateKey.pubKey + ) + + VextaTransactionSender.createAndSign( + spendableUtxos = + latestSpendableUtxos, + recipientAddress = recipient, + amountSatoshis = amountSatoshis, + privateKeysByIndex = + privateKeysByIndex, + pqSigningKeys = + pqSigningKeys, + changePubKeyHash = + changePubKeyHash, + chainHeight = + latestChainHeight + ) + } finally { + pqSigningKeys.values.forEach { key -> + key.secretKey.fill(0) + } + } val amountDisplay = String.format( "%.8f", @@ -4138,10 +5412,36 @@ class WalletActivity : FragmentActivity() { MnemonicCode.INSTANCE.check(words) saveMnemonic(words.joinToString(" ")) - getSharedPreferences(PREFS, MODE_PRIVATE) - .edit() + val preferences = + getSharedPreferences(PREFS, MODE_PRIVATE) + + val editor = preferences.edit() + + preferences.all.keys + .filter { + it.startsWith( + PREF_BACKGROUND_UTXO_PREFIX + ) + } + .forEach { + editor.remove(it) + } + + editor + .remove(PREF_BACKGROUND_TX_HISTORY) + .remove(PREF_LAST_BACKGROUND_SCAN_HEIGHT) + .remove(PREF_KNOWN_INCOMING_TXIDS) + .remove(PREF_OUTGOING_TRANSACTIONS) .putBoolean(PREF_BACKUP_CONFIRMED, true) .putInt(PREF_RECEIVE_ADDRESS_INDEX, 0) + .putInt(PREF_MLDSA_ADDRESS_INDEX, 0) + .putInt(PREF_SLHDSA_ADDRESS_INDEX, 0) + .putBoolean(PREF_MLDSA_ADDRESS_CREATED, false) + .putBoolean(PREF_SLHDSA_ADDRESS_CREATED, false) + .putString( + PREF_RECEIVE_ADDRESS_TYPE, + ReceiveAddressType.STANDARD.name + ) .putBoolean( PREF_RESTORE_ADDRESS_DISCOVERY, true @@ -4502,8 +5802,12 @@ class WalletActivity : FragmentActivity() { hrp: String, data: List ): List { + val witnessVersion = data.firstOrNull() ?: 0 + val checksumConstant = + if (witnessVersion == 0) 1 else 0x2bc830a3 + val values = expandBech32Hrp(hrp) + data + List(6) { 0 } - val polymod = bech32Polymod(values) xor 1 + val polymod = bech32Polymod(values) xor checksumConstant return (0 until 6).map { index -> (polymod shr (5 * (5 - index))) and 31 diff --git a/app/src/main/java/org/vextaproject/wallet/WalletMonitoringService.kt b/app/src/main/java/org/vextaproject/wallet/WalletMonitoringService.kt index 7f66ce7..227e883 100644 --- a/app/src/main/java/org/vextaproject/wallet/WalletMonitoringService.kt +++ b/app/src/main/java/org/vextaproject/wallet/WalletMonitoringService.kt @@ -37,6 +37,14 @@ class WalletMonitoringService : Service() { private const val PREF_IV = "seed_iv" private const val PREF_RECEIVE_ADDRESS_INDEX = "receive_address_index" + private const val PREF_MLDSA_ADDRESS_INDEX = + "mldsa_address_index" + private const val PREF_SLHDSA_ADDRESS_INDEX = + "slhdsa_address_index" + private const val PREF_MLDSA_ADDRESS_CREATED = + "mldsa_address_created" + private const val PREF_SLHDSA_ADDRESS_CREATED = + "slhdsa_address_created" private const val PREF_KNOWN_INCOMING_TXIDS = "known_incoming_txids" private const val PREF_TX_NOTIFICATIONS_INITIALIZED = @@ -54,6 +62,16 @@ class WalletMonitoringService : Service() { private const val BACKGROUND_TX_HISTORY_VERSION = 4 } + private fun backgroundUtxoKey( + addressType: BlockScanner.AddressType, + addressIndex: Int + ): String { + return PREF_BACKGROUND_UTXO_PREFIX + + addressType.name + + "_" + + addressIndex + } + private val handler = Handler(Looper.getMainLooper()) private var scanRunning = false @@ -136,6 +154,16 @@ class WalletMonitoringService : Service() { .getInt(PREF_RECEIVE_ADDRESS_INDEX, 0) .coerceAtLeast(0) + val highestMldsaAddressIndex = + preferences + .getInt(PREF_MLDSA_ADDRESS_INDEX, 0) + .coerceAtLeast(0) + + val highestSlhdsaAddressIndex = + preferences + .getInt(PREF_SLHDSA_ADDRESS_INDEX, 0) + .coerceAtLeast(0) + val localHeight = chain.last().height preferences.edit() @@ -153,14 +181,45 @@ class WalletMonitoringService : Service() { ) .coerceAtLeast(0) - val backgroundCacheAvailable = + val standardCacheAvailable = (0..highestAddressIndex).all { addressIndex -> preferences.contains( - PREF_BACKGROUND_UTXO_PREFIX + + backgroundUtxoKey( + BlockScanner.AddressType.STANDARD, addressIndex + ) + ) || + preferences.contains( + PREF_BACKGROUND_UTXO_PREFIX + + addressIndex + ) + } + + val mldsaCacheAvailable = + (0..highestMldsaAddressIndex).all { addressIndex -> + preferences.contains( + backgroundUtxoKey( + BlockScanner.AddressType.MLDSA, + addressIndex + ) ) } + val slhdsaCacheAvailable = + (0..highestSlhdsaAddressIndex).all { addressIndex -> + preferences.contains( + backgroundUtxoKey( + BlockScanner.AddressType.SLHDSA, + addressIndex + ) + ) + } + + val backgroundCacheAvailable = + standardCacheAvailable && + mldsaCacheAvailable && + slhdsaCacheAvailable + val transactionHistoryCurrent = preferences.getInt( PREF_BACKGROUND_TX_HISTORY_VERSION, @@ -192,12 +251,79 @@ class WalletMonitoringService : Service() { mutableListOf() val updatedUtxos = - linkedMapOf>() + linkedMapOf< + Pair, + List + >() - for (addressIndex in 0..highestAddressIndex) { - val script = - deriveWitnessScript(words, addressIndex) + val scanTargets = + buildList { + for (addressIndex in 0..highestAddressIndex) { + add( + Triple( + BlockScanner.AddressType.STANDARD, + addressIndex, + deriveWitnessScript( + words, + addressIndex + ) + ) + ) + } + if ( + preferences.getBoolean( + PREF_MLDSA_ADDRESS_CREATED, + false + ) + ) { + for ( + addressIndex in + 0..highestMldsaAddressIndex + ) { + add( + Triple( + BlockScanner.AddressType.MLDSA, + addressIndex, + derivePqWitnessScript( + words, + BlockScanner.AddressType.MLDSA, + addressIndex + ) + ) + ) + } + } + + if ( + preferences.getBoolean( + PREF_SLHDSA_ADDRESS_CREATED, + false + ) + ) { + for ( + addressIndex in + 0..highestSlhdsaAddressIndex + ) { + add( + Triple( + BlockScanner.AddressType.SLHDSA, + addressIndex, + derivePqWitnessScript( + words, + BlockScanner.AddressType.SLHDSA, + addressIndex + ) + ) + ) + } + } + } + + for ( + (addressType, addressIndex, script) + in scanTargets + ) { val filters = CompactFilterClient.scan( this, @@ -212,6 +338,7 @@ class WalletMonitoringService : Service() { } else { loadBackgroundUtxos( preferences, + addressType, addressIndex ) } @@ -223,13 +350,15 @@ class WalletMonitoringService : Service() { filters.matchingHeights, script, addressIndex, + addressType = addressType, initialUtxos = initialUtxos ) { _, _ -> } allTransactions.addAll(blocks.transactions) - updatedUtxos[addressIndex] = - blocks.utxos + updatedUtxos[ + addressType to addressIndex + ] = blocks.utxos } val mergedTransactions = @@ -297,15 +426,16 @@ class WalletMonitoringService : Service() { ) for ( - addressIndex in 0..highestAddressIndex + (target, utxos) in updatedUtxos ) { + val (addressType, addressIndex) = target + editor.putStringSet( - PREF_BACKGROUND_UTXO_PREFIX + - addressIndex, - encodeBackgroundUtxos( - updatedUtxos[addressIndex] - ?: emptyList() - ) + backgroundUtxoKey( + addressType, + addressIndex + ), + encodeBackgroundUtxos(utxos) ) } @@ -536,25 +666,47 @@ class WalletMonitoringService : Service() { utxo.value.toString(), utxo.height.toString(), utxo.addressIndex.toString(), - utxo.isCoinbase.toString() + utxo.isCoinbase.toString(), + utxo.addressType.name ).joinToString("|") }.toSet() private fun loadBackgroundUtxos( preferences: android.content.SharedPreferences, + addressType: BlockScanner.AddressType, addressIndex: Int ): List { + val newKey = + backgroundUtxoKey( + addressType, + addressIndex + ) + + val legacyKey = + PREF_BACKGROUND_UTXO_PREFIX + addressIndex + val values = - preferences.getStringSet( - PREF_BACKGROUND_UTXO_PREFIX + addressIndex, + if (preferences.contains(newKey)) { + preferences.getStringSet( + newKey, + emptySet() + ) ?: emptySet() + } else if ( + addressType == BlockScanner.AddressType.STANDARD + ) { + preferences.getStringSet( + legacyKey, + emptySet() + ) ?: emptySet() + } else { emptySet() - ) ?: emptySet() + } return values.mapNotNull { value -> try { val parts = value.split("|") - if (parts.size != 6) { + if (parts.size != 6 && parts.size != 7) { return@mapNotNull null } @@ -564,7 +716,13 @@ class WalletMonitoringService : Service() { value = parts[2].toLong(), height = parts[3].toInt(), addressIndex = parts[4].toInt(), - isCoinbase = parts[5].toBooleanStrict() + isCoinbase = parts[5].toBooleanStrict(), + addressType = + if (parts.size == 7) { + BlockScanner.AddressType.valueOf(parts[6]) + } else { + BlockScanner.AddressType.STANDARD + } ) } catch (_: Exception) { null @@ -619,6 +777,129 @@ class WalletMonitoringService : Service() { .toString(Charsets.UTF_8) } + private fun derivePqWitnessScript( + words: List, + addressType: BlockScanner.AddressType, + addressIndex: Int + ): ByteArray { + require( + addressType == BlockScanner.AddressType.MLDSA || + addressType == BlockScanner.AddressType.SLHDSA + ) + require(addressIndex >= 0) + + MnemonicCode.INSTANCE.check(words) + + val bip39Seed = + MnemonicCode.toSeed(words, "") + + val masterKey = + HDKeyDerivation.createMasterPrivateKey( + bip39Seed + ) + + val walletSeed = + masterKey.privKeyBytes + + val domain = + "Vexta-PQ-HD-v1" + .toByteArray(Charsets.US_ASCII) + + val data = + ByteArray(domain.size + 1 + 4) + + System.arraycopy( + domain, + 0, + data, + 0, + domain.size + ) + + var offset = domain.size + + data[offset++] = + when (addressType) { + BlockScanner.AddressType.MLDSA -> 2 + BlockScanner.AddressType.SLHDSA -> 3 + BlockScanner.AddressType.STANDARD -> + error("Standard address is not PQ") + }.toByte() + + data[offset++] = + ((addressIndex ushr 24) and 0xff).toByte() + data[offset++] = + ((addressIndex ushr 16) and 0xff).toByte() + data[offset++] = + ((addressIndex ushr 8) and 0xff).toByte() + data[offset] = + (addressIndex and 0xff).toByte() + + val mac = + javax.crypto.Mac.getInstance( + "HmacSHA512" + ) + + mac.init( + javax.crypto.spec.SecretKeySpec( + walletSeed, + "HmacSHA512" + ) + ) + + val seedMaterial = + mac.doFinal(data) + + val keyPair = + when (addressType) { + BlockScanner.AddressType.MLDSA -> + requireNotNull( + VextaPQ.mldsaKeypairFromSeed( + seedMaterial + ) + ) + + BlockScanner.AddressType.SLHDSA -> + requireNotNull( + VextaPQ.sphincsKeypairFromSeed( + seedMaterial + ) + ) + + BlockScanner.AddressType.STANDARD -> + error("Standard address is not PQ") + } + + require(keyPair.size == 2) + + val publicKey = keyPair[0] + val secretKey = keyPair[1] + + return try { + val keyId = + java.security.MessageDigest + .getInstance("SHA-256") + .digest(publicKey) + + byteArrayOf( + if ( + addressType == + BlockScanner.AddressType.MLDSA + ) { + 0x52.toByte() + } else { + 0x53.toByte() + }, + 0x20 + ) + keyId + } finally { + secretKey.fill(0) + seedMaterial.fill(0) + walletSeed.fill(0) + bip39Seed.fill(0) + } + } + private fun deriveWitnessScript( words: List, addressIndex: Int