├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── net │ └── thiim │ └── dilithium │ ├── impl │ ├── Dilithium.java │ ├── DilithiumPrivateKeyImpl.java │ ├── DilithiumPublicKeyImpl.java │ ├── PackingUtils.java │ ├── Poly.java │ ├── PolyVec.java │ └── Utils.java │ ├── interfaces │ ├── DilithiumKeySpec.java │ ├── DilithiumParameterSpec.java │ ├── DilithiumPrivateKey.java │ ├── DilithiumPrivateKeySpec.java │ ├── DilithiumPublicKey.java │ └── DilithiumPublicKeySpec.java │ └── provider │ ├── DilithiumKeyFactory.java │ ├── DilithiumKeyPairGenerator.java │ ├── DilithiumProvider.java │ └── DilithiumSignature.java └── test └── java └── net └── thiim └── dilithium └── test ├── BasicTests.java ├── KAT.java ├── PerfTest.java ├── Timings.java └── dbrg ├── ModifiedSP80090DRBG.java └── PseudoRNG.java /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /target/ 3 | /.classpath 4 | /.project 5 | /.settings/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 Martin Thiim (martin@thiim.net) 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CRYSTALS - Dilithium 2 | 3 | This is a Java implementation of Dilithium (version 3.1 -- see below), based on the C reference implementation and documentation (see https://github.com/pq-crystals/dilithium). Further, I've wrapped the raw primitives into a JCE provider, making it easy to use via a standardized interface. 4 | 5 | So what is Dilithium? The cryptographic algorithms RSA and ECC have long been known to be vulnerable to attacks using quantum computers via Shor's algorithm. While quantum computers of the prerequisite size do not yet exist in practice, there's an ongoing search for algorithms that don't have this vulnerability. In fact, [NIST](https://www.nist.gov/) has been running a competition for over 6 years in order to identify quantum-safe alternatives. On July 5th NIST [announced](https://www.nist.gov/news-events/news/2022/07/nist-announces-first-four-quantum-resistant-cryptographic-algorithms) the three picks for Post-quantum digital signature schemes. Dilithium was among the three and was in fact recommended as the primary algorithm. Big congratulations to the authors! I wanted to study this new algorithm, and what better way than to try and implement it. This is what you are looking at :-) 6 | 7 | Dilithium is part of the CRYSTALS suite of algorithms and is based on algebraic lattices. Think linear algebra but where the matrix/vector entries are polynomials in the ring $R_q = \mathbb{Z}_q[X]/(X^n+1)$. For much more information (including the specification and C reference implementation I used), see [their page](https://pq-crystals.org/index.shtml). Note, however, that the version described here is slightly different from the later 3.1 reference implementation, referred to in the above. 8 | 9 | Like the reference implementation, this implementation supports all three documented security levels (levels 2, 3 and 5), all using the deterministic signature scheme. It passes all the KAT tests from the package. It supports serialization and deserialization using the documented formats. 10 | 11 | I have a dependency on Bouncy castle, which provides the SHAKE128/256 algorithms used internally in Dilithium. 12 | 13 | *IMPORTANT! This is a "for fun" implementation written in a couple of days. It's not intended to be production-grade code. No warranty or support of any kind is provided. However, it can be useful for diving into and experimenting with post-quantum algorithms. Use it at your own risk. If you don't like those terms, you must refrain from using this software.* 14 | 15 | ## Version 16 | Originally I implemented the version that the Dilithium team submitted to the competition. The team has since made minor changes to some parameters (reducing the size of some, switching to SHAKE256 for some invocations etc.) which are reflected in the current reference implementation. This version is often referred to as 3.1. I've updated my implementation to reflect this. 17 | 18 | At the same time, Dilithium is currently in the process of standardization into what will eventually become FIPS 204 or ML-DSA. This version is different from both the original submission as well as Dilithium 3.1 implemented here (and so also the Dilithium team reference implementation). At the time of writing I don't think it's worth implementing those changes, since many comments have already been submitted so it likely won't be final. Also I'm not aware of other implementations following it. Once the standard seems more stable and the comment period has ended I will definitely add a mode to support this new version (even if still in draft). 19 | 20 | ## Loading the security provider 21 | 22 | ```bash 23 | DilithiumProvider provider = new DilithiumProvider(); 24 | Security.addProvider(provider); 25 | ``` 26 | 27 | If you wish, instead of adding the provider using addProvider(), you can omit this line and explicitly provide the provider-object when calling the .getInstance() methods (see below). 28 | 29 | ## Key pair generation 30 | To generate a key pair you use: 31 | 32 | ```bash 33 | SecureRandom sr = new SecureRandom(); 34 | KeyPairGenerator kpg = KeyPairGenerator.getInstance("Dilithium"); 35 | kpg.initialize(DilithiumParameterSpec.LEVEL2, sr); 36 | KeyPair kp = kpg.generateKeyPair(); 37 | ``` 38 | 39 | Note that you must provide an algorithm parameter spec representing the desired security level - the above example uses level 2, but you can select 3 and 5 as well. The three parameter spec objects are declared as static fields on the DilithiumParameterSpec class. Alternatively, a static method, getSpecForSecurityLevel(), is provided on DilithiumParameterSpec, allowing you to easily retrieve the spec for a given level at runtime. 40 | 41 | ## Signing 42 | Having generated a key pair, signing works just the same as for other JCE providers. The example below signs a byte representation of "Joy!". 43 | 44 | ```bash 45 | Signature sig = Signature.getInstance("Dilithium"); 46 | sig.initSign(kp.getPrivate()); 47 | sig.update("Joy!".getBytes()); 48 | byte[] signature = sig.sign(); 49 | ``` 50 | ## Signature verification 51 | Just as for signing, verification works as for other JCE providers 52 | 53 | ```bash 54 | Signature sig = Signature.getInstance("Dilithium"); 55 | sig.initVerify(kp.getPublic()); 56 | sig.update("Joy!".getBytes()); 57 | boolean b = sig.verify(signature); 58 | ``` 59 | The boolean variable b now contains the outcome of the verification. Note that exceptions may be thrown in case of malformed signatures (as opposed to signatures that are merely incorrect). 60 | 61 | ## Key serialization/deserialization 62 | You can use the .getEncoded() method on the public and private key objects to obtain a byte representation of the key. The formats are compatible with the reference implementation. 63 | In order to instantiate the keys from the byte representation, a key factory is provided. You can use this with the provided DilithiumPublicKeySpec and DilithiumPrivateKeySpec classes. 64 | They are constructed using two parameters, namely the parameter spec (same as used for generating) and the byte representation. Note that the parameter spec is not encoded into the byte representation, and I decided to make the parameter choice explit rather than trying to infer it from length. In the future, I anticipate that ASN.1-based formats with OID's etc. will be standardized, and they will then explcitly encode the parameters. Of course, the serialization format could change as well as the standardization process moves along. 65 | 66 | ```bash 67 | byte[] pubkeyBytes = kp.getPublic().getEncoded(); // This is our bytes to be instantiated 68 | KeyFactory kf = KeyFactory.getInstance("Dilithium"); 69 | PublicKey reconstructedPublicKey = kf.generatePublic(new DilithiumPublicKeySpec(spec, pubkeyBytes)); 70 | ``` 71 | 72 | The private key may be reconstructed in the same fashion, using the DilithiumPrivateKeySpec class. 73 | 74 | ## Low-level use 75 | As an alternative to the low-level interface you can also use the static methods in the Dilithium class directly to generate, sign and verify. See e.g. how the JCE classes do it. 76 | 77 | ## Running the known-answer tests 78 | The official Dilithium package contains a known-answer test generator that generates a request and response file. I've provided a Java utility in KAT.java that can read the 79 | request file generated by the reference implementation, run through the tests and generate a corresponding response file. You can then compare this response file to the one 80 | generated by the official known-answer test generator and verify that they are byte-identical. The KAT.java program is run with parameters: 81 | 82 | ```bash 83 | 84 | ``` 85 | Note that the desired security level (2, 3 or 5) must be provided as the 3rd argument. This must match what is configured in the config.h file in the C implementation when generating the response file used for comparison. 86 | 87 | ## DISCLAIMER 88 | This library is available under the Apache 2.0 license (see LICENSE). Note that the code has not been examined by a third party for potential vulnerabilities and as mentioned was not made to be used for production use. No warranty of any kind is provided. If you don't like those terms, you must refrain from using this software. 89 | 90 | ## References 91 | For more information on the CRYSTALS project, see their [website](https://pq-crystals.org/index.shtml). 92 | 93 | ## Contact 94 | Mail: martin@thiim.net 95 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | net.thiim.dilithium 8 | dilithium-java 9 | 0.0.1-SNAPSHOT 10 | 11 | dilithium-java 12 | jar 13 | 14 | 15 | UTF-8 16 | 1.8 17 | 1.8 18 | 19 | 20 | 21 | 22 | junit 23 | junit 24 | 4.11 25 | test 26 | 27 | 28 | org.bouncycastle 29 | bcprov-jdk18on 30 | 1.71 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | maven-clean-plugin 40 | 3.1.0 41 | 42 | 43 | 44 | maven-resources-plugin 45 | 3.0.2 46 | 47 | 48 | maven-compiler-plugin 49 | 3.8.0 50 | 51 | 52 | maven-surefire-plugin 53 | 2.22.1 54 | 55 | 56 | maven-jar-plugin 57 | 3.0.2 58 | 59 | 60 | maven-install-plugin 61 | 2.5.2 62 | 63 | 64 | maven-deploy-plugin 65 | 2.8.2 66 | 67 | 68 | 69 | maven-site-plugin 70 | 3.7.1 71 | 72 | 73 | maven-project-info-reports-plugin 74 | 3.0.0 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/impl/Dilithium.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.impl; 2 | 3 | import java.security.KeyPair; 4 | 5 | import org.bouncycastle.crypto.digests.SHAKEDigest; 6 | 7 | import net.thiim.dilithium.interfaces.DilithiumParameterSpec; 8 | import net.thiim.dilithium.interfaces.DilithiumPrivateKey; 9 | import net.thiim.dilithium.interfaces.DilithiumPublicKey; 10 | 11 | public class Dilithium { 12 | public final static int N = 256; 13 | public final static int Q = 8380417; 14 | public final static int QINV = 58728449; // q^(-1) mod 2^32 15 | public final static int D = 13; 16 | 17 | public final static int POLYT0_PACKEDBYTES = 416; 18 | public final static int POLYT1_PACKEDBYTES = 320; 19 | public final static int SEEDBYTES = 32; 20 | public final static int CRHBYTES = 32; 21 | public final static int SHAKE128_RATE = 168; 22 | public final static int SHAKE256_RATE = 136; 23 | public final static int STREAM128_BLOCKBYTES = Dilithium.SHAKE128_RATE; 24 | public final static int STREAM256_BLOCKBYTES = SHAKE256_RATE; 25 | public final static int POLY_UNIFORM_GAMMA1_NBLOCKS = ((576 + STREAM256_BLOCKBYTES - 1) / STREAM256_BLOCKBYTES); 26 | public final static int zetas[] = new int[] { 0, 25847, -2608894, -518909, 237124, -777960, -876248, 466468, 27 | 1826347, 2353451, -359251, -2091905, 3119733, -2884855, 3111497, 2680103, 2725464, 1024112, -1079900, 28 | 3585928, -549488, -1119584, 2619752, -2108549, -2118186, -3859737, -1399561, -3277672, 1757237, -19422, 29 | 4010497, 280005, 2706023, 95776, 3077325, 3530437, -1661693, -3592148, -2537516, 3915439, -3861115, 30 | -3043716, 3574422, -2867647, 3539968, -300467, 2348700, -539299, -1699267, -1643818, 3505694, -3821735, 31 | 3507263, -2140649, -1600420, 3699596, 811944, 531354, 954230, 3881043, 3900724, -2556880, 2071892, -2797779, 32 | -3930395, -1528703, -3677745, -3041255, -1452451, 3475950, 2176455, -1585221, -1257611, 1939314, -4083598, 33 | -1000202, -3190144, -3157330, -3632928, 126922, 3412210, -983419, 2147896, 2715295, -2967645, -3693493, 34 | -411027, -2477047, -671102, -1228525, -22981, -1308169, -381987, 1349076, 1852771, -1430430, -3343383, 35 | 264944, 508951, 3097992, 44288, -1100098, 904516, 3958618, -3724342, -8578, 1653064, -3249728, 2389356, 36 | -210977, 759969, -1316856, 189548, -3553272, 3159746, -1851402, -2409325, -177440, 1315589, 1341330, 37 | 1285669, -1584928, -812732, -1439742, -3019102, -3881060, -3628969, 3839961, 2091667, 3407706, 2316500, 38 | 3817976, -3342478, 2244091, -2446433, -3562462, 266997, 2434439, -1235728, 3513181, -3520352, -3759364, 39 | -1197226, -3193378, 900702, 1859098, 909542, 819034, 495491, -1613174, -43260, -522500, -655327, -3122442, 40 | 2031748, 3207046, -3556995, -525098, -768622, -3595838, 342297, 286988, -2437823, 4108315, 3437287, 41 | -3342277, 1735879, 203044, 2842341, 2691481, -2590150, 1265009, 4055324, 1247620, 2486353, 1595974, 42 | -3767016, 1250494, 2635921, -3548272, -2994039, 1869119, 1903435, -1050970, -1333058, 1237275, -3318210, 43 | -1430225, -451100, 1312455, 3306115, -1962642, -1279661, 1917081, -2546312, -1374803, 1500165, 777191, 44 | 2235880, 3406031, -542412, -2831860, -1671176, -1846953, -2584293, -3724270, 594136, -3776993, -2013608, 45 | 2432395, 2454455, -164721, 1957272, 3369112, 185531, -1207385, -3183426, 162844, 1616392, 3014001, 810149, 46 | 1652634, -3694233, -1799107, -3038916, 3523897, 3866901, 269760, 2213111, -975884, 1717735, 472078, -426683, 47 | 1723600, -1803090, 1910376, -1667432, -1104333, -260646, -3833893, -2939036, -2235985, -420899, -2286327, 48 | 183443, -976891, 1612842, -3545687, -554416, 3919660, -48306, -1362209, 3937738, 1400424, -846154, 49 | 1976782 }; 50 | public static final int MUBYTES = 64; 51 | 52 | 53 | public static KeyPair generateKeyPair(DilithiumParameterSpec spec, byte[] seed) { 54 | byte[] zeta = seed; 55 | 56 | byte[] o = Utils.getSHAKE256Digest(2*32+64, zeta); 57 | byte[] rho = new byte[32]; 58 | byte[] rhoprime = new byte[64]; 59 | byte[] K = new byte[32]; 60 | 61 | System.arraycopy(o, 0, rho, 0, 32); 62 | System.arraycopy(o, 32, rhoprime, 0, 64); 63 | System.arraycopy(o, 96, K, 0, 32); 64 | 65 | PolyVec s1 = PolyVec.randomVec(rhoprime, spec.eta, spec.l, 0); 66 | PolyVec s2 = PolyVec.randomVec(rhoprime, spec.eta, spec.k, spec.l); 67 | 68 | // Generate A 69 | PolyVec[] A = expandA(rho, spec.k, spec.l); 70 | 71 | PolyVec s1hat = s1.ntt(); 72 | PolyVec t1 = s1hat.mulMatrixPointwiseMontgomery(A); 73 | t1.reduce(); 74 | t1.invnttTomont(); 75 | 76 | t1 = t1.add(s2); 77 | t1.caddq(); 78 | 79 | PolyVec[] res = t1.powerRound(); 80 | byte[] pubbytes = PackingUtils.packPubKey(rho, res[1]); 81 | 82 | byte[] tr = Utils.crh(pubbytes); 83 | 84 | byte[] prvbytes = PackingUtils.packPrvKey(spec.eta, rho, tr, K, res[0], s1, s2); 85 | 86 | PolyVec s2hat = s2.ntt(); 87 | PolyVec t0hat = res[0].ntt(); 88 | 89 | DilithiumPrivateKeyImpl prv = new DilithiumPrivateKeyImpl(spec, rho, K, tr, s1, s2, res[0], prvbytes, A, s1hat, s2hat, t0hat); 90 | DilithiumPublicKeyImpl pub = new DilithiumPublicKeyImpl(spec, rho, res[1], pubbytes, A); 91 | return new KeyPair(pub, prv); 92 | } 93 | 94 | public static byte[] sign(DilithiumPrivateKey prv, byte[] M) { 95 | DilithiumParameterSpec spec = prv.getSpec(); 96 | 97 | final int CRYPTO_BYTES = Utils.getSigLength(spec); 98 | 99 | byte[] sig = new byte[CRYPTO_BYTES]; 100 | 101 | PolyVec[] A; 102 | if(prv instanceof DilithiumPrivateKeyImpl) { 103 | A = ((DilithiumPrivateKeyImpl)prv).getA(); 104 | } 105 | else { 106 | A = expandA(prv.getRho(), spec.k, spec.l); 107 | } 108 | 109 | 110 | byte[] conc = Utils.concat(prv.getTr(), M); 111 | byte[] mu = Utils.mucrh(conc); 112 | conc = Utils.concat(prv.getK(), mu); 113 | byte[] rhoprime = Utils.mucrh(conc); 114 | 115 | PolyVec s1, s2, t0; 116 | if(prv instanceof DilithiumPrivateKeyImpl) { 117 | A = ((DilithiumPrivateKeyImpl)prv).getA(); 118 | s1 = prv.getS1Hat(); 119 | s2 = prv.getS2Hat(); 120 | t0 = prv.getT0Hat(); 121 | } 122 | else { 123 | s1 = prv.getS1().ntt(); 124 | s2 = prv.getS2().ntt(); 125 | t0 = prv.getT0().ntt(); 126 | } 127 | 128 | 129 | int kappa = 0; 130 | for (;;) { 131 | PolyVec y = PolyVec.randomVecGamma1(rhoprime, spec.l, spec.gamma1, kappa++); 132 | PolyVec z = y.ntt(); 133 | PolyVec w = z.mulMatrixPointwiseMontgomery(A); 134 | w.reduce(); 135 | w.invnttTomont(); 136 | w.caddq(); 137 | PolyVec[] res = w.decompose(spec.gamma2); 138 | PackingUtils.packw1(spec.gamma2, res[1], sig); 139 | 140 | SHAKEDigest s = new SHAKEDigest(256); 141 | s.update(mu, 0, mu.length); 142 | s.update(sig, 0, res[1].length() * PackingUtils.getPolyW1PackedBytes(spec.gamma2)); 143 | s.doOutput(sig, 0, SEEDBYTES); 144 | 145 | Poly cp = generateChallenge(spec.tau, sig); 146 | cp = cp.ntt(); 147 | z = s1.pointwiseMontgomery(cp); 148 | z.invnttTomont(); 149 | z = z.add(y); 150 | z.reduce(); 151 | if (z.chknorm(spec.gamma1 - spec.beta)) { 152 | continue; 153 | } 154 | PolyVec h = s2.pointwiseMontgomery(cp); 155 | h.invnttTomont(); 156 | PolyVec w0 = res[0].sub(h); 157 | w0.reduce(); 158 | if (w0.chknorm(spec.gamma2 - spec.beta)) { 159 | continue; 160 | } 161 | 162 | h = t0.pointwiseMontgomery(cp); 163 | h.invnttTomont(); 164 | h.reduce(); 165 | if (h.chknorm(spec.gamma2)) { 166 | continue; 167 | } 168 | 169 | w0 = w0.add(h); 170 | w0.caddq(); 171 | 172 | Hints hints = makeHints(spec.gamma2, w0, res[1]); 173 | if (hints.cnt > spec.omega) { 174 | continue; 175 | } 176 | 177 | PackingUtils.packSig(spec.gamma1, spec.omega, sig, sig, z, hints.v); 178 | return sig; 179 | } 180 | } 181 | 182 | public static boolean verify(DilithiumPublicKey pk, byte[] sig, byte[] M) { 183 | DilithiumParameterSpec spec = pk.getSpec(); 184 | int CRYPTO_BYTES = Utils.getSigLength(spec); 185 | 186 | if (sig.length != CRYPTO_BYTES) { 187 | throw new RuntimeException("Bad signature"); 188 | } 189 | 190 | PolyVec t1 = pk.getT1(); 191 | 192 | int off = 0; 193 | byte[] c = new byte[SEEDBYTES]; 194 | System.arraycopy(sig, 0, c, 0, SEEDBYTES); 195 | off += SEEDBYTES; 196 | 197 | PolyVec z = new PolyVec(spec.l); 198 | for (int i = 0; i < spec.l; i++) { 199 | z.poly[i] = PackingUtils.zunpack(spec.gamma1, sig, off); 200 | off += PackingUtils.getPolyZPackedBytes(spec.gamma1); 201 | } 202 | 203 | PolyVec h = new PolyVec(spec.k); 204 | int k = 0; 205 | for (int i = 0; i < h.length(); i++) { 206 | h.poly[i] = new Poly(N); 207 | 208 | if ((sig[off + spec.omega + i] & 0xFF) < k || (sig[off + spec.omega + i] & 0xFF) > spec.omega) 209 | throw new RuntimeException("Bad signature"); 210 | 211 | for (int j = k; j < (sig[off + spec.omega + i] & 0xFF); j++) { 212 | /* Coefficients are ordered for strong unforgeability */ 213 | if (j > k && (sig[off + j] & 0xFF) <= (sig[off + j - 1] & 0xFF)) 214 | throw new RuntimeException("Bad signature"); 215 | h.poly[i].coef[sig[off + j] & 0xFF] = 1; 216 | } 217 | 218 | k = (sig[off + spec.omega + i] & 0xFF); 219 | } 220 | 221 | 222 | for (int j = k; j < spec.omega; j++) { 223 | if (sig[off + j] != 0) { 224 | throw new IllegalArgumentException("Invalid signature"); 225 | } 226 | } 227 | 228 | if (z.chknorm(spec.gamma1 - spec.beta)) { 229 | throw new RuntimeException("Bad signature"); 230 | } 231 | 232 | byte[] mu = Utils.crh(pk.getEncoded()); 233 | mu = Utils.getSHAKE256Digest(MUBYTES, mu, M); 234 | 235 | Poly cp = generateChallenge(spec.tau, c); 236 | 237 | PolyVec[] A; 238 | if(pk instanceof DilithiumPublicKeyImpl) { 239 | A = ((DilithiumPublicKeyImpl)pk).getA(); 240 | } 241 | else { 242 | A = expandA(pk.getRho(), spec.k, spec.l); 243 | } 244 | z = z.ntt(); 245 | PolyVec w = z.mulMatrixPointwiseMontgomery(A); 246 | 247 | cp = cp.ntt(); 248 | t1 = t1.shift(); 249 | t1 = t1.ntt(); 250 | 251 | t1 = t1.pointwiseMontgomery(cp); 252 | w = w.sub(t1); 253 | w.reduce(); 254 | w.invnttTomont(); 255 | w.caddq(); 256 | 257 | w = useHint(spec.gamma2, w, h); 258 | 259 | byte[] buf = new byte[PackingUtils.getPolyW1PackedBytes(spec.gamma2) * w.length()]; 260 | PackingUtils.packw1(spec.gamma2, w, buf); 261 | 262 | byte[] c2 = Utils.getSHAKE256Digest(SEEDBYTES, mu, buf); 263 | for (int i = 0; i < SEEDBYTES; i++) { 264 | if (c[i] != c2[i]) { 265 | return false; 266 | } 267 | } 268 | return true; 269 | } 270 | 271 | static PolyVec[] expandA(byte[] rho, int k, int l) { 272 | PolyVec[] A = new PolyVec[k]; 273 | for (int i = 0; i < k; i++) { 274 | A[i] = new PolyVec(l); 275 | for (int j = 0; j < l; j++) { 276 | A[i].poly[j] = Poly.genUniformRandom(rho, (i << 8) + j); 277 | } 278 | } 279 | return A; 280 | } 281 | 282 | private static PolyVec useHint(int gamma2, PolyVec u, PolyVec h) 283 | { 284 | PolyVec res = new PolyVec(u.poly.length); 285 | for (int i = 0; i < res.length(); i++) { 286 | res.poly[i] = useHint(gamma2, u.poly[i], h.poly[i]); 287 | } 288 | return res; 289 | } 290 | 291 | private static Poly useHint(int gamma2, Poly u, Poly h) { 292 | Poly res = new Poly(N); 293 | for (int i = 0; i < N; i++) { 294 | res.coef[i] = useHint(gamma2, u.coef[i], h.coef[i]); 295 | } 296 | return res; 297 | } 298 | 299 | private static int useHint(int gamma2, int a, int hint) { 300 | int a0, a1; 301 | 302 | a1 = (a + 127) >> 7; 303 | if (gamma2 == (Q - 1) / 32) { 304 | a1 = (a1 * 1025 + (1 << 21)) >> 22; 305 | a1 &= 15; 306 | 307 | } else if (gamma2 == (Q - 1) / 88) { 308 | a1 = (a1 * 11275 + (1 << 23)) >> 24; 309 | a1 ^= ((43 - a1) >> 31) & a1; 310 | } else { 311 | throw new RuntimeException("Invalid gamma2: " + gamma2); 312 | } 313 | a0 = a - a1 * 2 * gamma2; 314 | a0 -= (((Q - 1) / 2 - a0) >> 31) & Q; 315 | 316 | if (hint == 0) { 317 | return a1; 318 | } 319 | 320 | if (gamma2 == (Q - 1) / 32) { 321 | if (a0 > 0) 322 | return (a1 + 1) & 15; 323 | else 324 | return (a1 - 1) & 15; 325 | } else if (gamma2 == (Q - 1) / 88) { 326 | if (a0 > 0) 327 | return (a1 == 43) ? 0 : a1 + 1; 328 | else 329 | return (a1 == 0) ? 43 : a1 - 1; 330 | 331 | } else { 332 | throw new RuntimeException("Invalid gamma2: " + gamma2); 333 | } 334 | } 335 | 336 | private static class Hints { 337 | PolyVec v; 338 | int cnt; 339 | } 340 | 341 | private static class Hint { 342 | Poly v; 343 | int cnt; 344 | } 345 | 346 | private static Hints makeHints(int gamma2, PolyVec v0, PolyVec v1) { 347 | Hints hints = new Hints(); 348 | hints.v = new PolyVec(v0.length()); 349 | 350 | for (int i = 0; i < v0.length(); i++) { 351 | Hint hint = polyMakeHint(gamma2, v0.poly[i], v1.poly[i]); 352 | hints.cnt += hint.cnt; 353 | hints.v.poly[i] = hint.v; 354 | } 355 | return hints; 356 | 357 | } 358 | 359 | private static Hint polyMakeHint(int gamma2, Poly a, Poly b) { 360 | Hint hint = new Hint(); 361 | hint.v = new Poly(N); 362 | for (int i = 0; i < N; i++) { 363 | hint.v.coef[i] = makeHint(gamma2, a.coef[i], b.coef[i]); 364 | hint.cnt += hint.v.coef[i]; 365 | } 366 | return hint; 367 | 368 | } 369 | 370 | private static int makeHint(int gamma2, int a0, int a1) { 371 | if (a0 <= gamma2 || a0 > Q - gamma2 || (a0 == Q - gamma2 && a1 == 0)) 372 | return 0; 373 | 374 | return 1; 375 | } 376 | 377 | private static Poly generateChallenge(int tau, byte[] seed) { 378 | Poly pre = new Poly(N); 379 | int b, pos; 380 | long signs; 381 | byte[] buf = new byte[SHAKE256_RATE]; 382 | 383 | SHAKEDigest s = new SHAKEDigest(256); 384 | s.update(seed, 0, SEEDBYTES); 385 | s.doOutput(buf, 0, buf.length); 386 | 387 | signs = 0; 388 | for (int i = 0; i < 8; i++) 389 | signs |= (long) (buf[i] & 0xFF) << 8 * i; 390 | pos = 8; 391 | 392 | for (int i = N - tau; i < N; ++i) { 393 | do { 394 | if (pos >= SHAKE256_RATE) { 395 | s.doOutput(buf, 0, buf.length); 396 | pos = 0; 397 | } 398 | 399 | b = (buf[pos++] & 0xFF); 400 | } while (b > i); 401 | pre.coef[i] = pre.coef[b]; 402 | pre.coef[b] = (int) (1 - 2 * (signs & 1)); 403 | signs >>= 1; 404 | } 405 | return pre; 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/impl/DilithiumPrivateKeyImpl.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.impl; 2 | 3 | import net.thiim.dilithium.interfaces.DilithiumParameterSpec; 4 | import net.thiim.dilithium.interfaces.DilithiumPrivateKey; 5 | 6 | public class DilithiumPrivateKeyImpl implements DilithiumPrivateKey { 7 | private static final long serialVersionUID = 1L; 8 | private final byte[] rho; 9 | private final byte[] tr; 10 | private final byte[] K; 11 | private final PolyVec s1; 12 | private final PolyVec s2; 13 | private final PolyVec t0; 14 | private final PolyVec s1Hat; 15 | private final PolyVec s2Hat; 16 | private final PolyVec t0Hat; 17 | private final DilithiumParameterSpec spec; 18 | private final byte[] prvbytes; 19 | private final PolyVec[] A; 20 | 21 | public DilithiumPrivateKeyImpl(DilithiumParameterSpec spec, byte[] rho, byte[] K, byte[] tr, PolyVec s1, PolyVec s2, PolyVec t0, byte[] prvbytes, 22 | PolyVec[] A, PolyVec s1Hat, PolyVec s2Hat, PolyVec t0Hat) { 23 | this.rho = rho; 24 | this.tr = tr; 25 | this.K = K; 26 | this.s1 = s1; 27 | this.s2 = s2; 28 | this.t0 = t0; 29 | this.spec = spec; 30 | this.prvbytes = prvbytes; 31 | this.A = A; 32 | this.s1Hat = s1Hat; 33 | this.s2Hat = s2Hat; 34 | this.t0Hat = t0Hat; 35 | } 36 | 37 | @Override 38 | public String getAlgorithm() { 39 | return "Dilithium"; 40 | } 41 | 42 | @Override 43 | public String getFormat() { 44 | return "RAW"; 45 | } 46 | 47 | @Override 48 | public byte[] getEncoded() { 49 | return prvbytes; 50 | } 51 | 52 | @Override 53 | public DilithiumParameterSpec getSpec() { 54 | return spec; 55 | } 56 | 57 | @Override 58 | public byte[] getRho() { 59 | return rho; 60 | } 61 | 62 | @Override 63 | public byte[] getTr() { 64 | return tr; 65 | } 66 | 67 | @Override 68 | public byte[] getK() { 69 | return K; 70 | } 71 | 72 | @Override 73 | public PolyVec getS1() { 74 | return s1; 75 | } 76 | 77 | @Override 78 | public PolyVec getS2() { 79 | return s2; 80 | } 81 | 82 | @Override 83 | public PolyVec getT0() { 84 | return t0; 85 | } 86 | 87 | public PolyVec[] getA() { 88 | return A; 89 | } 90 | 91 | @Override 92 | public PolyVec getS1Hat() { 93 | return s1Hat; 94 | } 95 | 96 | @Override 97 | public PolyVec getS2Hat() { 98 | return s2Hat; 99 | } 100 | 101 | @Override 102 | public PolyVec getT0Hat() { 103 | return t0Hat; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/impl/DilithiumPublicKeyImpl.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.impl; 2 | 3 | import net.thiim.dilithium.interfaces.DilithiumParameterSpec; 4 | import net.thiim.dilithium.interfaces.DilithiumPublicKey; 5 | 6 | public class DilithiumPublicKeyImpl implements DilithiumPublicKey { 7 | private static final long serialVersionUID = 1L; 8 | private final byte[] rho; 9 | private final PolyVec t1; 10 | private final PolyVec[] A; 11 | private final DilithiumParameterSpec spec; 12 | private final byte[] pubbytes; 13 | 14 | public DilithiumPublicKeyImpl(DilithiumParameterSpec spec, byte[] rho, PolyVec t1, byte[] pubbytes, PolyVec[] A) { 15 | this.t1 = t1; 16 | this.rho = rho; 17 | this.spec = spec; 18 | this.pubbytes = pubbytes; 19 | this.A = A; 20 | } 21 | 22 | @Override 23 | public String getAlgorithm() { 24 | return "Dilithium"; 25 | } 26 | 27 | @Override 28 | public String getFormat() { 29 | return "RAW"; 30 | } 31 | 32 | @Override 33 | public byte[] getEncoded() { 34 | return pubbytes; 35 | } 36 | 37 | @Override 38 | public DilithiumParameterSpec getSpec() { 39 | return spec; 40 | } 41 | 42 | @Override 43 | public byte[] getRho() { 44 | return rho; 45 | } 46 | 47 | @Override 48 | public PolyVec getT1() { 49 | return t1; 50 | } 51 | 52 | public PolyVec[] getA() { 53 | return A; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/impl/PackingUtils.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.impl; 2 | 3 | import java.security.PrivateKey; 4 | 5 | import net.thiim.dilithium.interfaces.DilithiumParameterSpec; 6 | import net.thiim.dilithium.interfaces.DilithiumPublicKey; 7 | 8 | public class PackingUtils { 9 | 10 | static Poly etaunpack(int eta, byte[] bytes, int off) { 11 | Poly p = new Poly(Dilithium.N); 12 | if(eta == 2) { 13 | for(int i = 0; i < Dilithium.N/8; i++) { 14 | p.coef[8*i+0] = ( (bytes[off+3*i+0] & 0xFF) >> 0) & 7; 15 | p.coef[8*i+1] = ( (bytes[off+3*i+0] & 0xFF) >> 3) & 7; 16 | p.coef[8*i+2] = (( (bytes[off+3*i+0] & 0xFF) >> 6) | ((bytes[off+3*i+1] & 0xFF) << 2)) & 7; 17 | p.coef[8*i+3] = ( (bytes[off+3*i+1] & 0xFF) >> 1) & 7; 18 | p.coef[8*i+4] = ( (bytes[off+3*i+1] & 0xFF) >> 4) & 7; 19 | p.coef[8*i+5] = (( (bytes[off+3*i+1] & 0xFF) >> 7) | ( (bytes[off+3*i+2] & 0xFF) << 1)) & 7; 20 | p.coef[8*i+6] = ( (bytes[off+3*i+2] & 0xFF) >> 2) & 7; 21 | p.coef[8*i+7] = ( (bytes[off+3*i+2] & 0xFF) >> 5) & 7; 22 | 23 | p.coef[8*i+0] = eta - p.coef[8*i+0]; 24 | p.coef[8*i+1] = eta - p.coef[8*i+1]; 25 | p.coef[8*i+2] = eta - p.coef[8*i+2]; 26 | p.coef[8*i+3] = eta - p.coef[8*i+3]; 27 | p.coef[8*i+4] = eta - p.coef[8*i+4]; 28 | p.coef[8*i+5] = eta - p.coef[8*i+5]; 29 | p.coef[8*i+6] = eta - p.coef[8*i+6]; 30 | p.coef[8*i+7] = eta - p.coef[8*i+7]; 31 | } 32 | 33 | } 34 | else if(eta == 4) { 35 | for(int i = 0; i < Dilithium.N/2; i++) { 36 | p.coef[2*i+0] = (bytes[off+i] & 0xFF) & 0x0F; 37 | p.coef[2*i+1] = (bytes[off+i] & 0xFF) >> 4; 38 | p.coef[2*i+0] = eta - p.coef[2*i+0]; 39 | p.coef[2*i+1] = eta - p.coef[2*i+1]; 40 | } 41 | } 42 | else { 43 | throw new IllegalArgumentException("Unknown eta: " + eta); 44 | } 45 | return p; 46 | } 47 | 48 | static int getPolyEtaPackedBytes(int eta) { 49 | if(eta == 2) { 50 | return 96; 51 | } 52 | else if(eta == 4) { 53 | return 128; 54 | } 55 | else { 56 | throw new IllegalArgumentException("Invalid etA: " + eta); 57 | } 58 | } 59 | 60 | static int getPolyW1PackedBytes(int gamma2) { 61 | int POLYW1_PACKEDBYTES = 0; 62 | 63 | if (gamma2 == (Dilithium.Q - 1) / 88) { 64 | POLYW1_PACKEDBYTES = 192; 65 | } else if (gamma2 == (Dilithium.Q - 1) / 32) { 66 | POLYW1_PACKEDBYTES = 128; 67 | } else { 68 | throw new RuntimeException("Error invalid gamma2: " + gamma2); 69 | } 70 | 71 | return POLYW1_PACKEDBYTES; 72 | } 73 | 74 | static int getPolyZPackedBytes(int gamma1) { 75 | if (gamma1 == (1 << 17)) { 76 | return 576; 77 | } else if (gamma1 == (1 << 19)) { 78 | return 640; 79 | } else { 80 | throw new RuntimeException("Invalid gamma1: " + gamma1); 81 | } 82 | 83 | } 84 | 85 | static byte[] packPrvKey(int eta, byte[] rho, byte[] tr, byte[] K, PolyVec t0, PolyVec s1, PolyVec s2) { 86 | 87 | int off = 0; 88 | int POLYETA_PACKEDBYTES; 89 | switch (eta) { 90 | case 2: 91 | POLYETA_PACKEDBYTES = 96; 92 | break; 93 | case 4: 94 | POLYETA_PACKEDBYTES = 128; 95 | break; 96 | default: 97 | throw new RuntimeException("Illegal eta"); 98 | } 99 | 100 | final int CRYPTO_SECRETKEYBYTES = (2 * Dilithium.SEEDBYTES + Dilithium.CRHBYTES + s1.length() * POLYETA_PACKEDBYTES 101 | + s2.length() * POLYETA_PACKEDBYTES + s2.length() * Dilithium.POLYT0_PACKEDBYTES); 102 | byte[] buf = new byte[CRYPTO_SECRETKEYBYTES]; 103 | 104 | for (int i = 0; i < Dilithium.SEEDBYTES; i++) 105 | buf[off + i] = rho[i]; 106 | off += Dilithium.SEEDBYTES; 107 | 108 | for (int i = 0; i < Dilithium.SEEDBYTES; i++) 109 | buf[off + i] = K[i]; 110 | off += Dilithium.SEEDBYTES; 111 | 112 | for (int i = 0; i < Dilithium.CRHBYTES; i++) 113 | buf[off + i] = tr[i]; 114 | off += Dilithium.CRHBYTES; 115 | 116 | for (int i = 0; i < s1.length(); i++) { 117 | s1.poly[i].etapack(eta, buf, off); 118 | off += POLYETA_PACKEDBYTES; 119 | } 120 | 121 | for (int i = 0; i < s2.length(); i++) { 122 | s2.poly[i].etapack(eta, buf, off); 123 | off += POLYETA_PACKEDBYTES; 124 | } 125 | 126 | for (int i = 0; i < t0.length(); i++) { 127 | t0.poly[i].t0pack(buf, off); 128 | off += Dilithium.POLYT0_PACKEDBYTES; 129 | } 130 | return buf; 131 | } 132 | 133 | static byte[] packPubKey(byte[] rho, PolyVec t) { 134 | int CRYPTO_PUBLICKEYBYTES = Dilithium.SEEDBYTES + t.length() * Dilithium.POLYT1_PACKEDBYTES; 135 | 136 | byte[] pk = new byte[CRYPTO_PUBLICKEYBYTES]; 137 | for (int i = 0; i < Dilithium.SEEDBYTES; i++) 138 | pk[i] = rho[i]; 139 | 140 | for (int i = 0; i < t.length(); i++) { 141 | t.poly[i].t1pack(pk, Dilithium.SEEDBYTES + i * Dilithium.POLYT1_PACKEDBYTES); 142 | } 143 | return pk; 144 | } 145 | 146 | static void packSig(int gamma1, int omega, byte[] sig, byte[] c, PolyVec z, PolyVec h) { 147 | 148 | 149 | int POLYZ_PACKEDBYTES = getPolyZPackedBytes(gamma1); 150 | 151 | int off = 0; 152 | for (int i = 0; i < Dilithium.SEEDBYTES; i++) 153 | sig[i] = c[i]; 154 | off += Dilithium.SEEDBYTES; 155 | 156 | for (int i = 0; i < z.length(); i++) { 157 | z.poly[i].zpack(gamma1, sig, off); 158 | off += POLYZ_PACKEDBYTES; 159 | } 160 | 161 | /* Encode h */ 162 | for (int i = 0; i < omega + h.length(); i++) 163 | sig[off + i] = 0; 164 | int k = 0; 165 | for (int i = 0; i < h.length(); i++) { 166 | for (int j = 0; j < Dilithium.N; j++) { 167 | if (h.poly[i].coef[j] != 0) { 168 | sig[off + k++] = (byte) (j); 169 | } 170 | } 171 | 172 | sig[off + omega + i] = (byte) (k); 173 | } 174 | 175 | } 176 | 177 | static void packw1(int gamma2, PolyVec w, byte[] sig) { 178 | int POLYW1_PACKEDBYTES = getPolyW1PackedBytes(gamma2); 179 | int off = 0; 180 | for (int i = 0; i < w.length(); i++) { 181 | w.poly[i].w1pack(gamma2, sig, off); 182 | off += POLYW1_PACKEDBYTES; 183 | } 184 | } 185 | 186 | static Poly t0unpack(byte[] bytes, int off) { 187 | Poly p = new Poly(Dilithium.N); 188 | for(int i = 0; i < Dilithium.N/8; i++) { 189 | p.coef[8*i+0] = (bytes[off+13*i+0] & 0xFF); 190 | p.coef[8*i+0] |= (bytes[off+13*i+1] & 0xFF) << 8; 191 | p.coef[8*i+0] &= 0x1FFF; 192 | 193 | p.coef[8*i+1] = (bytes[off+13*i+1] & 0xFF) >> 5; 194 | p.coef[8*i+1] |= (bytes[off+13*i+2] & 0xFF) << 3; 195 | p.coef[8*i+1] |= (bytes[off+13*i+3] & 0xFF) << 11; 196 | p.coef[8*i+1] &= 0x1FFF; 197 | 198 | p.coef[8*i+2] = (bytes[off+13*i+3] & 0xFF) >> 2; 199 | p.coef[8*i+2] |= (bytes[off+13*i+4] & 0xFF) << 6; 200 | p.coef[8*i+2] &= 0x1FFF; 201 | 202 | p.coef[8*i+3] = (bytes[off+13*i+4] & 0xFF) >> 7; 203 | p.coef[8*i+3] |= (bytes[off+13*i+5] & 0xFF) << 1; 204 | p.coef[8*i+3] |= (bytes[off+13*i+6] & 0xFF) << 9; 205 | p.coef[8*i+3] &= 0x1FFF; 206 | 207 | p.coef[8*i+4] = (bytes[off+13*i+6] & 0xFF) >> 4; 208 | p.coef[8*i+4] |= (bytes[off+13*i+7] & 0xFF) << 4; 209 | p.coef[8*i+4] |= (bytes[off+13*i+8] & 0xFF) << 12; 210 | p.coef[8*i+4] &= 0x1FFF; 211 | 212 | p.coef[8*i+5] = (bytes[off+13*i+8] & 0xFF) >> 1; 213 | p.coef[8*i+5] |= (bytes[off+13*i+9] & 0xFF) << 7; 214 | p.coef[8*i+5] &= 0x1FFF; 215 | 216 | p.coef[8*i+6] = (bytes[off+13*i+9] & 0xFF) >> 6; 217 | p.coef[8*i+6] |= (bytes[off+13*i+10] & 0xFF) << 2; 218 | p.coef[8*i+6] |= (bytes[off+13*i+11] & 0xFF) << 10; 219 | p.coef[8*i+6] &= 0x1FFF; 220 | 221 | p.coef[8*i+7] = (bytes[off+13*i+11] & 0xFF) >> 3; 222 | p.coef[8*i+7] |= (bytes[off+13*i+12] & 0xFF) << 5; 223 | p.coef[8*i+7] &= 0x1FFF; 224 | 225 | p.coef[8*i+0] = (1 << (Dilithium.D-1)) - p.coef[8*i+0]; 226 | p.coef[8*i+1] = (1 << (Dilithium.D-1)) - p.coef[8*i+1]; 227 | p.coef[8*i+2] = (1 << (Dilithium.D-1)) - p.coef[8*i+2]; 228 | p.coef[8*i+3] = (1 << (Dilithium.D-1)) - p.coef[8*i+3]; 229 | p.coef[8*i+4] = (1 << (Dilithium.D-1)) - p.coef[8*i+4]; 230 | p.coef[8*i+5] = (1 << (Dilithium.D-1)) - p.coef[8*i+5]; 231 | p.coef[8*i+6] = (1 << (Dilithium.D-1)) - p.coef[8*i+6]; 232 | p.coef[8*i+7] = (1 << (Dilithium.D-1)) - p.coef[8*i+7]; 233 | } 234 | return p; 235 | } 236 | 237 | static Poly t1unpack(byte[] bytes, int off) { 238 | Poly p = new Poly(Dilithium.N); 239 | for (int i = 0; i < Dilithium.N / 4; i++) { 240 | p.coef[4 * i + 0] = (((bytes[off + 5 * i + 0] & 0xFF) >> 0) | ((bytes[off + 5 * i + 1] & 0xFF) << 8)) & 0x3FF; 241 | p.coef[4 * i + 1] = (((bytes[off + 5 * i + 1] & 0xFF) >> 2) | ((bytes[off + 5 * i + 2] & 0xFF) << 6)) & 0x3FF; 242 | p.coef[4 * i + 2] = (((bytes[off + 5 * i + 2] & 0xFF) >> 4) | ((bytes[off + 5 * i + 3] & 0xFF) << 4)) & 0x3FF; 243 | p.coef[4 * i + 3] = (((bytes[off + 5 * i + 3] & 0xFF) >> 6) | ((bytes[off + 5 * i + 4] & 0xFF) << 2)) & 0x3FF; 244 | } 245 | return p; 246 | } 247 | 248 | public static PrivateKey unpackPrivateKey(DilithiumParameterSpec parameterSpec, byte[] bytes) { 249 | final int POLYETA_PACKEDBYTES = getPolyEtaPackedBytes(parameterSpec.eta); 250 | 251 | int off = 0; 252 | byte[] rho = new byte[Dilithium.SEEDBYTES]; 253 | for(int i = 0; i < Dilithium.SEEDBYTES; i++) { 254 | rho[i] = bytes[i]; 255 | } 256 | off += Dilithium.SEEDBYTES; 257 | 258 | byte[] key = new byte[Dilithium.SEEDBYTES]; 259 | for(int i = 0; i < Dilithium.SEEDBYTES; i++) { 260 | key[i] = bytes[off+i]; 261 | } 262 | off += Dilithium.SEEDBYTES; 263 | 264 | byte[] tr = new byte[Dilithium.CRHBYTES]; 265 | for(int i = 0; i < Dilithium.CRHBYTES; i++) { 266 | tr[i] = bytes[off+i]; 267 | } 268 | off += Dilithium.CRHBYTES; 269 | 270 | PolyVec s1 = new PolyVec(parameterSpec.l); 271 | for(int i=0; i < parameterSpec.l; i++) { 272 | s1.poly[i] = etaunpack(parameterSpec.eta, bytes, off); 273 | off += POLYETA_PACKEDBYTES; 274 | } 275 | 276 | PolyVec s2 = new PolyVec(parameterSpec.k); 277 | for(int i=0; i < parameterSpec.k; i++) { 278 | s2.poly[i] = etaunpack(parameterSpec.eta, bytes, off); 279 | off += POLYETA_PACKEDBYTES; 280 | 281 | } 282 | 283 | PolyVec t0 = new PolyVec(parameterSpec.k); 284 | for(int i=0; i < parameterSpec.k; i++) { 285 | t0.poly[i] = t0unpack(bytes, off); 286 | off += Dilithium.POLYT0_PACKEDBYTES; 287 | } 288 | 289 | // Precompute A, s0, s1 & t0hat 290 | PolyVec[] A = Dilithium.expandA(rho, parameterSpec.k, parameterSpec.l); 291 | PolyVec s1hat = s1.ntt(); 292 | PolyVec s2hat = s2.ntt(); 293 | PolyVec t0hat = t0.ntt(); 294 | 295 | return new DilithiumPrivateKeyImpl(parameterSpec, rho, key, tr, s1, s2, t0, bytes, A, s1hat, s2hat, t0hat); 296 | } 297 | 298 | public static DilithiumPublicKey unpackPublicKey(DilithiumParameterSpec parameterSpec, byte[] bytes) { 299 | int off = 0; 300 | byte[] rho = new byte[Dilithium.SEEDBYTES]; 301 | for (int i = 0; i < Dilithium.SEEDBYTES; i++) { 302 | rho[i] = bytes[i]; 303 | } 304 | off += Dilithium.SEEDBYTES; 305 | 306 | PolyVec p = new PolyVec(parameterSpec.k); 307 | for (int i = 0; i < parameterSpec.k; i++) { 308 | p.poly[i] = t1unpack(bytes, off); 309 | off += Dilithium.POLYT1_PACKEDBYTES; 310 | } 311 | 312 | // Precompute A 313 | PolyVec[] A = Dilithium.expandA(rho, parameterSpec.k, parameterSpec.l); 314 | return new DilithiumPublicKeyImpl(parameterSpec, rho, p, bytes, A); 315 | 316 | } 317 | 318 | static Poly zunpack(int gamma1, byte[] sig, int off) { 319 | Poly pre = new Poly(Dilithium.N); 320 | 321 | if (gamma1 == (1 << 17)) { 322 | for (int i = 0; i < Dilithium.N / 4; i++) { 323 | pre.coef[4 * i + 0] = sig[off + 9 * i + 0] & 0xFF; 324 | pre.coef[4 * i + 0] |= (sig[off + 9 * i + 1] & 0xFF) << 8; 325 | pre.coef[4 * i + 0] |= (sig[off + 9 * i + 2] & 0xFF) << 16; 326 | pre.coef[4 * i + 0] &= 0x3FFFF; 327 | 328 | pre.coef[4 * i + 1] = (sig[off + 9 * i + 2] & 0xFF) >> 2; 329 | pre.coef[4 * i + 1] |= (sig[off + 9 * i + 3] & 0xFF) << 6; 330 | pre.coef[4 * i + 1] |= (sig[off + 9 * i + 4] & 0xFF) << 14; 331 | pre.coef[4 * i + 1] &= 0x3FFFF; 332 | 333 | pre.coef[4 * i + 2] = (sig[off + 9 * i + 4] & 0xFF) >> 4; 334 | pre.coef[4 * i + 2] |= (sig[off + 9 * i + 5] & 0xFF) << 4; 335 | pre.coef[4 * i + 2] |= (sig[off + 9 * i + 6] & 0xFF) << 12; 336 | pre.coef[4 * i + 2] &= 0x3FFFF; 337 | 338 | pre.coef[4 * i + 3] = (sig[off + 9 * i + 6] & 0xFF) >> 6; 339 | pre.coef[4 * i + 3] |= (sig[off + 9 * i + 7] & 0xFF) << 2; 340 | pre.coef[4 * i + 3] |= (sig[off + 9 * i + 8] & 0xFF) << 10; 341 | pre.coef[4 * i + 3] &= 0x3FFFF; 342 | 343 | pre.coef[4 * i + 0] = gamma1 - pre.coef[4 * i + 0]; 344 | pre.coef[4 * i + 1] = gamma1 - pre.coef[4 * i + 1]; 345 | pre.coef[4 * i + 2] = gamma1 - pre.coef[4 * i + 2]; 346 | pre.coef[4 * i + 3] = gamma1 - pre.coef[4 * i + 3]; 347 | } 348 | } else if (gamma1 == (1 << 19)) { 349 | for (int i = 0; i < Dilithium.N / 2; ++i) { 350 | pre.coef[2 * i + 0] = (sig[off + 5 * i + 0] & 0xFF); 351 | pre.coef[2 * i + 0] |= (sig[off + 5 * i + 1] & 0xFF) << 8; 352 | pre.coef[2 * i + 0] |= (sig[off + 5 * i + 2] & 0xFF) << 16; 353 | pre.coef[2 * i + 0] &= 0xFFFFF; 354 | 355 | pre.coef[2 * i + 1] = (sig[off + 5 * i + 2] & 0xFF) >> 4; 356 | pre.coef[2 * i + 1] |= (sig[off + 5 * i + 3] & 0xFF) << 4; 357 | pre.coef[2 * i + 1] |= (sig[off + 5 * i + 4] & 0xFF) << 12; 358 | pre.coef[2 * i + 0] &= 0xFFFFF; 359 | 360 | pre.coef[2 * i + 0] = gamma1 - pre.coef[2 * i + 0]; 361 | pre.coef[2 * i + 1] = gamma1 - pre.coef[2 * i + 1]; 362 | } 363 | } 364 | 365 | return pre; 366 | } 367 | 368 | } 369 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/impl/Poly.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.impl; 2 | 3 | import org.bouncycastle.crypto.digests.SHAKEDigest; 4 | 5 | public class Poly { 6 | int[] coef; 7 | 8 | public Poly(int n) { 9 | this.coef = new int[n]; 10 | } 11 | 12 | public Poly add(Poly other) { 13 | Poly res = new Poly(this.coef.length); 14 | for (int i = 0; i < coef.length; i++) { 15 | res.coef[i] = (coef[i] + other.coef[i]) % Dilithium.Q; 16 | } 17 | return res; 18 | } 19 | 20 | public Poly sub(Poly other) { 21 | Poly res = new Poly(this.coef.length); 22 | for (int i = 0; i < coef.length; i++) { 23 | res.coef[i] = (coef[i] - other.coef[i]) % Dilithium.Q; 24 | } 25 | return res; 26 | 27 | } 28 | 29 | public String toString() { 30 | StringBuffer sb = new StringBuffer(); 31 | sb.append("["); 32 | for (int i = 0; i < coef.length; i++) { 33 | if (i != 0) { 34 | sb.append(", "); 35 | } 36 | sb.append("" + coef[i]); 37 | } 38 | sb.append("]"); 39 | return sb.toString(); 40 | } 41 | 42 | public static Poly genRandom(byte[] rho, int eta, int nonce) { 43 | int POLY_UNIFORM_ETA_NBLOCKS; 44 | if (eta == 2) { 45 | POLY_UNIFORM_ETA_NBLOCKS = ((136 + Dilithium.STREAM256_BLOCKBYTES - 1) / Dilithium.STREAM256_BLOCKBYTES); 46 | } else if (eta == 4) { 47 | POLY_UNIFORM_ETA_NBLOCKS = ((227 + Dilithium.STREAM256_BLOCKBYTES - 1) / Dilithium.STREAM256_BLOCKBYTES); 48 | } else { 49 | throw new IllegalArgumentException("Illegal eta: " + eta); 50 | } 51 | 52 | int ctr; 53 | SHAKEDigest s = new SHAKEDigest(256); 54 | s.update(rho, 0, rho.length); 55 | 56 | byte[] non = new byte[2]; 57 | non[0] = (byte) (nonce & 0xFF); 58 | non[1] = (byte) ((nonce >> 8) & 0xFF); 59 | s.update(non, 0, 2); 60 | 61 | byte[] bb = new byte[POLY_UNIFORM_ETA_NBLOCKS * Dilithium.STREAM256_BLOCKBYTES]; 62 | s.doOutput(bb, 0, bb.length); 63 | 64 | Poly pre = new Poly(Dilithium.N); 65 | ctr = rej_eta(eta, pre.coef, 0, Dilithium.N, bb, bb.length); 66 | 67 | while (ctr < Dilithium.N) { 68 | s.doOutput(bb, 0, Dilithium.STREAM256_BLOCKBYTES); 69 | ctr += rej_eta(eta, pre.coef, ctr, Dilithium.N - ctr, bb, Dilithium.STREAM256_BLOCKBYTES); 70 | 71 | } 72 | return pre; 73 | 74 | } 75 | 76 | private static int rej_eta(int eta, int[] coef, int off, int len, byte[] buf, int buflen) { 77 | int ctr, pos; 78 | int t0, t1; 79 | 80 | ctr = pos = 0; 81 | if (eta == 2) { 82 | while (ctr < len && pos < buflen) { 83 | t0 = buf[pos] & 0x0F; 84 | t1 = (buf[pos++] >> 4) & 0x0F; 85 | if (t0 < 15) { 86 | t0 = t0 - (205 * t0 >>> 10) * 5; 87 | coef[off + ctr++] = 2 - t0; 88 | } 89 | if (t1 < 15 && ctr < len) { 90 | t1 = t1 - (205 * t1 >>> 10) * 5; 91 | coef[off + ctr++] = 2 - t1; 92 | } 93 | } 94 | } else { 95 | while (ctr < len && pos < buflen) { 96 | t0 = buf[pos] & 0x0F; 97 | t1 = (buf[pos++] >> 4) & 0x0F; 98 | if (t0 < 9) 99 | coef[off + ctr++] = 4 - t0; 100 | if (t1 < 9 && ctr < len) 101 | coef[off + ctr++] = 4 - t1; 102 | } 103 | } 104 | 105 | return ctr; 106 | } 107 | 108 | public Poly ntt() { 109 | Poly ret = new Poly(this.coef.length); 110 | for (int i = 0; i < this.coef.length; i++) { 111 | ret.coef[i] = this.coef[i]; 112 | } 113 | int len, start, j, k; 114 | int zeta, t; 115 | 116 | k = 0; 117 | for (len = 128; len > 0; len >>= 1) { 118 | for (start = 0; start < Dilithium.N; start = j + len) { 119 | zeta = Dilithium.zetas[++k]; 120 | for (j = start; j < start + len; ++j) { 121 | t = montgomery_reduce((long) zeta * ret.coef[j + len]); 122 | ret.coef[j + len] = ret.coef[j] - t; 123 | ret.coef[j] = ret.coef[j] + t; 124 | } 125 | } 126 | } 127 | return ret; 128 | } 129 | 130 | static int montgomery_reduce(long a) { 131 | int t; 132 | 133 | t = (int) (a * Dilithium.QINV); 134 | t = (int) (((a - ((long) t) * Dilithium.Q) >> 32) & 0xFFFFFFFF); 135 | return t; 136 | } 137 | 138 | public static Poly genUniformRandom(byte[] rho, int nonce) { 139 | final int POLY_UNIFORM_NBLOCKS = ((768 + Dilithium.STREAM128_BLOCKBYTES - 1) / Dilithium.STREAM128_BLOCKBYTES); 140 | int ctr, off; 141 | int buflen = POLY_UNIFORM_NBLOCKS * Dilithium.STREAM128_BLOCKBYTES; 142 | byte[] buf = new byte[buflen + 2]; 143 | 144 | SHAKEDigest s = new SHAKEDigest(128); 145 | s.update(rho, 0, rho.length); 146 | 147 | byte[] non = new byte[2]; 148 | non[0] = (byte) (nonce & 0xFF); 149 | non[1] = (byte) ((nonce >> 8) & 0xFF); 150 | s.update(non, 0, 2); 151 | s.doOutput(buf, 0, buflen); 152 | 153 | 154 | Poly pre = new Poly(Dilithium.N); 155 | ctr = rej_uniform(pre.coef, 0, Dilithium.N, buf, buflen); 156 | 157 | while (ctr < Dilithium.N) { 158 | off = buflen % 3; 159 | for (int i = 0; i < off; i++) 160 | buf[i] = buf[buflen - off + i]; 161 | s.doOutput(buf, off, Dilithium.STREAM128_BLOCKBYTES); 162 | buflen = Dilithium.STREAM128_BLOCKBYTES + off; 163 | ctr += rej_uniform(pre.coef, ctr, Dilithium.N - ctr, buf, buflen); 164 | 165 | } 166 | return pre; 167 | 168 | } 169 | 170 | private static int rej_uniform(int[] coef, int off, int len, byte[] buf, int buflen) { 171 | int ctr, pos; 172 | int t; 173 | 174 | ctr = pos = 0; 175 | while (ctr < len && pos + 3 <= buflen) { 176 | t = (buf[pos++] & 0xFF); 177 | t |= ((int) buf[pos++] & 0xFF) << 8; 178 | t |= ((int) buf[pos++] & 0xFF) << 16; 179 | t &= 0x7FFFFF; 180 | 181 | if (t < Dilithium.Q) 182 | coef[off + ctr++] = t; 183 | } 184 | return ctr; 185 | } 186 | 187 | public Poly pointwiseMontgomery(Poly other) { 188 | Poly c = new Poly(Dilithium.N); 189 | for (int i = 0; i < Dilithium.N; i++) { 190 | c.coef[i] = montgomery_reduce(((long) (this.coef[i])) * other.coef[i]); 191 | } 192 | return c; 193 | 194 | } 195 | 196 | public void reduce() { 197 | for (int i = 0; i < Dilithium.N; i++) { 198 | coef[i] = reduce32(coef[i]); 199 | } 200 | } 201 | 202 | private static int reduce32(int a) { 203 | int t; 204 | t = (a + (1 << 22)) >> 23; 205 | t = a - t * Dilithium.Q; 206 | return t; 207 | } 208 | 209 | public void invnttTomont() { 210 | int start, len, j, k; 211 | int t, zeta; 212 | final int f = 41978; // mont^2/256 213 | 214 | k = 256; 215 | for (len = 1; len < Dilithium.N; len <<= 1) { 216 | for (start = 0; start < Dilithium.N; start = j + len) { 217 | zeta = -Dilithium.zetas[--k]; 218 | for (j = start; j < start + len; ++j) { 219 | t = coef[j]; 220 | coef[j] = t + coef[j + len]; 221 | coef[j + len] = t - coef[j + len]; 222 | coef[j + len] = montgomery_reduce(((long) zeta) * coef[j + len]); 223 | } 224 | } 225 | } 226 | 227 | for (j = 0; j < Dilithium.N; ++j) { 228 | coef[j] = montgomery_reduce(((long) f) * coef[j]); 229 | } 230 | } 231 | 232 | public void caddq() { 233 | for (int i = 0; i < coef.length; i++) { 234 | coef[i] = caddq(coef[i]); 235 | } 236 | 237 | } 238 | 239 | private int caddq(int a) { 240 | a += (a >> 31) & Dilithium.Q; 241 | return a; 242 | } 243 | 244 | public Poly[] powerRound() { 245 | Poly[] pr = new Poly[2]; 246 | pr[0] = new Poly(Dilithium.N); 247 | pr[1] = new Poly(Dilithium.N); 248 | 249 | for (int i = 0; i < this.coef.length; i++) { 250 | int a = this.coef[i]; 251 | pr[1].coef[i] = (a + (1 << (Dilithium.D - 1)) - 1) >> Dilithium.D; 252 | pr[0].coef[i] = a - (pr[1].coef[i] << Dilithium.D); 253 | } 254 | return pr; 255 | } 256 | 257 | public void t1pack(byte[] r, int off) { 258 | for (int i = 0; i < Dilithium.N / 4; i++) { 259 | r[5 * i + 0 + off] = (byte) ((coef[4 * i + 0] >>> 0)); 260 | r[5 * i + 1 + off] = (byte) ((coef[4 * i + 0] >>> 8) | (coef[4 * i + 1] << 2)); 261 | r[5 * i + 2 + off] = (byte) ((coef[4 * i + 1] >>> 6) | (coef[4 * i + 2] << 4)); 262 | r[5 * i + 3 + off] = (byte) ((coef[4 * i + 2] >>> 4) | (coef[4 * i + 3] << 6)); 263 | r[5 * i + 4 + off] = (byte) ((coef[4 * i + 3] >>> 2)); 264 | } 265 | 266 | } 267 | 268 | public void etapack(int eta, byte[] buf, int off) { 269 | byte[] t = new byte[8]; 270 | if (eta == 2) { 271 | for (int i = 0; i < Dilithium.N / 8; i++) { 272 | t[0] = (byte) (eta - this.coef[8 * i + 0]); 273 | t[1] = (byte) (eta - this.coef[8 * i + 1]); 274 | t[2] = (byte) (eta - this.coef[8 * i + 2]); 275 | t[3] = (byte) (eta - this.coef[8 * i + 3]); 276 | t[4] = (byte) (eta - this.coef[8 * i + 4]); 277 | t[5] = (byte) (eta - this.coef[8 * i + 5]); 278 | t[6] = (byte) (eta - this.coef[8 * i + 6]); 279 | t[7] = (byte) (eta - this.coef[8 * i + 7]); 280 | 281 | buf[off + 3 * i + 0] = (byte) ((t[0] >> 0) | (t[1] << 3) | (t[2] << 6)); 282 | buf[off + 3 * i + 1] = (byte) ((t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7)); 283 | buf[off + 3 * i + 2] = (byte) ((t[5] >> 1) | (t[6] << 2) | (t[7] << 5)); 284 | } 285 | 286 | } else if (eta == 4) { 287 | for (int i = 0; i < Dilithium.N / 2; i++) { 288 | t[0] = (byte) (eta - this.coef[2 * i + 0]); 289 | t[1] = (byte) (eta - this.coef[2 * i + 1]); 290 | buf[off + i] = (byte) (t[0] | (t[1] << 4)); 291 | } 292 | } else { 293 | throw new IllegalArgumentException("Illegal eta: " + eta); 294 | } 295 | } 296 | 297 | public void t0pack(byte[] buf, int off) { 298 | int[] t = new int[8]; 299 | 300 | for (int i = 0; i < Dilithium.N / 8; i++) { 301 | t[0] = (1 << (Dilithium.D - 1)) - this.coef[8 * i + 0]; 302 | t[1] = (1 << (Dilithium.D - 1)) - this.coef[8 * i + 1]; 303 | t[2] = (1 << (Dilithium.D - 1)) - this.coef[8 * i + 2]; 304 | t[3] = (1 << (Dilithium.D - 1)) - this.coef[8 * i + 3]; 305 | t[4] = (1 << (Dilithium.D - 1)) - this.coef[8 * i + 4]; 306 | t[5] = (1 << (Dilithium.D - 1)) - this.coef[8 * i + 5]; 307 | t[6] = (1 << (Dilithium.D - 1)) - this.coef[8 * i + 6]; 308 | t[7] = (1 << (Dilithium.D - 1)) - this.coef[8 * i + 7]; 309 | 310 | buf[off + 13 * i + 0] = (byte) (t[0]); 311 | buf[off + 13 * i + 1] = (byte) (t[0] >> 8); 312 | buf[off + 13 * i + 1] |= (byte) (t[1] << 5); 313 | buf[off + 13 * i + 2] = (byte) (t[1] >> 3); 314 | buf[off + 13 * i + 3] = (byte) (t[1] >> 11); 315 | buf[off + 13 * i + 3] |= (byte) (t[2] << 2); 316 | buf[off + 13 * i + 4] = (byte) (t[2] >> 6); 317 | buf[off + 13 * i + 4] |= (byte) (t[3] << 7); 318 | buf[off + 13 * i + 5] = (byte) (t[3] >> 1); 319 | buf[off + 13 * i + 6] = (byte) (t[3] >> 9); 320 | buf[off + 13 * i + 6] |= (byte) (t[4] << 4); 321 | buf[off + 13 * i + 7] = (byte) (t[4] >> 4); 322 | buf[off + 13 * i + 8] = (byte) (t[4] >> 12); 323 | buf[off + 13 * i + 8] |= (byte) (t[5] << 1); 324 | buf[off + 13 * i + 9] = (byte) (t[5] >> 7); 325 | buf[off + 13 * i + 9] |= (byte) (t[6] << 6); 326 | buf[off + 13 * i + 10] = (byte) (t[6] >> 2); 327 | buf[off + 13 * i + 11] = (byte) (t[6] >> 10); 328 | buf[off + 13 * i + 11] |= (byte) (t[7] << 3); 329 | buf[off + 13 * i + 12] = (byte) (t[7] >> 5); 330 | } 331 | 332 | } 333 | 334 | public static Poly genRandomGamma1(byte[] seed, int nonce, int N, int gamma1) { 335 | Poly pre = new Poly(N); 336 | byte[] buf = new byte[Dilithium.POLY_UNIFORM_GAMMA1_NBLOCKS * Dilithium.STREAM256_BLOCKBYTES]; 337 | SHAKEDigest s = new SHAKEDigest(256); 338 | s.update(seed, 0, seed.length); 339 | 340 | byte[] non = new byte[2]; 341 | non[0] = (byte) (nonce & 0xFF); 342 | non[1] = (byte) ((nonce >> 8) & 0xFF); 343 | s.update(non, 0, 2); 344 | s.doOutput(buf, 0, buf.length); 345 | 346 | if (gamma1 == (1 << 17)) { 347 | for (int i = 0; i < N / 4; i++) { 348 | pre.coef[4 * i + 0] = (buf[9 * i + 0] & 0xFF); 349 | pre.coef[4 * i + 0] |= (int) (buf[9 * i + 1] & 0xFF) << 8; 350 | pre.coef[4 * i + 0] |= (int) (buf[9 * i + 2] & 0xFF) << 16; 351 | pre.coef[4 * i + 0] &= 0x3FFFF; 352 | 353 | pre.coef[4 * i + 1] = (buf[9 * i + 2] & 0xFF) >> 2; 354 | pre.coef[4 * i + 1] |= (int) (buf[9 * i + 3] & 0xFF) << 6; 355 | pre.coef[4 * i + 1] |= (int) (buf[9 * i + 4] & 0xFF) << 14; 356 | pre.coef[4 * i + 1] &= 0x3FFFF; 357 | 358 | pre.coef[4 * i + 2] = (buf[9 * i + 4] & 0xFF) >> 4; 359 | pre.coef[4 * i + 2] |= (int) (buf[9 * i + 5] & 0xFF) << 4; 360 | pre.coef[4 * i + 2] |= (int) (buf[9 * i + 6] & 0xFF) << 12; 361 | pre.coef[4 * i + 2] &= 0x3FFFF; 362 | 363 | pre.coef[4 * i + 3] = (buf[9 * i + 6] & 0xFF) >> 6; 364 | pre.coef[4 * i + 3] |= (int) (buf[9 * i + 7] & 0xFF) << 2; 365 | pre.coef[4 * i + 3] |= (int) (buf[9 * i + 8] & 0xFF) << 10; 366 | pre.coef[4 * i + 3] &= 0x3FFFF; 367 | 368 | pre.coef[4 * i + 0] = gamma1 - pre.coef[4 * i + 0]; 369 | pre.coef[4 * i + 1] = gamma1 - pre.coef[4 * i + 1]; 370 | pre.coef[4 * i + 2] = gamma1 - pre.coef[4 * i + 2]; 371 | pre.coef[4 * i + 3] = gamma1 - pre.coef[4 * i + 3]; 372 | } 373 | 374 | } else if (gamma1 == (1 << 19)) { 375 | for (int i = 0; i < N / 2; i++) { 376 | pre.coef[2 * i + 0] = buf[5 * i + 0] & 0xFF; 377 | pre.coef[2 * i + 0] |= (buf[5 * i + 1] & 0xFF) << 8; 378 | pre.coef[2 * i + 0] |= (buf[5 * i + 2] & 0xFF) << 16; 379 | pre.coef[2 * i + 0] &= 0xFFFFF; 380 | 381 | pre.coef[2 * i + 1] = (buf[5 * i + 2] & 0xFF) >> 4; 382 | pre.coef[2 * i + 1] |= (buf[5 * i + 3] & 0xFF) << 4; 383 | pre.coef[2 * i + 1] |= (buf[5 * i + 4] & 0xFF) << 12; 384 | pre.coef[2 * i + 0] &= 0xFFFFF; 385 | 386 | pre.coef[2 * i + 0] = gamma1 - pre.coef[2 * i + 0]; 387 | pre.coef[2 * i + 1] = gamma1 - pre.coef[2 * i + 1]; 388 | } 389 | 390 | } else { 391 | throw new IllegalArgumentException("Invalid gamma1: " + gamma1); 392 | } 393 | return pre; 394 | } 395 | 396 | public Poly[] decompose(final int gamma2) { 397 | Poly[] pr = new Poly[2]; 398 | pr[0] = new Poly(Dilithium.N); 399 | pr[1] = new Poly(Dilithium.N); 400 | 401 | for (int i = 0; i < this.coef.length; i++) { 402 | int a = this.coef[i]; 403 | 404 | int a1 = (a + 127) >> 7; 405 | if (gamma2 == (Dilithium.Q - 1) / 32) { 406 | a1 = (a1 * 1025 + (1 << 21)) >> 22; 407 | a1 &= 15; 408 | 409 | } else if (gamma2 == (Dilithium.Q - 1) / 88) { 410 | a1 = (a1 * 11275 + (1 << 23)) >> 24; 411 | a1 ^= ((43 - a1) >> 31) & a1; 412 | } else { 413 | throw new IllegalArgumentException("Invalid gamma2: " + gamma2); 414 | } 415 | pr[0].coef[i] = a - a1 * 2 * gamma2; 416 | pr[0].coef[i] -= (((Dilithium.Q - 1) / 2 - pr[0].coef[i]) >> 31) & Dilithium.Q; 417 | pr[1].coef[i] = a1; 418 | } 419 | return pr; 420 | } 421 | 422 | public void w1pack(int gamma2, byte[] buf, int off) { 423 | if (gamma2 == (Dilithium.Q - 1) / 88) { 424 | for (int i = 0; i < Dilithium.N / 4; i++) { 425 | buf[off + 3 * i + 0] = (byte) this.coef[4 * i + 0]; 426 | buf[off + 3 * i + 0] |= (byte) (this.coef[4 * i + 1] << 6); 427 | buf[off + 3 * i + 1] = (byte) (this.coef[4 * i + 1] >> 2); 428 | buf[off + 3 * i + 1] |= (byte) (this.coef[4 * i + 2] << 4); 429 | buf[off + 3 * i + 2] = (byte) (this.coef[4 * i + 2] >> 4); 430 | buf[off + 3 * i + 2] |= (byte) (this.coef[4 * i + 3] << 2); 431 | } 432 | 433 | } else if (gamma2 == (Dilithium.Q - 1) / 32) { 434 | for (int i = 0; i < Dilithium.N / 2; i++) 435 | buf[off + i] = (byte) (this.coef[2 * i + 0] | (this.coef[2 * i + 1] << 4)); 436 | } else { 437 | throw new IllegalArgumentException("Invalid gamma2: " + gamma2); 438 | 439 | } 440 | } 441 | 442 | public boolean chknorm(int B) { 443 | int t; 444 | 445 | if (B > (Dilithium.Q - 1) / 8) 446 | return true; 447 | 448 | /* 449 | * It is ok to leak which coefficient violates the bound since the probability 450 | * for each coefficient is independent of secret data but we must not leak the 451 | * sign of the centralized representative. 452 | */ 453 | for (int i = 0; i < Dilithium.N; i++) { 454 | /* Absolute value */ 455 | t = coef[i] >> 31; 456 | t = coef[i] - (t & 2 * coef[i]); 457 | 458 | if (t >= B) { 459 | return true; 460 | } 461 | } 462 | 463 | return false; 464 | 465 | } 466 | 467 | public void zpack(int gamma1, byte[] sign, int off) { 468 | long[] t = new long[4]; 469 | 470 | if (gamma1 == (1 << 17)) { 471 | for (int i = 0; i < Dilithium.N / 4; i++) { 472 | t[0] = (gamma1 - this.coef[4 * i + 0]) & 0xFFFFFFFFL; 473 | t[1] = (gamma1 - this.coef[4 * i + 1]) & 0xFFFFFFFFL; 474 | t[2] = (gamma1 - this.coef[4 * i + 2]) & 0xFFFFFFFFL; 475 | t[3] = (gamma1 - this.coef[4 * i + 3]) & 0xFFFFFFFFL; 476 | 477 | sign[off + 9 * i + 0] = (byte) t[0]; 478 | sign[off + 9 * i + 1] = (byte) (t[0] >> 8); 479 | sign[off + 9 * i + 2] = (byte) (t[0] >> 16); 480 | sign[off + 9 * i + 2] |= (byte) (t[1] << 2); 481 | sign[off + 9 * i + 3] = (byte) (t[1] >> 6); 482 | sign[off + 9 * i + 4] = (byte) (t[1] >> 14); 483 | sign[off + 9 * i + 4] |= (byte) (t[2] << 4); 484 | sign[off + 9 * i + 5] = (byte) (t[2] >> 4); 485 | sign[off + 9 * i + 6] = (byte) (t[2] >> 12); 486 | sign[off + 9 * i + 6] |= (byte) (t[3] << 6); 487 | sign[off + 9 * i + 7] = (byte) (t[3] >> 2); 488 | sign[off + 9 * i + 8] = (byte) (t[3] >> 10); 489 | } 490 | 491 | } else if (gamma1 == (1 << 19)) { 492 | for (int i = 0; i < Dilithium.N / 2; i++) { 493 | t[0] = gamma1 - this.coef[2 * i + 0]; 494 | t[1] = gamma1 - this.coef[2 * i + 1]; 495 | 496 | sign[off + 5 * i + 0] = (byte) (t[0]); 497 | sign[off + 5 * i + 1] = (byte) (t[0] >> 8); 498 | sign[off + 5 * i + 2] = (byte) (t[0] >> 16); 499 | sign[off + 5 * i + 2] |= (byte) (t[1] << 4); 500 | sign[off + 5 * i + 3] = (byte) (t[1] >> 4); 501 | sign[off + 5 * i + 4] = (byte) (t[1] >> 12); 502 | } 503 | 504 | } else { 505 | throw new IllegalArgumentException("Invalid gamma1: " + gamma1); 506 | } 507 | } 508 | 509 | public Poly shiftl() { 510 | Poly pr = new Poly(Dilithium.N); 511 | for (int i = 0; i < Dilithium.N; i++) 512 | pr.coef[i] = (this.coef[i] << Dilithium.D); 513 | return pr; 514 | } 515 | } 516 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/impl/PolyVec.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.impl; 2 | 3 | import java.util.Arrays; 4 | import java.util.stream.IntStream; 5 | 6 | public class PolyVec { 7 | Poly[] poly; 8 | public PolyVec(int sz) 9 | { 10 | this.poly = new Poly[sz]; 11 | } 12 | 13 | private PolyVec() 14 | { 15 | } 16 | 17 | public static PolyVec randomVec(byte[] rho, int eta, int length, int nonce) { 18 | PolyVec pv = new PolyVec(length); 19 | for (int i = 0; i < length; i++) { 20 | pv.poly[i] = Poly.genRandom(rho, eta, nonce++); 21 | } 22 | return pv; 23 | } 24 | 25 | public static PolyVec randomVecGamma1(byte[] seed, int length, int gamma1, int nonce) { 26 | PolyVec z = new PolyVec(length); 27 | for (int i = 0; i < length; i++) { 28 | z.poly[i] = Poly.genRandomGamma1(seed, length * nonce + i, Dilithium.N, gamma1); 29 | } 30 | return z; 31 | } 32 | 33 | 34 | public PolyVec ntt() { 35 | PolyVec z = new PolyVec(); 36 | z.poly = Arrays.stream(poly).map(Poly::ntt).toArray(Poly[]::new); 37 | return z; 38 | } 39 | 40 | public void reduce() { 41 | Arrays.stream(poly).forEach(Poly::reduce); 42 | } 43 | 44 | public PolyVec[] decompose(final int gamma2) { 45 | PolyVec[] res = new PolyVec[2]; 46 | res[0] = new PolyVec(length()); 47 | res[1] = new PolyVec(length()); 48 | for (int i = 0; i < length(); i++) { 49 | Poly[] r = poly[i].decompose(gamma2); 50 | res[0].poly[i] = r[0]; 51 | res[1].poly[i] = r[1]; 52 | } 53 | return res; 54 | } 55 | 56 | 57 | public void invnttTomont() { 58 | Arrays.stream(poly).forEach(Poly::invnttTomont); 59 | } 60 | 61 | public PolyVec add(PolyVec other) { 62 | PolyVec res = new PolyVec(); 63 | res.poly = IntStream.range(0, poly.length) 64 | .mapToObj(i -> poly[i].add(other.poly[i])) 65 | .toArray(Poly[]::new); 66 | return res; 67 | } 68 | 69 | public PolyVec sub(PolyVec other) { 70 | PolyVec res = new PolyVec(); 71 | res.poly = IntStream.range(0, poly.length) 72 | .mapToObj(i -> poly[i].sub(other.poly[i])) 73 | .toArray(Poly[]::new); 74 | return res; 75 | } 76 | 77 | 78 | public void caddq() { 79 | Arrays.stream(poly).forEach(Poly::caddq); 80 | } 81 | 82 | public PolyVec shift() { 83 | PolyVec res = new PolyVec(); 84 | res.poly = Arrays.stream(poly).map(Poly::shiftl).toArray(Poly[]::new); 85 | return res; 86 | } 87 | 88 | public PolyVec[] powerRound() { 89 | PolyVec[] res = new PolyVec[2]; 90 | res[0] = new PolyVec(length()); 91 | res[1] = new PolyVec(length()); 92 | for (int i = 0; i < poly.length; i++) { 93 | Poly[] r = poly[i].powerRound(); 94 | res[0].poly[i] = r[0]; 95 | res[1].poly[i] = r[1]; 96 | } 97 | return res; 98 | } 99 | 100 | public PolyVec pointwiseMontgomery(Poly u) { 101 | PolyVec r = new PolyVec(); 102 | r.poly = Arrays.stream(poly).map(x -> u.pointwiseMontgomery(x)).toArray(Poly[]::new); 103 | return r; 104 | } 105 | 106 | public PolyVec mulMatrixPointwiseMontgomery(PolyVec[] M) { 107 | PolyVec pv = new PolyVec(M.length); 108 | for (int i = 0; i < M.length; i++) { 109 | pv.poly[i] = pointwiseAccMontgomery(M[i], this); 110 | } 111 | return pv; 112 | } 113 | 114 | private Poly pointwiseAccMontgomery(PolyVec u, PolyVec v) { 115 | Poly w = u.poly[0].pointwiseMontgomery(v.poly[0]); 116 | for (int i = 1; i < v.length(); i++) { 117 | Poly t = u.poly[i].pointwiseMontgomery(v.poly[i]); 118 | w = w.add(t); 119 | } 120 | return w; 121 | } 122 | 123 | public int length() { 124 | return poly.length; 125 | } 126 | 127 | public boolean chknorm(int bound) { 128 | for(Poly p : poly) { 129 | if (p.chknorm(bound)) { 130 | return true; 131 | } 132 | } 133 | return false; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/impl/Utils.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.impl; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | 6 | import org.bouncycastle.crypto.digests.SHAKEDigest; 7 | 8 | import net.thiim.dilithium.interfaces.DilithiumParameterSpec; 9 | 10 | public class Utils { 11 | public static void clear(byte[] x) 12 | { 13 | for(int i = 0; i < x.length; i++) { 14 | x[i] = 0; 15 | } 16 | } 17 | 18 | public static byte[] concat(byte[]... arr) { 19 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 20 | for (byte[] x : arr) { 21 | try { 22 | baos.write(x); 23 | } catch (IOException e) { 24 | throw new RuntimeException("Unexpected error"); 25 | } 26 | } 27 | return baos.toByteArray(); 28 | } 29 | 30 | public static byte[] getSHAKE256Digest(int sz, byte[]... arr) { 31 | byte[] c = concat(arr); 32 | SHAKEDigest s = new SHAKEDigest(256); 33 | s.update(c, 0, c.length); 34 | byte[] o = new byte[sz]; 35 | s.doOutput(o, 0, o.length); 36 | return o; 37 | } 38 | 39 | static byte[] crh(byte[] p) { 40 | return getSHAKE256Digest(Dilithium.CRHBYTES, p); 41 | } 42 | 43 | static byte[] mucrh(byte[] p) { 44 | return getSHAKE256Digest(Dilithium.MUBYTES, p); 45 | } 46 | 47 | public static int getSigLength(DilithiumParameterSpec spec) { 48 | return (Dilithium.SEEDBYTES + spec.l*PackingUtils.getPolyZPackedBytes(spec.gamma1) + spec.omega + spec.k); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/interfaces/DilithiumKeySpec.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.interfaces; 2 | 3 | import java.security.spec.KeySpec; 4 | 5 | public class DilithiumKeySpec implements KeySpec { 6 | private byte[] bytes; 7 | private DilithiumParameterSpec paramSpec; 8 | public DilithiumKeySpec(DilithiumParameterSpec paramSpec, byte[] bytes) 9 | { 10 | this.bytes = bytes; 11 | this.paramSpec = paramSpec; 12 | } 13 | 14 | public byte[] getBytes() 15 | { 16 | return this.bytes; 17 | } 18 | 19 | public DilithiumParameterSpec getParameterSpec() { 20 | return paramSpec; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/interfaces/DilithiumParameterSpec.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.interfaces; 2 | 3 | import java.security.spec.AlgorithmParameterSpec; 4 | 5 | import net.thiim.dilithium.impl.Dilithium; 6 | 7 | public class DilithiumParameterSpec implements AlgorithmParameterSpec { 8 | public final int k; 9 | public final int l; 10 | public final int gamma1; 11 | public final int gamma2; 12 | public final int tau; 13 | public final int d; 14 | public final int chalentropy; 15 | public final int eta; 16 | public final int beta; 17 | public final int omega; 18 | public final String name; 19 | private DilithiumParameterSpec(String name, int k, int l, int gamma1, int gamma2, int tau, int d, int chalentropy, int eta, int beta, int omega) 20 | { 21 | this.name = name; 22 | this.k = k; 23 | this.l = l; 24 | this.gamma1 = gamma1; 25 | this.gamma2 = gamma2; 26 | this.tau = tau; 27 | this.d = d; 28 | this.chalentropy = chalentropy; 29 | this.eta = eta; 30 | this.beta = beta; 31 | this.omega = omega; 32 | } 33 | 34 | public final static DilithiumParameterSpec LEVEL2 = new DilithiumParameterSpec("Dilithium level 2 parameters", 4, 4, 1 << 17, (Dilithium.Q-1)/88, 39, 13, 192, 2, 78, 80); 35 | public final static DilithiumParameterSpec LEVEL3 = new DilithiumParameterSpec("Dilithium level 3 parameters", 6, 5, 1 << 19, (Dilithium.Q-1)/32, 49, 13, 225, 4, 196, 55); 36 | public final static DilithiumParameterSpec LEVEL5 = new DilithiumParameterSpec("Dilithium level 5 parameters", 8, 7, 1 << 19, (Dilithium.Q-1)/32, 60, 13, 257, 2, 120, 75); 37 | 38 | public static DilithiumParameterSpec getSpecForSecurityLevel(int level) 39 | { 40 | switch(level) { 41 | case 2: 42 | return LEVEL2; 43 | case 3: 44 | return LEVEL3; 45 | case 5: 46 | return LEVEL5; 47 | 48 | default: 49 | throw new UnsupportedOperationException("Unsupported level: " + level); 50 | 51 | } 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return name; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/interfaces/DilithiumPrivateKey.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.interfaces; 2 | 3 | import java.security.PrivateKey; 4 | 5 | import net.thiim.dilithium.impl.Poly; 6 | import net.thiim.dilithium.impl.PolyVec; 7 | 8 | public interface DilithiumPrivateKey extends PrivateKey { 9 | public DilithiumParameterSpec getSpec(); 10 | public byte[] getRho(); 11 | public byte[] getTr(); 12 | public byte[] getK(); 13 | public PolyVec getS1(); 14 | public PolyVec getS2(); 15 | public PolyVec getT0(); 16 | public PolyVec getS1Hat(); 17 | public PolyVec getS2Hat(); 18 | public PolyVec getT0Hat(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/interfaces/DilithiumPrivateKeySpec.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.interfaces; 2 | 3 | public class DilithiumPrivateKeySpec extends DilithiumKeySpec { 4 | public DilithiumPrivateKeySpec(DilithiumParameterSpec paramSpec, byte[] bytes) { 5 | super(paramSpec, bytes); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/interfaces/DilithiumPublicKey.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.interfaces; 2 | 3 | import java.security.PublicKey; 4 | 5 | import net.thiim.dilithium.impl.Poly; 6 | import net.thiim.dilithium.impl.PolyVec; 7 | 8 | public interface DilithiumPublicKey extends PublicKey { 9 | public byte[] getRho(); 10 | public PolyVec getT1(); 11 | public DilithiumParameterSpec getSpec(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/interfaces/DilithiumPublicKeySpec.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.interfaces; 2 | 3 | public class DilithiumPublicKeySpec extends DilithiumKeySpec { 4 | public DilithiumPublicKeySpec(DilithiumParameterSpec paramSpec, byte[] data) { 5 | super(paramSpec, data); 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/provider/DilithiumKeyFactory.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.provider; 2 | 3 | import java.security.InvalidKeyException; 4 | import java.security.Key; 5 | import java.security.KeyFactorySpi; 6 | import java.security.PrivateKey; 7 | import java.security.PublicKey; 8 | import java.security.spec.InvalidKeySpecException; 9 | import java.security.spec.KeySpec; 10 | 11 | import net.thiim.dilithium.impl.PackingUtils; 12 | import net.thiim.dilithium.interfaces.DilithiumPrivateKeySpec; 13 | import net.thiim.dilithium.interfaces.DilithiumPublicKeySpec; 14 | 15 | public class DilithiumKeyFactory extends KeyFactorySpi { 16 | @Override 17 | protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException { 18 | if (!(keySpec instanceof DilithiumPublicKeySpec)) { 19 | throw new IllegalArgumentException("Invalid key spec"); 20 | } 21 | DilithiumPublicKeySpec pubspec = (DilithiumPublicKeySpec) keySpec; 22 | return PackingUtils.unpackPublicKey(pubspec.getParameterSpec(), pubspec.getBytes()); 23 | } 24 | 25 | @Override 26 | protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException { 27 | if (!(keySpec instanceof DilithiumPrivateKeySpec)) { 28 | throw new IllegalArgumentException("Invalid key spec"); 29 | } 30 | DilithiumPrivateKeySpec prvspec = (DilithiumPrivateKeySpec) keySpec; 31 | return PackingUtils.unpackPrivateKey(prvspec.getParameterSpec(), prvspec.getBytes()); 32 | } 33 | 34 | @Override 35 | protected T engineGetKeySpec(Key key, Class keySpec) throws InvalidKeySpecException { 36 | throw new UnsupportedOperationException("Unsupported!"); 37 | } 38 | 39 | @Override 40 | protected Key engineTranslateKey(Key key) throws InvalidKeyException { 41 | throw new UnsupportedOperationException("Unsupported!"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/provider/DilithiumKeyPairGenerator.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.provider; 2 | 3 | import java.security.InvalidAlgorithmParameterException; 4 | import java.security.KeyPair; 5 | import java.security.KeyPairGeneratorSpi; 6 | import java.security.SecureRandom; 7 | import java.security.spec.AlgorithmParameterSpec; 8 | 9 | import net.thiim.dilithium.impl.Dilithium; 10 | import net.thiim.dilithium.impl.Utils; 11 | import net.thiim.dilithium.interfaces.DilithiumParameterSpec; 12 | 13 | public class DilithiumKeyPairGenerator extends KeyPairGeneratorSpi { 14 | private DilithiumParameterSpec params; 15 | private SecureRandom random; 16 | 17 | @Override 18 | public void initialize(int keysize, SecureRandom random) { 19 | throw new UnsupportedOperationException("Not implemented - you must specify a parameter spec"); 20 | } 21 | 22 | @Override 23 | public KeyPair generateKeyPair() { 24 | if(random == null || params == null) { 25 | throw new IllegalStateException("The generator is not configured"); 26 | } 27 | byte[] seed = new byte[32]; 28 | try { 29 | random.nextBytes(seed); 30 | return Dilithium.generateKeyPair(params, seed); 31 | } 32 | finally { 33 | Utils.clear(seed); 34 | } 35 | } 36 | 37 | @Override 38 | public void initialize(AlgorithmParameterSpec params, SecureRandom random) 39 | throws InvalidAlgorithmParameterException { 40 | if (!(params instanceof DilithiumParameterSpec)) { 41 | throw new InvalidAlgorithmParameterException("Inappropriate parameter type"); 42 | } 43 | this.params = (DilithiumParameterSpec) params; 44 | this.random = random; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/provider/DilithiumProvider.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.provider; 2 | 3 | import java.security.AccessController; 4 | import java.security.Provider; 5 | 6 | public class DilithiumProvider extends Provider { 7 | 8 | public DilithiumProvider() { 9 | super("Dilithium Provider", "0.1", "For experimental use only"); 10 | 11 | AccessController.doPrivileged(new java.security.PrivilegedAction() { 12 | @Override 13 | public Object run() { 14 | /* 15 | * Key(pair) Generator engines 16 | */ 17 | put("KeyPairGenerator.Dilithium", 18 | "net.thiim.dilithium.provider.DilithiumKeyPairGenerator"); 19 | put("Alg.Alias.KeyPairGenerator.Dilithium", "Dilithium"); 20 | 21 | /* 22 | * Key factories 23 | */ 24 | put("KeyFactory.Dilithium", 25 | "net.thiim.dilithium.provider.DilithiumKeyFactory"); 26 | put("Alg.Alias.KeyFactory.Dilithium", "Dilithium"); 27 | 28 | /* 29 | * Key factories 30 | */ 31 | put("Signature.Dilithium", 32 | "net.thiim.dilithium.provider.DilithiumSignature"); 33 | put("Alg.Alias.Signature.Dilithium", "Dilithium"); 34 | 35 | return null; 36 | } 37 | }); 38 | 39 | } 40 | 41 | /** 42 | * 43 | */ 44 | private static final long serialVersionUID = 1L; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/net/thiim/dilithium/provider/DilithiumSignature.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.provider; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.security.InvalidKeyException; 5 | import java.security.InvalidParameterException; 6 | import java.security.PrivateKey; 7 | import java.security.PublicKey; 8 | import java.security.SignatureException; 9 | import java.security.SignatureSpi; 10 | 11 | import net.thiim.dilithium.impl.Dilithium; 12 | import net.thiim.dilithium.interfaces.DilithiumPrivateKey; 13 | import net.thiim.dilithium.interfaces.DilithiumPublicKey; 14 | 15 | public class DilithiumSignature extends SignatureSpi { 16 | static enum Mode { 17 | VERIFY, SIGN; 18 | } 19 | 20 | private DilithiumPublicKey pubk; 21 | private DilithiumPrivateKey prvk; 22 | private ByteArrayOutputStream baos; 23 | 24 | private Mode mode; 25 | 26 | @Override 27 | protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { 28 | if(!(publicKey instanceof DilithiumPublicKey)) { 29 | throw new IllegalArgumentException("Not a valid public key"); 30 | } 31 | 32 | mode = Mode.VERIFY; 33 | pubk = (DilithiumPublicKey)publicKey; 34 | baos = new ByteArrayOutputStream(); 35 | } 36 | 37 | @Override 38 | protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { 39 | if(!(privateKey instanceof DilithiumPrivateKey)) { 40 | throw new IllegalArgumentException("Not a valid private key"); 41 | } 42 | 43 | mode = Mode.SIGN; 44 | prvk = (DilithiumPrivateKey)privateKey; 45 | baos = new ByteArrayOutputStream(); 46 | } 47 | 48 | @Override 49 | protected void engineUpdate(byte b) throws SignatureException { 50 | baos.write(b & 0xFF); 51 | } 52 | 53 | @Override 54 | protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { 55 | baos.write(b, off, len); 56 | } 57 | 58 | @Override 59 | protected byte[] engineSign() throws SignatureException { 60 | if(mode != Mode.SIGN || baos == null || prvk == null) { 61 | throw new IllegalStateException("Not in signing mode"); 62 | } 63 | byte[] M = baos.toByteArray(); 64 | byte[] sig = Dilithium.sign(prvk, M); 65 | baos = new ByteArrayOutputStream(); 66 | return sig; 67 | } 68 | 69 | @Override 70 | protected boolean engineVerify(byte[] sigBytes) throws SignatureException { 71 | if(mode != Mode.VERIFY || baos == null || pubk == null) { 72 | throw new IllegalStateException("Not in verify mode"); 73 | } 74 | byte[] M = baos.toByteArray(); 75 | boolean match = Dilithium.verify(pubk, sigBytes, M); 76 | baos = new ByteArrayOutputStream(); 77 | return match; 78 | } 79 | 80 | @Override 81 | protected void engineSetParameter(String param, Object value) throws InvalidParameterException { 82 | throw new UnsupportedOperationException("Not supported"); 83 | 84 | } 85 | 86 | @Override 87 | protected Object engineGetParameter(String param) throws InvalidParameterException { 88 | throw new UnsupportedOperationException("Not supported"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/net/thiim/dilithium/test/BasicTests.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.test; 2 | 3 | import static org.junit.Assert.assertFalse; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.security.KeyFactory; 7 | import java.security.KeyPair; 8 | import java.security.KeyPairGenerator; 9 | import java.security.PrivateKey; 10 | import java.security.PublicKey; 11 | import java.security.Signature; 12 | import java.util.Arrays; 13 | 14 | import org.junit.Test; 15 | 16 | import net.thiim.dilithium.interfaces.DilithiumParameterSpec; 17 | import net.thiim.dilithium.interfaces.DilithiumPrivateKey; 18 | import net.thiim.dilithium.interfaces.DilithiumPrivateKeySpec; 19 | import net.thiim.dilithium.interfaces.DilithiumPublicKey; 20 | import net.thiim.dilithium.interfaces.DilithiumPublicKeySpec; 21 | import net.thiim.dilithium.provider.DilithiumProvider; 22 | 23 | public class BasicTests { 24 | private final static DilithiumParameterSpec[] specs = new DilithiumParameterSpec[] { 25 | DilithiumParameterSpec.LEVEL2, 26 | DilithiumParameterSpec.LEVEL3, 27 | DilithiumParameterSpec.LEVEL5 28 | 29 | }; 30 | 31 | @Test 32 | public void keygen() throws Exception 33 | { 34 | for(DilithiumParameterSpec spec : specs) { 35 | DilithiumProvider pv = new DilithiumProvider(); 36 | KeyPairGenerator kpg = KeyPairGenerator.getInstance("Dilithium", pv); 37 | kpg.initialize(spec); 38 | KeyPair kp = kpg.generateKeyPair(); 39 | assertTrue(kp.getPublic() instanceof DilithiumPublicKey); 40 | assertTrue(kp.getPrivate() instanceof DilithiumPrivateKey); 41 | } 42 | } 43 | 44 | @Test 45 | public void signAndVerify() throws Exception 46 | { 47 | for(DilithiumParameterSpec spec : specs) { 48 | signAndVerifyWithSpec(spec); 49 | } 50 | } 51 | 52 | private void signAndVerifyWithSpec(DilithiumParameterSpec spec) throws Exception { 53 | DilithiumProvider pv = new DilithiumProvider(); 54 | KeyPairGenerator kpg = KeyPairGenerator.getInstance("Dilithium", pv); 55 | kpg.initialize(spec); 56 | KeyPair kp = kpg.generateKeyPair(); 57 | 58 | KeyPair altKeyPair = kpg.generateKeyPair(); 59 | 60 | for(int i = 0; i < 32; i++) { 61 | byte[] text = new byte[i]; 62 | for(int j = 0; j < i; j++) { 63 | text[j] = (byte)j; 64 | } 65 | 66 | Signature signature = Signature.getInstance("Dilithium", pv); 67 | signature.initSign(kp.getPrivate()); 68 | signature.update(text); 69 | byte[] sig = signature.sign(); 70 | 71 | // Check we can verify with the correct public key 72 | signature.initVerify(kp.getPublic()); 73 | signature.update(text); 74 | assertTrue(signature.verify(sig)); 75 | 76 | // Check we can't with incorrect public key 77 | signature.initVerify(altKeyPair.getPublic()); 78 | signature.update(text); 79 | assertFalse(signature.verify(sig)); 80 | 81 | // Check we can detect any bit-level modification using the correct public key 82 | for(int j = 0; j < i; j++) { 83 | for(int k = 0; k < 8; k++) { 84 | byte[] alttext = Arrays.copyOf(text, text.length); 85 | alttext[j] ^= (1 << k); 86 | 87 | signature.initVerify(kp.getPublic()); 88 | signature.update(alttext); 89 | assertFalse(signature.verify(sig)); 90 | } 91 | } 92 | 93 | } 94 | } 95 | 96 | @Test 97 | public void testSerialization() throws Exception 98 | { 99 | for(DilithiumParameterSpec spec : specs) { 100 | testSerializationForSpec(spec); 101 | } 102 | } 103 | 104 | private void testSerializationForSpec(DilithiumParameterSpec spec) throws Exception { 105 | DilithiumProvider pv = new DilithiumProvider(); 106 | KeyPairGenerator kpg = KeyPairGenerator.getInstance("Dilithium", pv); 107 | kpg.initialize(spec); 108 | KeyPair kp = kpg.generateKeyPair(); 109 | 110 | Signature signature = Signature.getInstance("Dilithium", pv); 111 | signature.initSign(kp.getPrivate()); 112 | signature.update("Joy!".getBytes()); 113 | byte[] sig = signature.sign(); 114 | 115 | // Check we can verify with reinstantiated public key 116 | KeyFactory kf = KeyFactory.getInstance("Dilithium", pv); 117 | PublicKey reconsPublicKey = kf.generatePublic(new DilithiumPublicKeySpec(spec, kp.getPublic().getEncoded())); 118 | signature.initVerify(reconsPublicKey); 119 | signature.update("Joy!".getBytes()); 120 | assertTrue(signature.verify(sig)); 121 | 122 | // Now sign with reinstantiated private key 123 | PrivateKey reconsPrivateKey = kf.generatePrivate(new DilithiumPrivateKeySpec(spec, kp.getPrivate().getEncoded())); 124 | signature.initSign(reconsPrivateKey); 125 | signature.update("Joy!".getBytes()); 126 | sig = signature.sign(); 127 | 128 | // Check we can verify wtih originap public key 129 | signature.initVerify(kp.getPublic()); 130 | signature.update("Joy!".getBytes()); 131 | assertTrue(signature.verify(sig)); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/test/java/net/thiim/dilithium/test/KAT.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.test; 2 | 3 | import java.io.FileReader; 4 | import java.io.FileWriter; 5 | import java.io.LineNumberReader; 6 | import java.io.PrintWriter; 7 | import java.security.KeyPair; 8 | import java.security.PrivateKey; 9 | 10 | import org.bouncycastle.util.encoders.Hex; 11 | 12 | import net.thiim.dilithium.impl.Dilithium; 13 | import net.thiim.dilithium.impl.PackingUtils; 14 | import net.thiim.dilithium.impl.Utils; 15 | import net.thiim.dilithium.interfaces.DilithiumParameterSpec; 16 | import net.thiim.dilithium.interfaces.DilithiumPrivateKey; 17 | import net.thiim.dilithium.interfaces.DilithiumPublicKey; 18 | import net.thiim.dilithium.test.dbrg.PseudoRNG; 19 | 20 | public class KAT { 21 | public static void main(String[] args) throws Exception { 22 | if(args.length != 3) { 23 | System.out.println("Use with parameters: "); 24 | } 25 | int level = Integer.parseInt(args[2]); 26 | DilithiumParameterSpec spec = DilithiumParameterSpec.getSpecForSecurityLevel(level); 27 | 28 | try(LineNumberReader lnr = new LineNumberReader(new FileReader(args[0])); 29 | PrintWriter pw = new PrintWriter(new FileWriter(args[1]))) { 30 | pw.println("# Dilithium" + level); 31 | pw.println(""); 32 | 33 | String seed = null; 34 | String msg = null; 35 | String count = null; 36 | 37 | for (;;) { 38 | String line = lnr.readLine(); 39 | if (line == null) 40 | break; 41 | if (line.startsWith("seed")) { 42 | seed = line.substring(7); 43 | } else if (line.startsWith("count")) { 44 | count = line.substring(8); 45 | } else if (line.startsWith("msg")) { 46 | msg = line.substring(6); 47 | doRun(pw, spec, count, seed, msg); 48 | } 49 | } 50 | } 51 | 52 | } 53 | 54 | private static void doRun(PrintWriter pw, DilithiumParameterSpec spec, String count, String seed, String msg) throws Exception { 55 | byte[] bseed = Hex.decodeStrict(seed); 56 | byte[] bmsg = Hex.decodeStrict(msg); 57 | PseudoRNG innerpr = new PseudoRNG(bseed, null, 256); 58 | byte[] innerseed = innerpr.generate(32); 59 | KeyPair kp = Dilithium.generateKeyPair(spec, innerseed); 60 | 61 | DilithiumPrivateKey sk = (DilithiumPrivateKey) kp.getPrivate(); 62 | DilithiumPublicKey pk = (DilithiumPublicKey) kp.getPublic(); 63 | 64 | byte[] sm = Dilithium.sign(sk, bmsg); 65 | if (!Dilithium.verify(pk, sm, bmsg)) { 66 | throw new Exception("Verification failed!"); 67 | } 68 | 69 | byte[] conc = Utils.concat(sm, bmsg); 70 | 71 | pw.println("count = " + count); 72 | pw.println("seed = " + seed); 73 | pw.println("mlen = " + bmsg.length); 74 | pw.println("msg = " + msg); 75 | pw.println("pk = " + Hex.toHexString(pk.getEncoded()).toUpperCase()); 76 | pw.println("sk = " + Hex.toHexString(sk.getEncoded()).toUpperCase()); 77 | pw.println("smlen = " + conc.length); 78 | pw.println("sm = " + Hex.toHexString(conc).toUpperCase()); 79 | pw.println(""); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/net/thiim/dilithium/test/PerfTest.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.test; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | 5 | import java.security.KeyPair; 6 | import java.security.KeyPairGenerator; 7 | import java.security.SecureRandom; 8 | import java.security.Security; 9 | import java.security.Signature; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import org.junit.Test; 14 | 15 | import net.thiim.dilithium.interfaces.DilithiumParameterSpec; 16 | import net.thiim.dilithium.provider.DilithiumProvider; 17 | 18 | /** 19 | * Quick and dirty test that exercises the functionality, including testing that serialization/deserialization works as expected. 20 | * It also provides some simple performance measurements. 21 | * 22 | */ 23 | public class PerfTest { 24 | @Test 25 | public void test() throws Exception 26 | { 27 | DilithiumProvider provider = new DilithiumProvider(); 28 | Security.addProvider(provider); 29 | 30 | SecureRandom sr = new SecureRandom(); 31 | 32 | DilithiumParameterSpec[] specs = new DilithiumParameterSpec[] { 33 | DilithiumParameterSpec.LEVEL2, 34 | DilithiumParameterSpec.LEVEL3, 35 | DilithiumParameterSpec.LEVEL5 36 | 37 | }; 38 | 39 | Map timings = new HashMap(); 40 | for(DilithiumParameterSpec s : specs) { 41 | timings.put(s, new Timings()); 42 | } 43 | 44 | final int CNT = 1000; 45 | final int WARMUP = 500; 46 | boolean warmingup = true; 47 | System.out.println("Test running...please hold on..."); 48 | for(int i = 0; i < CNT; i++) { 49 | if(i % 100 == 0) { 50 | System.out.println("" + i + " out of " + CNT + " iterations."); 51 | } 52 | if(i >= WARMUP) { 53 | warmingup = false; 54 | } 55 | for(DilithiumParameterSpec spec : specs) { 56 | Timings timing = timings.get(spec); 57 | 58 | // Generate 59 | KeyPairGenerator kpg = KeyPairGenerator.getInstance("Dilithium"); 60 | kpg.initialize(spec, sr); 61 | 62 | long start = System.currentTimeMillis(); 63 | KeyPair kp = kpg.generateKeyPair(); 64 | long end = System.currentTimeMillis(); 65 | if(!warmingup) { 66 | timing.generate += end - start; 67 | } 68 | 69 | // Sign 70 | Signature sig = Signature.getInstance("Dilithium"); 71 | sig.initSign(kp.getPrivate()); 72 | sig.update("Joy!".getBytes()); 73 | start = System.currentTimeMillis(); 74 | byte[] signature = sig.sign(); 75 | end = System.currentTimeMillis(); 76 | if(!warmingup) { 77 | timing.sign += end - start; 78 | } 79 | 80 | // Verify 81 | sig.initVerify(kp.getPublic()); 82 | sig.update("Joy!".getBytes()); 83 | start = System.currentTimeMillis(); 84 | assertTrue(sig.verify(signature)); 85 | end = System.currentTimeMillis(); 86 | if(!warmingup) { 87 | timing.verify += end - start; 88 | } 89 | } 90 | } 91 | final int iterations = CNT - WARMUP; 92 | System.out.println("Iterations (ex warmup): " + iterations); 93 | for(DilithiumParameterSpec s : specs) { 94 | Timings t = timings.get(s); 95 | double gen = t.generate; 96 | gen /= iterations; 97 | 98 | double sign = t.sign; 99 | sign /= iterations; 100 | 101 | double verify = t.verify; 102 | verify /= iterations; 103 | 104 | System.out.println("Level: " + s); 105 | System.out.println("Generate: " + gen); 106 | System.out.println("Sign: " + sign); 107 | System.out.println("Verify: " + verify); 108 | } 109 | } 110 | 111 | public static void main(String[] args) throws Exception { 112 | PerfTest pt = new PerfTest(); 113 | pt.test(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/net/thiim/dilithium/test/Timings.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.test; 2 | 3 | class Timings 4 | { 5 | public long generate; 6 | public long sign; 7 | public long verify; 8 | } -------------------------------------------------------------------------------- /src/test/java/net/thiim/dilithium/test/dbrg/ModifiedSP80090DRBG.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.test.dbrg; 2 | 3 | import org.bouncycastle.crypto.BlockCipher; 4 | import org.bouncycastle.crypto.params.KeyParameter; 5 | import org.bouncycastle.crypto.prng.EntropySource; 6 | import org.bouncycastle.crypto.prng.drbg.SP80090DRBG; 7 | import org.bouncycastle.util.Arrays; 8 | import org.bouncycastle.util.encoders.Hex; 9 | 10 | /** 11 | * Slightly modified P800-90A CTR DRBG for compatibility with the one used in KAT tests in the Dilithium reference implementation. Specifically, the following processing of the 12 | * seed material is skipped during initialization: 13 | * 14 | * //byte[] seed = Block_Cipher_df(seedMaterial, _seedLength); 15 | */ 16 | public class ModifiedSP80090DRBG 17 | implements SP80090DRBG 18 | { 19 | private static final long TDEA_RESEED_MAX = 1L << (32 - 1); 20 | private static final long AES_RESEED_MAX = 1L << (48 - 1); 21 | private static final int TDEA_MAX_BITS_REQUEST = 1 << (13 - 1); 22 | private static final int AES_MAX_BITS_REQUEST = 1 << (19 - 1); 23 | 24 | private EntropySource _entropySource; 25 | private BlockCipher _engine; 26 | private int _keySizeInBits; 27 | private int _seedLength; 28 | private int _securityStrength; 29 | 30 | // internal state 31 | private byte[] _Key; 32 | private byte[] _V; 33 | private long _reseedCounter = 0; 34 | private boolean _isTDEA = false; 35 | 36 | /** 37 | * Construct a SP800-90A CTR DRBG. 38 | *

39 | * Minimum entropy requirement is the security strength requested. 40 | *

41 | * @param engine underlying block cipher to use to support DRBG 42 | * @param keySizeInBits size of the key to use with the block cipher. 43 | * @param securityStrength security strength required (in bits) 44 | * @param entropySource source of entropy to use for seeding/reseeding. 45 | * @param personalizationString personalization string to distinguish this DRBG (may be null). 46 | * @param nonce nonce to further distinguish this DRBG (may be null). 47 | */ 48 | public ModifiedSP80090DRBG(BlockCipher engine, int keySizeInBits, int securityStrength, EntropySource entropySource, byte[] personalizationString, byte[] nonce) 49 | { 50 | _entropySource = entropySource; 51 | _engine = engine; 52 | 53 | _keySizeInBits = keySizeInBits; 54 | _securityStrength = securityStrength; 55 | _seedLength = keySizeInBits + engine.getBlockSize() * 8; 56 | _isTDEA = isTDEA(engine); 57 | 58 | if (securityStrength > 256) 59 | { 60 | throw new IllegalArgumentException("Requested security strength is not supported by the derivation function"); 61 | } 62 | 63 | if (getMaxSecurityStrength(engine, keySizeInBits) < securityStrength) 64 | { 65 | throw new IllegalArgumentException("Requested security strength is not supported by block cipher and key size"); 66 | } 67 | 68 | if (entropySource.entropySize() < securityStrength) 69 | { 70 | throw new IllegalArgumentException("Not enough entropy for security strength required"); 71 | } 72 | 73 | byte[] entropy = getEntropy(); // Get_entropy_input 74 | 75 | CTR_DRBG_Instantiate_algorithm(entropy, nonce, personalizationString); 76 | } 77 | 78 | private void CTR_DRBG_Instantiate_algorithm(byte[] entropy, byte[] nonce, 79 | byte[] personalisationString) 80 | { 81 | byte[] seedMaterial = Arrays.concatenate(entropy, nonce, personalisationString); 82 | //byte[] seed = Block_Cipher_df(seedMaterial, _seedLength); 83 | byte[] seed = seedMaterial; 84 | 85 | int outlen = _engine.getBlockSize(); 86 | 87 | _Key = new byte[(_keySizeInBits + 7) / 8]; 88 | _V = new byte[outlen]; 89 | 90 | // _Key & _V are modified by this call 91 | CTR_DRBG_Update(seed, _Key, _V); 92 | 93 | _reseedCounter = 1; 94 | } 95 | 96 | private void CTR_DRBG_Update(byte[] seed, byte[] key, byte[] v) 97 | { 98 | byte[] temp = new byte[seed.length]; 99 | byte[] outputBlock = new byte[_engine.getBlockSize()]; 100 | 101 | int i=0; 102 | int outLen = _engine.getBlockSize(); 103 | 104 | _engine.init(true, new KeyParameter(expandKey(key))); 105 | while (i*outLen < seed.length) 106 | { 107 | addOneTo(v); 108 | _engine.processBlock(v, 0, outputBlock, 0); 109 | 110 | int bytesToCopy = ((temp.length - i * outLen) > outLen) 111 | ? outLen : (temp.length - i * outLen); 112 | 113 | System.arraycopy(outputBlock, 0, temp, i * outLen, bytesToCopy); 114 | ++i; 115 | } 116 | 117 | XOR(temp, seed, temp, 0); 118 | 119 | System.arraycopy(temp, 0, key, 0, key.length); 120 | System.arraycopy(temp, key.length, v, 0, v.length); 121 | } 122 | 123 | private void CTR_DRBG_Reseed_algorithm(byte[] additionalInput) 124 | { 125 | byte[] seedMaterial = Arrays.concatenate(getEntropy(), additionalInput); 126 | 127 | seedMaterial = Block_Cipher_df(seedMaterial, _seedLength); 128 | 129 | CTR_DRBG_Update(seedMaterial, _Key, _V); 130 | 131 | _reseedCounter = 1; 132 | } 133 | 134 | private void XOR(byte[] out, byte[] a, byte[] b, int bOff) 135 | { 136 | for (int i=0; i< out.length; i++) 137 | { 138 | out[i] = (byte)(a[i] ^ b[i+bOff]); 139 | } 140 | } 141 | 142 | private void addOneTo(byte[] longer) 143 | { 144 | int carry = 1; 145 | for (int i = 1; i <= longer.length; i++) // warning 146 | { 147 | int res = (longer[longer.length - i] & 0xff) + carry; 148 | carry = (res > 0xff) ? 1 : 0; 149 | longer[longer.length - i] = (byte)res; 150 | } 151 | } 152 | 153 | private byte[] getEntropy() 154 | { 155 | byte[] entropy = _entropySource.getEntropy(); 156 | if (entropy.length < (_securityStrength + 7) / 8) 157 | { 158 | throw new IllegalStateException("Insufficient entropy provided by entropy source"); 159 | } 160 | return entropy; 161 | } 162 | 163 | // -- Internal state migration --- 164 | 165 | private static final byte[] K_BITS = Hex.decodeStrict("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); 166 | 167 | // 1. If (number_of_bits_to_return > max_number_of_bits), then return an 168 | // ERROR_FLAG. 169 | // 2. L = len (input_string)/8. 170 | // 3. N = number_of_bits_to_return/8. 171 | // Comment: L is the bitstring represention of 172 | // the integer resulting from len (input_string)/8. 173 | // L shall be represented as a 32-bit integer. 174 | // 175 | // Comment : N is the bitstring represention of 176 | // the integer resulting from 177 | // number_of_bits_to_return/8. N shall be 178 | // represented as a 32-bit integer. 179 | // 180 | // 4. S = L || N || input_string || 0x80. 181 | // 5. While (len (S) mod outlen) 182 | // Comment : Pad S with zeros, if necessary. 183 | // 0, S = S || 0x00. 184 | // 185 | // Comment : Compute the starting value. 186 | // 6. temp = the Null string. 187 | // 7. i = 0. 188 | // 8. K = Leftmost keylen bits of 0x00010203...1D1E1F. 189 | // 9. While len (temp) < keylen + outlen, do 190 | // 191 | // IV = i || 0outlen - len (i). 192 | // 193 | // 9.1 194 | // 195 | // temp = temp || BCC (K, (IV || S)). 196 | // 197 | // 9.2 198 | // 199 | // i = i + 1. 200 | // 201 | // 9.3 202 | // 203 | // Comment : i shall be represented as a 32-bit 204 | // integer, i.e., len (i) = 32. 205 | // 206 | // Comment: The 32-bit integer represenation of 207 | // i is padded with zeros to outlen bits. 208 | // 209 | // Comment: Compute the requested number of 210 | // bits. 211 | // 212 | // 10. K = Leftmost keylen bits of temp. 213 | // 214 | // 11. X = Next outlen bits of temp. 215 | // 216 | // 12. temp = the Null string. 217 | // 218 | // 13. While len (temp) < number_of_bits_to_return, do 219 | // 220 | // 13.1 X = Block_Encrypt (K, X). 221 | // 222 | // 13.2 temp = temp || X. 223 | // 224 | // 14. requested_bits = Leftmost number_of_bits_to_return of temp. 225 | // 226 | // 15. Return SUCCESS and requested_bits. 227 | private byte[] Block_Cipher_df(byte[] inputString, int bitLength) 228 | { 229 | int outLen = _engine.getBlockSize(); 230 | int L = inputString.length; // already in bytes 231 | int N = bitLength / 8; 232 | // 4 S = L || N || inputstring || 0x80 233 | int sLen = 4 + 4 + L + 1; 234 | int blockLen = ((sLen + outLen - 1) / outLen) * outLen; 235 | byte[] S = new byte[blockLen]; 236 | copyIntToByteArray(S, L, 0); 237 | copyIntToByteArray(S, N, 4); 238 | System.arraycopy(inputString, 0, S, 8, L); 239 | S[8 + L] = (byte)0x80; 240 | // S already padded with zeros 241 | 242 | byte[] temp = new byte[_keySizeInBits / 8 + outLen]; 243 | byte[] bccOut = new byte[outLen]; 244 | 245 | byte[] IV = new byte[outLen]; 246 | 247 | int i = 0; 248 | byte[] K = new byte[_keySizeInBits / 8]; 249 | System.arraycopy(K_BITS, 0, K, 0, K.length); 250 | 251 | while (i*outLen*8 < _keySizeInBits + outLen *8) 252 | { 253 | copyIntToByteArray(IV, i, 0); 254 | BCC(bccOut, K, IV, S); 255 | 256 | int bytesToCopy = ((temp.length - i * outLen) > outLen) 257 | ? outLen 258 | : (temp.length - i * outLen); 259 | 260 | System.arraycopy(bccOut, 0, temp, i * outLen, bytesToCopy); 261 | ++i; 262 | } 263 | 264 | byte[] X = new byte[outLen]; 265 | System.arraycopy(temp, 0, K, 0, K.length); 266 | System.arraycopy(temp, K.length, X, 0, X.length); 267 | 268 | temp = new byte[bitLength / 8]; 269 | 270 | i = 0; 271 | _engine.init(true, new KeyParameter(expandKey(K))); 272 | 273 | while (i * outLen < temp.length) 274 | { 275 | _engine.processBlock(X, 0, X, 0); 276 | 277 | int bytesToCopy = ((temp.length - i * outLen) > outLen) 278 | ? outLen 279 | : (temp.length - i * outLen); 280 | 281 | System.arraycopy(X, 0, temp, i * outLen, bytesToCopy); 282 | i++; 283 | } 284 | 285 | return temp; 286 | } 287 | 288 | /* 289 | * 1. chaining_value = 0^outlen 290 | * . Comment: Set the first chaining value to outlen zeros. 291 | * 2. n = len (data)/outlen. 292 | * 3. Starting with the leftmost bits of data, split the data into n blocks of outlen bits 293 | * each, forming block(1) to block(n). 294 | * 4. For i = 1 to n do 295 | * 4.1 input_block = chaining_value ^ block(i) . 296 | * 4.2 chaining_value = Block_Encrypt (Key, input_block). 297 | * 5. output_block = chaining_value. 298 | * 6. Return output_block. 299 | */ 300 | private void BCC(byte[] bccOut, byte[] k, byte[] iV, byte[] data) 301 | { 302 | int outlen = _engine.getBlockSize(); 303 | byte[] chainingValue = new byte[outlen]; // initial values = 0 304 | int n = data.length / outlen; 305 | 306 | byte[] inputBlock = new byte[outlen]; 307 | 308 | _engine.init(true, new KeyParameter(expandKey(k))); 309 | 310 | _engine.processBlock(iV, 0, chainingValue, 0); 311 | 312 | for (int i = 0; i < n; i++) 313 | { 314 | XOR(inputBlock, chainingValue, data, i*outlen); 315 | _engine.processBlock(inputBlock, 0, chainingValue, 0); 316 | } 317 | 318 | System.arraycopy(chainingValue, 0, bccOut, 0, bccOut.length); 319 | } 320 | 321 | private void copyIntToByteArray(byte[] buf, int value, int offSet) 322 | { 323 | buf[offSet + 0] = ((byte)(value >> 24)); 324 | buf[offSet + 1] = ((byte)(value >> 16)); 325 | buf[offSet + 2] = ((byte)(value >> 8)); 326 | buf[offSet + 3] = ((byte)(value)); 327 | } 328 | 329 | /** 330 | * Return the block size (in bits) of the DRBG. 331 | * 332 | * @return the number of bits produced on each internal round of the DRBG. 333 | */ 334 | public int getBlockSize() 335 | { 336 | return _V.length * 8; 337 | } 338 | 339 | /** 340 | * Populate a passed in array with random data. 341 | * 342 | * @param output output array for generated bits. 343 | * @param additionalInput additional input to be added to the DRBG in this step. 344 | * @param predictionResistant true if a reseed should be forced, false otherwise. 345 | * 346 | * @return number of bits generated, -1 if a reseed required. 347 | */ 348 | public int generate(byte[] output, byte[] additionalInput, boolean predictionResistant) 349 | { 350 | if (_isTDEA) 351 | { 352 | if (_reseedCounter > TDEA_RESEED_MAX) 353 | { 354 | return -1; 355 | } 356 | 357 | if (isTooLarge(output, TDEA_MAX_BITS_REQUEST / 8)) 358 | { 359 | throw new IllegalArgumentException("Number of bits per request limited to " + TDEA_MAX_BITS_REQUEST); 360 | } 361 | } 362 | else 363 | { 364 | if (_reseedCounter > AES_RESEED_MAX) 365 | { 366 | return -1; 367 | } 368 | 369 | if (isTooLarge(output, AES_MAX_BITS_REQUEST / 8)) 370 | { 371 | throw new IllegalArgumentException("Number of bits per request limited to " + AES_MAX_BITS_REQUEST); 372 | } 373 | } 374 | 375 | if (predictionResistant) 376 | { 377 | CTR_DRBG_Reseed_algorithm(additionalInput); 378 | additionalInput = null; 379 | } 380 | 381 | if (additionalInput != null) 382 | { 383 | additionalInput = Block_Cipher_df(additionalInput, _seedLength); 384 | CTR_DRBG_Update(additionalInput, _Key, _V); 385 | } 386 | else 387 | { 388 | additionalInput = new byte[_seedLength / 8]; 389 | } 390 | 391 | byte[] out = new byte[_V.length]; 392 | 393 | _engine.init(true, new KeyParameter(expandKey(_Key))); 394 | 395 | for (int i = 0; i <= output.length / out.length; i++) 396 | { 397 | int bytesToCopy = ((output.length - i * out.length) > out.length) 398 | ? out.length 399 | : (output.length - i * _V.length); 400 | 401 | if (bytesToCopy != 0) 402 | { 403 | addOneTo(_V); 404 | 405 | _engine.processBlock(_V, 0, out, 0); 406 | 407 | System.arraycopy(out, 0, output, i * out.length, bytesToCopy); 408 | } 409 | } 410 | 411 | CTR_DRBG_Update(additionalInput, _Key, _V); 412 | 413 | _reseedCounter++; 414 | 415 | return output.length * 8; 416 | } 417 | 418 | private boolean isTooLarge(byte[] output, int i) { 419 | return i < output.length; 420 | } 421 | 422 | /** 423 | * Reseed the DRBG. 424 | * 425 | * @param additionalInput additional input to be added to the DRBG in this step. 426 | */ 427 | public void reseed(byte[] additionalInput) 428 | { 429 | CTR_DRBG_Reseed_algorithm(additionalInput); 430 | } 431 | 432 | private boolean isTDEA(BlockCipher cipher) 433 | { 434 | return cipher.getAlgorithmName().equals("DESede") || cipher.getAlgorithmName().equals("TDEA"); 435 | } 436 | 437 | private int getMaxSecurityStrength(BlockCipher cipher, int keySizeInBits) 438 | { 439 | if (isTDEA(cipher) && keySizeInBits == 168) 440 | { 441 | return 112; 442 | } 443 | if (cipher.getAlgorithmName().equals("AES")) 444 | { 445 | return keySizeInBits; 446 | } 447 | 448 | return -1; 449 | } 450 | 451 | byte[] expandKey(byte[] key) 452 | { 453 | if (_isTDEA) 454 | { 455 | // expand key to 192 bits. 456 | byte[] tmp = new byte[24]; 457 | 458 | padKey(key, 0, tmp, 0); 459 | padKey(key, 7, tmp, 8); 460 | padKey(key, 14, tmp, 16); 461 | 462 | return tmp; 463 | } 464 | else 465 | { 466 | return key; 467 | } 468 | } 469 | 470 | /** 471 | * Pad out a key for TDEA, setting odd parity for each byte. 472 | * 473 | * @param keyMaster 474 | * @param keyOff 475 | * @param tmp 476 | * @param tmpOff 477 | */ 478 | private void padKey(byte[] keyMaster, int keyOff, byte[] tmp, int tmpOff) 479 | { 480 | tmp[tmpOff + 0] = (byte)(keyMaster[keyOff + 0] & 0xfe); 481 | tmp[tmpOff + 1] = (byte)((keyMaster[keyOff + 0] << 7) | ((keyMaster[keyOff + 1] & 0xfc) >>> 1)); 482 | tmp[tmpOff + 2] = (byte)((keyMaster[keyOff + 1] << 6) | ((keyMaster[keyOff + 2] & 0xf8) >>> 2)); 483 | tmp[tmpOff + 3] = (byte)((keyMaster[keyOff + 2] << 5) | ((keyMaster[keyOff + 3] & 0xf0) >>> 3)); 484 | tmp[tmpOff + 4] = (byte)((keyMaster[keyOff + 3] << 4) | ((keyMaster[keyOff + 4] & 0xe0) >>> 4)); 485 | tmp[tmpOff + 5] = (byte)((keyMaster[keyOff + 4] << 3) | ((keyMaster[keyOff + 5] & 0xc0) >>> 5)); 486 | tmp[tmpOff + 6] = (byte)((keyMaster[keyOff + 5] << 2) | ((keyMaster[keyOff + 6] & 0x80) >>> 6)); 487 | tmp[tmpOff + 7] = (byte)(keyMaster[keyOff + 6] << 1); 488 | 489 | for (int i = tmpOff; i <= tmpOff + 7; i++) 490 | { 491 | int b = tmp[i]; 492 | tmp[i] = (byte)((b & 0xfe) | 493 | ((((b >> 1) ^ 494 | (b >> 2) ^ 495 | (b >> 3) ^ 496 | (b >> 4) ^ 497 | (b >> 5) ^ 498 | (b >> 6) ^ 499 | (b >> 7)) ^ 0x01) & 0x01)); 500 | } 501 | } 502 | } 503 | -------------------------------------------------------------------------------- /src/test/java/net/thiim/dilithium/test/dbrg/PseudoRNG.java: -------------------------------------------------------------------------------- 1 | package net.thiim.dilithium.test.dbrg; 2 | 3 | import org.bouncycastle.crypto.BlockCipher; 4 | import org.bouncycastle.crypto.engines.AESEngine; 5 | import org.bouncycastle.crypto.prng.EntropySource; 6 | import org.bouncycastle.crypto.prng.drbg.CTRSP800DRBG; 7 | 8 | public class PseudoRNG { 9 | private ModifiedSP80090DRBG ctr; 10 | 11 | public PseudoRNG(final byte[] entropy, byte[] personalization, int strength) 12 | { 13 | AESEngine eng = new AESEngine(); 14 | BlockCipher bc; 15 | EntropySource es = new EntropySource() { 16 | @Override 17 | public int entropySize() { 18 | return 8*entropy.length; 19 | } 20 | 21 | @Override 22 | public byte[] getEntropy() { 23 | return entropy; 24 | } 25 | 26 | @Override 27 | public boolean isPredictionResistant() { 28 | return false; 29 | } 30 | }; 31 | this.ctr = new ModifiedSP80090DRBG(eng, 256, strength, es, personalization, null); 32 | 33 | } 34 | 35 | public byte[] generate(int amount) 36 | { 37 | byte[] b = new byte[amount]; 38 | this.ctr.generate(b, null, false); 39 | return b; 40 | } 41 | } 42 | --------------------------------------------------------------------------------