diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b29741a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,31 @@ +root = true + +[*] +indent_style=tab +indent_size=tab +tab_width=4 +end_of_line=lf +charset=utf-8 +trim_trailing_whitespace=true +max_line_length=100 +insert_final_newline=true + +[*.toml] +max_line_length=off + +[*.md] +max_line_length=80 +indent_style=space +indent_size=2 + +[*.yml] +indent_style=space +indent_size=2 +tab_width=8 +end_of_line=lf + +[*.sh] +indent_style=space +indent_size=2 +tab_width=8 +end_of_line=lf diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..c342153 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,24 @@ +# Basic +edition = "2021" +hard_tabs = true +max_width = 100 +use_small_heuristics = "Max" +# Imports +imports_granularity = "Crate" +reorder_imports = true +# Consistency +newline_style = "Unix" +# Misc +chain_width = 80 +spaces_around_ranges = false +binop_separator = "Back" +reorder_impl_items = false +match_arm_leading_pipes = "Preserve" +match_arm_blocks = false +match_block_trailing_comma = true +trailing_comma = "Vertical" +trailing_semicolon = false +use_field_init_shorthand = true +# Format comments +comment_width = 100 +wrap_comments = true diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b4d177d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "falcon-det" +version = "0.1.0" +description = "Deterministic Falcon post-quantum signature scheme" +authors = ["Jeeyong Um "] +edition = "2021" +license = "MIT OR Apache-2.0" +repository = "https://github.com/conr2d/falcon-det.git" +documentation = "https://docs.rs/falcon-det" + +[dependencies] +falcon-det-sys = { version = "0.1", path = "sys" } +signature = { version = "2.2", optional = true } +static_assertions = "1.1" +zeroize = { version = "1.8", features = ["derive"], optional = true } + +[features] +default = ["signature", "zeroize"] + +[workspace] +members = ["sys"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..4a90701 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Jeeyong Um + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d673dfe --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# falcon-det + +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/conr2d/falcon-det/ci.yml?event=push)](https://github.com/conr2d/falcon-det) +[![Crates.io Version](https://img.shields.io/crates/v/falcon-det)](https://crates.io/crates/falcon-det) +[![GitHub License](https://img.shields.io/badge/license-MIT%2FApache2-blue)](#LICENSE) + +This crate provides Rust bindings to a deterministic version of +[Falcon](https://falcon-sign.info) post-quantum signature scheme by +[Algorand](https://github.com/algorand/falcon). + +## MSRV + +The `falcon-det` minimum supported Rust version is **1.82.0**. + +## License + +Licensed under either of: + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) + +at your option. diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..dca67fb --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.82.0" +components = ["clippy", "rustfmt"] +profile = "minimal" diff --git a/src/aux.rs b/src/aux.rs new file mode 100644 index 0000000..d5bb93b --- /dev/null +++ b/src/aux.rs @@ -0,0 +1,73 @@ +// Copyright (c) Jeeyong Um +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/// Private key size (in bytes). The size is exact. +pub const fn falcon_privkey_size(logn: usize) -> usize { + (if logn <= 3 { 3 << logn } else { ((10 - (logn >> 1)) << (logn - 2)) + (1 << logn) }) + 1 +} + +/// Public key size (in bytes). The size is exact. +pub const fn falcon_pubkey_size(logn: usize) -> usize { + (if logn <= 1 { 4 } else { 7 << (logn - 2) }) + 1 +} + +/// Maximum signature size (in bytes) when using the COMPRESSED format. +pub const fn falcon_sig_compressed_maxsize(logn: usize) -> usize { + ((((11 << logn) + (101 >> (10 - logn))) + 7) >> 3) + 41 +} + +/// Signature size (in bytes) when using the PADDED format. The size is exact. +pub const fn falcon_sig_padded_size(logn: usize) -> usize { + 44 + 3 * (256 >> (10 - logn)) + + 2 * (128 >> (10 - logn)) + + 3 * (64 >> (10 - logn)) + + 2 * (16 >> (10 - logn)) + - 2 * (2 >> (10 - logn)) + - 8 * (1 >> (10 - logn)) +} + +/// Signature size (in bytes) when using the CT format. The size is exact. +pub const fn falcon_sig_ct_size(logn: usize) -> usize { + (3 << (logn - 1)) - if logn == 3 { 1 } else { 0 } + 41 +} + +/// Temporary buffer size for key pair generation. +pub const fn falcon_tmpsize_keygen(logn: usize) -> usize { + (if logn <= 3 { 272 } else { 28 << logn }) + (3 << logn) + 7 +} + +/// Temporary buffer size for computing the public key from the private key. +pub const fn falcon_tmpsize_makepub(logn: usize) -> usize { + (6 << logn) + 1 +} + +/// Temporary buffer size for generating a signature ("dynamic" variant). +pub const fn falcon_tmpsize_signdyn(logn: usize) -> usize { + (78 << logn) + 7 +} + +/// Temporary buffer size for generating a signature ("tree" variant, with an expanded key). +pub const fn falcon_tmpsize_signtree(logn: usize) -> usize { + (50 << logn) + 7 +} + +/// Temporary buffer size for expanding a private key. +pub const fn falcon_tmpsize_expandpriv(logn: usize) -> usize { + (52 << logn) + 7 +} + +/// Size of an expanded private key. +pub const fn falcon_expandedkey_size(logn: usize) -> usize { + ((8 * logn + 40) << logn) + 8 +} + +/// Temporary buffer size for verifying a signature. +pub const fn falcon_tmpsize_verify(logn: usize) -> usize { + (8 << logn) + 1 +} diff --git a/src/det1024.rs b/src/det1024.rs new file mode 100644 index 0000000..a2f586d --- /dev/null +++ b/src/det1024.rs @@ -0,0 +1,268 @@ +// Copyright (c) Jeeyong Um +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Deterministic Falcon-1024 signature scheme. + +use crate::{aux::*, shake256::Shake256Context, Error}; +use static_assertions::const_assert_eq; + +pub const FALCON_DET1024_LOGN: usize = 10; +pub const FALCON_DET1024_PUBKEY_SIZE: usize = falcon_pubkey_size(FALCON_DET1024_LOGN); +pub const FALCON_DET1024_PRIVKEY_SIZE: usize = falcon_privkey_size(FALCON_DET1024_LOGN); + +// Replace the 40 byte salt (nonce) with a single byte representing +// the salt version: +pub const FALCON_DET1024_SIG_COMPRESSED_MAXSIZE: usize = + falcon_sig_compressed_maxsize(FALCON_DET1024_LOGN) - 40 + 1; +pub const FALCON_DET1024_SIG_CT_SIZE: usize = falcon_sig_ct_size(FALCON_DET1024_LOGN) - 40 + 1; + +const_assert_eq!(FALCON_DET1024_PUBKEY_SIZE, 1793); +const_assert_eq!(FALCON_DET1024_PRIVKEY_SIZE, 2305); +const_assert_eq!(FALCON_DET1024_SIG_COMPRESSED_MAXSIZE, 1423); +const_assert_eq!(FALCON_DET1024_SIG_CT_SIZE, 1538); + +/// Generate a keypair (for Falcon parameter n=1024). +/// +/// The source of randomness is the provided SHAKE256 context `rng`, +/// which must have been already initialized, seeded, and set to output +/// mode (see [`Shake256Context::new_prng_from_seed()`] and +/// [`Shake256Context::new_prng_from_system()`]). +pub fn generate_keypair(rng: &mut Shake256Context) -> Result<(SigningKey, VerifyingKey), Error> { + let mut secret = [0u8; FALCON_DET1024_PRIVKEY_SIZE]; + let mut public = [0u8; FALCON_DET1024_PUBKEY_SIZE]; + match unsafe { + sys::falcon_det1024_keygen( + &mut rng.0 as *mut _, + secret.as_mut_ptr() as *mut _, + public.as_mut_ptr() as *mut _, + ) + } { + 0 => Ok((SigningKey(secret), VerifyingKey(public))), + e => Err(e.into()), + } +} + +/// Deterministic Falcon-1024 signing key. +#[cfg_attr(feature = "zeroize", derive(zeroize::ZeroizeOnDrop))] +pub struct SigningKey(pub(crate) [u8; FALCON_DET1024_PRIVKEY_SIZE]); + +impl SigningKey { + /// Initialize signing key from a byte array. + pub const fn from_bytes(bytes: [u8; FALCON_DET1024_PRIVKEY_SIZE]) -> Self { + SigningKey(bytes) + } + + /// Initialize signing key from a byte slice. + pub fn from_slice(bytes: &[u8]) -> Result { + if bytes.len() != FALCON_DET1024_PRIVKEY_SIZE { + return Err(Error::Size); + } + let mut key = [0u8; FALCON_DET1024_PRIVKEY_SIZE]; + key.copy_from_slice(bytes); + Ok(SigningKey(key)) + } + + /// Get the [`VerifyingKey`] which corresponds to this [`SigningKey`]. + pub fn verifying_key(&self) -> VerifyingKey { + let mut pubkey = [0u8; FALCON_DET1024_PUBKEY_SIZE]; + let mut tmp = [0u8; falcon_tmpsize_makepub(FALCON_DET1024_LOGN)]; + unsafe { + sys::falcon_make_public( + pubkey.as_mut_ptr() as *mut _, + FALCON_DET1024_PUBKEY_SIZE, + self.0.as_ptr() as *const _, + self.0.len(), + tmp.as_mut_ptr() as *mut _, + tmp.len(), + ); + } + VerifyingKey(pubkey) + } + + /// Deterministically sign the data provided in `msg` slice. + /// + /// The resulting compressed-format, variable-length signature is + /// returned in a [`Signature`] object. + /// + /// The resulting signature is incompatible with randomized ("salted") + /// Falcon signatures: it excludes the salt (nonce), adds a salt + /// version byte, and changes the header byte. See the [Deterministic Falcon] + /// specification for further details. + /// + /// [Deterministic Falcon]: https://github.com/algorand/falcon/blob/ce15e75bceb372867daf6b8e81918ab6978686eb/falcon-det.pdf + /// + /// This function implements only the following subset of the + /// specification: + /// + /// - the parameter n is fixed to n=1024 + /// - the signature format is 'compressed' + pub fn sign_compressed(&self, msg: &[u8]) -> Result { + let mut sig = [0u8; FALCON_DET1024_SIG_COMPRESSED_MAXSIZE]; + let mut siglen = 0; + match unsafe { + sys::falcon_det1024_sign_compressed( + sig.as_mut_ptr() as *mut _, + &mut siglen, + self.0.as_ptr() as *const _, + msg.as_ptr() as *const _, + msg.len(), + ) + } { + 0 => Ok(Signature(sig[..siglen].to_vec())), + e => Err(e.into()), + } + } +} + +#[cfg(feature = "signature")] +impl signature::Signer for SigningKey { + fn try_sign(&self, msg: &[u8]) -> Result { + Self::sign_compressed(self, msg).map_err(|_| signature::Error::new()) + } +} + +/// Deterministic Falcon-1024 verifying key (i.e. public key). +#[derive(Clone, Debug)] +pub struct VerifyingKey(pub(crate) [u8; FALCON_DET1024_PUBKEY_SIZE]); + +impl VerifyingKey { + /// Verify the compressed-format, deterministic-mode (det1024) + /// signature provided in `signature` with respect to this verifying + /// key and the data provided in `msg`. + /// + /// This function accepts a strict subset of valid deterministic-mode + /// Falcon signatures, namely, only those having n=1024 and + /// "compressed" signature format (thus matching the choices + /// implemented by [`SigningKey::sign_compressed()`]). + pub fn verify_compressed(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { + match unsafe { + sys::falcon_det1024_verify_compressed( + signature.0.as_ptr() as *const _, + signature.0.len(), + self.0.as_ptr() as *const _, + msg.as_ptr() as *const _, + msg.len(), + ) + } { + 0 => Ok(()), + e => Err(e.into()), + } + } + + /// Verify the CT-format, deterministic-mode (det1024) signature + /// provided in `signature` with respect to this verifying key and the + /// data provided in `msg`. + /// + /// This function accepts a strict subset of valid deterministic-mode + /// Falcon signatures, namely, only those having n=1024 and "CT" + /// signature format. + pub fn verify_ct(&self, msg: &[u8], signature: &CtSignature) -> Result<(), Error> { + match unsafe { + sys::falcon_det1024_verify_ct( + signature.0.as_ptr() as *const _, + self.0.as_ptr() as *const _, + msg.as_ptr() as *const _, + msg.len(), + ) + } { + 0 => Ok(()), + e => Err(e.into()), + } + } +} + +impl AsRef<[u8]> for VerifyingKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +/// Deterministic Falcon-1024 compressed signature (variable-length). +#[derive(Clone, Eq, PartialEq)] +pub struct Signature(pub(crate) Vec); + +impl Signature { + /// Returns the number of bytes in the signature, also referred to as its ‘length’. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns the salt version of a signature. + pub fn salt_version(&self) -> i32 { + unsafe { sys::falcon_det1024_get_salt_version(self.0.as_ptr() as *const _) } + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +/// Deterministic Falcon-1024 constant-time signature (fixed-size). +pub struct CtSignature(pub(crate) [u8; FALCON_DET1024_SIG_CT_SIZE]); + +impl CtSignature { + /// Returns the number of bytes in the signature, also referred to as its ‘length’. + pub const fn len(&self) -> usize { + FALCON_DET1024_SIG_CT_SIZE + } + + /// Returns the salt version of a signature. + pub fn salt_version(&self) -> i32 { + unsafe { sys::falcon_det1024_get_salt_version(self.0.as_ptr() as *const _) } + } +} + +impl TryFrom for CtSignature { + type Error = Error; + + fn try_from(sig: Signature) -> Result { + let mut ct = [0u8; FALCON_DET1024_SIG_CT_SIZE]; + match unsafe { + sys::falcon_det1024_convert_compressed_to_ct( + ct.as_mut_ptr() as *mut _, + sig.0.as_ptr() as *const _, + sig.0.len(), + ) + } { + 0 => Ok(CtSignature(ct)), + e => Err(e.into()), + } + } +} + +#[cfg(feature = "signature")] +impl signature::Verifier for VerifyingKey { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), signature::Error> { + Self::verify_compressed(self, msg, signature).map_err(|_| signature::Error::new()) + } +} + +#[cfg(feature = "signature")] +impl signature::Verifier for VerifyingKey { + fn verify(&self, msg: &[u8], signature: &CtSignature) -> Result<(), signature::Error> { + Self::verify_ct(self, msg, signature).map_err(|_| signature::Error::new()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use signature::{Signer, Verifier}; + + #[test] + fn test_det1024() { + let mut rng = Shake256Context::new_prng_from_system().expect("RNG failed"); + let (sk, vk) = generate_keypair(&mut rng).expect("Keygen failed"); + let msg = b"hello, world!"; + let sig = sk.try_sign(msg).expect("Sign failed"); + assert!(vk.verify(msg, &sig).is_ok()); + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..c512712 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,45 @@ +// Copyright (c) Jeeyong Um +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[derive(Debug)] +pub enum Error { + /// [`Error::Random`] is returned when the library tries to use an + /// OS-provided RNG, but either none is supported, or that RNG fails. + Random, + /// [`Error::Size`] is returned when a buffer has been provided to + /// the library but is too small to receive the intended value. + Size, + /// [`Error::Format`] is returned when decoding of an external object + /// (public key, private key, signature) fails. + Format, + /// [`Error::BadSig`] is returned when verifying a signature, the signature + /// is validly encoded, but its value does not match the provided message + /// and public key. + BadSig, + /// [`Error::BadArg`] is returned when a provided parameter is not in + /// a valid range. + BadArg, + /// [`Error::Internal`] is returned when some internal computation failed. + Internal, + Unknown(i32), +} + +impl From for Error { + fn from(e: i32) -> Self { + match e { + sys::FALCON_ERR_RANDOM => Error::Random, + sys::FALCON_ERR_SIZE => Error::Size, + sys::FALCON_ERR_FORMAT => Error::Format, + sys::FALCON_ERR_BADSIG => Error::BadSig, + sys::FALCON_ERR_BADARG => Error::BadArg, + sys::FALCON_ERR_INTERNAL => Error::Internal, + _ => Error::Unknown(e), + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b132e4f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,31 @@ +// Copyright (c) Jeeyong Um +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Deterministic Falcon post-quantum signature scheme. +//! +//! This crate provides Rust bindings to a deterministic version of +//! [Falcon] post-quantum signature scheme by [Algorand]. +//! +//! [Falcon]: https://falcon-sign.info +//! [Algorand]: https://github.com/algorand/falcon +//! +//! ## MSRV +//! +//! The `falcon-det` minimum supported Rust version is **1.82.0**. + +extern crate falcon_det_sys as sys; + +#[doc(hidden)] +pub mod aux; +pub mod det1024; +pub mod error; +pub mod shake256; + +#[doc(hidden)] +pub use error::Error; diff --git a/src/shake256.rs b/src/shake256.rs new file mode 100644 index 0000000..c92d058 --- /dev/null +++ b/src/shake256.rs @@ -0,0 +1,120 @@ +// Copyright (c) Jeeyong Um +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! SHAKE256 context for PRNG and hashing. +//! +//! SHAKE256 is used in two places: +//! +//! - As a PRNG: all functions that require randomness (key pair +//! generation, signature generation) receive as parameter a [`Shake256Context`], +//! in output mode, from which pseudorandom data is obtained. +//! +//! A SHAKE256 instance, to be used as a RNG, can be initialized +//! from an explicit 48-byte seed, or from an OS-provided RNG. Using +//! an explicit seed is meant for reproducibility of test vectors, +//! or to be used in cases where no OS-provided RNG is available and +//! supported. +//! +//! - As the hashing mechanism for the message which should be signed. +//! The streamed signature API exposes that SHAKE256 object, since +//! the caller then performs the hashing externally. + +use crate::Error; + +/// Context for a SHAKE256 computation. Contents are opaque. +pub struct Shake256Context(pub(crate) sys::shake256_context); + +impl Default for Shake256Context { + fn default() -> Self { + Self::new() + } +} + +impl Shake256Context { + /// Initialize a SHAKE256 context to its initial state. The state is + /// then ready to receive data (with [`Shake256Context::inject()`]). + pub fn new() -> Self { + let mut ctx = core::mem::MaybeUninit::::uninit(); + unsafe { + sys::shake256_init(ctx.as_mut_ptr() as *mut _); + Self(ctx.assume_init()) + } + } + + /// Initialize a SHAKE256 context as a PRNG from the provided seed. + /// This initializes the context, injects the seed, then flips the context + /// to output mode to make it ready to produce bytes. + pub fn new_prng_from_seed(seed: &[u8]) -> Self { + let mut ctx = core::mem::MaybeUninit::::uninit(); + unsafe { + sys::shake256_init_prng_from_seed( + ctx.as_mut_ptr() as *mut _, + seed.as_ptr() as *const _, + seed.len(), + ); + Self(ctx.assume_init()) + } + } + + /// Initialize a SHAKE256 context as a PRNG, using an initial seed from + /// the OS-provided RNG. If there is no known/supported OS-provided RNG, + /// or if that RNG fails, then the context is not properly initialized + /// and [`Error::Random`] is returned. + pub fn new_prng_from_system() -> Result { + let mut ctx = core::mem::MaybeUninit::::uninit(); + unsafe { + match sys::shake256_init_prng_from_system(ctx.as_mut_ptr() as *mut _) { + 0 => Ok(Self(ctx.assume_init())), + e => Err(e.into()), + } + } + } + + /// Inject some data bytes into the SHAKE256 context ("absorb" operation). + /// This function can be called several times, to inject several chunks + /// of data of arbitrary length. + pub fn inject(&mut self, data: &[u8]) { + unsafe { + sys::shake256_inject(&mut self.0, data.as_ptr() as *const _, data.len()); + } + } + + /// Flip the SHAKE256 state to output mode. After this call, [`Shake256Context::inject()`] + /// can no longer be called on the context, but [`Shake256Context::extract()`] can be + /// called. + + /// Flipping is one-way; a given context can be converted back to input + /// mode only by initializing it again, which forgets all previously + /// injected data. + pub fn flip(&mut self) { + unsafe { + sys::shake256_flip(&mut self.0); + } + } + + /// Extract bytes from the SHAKE256 context ("squeeze" operation). The + /// context must have been flipped to output mode (with [`Shake256Context::flip()`]). + /// Arbitrary amounts of data can be extracted, in one or several calls + /// to this function. + pub fn extract_into(&mut self, out: &mut [u8]) { + unsafe { + sys::shake256_extract(&mut self.0, out.as_mut_ptr() as *mut _, out.len()); + } + } + + /// Extract bytes from the SHAKE256 context ("squeeze" operation). The + /// context must have been flipped to output mode (with [`Shake256Context::flip()`]). + /// Arbitrary amounts of data can be extracted, in one or several calls + /// to this function. + pub fn extract(&mut self, len: usize) -> Vec { + let mut out = vec![0u8; len]; + self.extract_into(&mut out); + out + } +}