From 5006104d49de81aa3bf2deea9763fabca60cd1c8 Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Wed, 12 Mar 2025 17:05:21 -0400 Subject: [PATCH 01/12] initial vrf crate structure --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + vrf/Cargo.toml | 7 +++++++ vrf/src/lib.rs | 26 ++++++++++++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 vrf/Cargo.toml create mode 100644 vrf/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 33d30686a..9187f0c89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -663,6 +663,13 @@ dependencies = [ "subtle", ] +[[package]] +name = "vrf" +version = "0.1.0" +dependencies = [ + "digest 0.11.0-pre.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wasi" version = "0.13.3+wasi-0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 21a734666..afcd225a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "signature_derive", "universal-hash", "signature", + "vrf", ] [patch.crates-io] diff --git a/vrf/Cargo.toml b/vrf/Cargo.toml new file mode 100644 index 000000000..9f6a38f8a --- /dev/null +++ b/vrf/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "vrf" +version = "0.1.0" +edition = "2024" + +[dependencies] +digest = "=0.11.0-pre.10" diff --git a/vrf/src/lib.rs b/vrf/src/lib.rs new file mode 100644 index 000000000..227d41389 --- /dev/null +++ b/vrf/src/lib.rs @@ -0,0 +1,26 @@ +use digest::{Output, OutputSizeUser}; + +pub trait Proof +where + H: OutputSizeUser, +{ + fn to_hash(&self) -> Output; +} + +pub trait Prover +where + H: OutputSizeUser, +{ + type Proof: Proof; + + fn prove(&self, alpha: &[u8]) -> Self::Proof; +} + +pub trait Verifier +where + H: OutputSizeUser, +{ + type Proof: Proof; + + fn verify(&self, alpha: &[u8], proof: Self::Proof) -> bool; +} From 35f893fbd363cfc019f0d7f67f7ab86be8f31b16 Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Thu, 13 Mar 2025 08:29:12 -0400 Subject: [PATCH 02/12] init changelog, licenses & readme --- vrf/CHANGELOG.md | 8 ++ vrf/LICENSE-APACHE | 201 +++++++++++++++++++++++++++++++++++++++++++++ vrf/LICENSE-MIT | 25 ++++++ vrf/README.md | 1 + 4 files changed, 235 insertions(+) create mode 100644 vrf/CHANGELOG.md create mode 100644 vrf/LICENSE-APACHE create mode 100644 vrf/LICENSE-MIT create mode 100644 vrf/README.md diff --git a/vrf/CHANGELOG.md b/vrf/CHANGELOG.md new file mode 100644 index 000000000..15a61afa6 --- /dev/null +++ b/vrf/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] diff --git a/vrf/LICENSE-APACHE b/vrf/LICENSE-APACHE new file mode 100644 index 000000000..78173fa2e --- /dev/null +++ b/vrf/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/vrf/LICENSE-MIT b/vrf/LICENSE-MIT new file mode 100644 index 000000000..b723e3eee --- /dev/null +++ b/vrf/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2020-2025 RustCrypto Developers + +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/vrf/README.md b/vrf/README.md new file mode 100644 index 000000000..bd78e7b8a --- /dev/null +++ b/vrf/README.md @@ -0,0 +1 @@ +# [RustCrypto]: Verifiable Random Functions From 97422dad7eaeda05ae75775afef163bcf145e962 Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Thu, 13 Mar 2025 08:31:06 -0400 Subject: [PATCH 03/12] add: license notice in README --- vrf/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/vrf/README.md b/vrf/README.md index bd78e7b8a..bb0974cfe 100644 --- a/vrf/README.md +++ b/vrf/README.md @@ -1 +1,17 @@ # [RustCrypto]: Verifiable Random Functions + +## License + +Licensed under either of + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + From 9884a44b0411d4bde4d526a5cebb18d9ed5e68b2 Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Thu, 13 Mar 2025 08:33:11 -0400 Subject: [PATCH 04/12] add: semver notice in README --- vrf/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vrf/README.md b/vrf/README.md index bb0974cfe..c33f37b14 100644 --- a/vrf/README.md +++ b/vrf/README.md @@ -1,5 +1,10 @@ # [RustCrypto]: Verifiable Random Functions +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + ## License Licensed under either of From b4658fecbea2d1b9c865be5f328f6b2722e56fd7 Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Thu, 13 Mar 2025 08:33:54 -0400 Subject: [PATCH 05/12] chore: rustfmt --- vrf/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vrf/src/lib.rs b/vrf/src/lib.rs index 227d41389..45b926fc1 100644 --- a/vrf/src/lib.rs +++ b/vrf/src/lib.rs @@ -1,7 +1,7 @@ use digest::{Output, OutputSizeUser}; pub trait Proof -where +where H: OutputSizeUser, { fn to_hash(&self) -> Output; @@ -12,7 +12,7 @@ where H: OutputSizeUser, { type Proof: Proof; - + fn prove(&self, alpha: &[u8]) -> Self::Proof; } @@ -21,6 +21,6 @@ where H: OutputSizeUser, { type Proof: Proof; - + fn verify(&self, alpha: &[u8], proof: Self::Proof) -> bool; } From db64078997f9871c7dace849473c2d25e1ad2e0f Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Thu, 24 Apr 2025 19:53:41 -0400 Subject: [PATCH 06/12] expose signature in vrf crate --- Cargo.lock | 1 + vrf/Cargo.toml | 1 + vrf/src/lib.rs | 11 ++--------- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ef71c9f4..8f1b2df1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -677,6 +677,7 @@ name = "vrf" version = "0.1.0" dependencies = [ "digest 0.11.0-pre.10 (registry+https://github.com/rust-lang/crates.io-index)", + "signature 2.3.0-pre.7", ] [[package]] diff --git a/vrf/Cargo.toml b/vrf/Cargo.toml index 9f6a38f8a..e524a0eff 100644 --- a/vrf/Cargo.toml +++ b/vrf/Cargo.toml @@ -5,3 +5,4 @@ edition = "2024" [dependencies] digest = "=0.11.0-pre.10" +signature = "2.3.0-pre.7" diff --git a/vrf/src/lib.rs b/vrf/src/lib.rs index 45b926fc1..09def6cdf 100644 --- a/vrf/src/lib.rs +++ b/vrf/src/lib.rs @@ -1,5 +1,7 @@ use digest::{Output, OutputSizeUser}; +pub use signature::Verifier; + pub trait Proof where H: OutputSizeUser, @@ -15,12 +17,3 @@ where fn prove(&self, alpha: &[u8]) -> Self::Proof; } - -pub trait Verifier -where - H: OutputSizeUser, -{ - type Proof: Proof; - - fn verify(&self, alpha: &[u8], proof: Self::Proof) -> bool; -} From f275a059151af3db4e666abf09d8e51586bde5a7 Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Sun, 8 Jun 2025 17:50:46 -0400 Subject: [PATCH 07/12] Document all items --- Cargo.lock | 8 +++++++- vrf/Cargo.toml | 2 +- vrf/README.md | 8 +++++++- vrf/src/lib.rs | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f1b2df1e..1398451c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -602,6 +602,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "signature" +version = "3.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8852cecbd17ba45978bbbe43061ebe36a2ae376058c5c172e09f72888f8f7de" + [[package]] name = "spki" version = "0.8.0-rc.1" @@ -677,7 +683,7 @@ name = "vrf" version = "0.1.0" dependencies = [ "digest 0.11.0-pre.10 (registry+https://github.com/rust-lang/crates.io-index)", - "signature 2.3.0-pre.7", + "signature 3.0.0-rc.1", ] [[package]] diff --git a/vrf/Cargo.toml b/vrf/Cargo.toml index e524a0eff..f4eeb45e0 100644 --- a/vrf/Cargo.toml +++ b/vrf/Cargo.toml @@ -5,4 +5,4 @@ edition = "2024" [dependencies] digest = "=0.11.0-pre.10" -signature = "2.3.0-pre.7" +signature = "3.0.0-rc.1" diff --git a/vrf/README.md b/vrf/README.md index c33f37b14..c3efa81d4 100644 --- a/vrf/README.md +++ b/vrf/README.md @@ -1,9 +1,11 @@ # [RustCrypto]: Verifiable Random Functions +Traits which provide generic, object-safe APIs for [verifiable random functions]. + ## SemVer Policy - All on-by-default features of this library are covered by SemVer -- MSRV is considered exempt from SemVer as noted above +- MSRV is considered exempt from SemVer ## License @@ -20,3 +22,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. +[//]: # (links) + +[verifiable random functions]: https://en.wikipedia.org/wiki/Verifiable_random_function +[RustCrypto]: https://github.com/RustCrypto/ diff --git a/vrf/src/lib.rs b/vrf/src/lib.rs index 09def6cdf..147171fb0 100644 --- a/vrf/src/lib.rs +++ b/vrf/src/lib.rs @@ -1,19 +1,58 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" +)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![forbid(unsafe_code)] +#![warn( + clippy::mod_module_files, + clippy::unwrap_used, + missing_docs, + rust_2018_idioms, + unused_lifetimes, + missing_debug_implementations, + unused_qualifications +)] + +//! # Design +//! +//! Traits are defined to match the functionality of verifiable random functions in +//! [RFC9381](https://www.rfc-editor.org/rfc/rfc9381.pdf). +//! +//! ## Verifying Proofs +//! +//! Trait based proof verification is delegated to the [`signature::Verifier`] trait, defined in +//! the `signature` crate and re-exported here. The message corresponds to the `alpha` or +//! `alpha_string` in RFC9381 (see section 1.2), and the signature corresponds to the [`Proof`]. + use digest::{Output, OutputSizeUser}; pub use signature::Verifier; + +/// A VRF Proof, denoted `pi` or `pi_string` in RFC9381. See RFC9381 section 1.2 for details. pub trait Proof where H: OutputSizeUser, { + /// Get the hash of the VRF proof. + /// + /// Defined as `VRF_proof_to_hash` in RFC9381 section 2. fn to_hash(&self) -> Output; } +/// A cryptographic key that has the capability to generate VRF proofs. pub trait Prover where H: OutputSizeUser, { + /// Proofs generated by this algorithm. type Proof: Proof; + /// Generate a proof from the given alpha value. + /// + /// defined as `VRF_proof` in RFC9381 section 2. fn prove(&self, alpha: &[u8]) -> Self::Proof; } From 4e44724b77db26da5228527e16d8a1276ceb8516 Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Tue, 24 Jun 2025 19:26:30 -0400 Subject: [PATCH 08/12] remove blanket `SignerMut` impl (breaking change) --- signature/src/signer.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/signature/src/signer.rs b/signature/src/signer.rs index b9eec6eab..7fc10764f 100644 --- a/signature/src/signer.rs +++ b/signature/src/signer.rs @@ -55,13 +55,6 @@ pub trait SignerMut { fn try_sign(&mut self, msg: &[u8]) -> Result; } -/// Blanket impl of [`SignerMut`] for all [`Signer`] types. -impl> SignerMut for T { - fn try_sign(&mut self, msg: &[u8]) -> Result { - T::try_sign(self, msg) - } -} - /// Sign the given prehashed message [`Digest`] using `Self`. /// /// ## Notes From c6f73c32b927dbd783db881fb1ab3de0bf271893 Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Tue, 24 Jun 2025 19:36:07 -0400 Subject: [PATCH 09/12] chore: rustfmt --- Cargo.lock | 45 ++++++++++++++++++++++++++++++++------------- vrf/src/lib.rs | 1 - 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91a3f6b64..1c797ec5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,7 +20,7 @@ dependencies = [ "arrayvec", "blobby", "bytes", - "crypto-common", + "crypto-common 0.2.0-rc.3", "heapless", "inout", ] @@ -115,7 +115,7 @@ name = "cipher" version = "0.5.0-rc.0" dependencies = [ "blobby", - "crypto-common", + "crypto-common 0.2.0-rc.3", "inout", "zeroize", ] @@ -141,8 +141,8 @@ version = "0.6.0-pre" dependencies = [ "aead", "cipher", - "crypto-common", - "digest", + "crypto-common 0.2.0-rc.3", + "digest 0.11.0-rc.0", "elliptic-curve", "password-hash", "signature", @@ -170,6 +170,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "crypto-common" +version = "0.2.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a23fa214dea9efd4dacee5a5614646b30216ae0f05d4bb51bafb50e9da1c5be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "der" version = "0.8.0-rc.5" @@ -180,6 +189,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "digest" +version = "0.11.0-pre.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c478574b20020306f98d61c8ca3322d762e1ff08117422ac6106438605ea516" +dependencies = [ + "block-buffer", + "crypto-common 0.2.0-rc.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "digest" version = "0.11.0-rc.0" @@ -187,7 +206,7 @@ dependencies = [ "blobby", "block-buffer", "const-oid", - "crypto-common", + "crypto-common 0.2.0-rc.3", "subtle", "zeroize", ] @@ -199,7 +218,7 @@ dependencies = [ "base16ct", "base64ct", "crypto-bigint", - "digest", + "digest 0.11.0-rc.0", "ff", "group", "hex-literal", @@ -313,7 +332,7 @@ version = "0.13.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dc6a2fcc35ab09136c6df2cdf9ca49790701420a3a6b5db0987dddbabc79b21" dependencies = [ - "digest", + "digest 0.11.0-rc.0", ] [[package]] @@ -537,7 +556,7 @@ checksum = "aa1d2e6b3cc4e43a8258a9a3b17aa5dfd2cc5186c7024bba8a64aa65b2c71a59" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.11.0-rc.0", ] [[package]] @@ -546,7 +565,7 @@ version = "0.11.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e6a92fd180fd205defdc0b78288ce847c7309d329fd6647a814567e67db50e" dependencies = [ - "digest", + "digest 0.11.0-rc.0", "keccak", ] @@ -554,7 +573,7 @@ dependencies = [ name = "signature" version = "3.0.0-rc.1" dependencies = [ - "digest", + "digest 0.11.0-rc.0", "rand_core", ] @@ -624,7 +643,7 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" name = "universal-hash" version = "0.6.0-rc.1" dependencies = [ - "crypto-common", + "crypto-common 0.2.0-rc.3", "subtle", ] @@ -632,8 +651,8 @@ dependencies = [ name = "vrf" version = "0.1.0" dependencies = [ - "digest 0.11.0-pre.10 (registry+https://github.com/rust-lang/crates.io-index)", - "signature 3.0.0-rc.1", + "digest 0.11.0-pre.10", + "signature", ] [[package]] diff --git a/vrf/src/lib.rs b/vrf/src/lib.rs index 147171fb0..e5c5be0ef 100644 --- a/vrf/src/lib.rs +++ b/vrf/src/lib.rs @@ -31,7 +31,6 @@ use digest::{Output, OutputSizeUser}; pub use signature::Verifier; - /// A VRF Proof, denoted `pi` or `pi_string` in RFC9381. See RFC9381 section 1.2 for details. pub trait Proof where From 55cb8c2dee0de0364ed6064cae83005e6448734c Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Tue, 2 Sep 2025 21:12:54 -0400 Subject: [PATCH 10/12] Merge upstream changes --- .github/workflows/aead.yml | 9 +- .github/workflows/cipher.yml | 11 +- .github/workflows/crypto-common.yml | 9 +- .github/workflows/crypto.yml | 11 +- .github/workflows/digest.yml | 9 +- .github/workflows/elliptic-curve.yml | 20 +- .github/workflows/kem.yml | 9 +- .github/workflows/password-hash.yml | 11 +- .github/workflows/security-audit.yml | 2 +- .github/workflows/signature.yml | 9 +- .github/workflows/universal-hash.yml | 9 +- .github/workflows/workspace.yml | 15 +- Cargo.lock | 195 ++--- Cargo.toml | 4 +- aead/Cargo.toml | 6 +- aead/src/dev.rs | 137 ++-- aead/tests/dummy.rs | 7 +- cipher/Cargo.toml | 10 +- cipher/src/dev.rs | 7 +- cipher/src/dev/block.rs | 335 ++++----- cipher/src/dev/stream.rs | 80 ++- cipher/src/lib.rs | 5 +- cipher/src/stream.rs | 2 + cipher/src/stream/wrapper.rs | 223 ++---- crypto-common/Cargo.toml | 4 +- crypto/Cargo.toml | 2 +- digest/CHANGELOG.md | 4 + digest/Cargo.toml | 6 +- digest/src/block_api/ct_variable.rs | 38 +- digest/src/buffer_macros/fixed.rs | 40 +- digest/src/buffer_macros/variable_ct.rs | 50 +- digest/src/buffer_macros/xof.rs | 40 +- digest/src/dev.rs | 39 +- digest/src/dev/fixed.rs | 33 +- digest/src/dev/mac.rs | 255 ++++--- digest/src/dev/rng.rs | 2 +- digest/src/dev/variable.rs | 33 +- digest/src/dev/xof.rs | 19 +- .../tests/data/fixed_hash_serialization.bin | Bin 0 -> 16 bytes digest/tests/dummy_fixed.rs | 3 + elliptic-curve/Cargo.toml | 22 +- elliptic-curve/src/arithmetic.rs | 3 +- elliptic-curve/src/dev.rs | 25 +- elliptic-curve/src/jwk.rs | 676 ------------------ elliptic-curve/src/lib.rs | 12 +- elliptic-curve/src/ops.rs | 23 +- elliptic-curve/src/point/non_identity.rs | 11 +- elliptic-curve/src/public_key.rs | 63 +- elliptic-curve/src/scalar/nonzero.rs | 38 +- elliptic-curve/src/scalar/primitive.rs | 6 +- elliptic-curve/src/secret_key.rs | 50 +- password-hash/src/salt.rs | 36 - signature/Cargo.toml | 4 +- universal-hash/Cargo.toml | 4 +- 54 files changed, 813 insertions(+), 1863 deletions(-) create mode 100644 digest/tests/data/fixed_hash_serialization.bin delete mode 100644 elliptic-curve/src/jwk.rs diff --git a/.github/workflows/aead.yml b/.github/workflows/aead.yml index 5e052be8f..b5241969f 100644 --- a/.github/workflows/aead.yml +++ b/.github/workflows/aead.yml @@ -15,6 +15,11 @@ defaults: env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Dwarnings" + +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true jobs: build: @@ -28,7 +33,7 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -55,7 +60,7 @@ jobs: - 1.85.0 # MSRV - stable steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/cipher.yml b/.github/workflows/cipher.yml index 048b0c562..23f51d0ae 100644 --- a/.github/workflows/cipher.yml +++ b/.github/workflows/cipher.yml @@ -17,6 +17,11 @@ env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Dwarnings" +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest @@ -29,7 +34,7 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -45,7 +50,7 @@ jobs: if: false # disabled until we stop using pre-releases runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@nightly - uses: RustCrypto/actions/cargo-hack-install@master @@ -61,7 +66,7 @@ jobs: - 1.85.0 # MSRV - stable steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/crypto-common.yml b/.github/workflows/crypto-common.yml index 689ae58a5..3d93597ed 100644 --- a/.github/workflows/crypto-common.yml +++ b/.github/workflows/crypto-common.yml @@ -16,6 +16,11 @@ env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Dwarnings" +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest @@ -28,7 +33,7 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -49,7 +54,7 @@ jobs: - 1.85.0 # MSRV - stable steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/crypto.yml b/.github/workflows/crypto.yml index e2926600c..88894e8f0 100644 --- a/.github/workflows/crypto.yml +++ b/.github/workflows/crypto.yml @@ -16,6 +16,11 @@ env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Dwarnings" +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest @@ -28,7 +33,7 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -42,7 +47,7 @@ jobs: if: false # disabled until we stop using pre-releases runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -59,7 +64,7 @@ jobs: - 1.85.0 # MSRV - stable steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/digest.yml b/.github/workflows/digest.yml index f89b13c5d..c6b67ea1a 100644 --- a/.github/workflows/digest.yml +++ b/.github/workflows/digest.yml @@ -15,6 +15,11 @@ defaults: env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Dwarnings" + +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true jobs: build: @@ -28,7 +33,7 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -50,7 +55,7 @@ jobs: - 1.85.0 # MSRV - stable steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/elliptic-curve.yml b/.github/workflows/elliptic-curve.yml index 43343005d..b35b68ff0 100644 --- a/.github/workflows/elliptic-curve.yml +++ b/.github/workflows/elliptic-curve.yml @@ -18,6 +18,11 @@ env: RUSTFLAGS: "-Dwarnings" RUSTDOCFLAGS: "-Dwarnings" +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest @@ -30,7 +35,7 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -43,7 +48,6 @@ jobs: - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features dev - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features digest - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features ecdh - - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features jwk - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features pem - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features pkcs8 - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features sec1 @@ -52,14 +56,14 @@ jobs: - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,arithmetic,pkcs8 - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,serde - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features arithmetic,serde - - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,digest,ecdh,jwk,pem,pkcs8,sec1,serde + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,digest,ecdh,pem,pkcs8,sec1,serde minimal-versions: - # Temporarily disabled until elliptic-curve 0.13.0-pre.0 is published + # Temporarily disabled until elliptic-curve 0.14.0 is published if: false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -77,7 +81,7 @@ jobs: - stable - nightly steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -90,7 +94,7 @@ jobs: test-careful: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@nightly - run: cargo install cargo-careful - run: cargo careful test --all-features @@ -100,7 +104,7 @@ jobs: env: MIRIFLAGS: "-Zmiri-symbolic-alignment-check -Zmiri-strict-provenance" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@nightly - run: rustup component add miri && cargo miri setup - run: cargo miri test --all-features diff --git a/.github/workflows/kem.yml b/.github/workflows/kem.yml index 168f270dc..6ca2b5b73 100644 --- a/.github/workflows/kem.yml +++ b/.github/workflows/kem.yml @@ -16,6 +16,11 @@ env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Dwarnings" +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest @@ -28,7 +33,7 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -44,7 +49,7 @@ jobs: - 1.85.0 # MSRV - stable steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/password-hash.yml b/.github/workflows/password-hash.yml index 670fd7e54..2b08a42e8 100644 --- a/.github/workflows/password-hash.yml +++ b/.github/workflows/password-hash.yml @@ -16,6 +16,11 @@ env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Dwarnings" +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest @@ -28,7 +33,7 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -44,7 +49,7 @@ jobs: if: false # disabled until we stop using pre-releases runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -61,7 +66,7 @@ jobs: - 1.85.0 # MSRV - stable steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index 8c535dc00..d07cd6025 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -15,7 +15,7 @@ jobs: name: Security Audit Workspace runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Cache cargo bin uses: actions/cache@v4 with: diff --git a/.github/workflows/signature.yml b/.github/workflows/signature.yml index 1105f50b1..8c067c1ec 100644 --- a/.github/workflows/signature.yml +++ b/.github/workflows/signature.yml @@ -17,6 +17,11 @@ env: RUSTFLAGS: "-Dwarnings" RUSTDOCFLAGS: "-Dwarnings" +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest @@ -29,7 +34,7 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -52,7 +57,7 @@ jobs: - 1.85.0 # Minimum Rust version the tests pass on - stable steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/universal-hash.yml b/.github/workflows/universal-hash.yml index 232dc4676..f5777667a 100644 --- a/.github/workflows/universal-hash.yml +++ b/.github/workflows/universal-hash.yml @@ -16,6 +16,11 @@ env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Dwarnings" +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest @@ -28,7 +33,7 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -50,7 +55,7 @@ jobs: - 1.85.0 # MSRV - stable steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 564f5925a..1638edd4b 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -16,11 +16,16 @@ env: RUSTFLAGS: "-Dwarnings" RUSTDOCFLAGS: "-Dwarnings" +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: RustCrypto/actions/cargo-cache@master - uses: dtolnay/rust-toolchain@master with: @@ -31,7 +36,7 @@ jobs: doc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -41,7 +46,7 @@ jobs: rustfmt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -51,5 +56,5 @@ jobs: typos: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: crate-ci/typos@v1.34.0 + - uses: actions/checkout@v5 + - uses: crate-ci/typos@v1.35.5 diff --git a/Cargo.lock b/Cargo.lock index 985310321..1fa924650 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,7 +15,7 @@ dependencies = [ [[package]] name = "aead" -version = "0.6.0-rc.1" +version = "0.6.0-rc.2" dependencies = [ "arrayvec", "blobby", @@ -39,9 +39,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base16ct" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +checksum = "d8b59d472eab27ade8d770dcb11da7201c11234bef9f82ce7aa517be028d462b" [[package]] name = "base64ct" @@ -51,9 +51,9 @@ checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "bitvec" @@ -70,13 +70,13 @@ dependencies = [ [[package]] name = "blobby" version = "0.4.0-pre.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a859067dcb257cb2ae028cb821399b55140b76fb8b2a360e052fe109019db43" +source = "git+https://github.com/RustCrypto/utils#adfccfea2686ef191b607f653cc3587753b6ec66" [[package]] name = "block-buffer" -version = "0.11.0-rc.4" -source = "git+https://github.com/RustCrypto/utils.git#6fd0e8ddc827d7e7d9242e130f8944fc9ca328cf" +version = "0.11.0-rc.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9ef36a6fcdb072aa548f3da057640ec10859eb4e91ddf526ee648d50c76a949" dependencies = [ "hybrid-array", "zeroize", @@ -84,9 +84,9 @@ dependencies = [ [[package]] name = "block-padding" -version = "0.4.0-rc.3" +version = "0.4.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee88d14c41bbae2e333f574a27fc73d96fe1039e5a356c20d06a7f2a34cd8e5a" +checksum = "7e59c1aab3e6c5e56afe1b7e8650be9b5a791cb997bdea449194ae62e4bf8c73" dependencies = [ "hybrid-array", ] @@ -99,22 +99,23 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cipher" -version = "0.5.0-rc.0" +version = "0.5.0-rc.1" dependencies = [ "blobby", - "crypto-common 0.2.0-rc.3", + "block-buffer", + "crypto-common", "inout", "zeroize", ] @@ -125,15 +126,6 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e" -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - [[package]] name = "crypto" version = "0.6.0-pre" @@ -150,9 +142,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.7.0-pre.7" +version = "0.7.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ff38607b7ebe30e4715eeb0a0427ff42e3b6b47b2df55a775e767ef2658ccd" +checksum = "191664d1a454b91a62c539643b9742dd178357e50920060163bdbe72ed82fdfa" dependencies = [ "hybrid-array", "num-traits", @@ -163,7 +155,7 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0-rc.3" +version = "0.2.0-rc.4" dependencies = [ "hybrid-array", "rand_core", @@ -180,9 +172,9 @@ dependencies = [ [[package]] name = "der" -version = "0.8.0-rc.7" +version = "0.8.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2fe0a4fafae25053c19a03fefe040607bda956b4941d692ed9fb9d3c18a3193" +checksum = "7050e8041c28720851f7db83183195b6acf375bb7bb28e3b86f0fe6cbd69459d" dependencies = [ "const-oid", "pem-rfc7468", @@ -191,17 +183,7 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.0-pre.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c478574b20020306f98d61c8ca3322d762e1ff08117422ac6106438605ea516" -dependencies = [ - "block-buffer", - "crypto-common 0.2.0-rc.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "digest" -version = "0.11.0-rc.0" +version = "0.11.0-rc.1" dependencies = [ "blobby", "block-buffer", @@ -213,10 +195,9 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.14.0-rc.10" +version = "0.14.0-rc.12" dependencies = [ "base16ct", - "base64ct", "crypto-bigint", "digest 0.11.0-rc.0", "ff", @@ -228,10 +209,7 @@ dependencies = [ "pkcs8", "rand_core", "sec1", - "serde_json", "serdect", - "sha2", - "sha3", "subtle", "zeroize", ] @@ -337,9 +315,9 @@ dependencies = [ [[package]] name = "hybrid-array" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d15931895091dea5c47afa5b3c9a01ba634b311919fd4d41388fa0e3d76af" +checksum = "6fe39a812f039072707ce38020acbab2f769087952eddd9e2b890f37654b2349" dependencies = [ "typenum", "zeroize", @@ -347,29 +325,14 @@ dependencies = [ [[package]] name = "inout" -version = "0.2.0-rc.5" +version = "0.2.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c774c86bce20ea04abe1c37cf0051c5690079a3a28ef5fdac2a5a0412b3d7d74" +checksum = "1603f76010ff924b616c8f44815a42eb10fb0b93d308b41deaa8da6d4251fd4b" dependencies = [ "block-padding", "hybrid-array", ] -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "keccak" -version = "0.2.0-pre.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cdd4f0dc5807b9a2b25dd48a3f58e862606fe7bd47f41ecde36e97422d7e90" -dependencies = [ - "cpufeatures", -] - [[package]] name = "kem" version = "0.3.0-pre.0" @@ -380,15 +343,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" - -[[package]] -name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "num-bigint" @@ -449,18 +406,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -479,25 +436,18 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand_core" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a509b1a2ffbe92afab0e55c8fd99dea1c280e8171bd2d88682bb20bc41cbc2c" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom", - "zerocopy", ] -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "sec1" -version = "0.8.0-rc.8" +version = "0.8.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54dee398d74b1d03d78ddc09c90e456bf906b5b7aa790ba4f48b025b2179e5d" +checksum = "f5e67a3c9fb9a8f065af9fa30d65812fcc16a66cbf911eff1f6946957ce48f16" dependencies = [ "base16ct", "der", @@ -524,55 +474,22 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "serde_json" -version = "1.0.139" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", + "syn 2.0.106", ] [[package]] name = "serdect" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53" +checksum = "d3ef0e35b322ddfaecbc60f34ab448e157e48531288ee49fafbb053696b8ffe2" dependencies = [ "base16ct", "serde", ] -[[package]] -name = "sha2" -version = "0.11.0-rc.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa1d2e6b3cc4e43a8258a9a3b17aa5dfd2cc5186c7024bba8a64aa65b2c71a59" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.11.0-rc.0", -] - -[[package]] -name = "sha3" -version = "0.11.0-rc.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e6a92fd180fd205defdc0b78288ce847c7309d329fd6647a814567e67db50e" -dependencies = [ - "digest 0.11.0-rc.0", - "keccak", -] - [[package]] name = "signature" -version = "3.0.0-rc.2" +version = "3.0.0-rc.3" dependencies = [ "digest 0.11.0-rc.0", "rand_core", @@ -613,9 +530,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.98" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -642,7 +559,7 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "universal-hash" -version = "0.6.0-rc.1" +version = "0.6.0-rc.2" dependencies = [ "crypto-common 0.2.0-rc.3", "subtle", @@ -683,26 +600,6 @@ dependencies = [ "tap", ] -[[package]] -name = "zerocopy" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - [[package]] name = "zeroize" version = "1.8.1" diff --git a/Cargo.toml b/Cargo.toml index cabbff8ea..af3b49db9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,5 +18,5 @@ members = [ digest = { path = "digest" } signature = { path = "signature" } -# https://github.com/RustCrypto/utils/pull/1192 -block-buffer = { git = "https://github.com/RustCrypto/utils.git" } +# https://github.com/RustCrypto/utils/pull/1187 +blobby = { git = "https://github.com/RustCrypto/utils" } diff --git a/aead/Cargo.toml b/aead/Cargo.toml index fbfdc8023..884ad61cb 100644 --- a/aead/Cargo.toml +++ b/aead/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aead" -version = "0.6.0-rc.1" +version = "0.6.0-rc.2" authors = ["RustCrypto Developers"] edition = "2024" rust-version = "1.85" @@ -16,8 +16,8 @@ such as AES-GCM as ChaCha20Poly1305, which provide a high-level API """ [dependencies] -crypto-common = { version = "0.2.0-rc.3", path = "../crypto-common" } -inout = "0.2.0-rc.4" +crypto-common = { version = "0.2.0-rc.4", path = "../crypto-common" } +inout = "0.2.0-rc.6" # optional dependencies arrayvec = { version = "0.7", optional = true, default-features = false } diff --git a/aead/src/dev.rs b/aead/src/dev.rs index f216b72cc..2f942d557 100644 --- a/aead/src/dev.rs +++ b/aead/src/dev.rs @@ -1,43 +1,80 @@ //! Development-related functionality use crate::{ - Aead, AeadInOut, Nonce, Payload, Tag, TagPosition, array::typenum::Unsigned, inout::InOutBuf, + Aead, AeadInOut, Payload, Tag, TagPosition, array::typenum::Unsigned, inout::InOutBuf, }; pub use blobby; +use crypto_common::KeyInit; + +/// AEAD test vector +#[derive(Debug, Clone, Copy)] +pub struct TestVector { + /// Initialization key + pub key: &'static [u8], + /// Nonce + pub nonce: &'static [u8], + /// Additional associated data + pub aad: &'static [u8], + /// Plaintext + pub plaintext: &'static [u8], + /// Ciphertext + pub ciphertext: &'static [u8], + /// Whether the test vector should pass (`[1]`) or fail (`[0]`) + pub pass: &'static [u8], +} /// Run AEAD test for the provided passing test vector -pub fn run_pass_test( - cipher: &C, - nonce: &Nonce, - aad: &[u8], - pt: &[u8], - ct: &[u8], +pub fn pass_test( + &TestVector { + key, + nonce, + aad, + plaintext, + ciphertext, + pass, + }: &TestVector, ) -> Result<(), &'static str> { + assert_eq!(pass, &[1]); + let nonce = nonce.try_into().expect("wrong nonce size"); + let cipher = ::new_from_slice(key).expect("failed to initialize the cipher"); + let res = cipher - .encrypt(nonce, Payload { aad, msg: pt }) + .encrypt( + nonce, + Payload { + aad, + msg: plaintext, + }, + ) .map_err(|_| "encryption failure")?; - if res != ct { + if res != ciphertext { return Err("encrypted data is different from target ciphertext"); } let res = cipher - .decrypt(nonce, Payload { aad, msg: ct }) + .decrypt( + nonce, + Payload { + aad, + msg: ciphertext, + }, + ) .map_err(|_| "decryption failure")?; - if res != pt { + if res != plaintext { return Err("decrypted data is different from target plaintext"); } let (ct, tag) = match C::TAG_POSITION { TagPosition::Prefix => { - let (tag, ct) = ct.split_at(C::TagSize::USIZE); + let (tag, ct) = ciphertext.split_at(C::TagSize::USIZE); (ct, tag) } - TagPosition::Postfix => ct.split_at(pt.len()), + TagPosition::Postfix => ciphertext.split_at(plaintext.len()), }; let tag: &Tag = tag.try_into().expect("tag has correct length"); // Fill output buffer with "garbage" to test that its data does not get read during encryption - let mut buf: alloc::vec::Vec = (0..pt.len()).map(|i| i as u8).collect(); - let inout_buf = InOutBuf::new(pt, &mut buf).expect("pt and buf have the same length"); + let mut buf: alloc::vec::Vec = (0..plaintext.len()).map(|i| i as u8).collect(); + let inout_buf = InOutBuf::new(plaintext, &mut buf).expect("pt and buf have the same length"); let calc_tag = cipher .encrypt_inout_detached(nonce, aad, inout_buf) @@ -50,13 +87,15 @@ pub fn run_pass_test( } // Fill output buffer with "garbage" - buf.iter_mut().enumerate().for_each(|(i, v)| *v = i as u8); + buf.iter_mut() + .enumerate() + .for_each(|(i, v): (usize, &mut u8)| *v = i as u8); let inout_buf = InOutBuf::new(ct, &mut buf).expect("ct and buf have the same length"); cipher .decrypt_inout_detached(nonce, aad, inout_buf, tag) .map_err(|_| "decrypt_inout_detached: decryption failure")?; - if pt != buf { + if plaintext != buf { return Err("decrypt_inout_detached: plaintext mismatch"); } @@ -64,13 +103,27 @@ pub fn run_pass_test( } /// Run AEAD test for the provided failing test vector -pub fn run_fail_test( - cipher: &C, - nonce: &Nonce, - aad: &[u8], - ct: &[u8], +pub fn fail_test( + &TestVector { + key, + nonce, + aad, + ciphertext, + pass, + .. + }: &TestVector, ) -> Result<(), &'static str> { - let res = cipher.decrypt(nonce, Payload { aad, msg: ct }); + assert_eq!(pass, &[0]); + let nonce = nonce.try_into().expect("wrong nonce size"); + let cipher = ::new_from_slice(key).expect("failed to initialize the cipher"); + + let res = cipher.decrypt( + nonce, + Payload { + aad, + msg: ciphertext, + }, + ); if res.is_ok() { Err("decryption must return error") } else { @@ -84,33 +137,29 @@ macro_rules! new_test { ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { #[test] fn $name() { - use $crate::KeyInit; - use $crate::dev::blobby::Blob6Iterator; - - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - for (i, row) in Blob6Iterator::new(data).unwrap().enumerate() { - let [key, nonce, aad, pt, ct, status] = row.unwrap(); - let key = key.try_into().expect("wrong key size"); - let nonce = nonce.try_into().expect("wrong nonce size"); - let cipher = <$cipher as KeyInit>::new(key); - - let res = match status { - [0] => $crate::dev::run_fail_test(&cipher, nonce, aad, ct), - [1] => $crate::dev::run_pass_test(&cipher, nonce, aad, pt, ct), - _ => panic!("invalid value for pass flag"), + use $crate::dev::TestVector; + + $crate::dev::blobby::parse_into_structs!( + include_bytes!(concat!("data/", $test_name, ".blb")); + static TEST_VECTORS: &[ + TestVector { key, nonce, aad, plaintext, ciphertext, pass } + ]; + ); + + for (i, tv) in TEST_VECTORS.iter().enumerate() { + let pass = tv.pass[0] == 1; + let res = if pass { + $crate::dev::pass_test::<$cipher>(tv) + } else { + $crate::dev::fail_test::<$cipher>(tv) }; - let mut pass = status[0] == 1; + if let Err(reason) = res { panic!( "\n\ Failed test #{i}\n\ reason:\t{reason:?}\n\ - key:\t{key:?}\n\ - nonce:\t{nonce:?}\n\ - aad:\t{aad:?}\n\ - plaintext:\t{pt:?}\n\ - ciphertext:\t{ct:?}\n\ - pass:\t{pass}\n" + test vector:\t{tv:?}\n" ); } } diff --git a/aead/tests/dummy.rs b/aead/tests/dummy.rs index 817ab945c..ef7606f00 100644 --- a/aead/tests/dummy.rs +++ b/aead/tests/dummy.rs @@ -1,5 +1,6 @@ //! This module defines dummy (horribly insecure!) AEAD implementations //! to test implementation of the AEAD traits and helper macros in the `dev` module. +#![cfg(feature = "dev")] use aead::{ AeadCore, AeadInOut, Error, Key, KeyInit, KeySizeUser, Nonce, Result, Tag, TagPosition, array::Array, consts::U8, @@ -91,7 +92,7 @@ impl DummyAead { } } -struct PrefixDummyAead(DummyAead); +pub struct PrefixDummyAead(DummyAead); impl KeySizeUser for PrefixDummyAead { type KeySize = U8; @@ -130,7 +131,7 @@ impl AeadInOut for PrefixDummyAead { } } -struct PostfixDummyAead(DummyAead); +pub struct PostfixDummyAead(DummyAead); impl KeySizeUser for PostfixDummyAead { type KeySize = U8; @@ -169,7 +170,5 @@ impl AeadInOut for PostfixDummyAead { } } -#[cfg(feature = "dev")] aead::new_test!(dummy_prefix, "prefix", PrefixDummyAead); -#[cfg(feature = "dev")] aead::new_test!(dummy_postfix, "postfix", PostfixDummyAead); diff --git a/cipher/Cargo.toml b/cipher/Cargo.toml index 393703371..d5e24b470 100644 --- a/cipher/Cargo.toml +++ b/cipher/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cipher" -version = "0.5.0-rc.0" +version = "0.5.0-rc.1" authors = ["RustCrypto Developers"] edition = "2024" rust-version = "1.85" @@ -13,21 +13,23 @@ categories = ["cryptography", "no-std"] description = "Traits for describing block ciphers and stream ciphers" [dependencies] -crypto-common = { version = "0.2.0-rc.3", path = "../crypto-common" } -inout = "0.2.0-rc.4" +crypto-common = { version = "0.2.0-rc.4", path = "../crypto-common" } +inout = "0.2.0-rc.6" # optional dependencies blobby = { version = "0.4.0-pre.0", optional = true } +block-buffer = { version = "0.11.0-rc.5", optional = true } zeroize = { version = "1.8", optional = true, default-features = false } [features] alloc = [] block-padding = ["inout/block-padding"] +stream-wrapper = ["block-buffer"] # Enable random key and IV generation methods rand_core = ["crypto-common/rand_core"] os_rng = ["crypto-common/os_rng", "rand_core"] dev = ["blobby"] -zeroize = ["dep:zeroize", "crypto-common/zeroize"] +zeroize = ["dep:zeroize", "crypto-common/zeroize", "block-buffer?/zeroize"] [package.metadata.docs.rs] all-features = true diff --git a/cipher/src/dev.rs b/cipher/src/dev.rs index 6bedbe989..efd050267 100644 --- a/cipher/src/dev.rs +++ b/cipher/src/dev.rs @@ -1,2 +1,5 @@ -mod block; -mod stream; +//! Development-related functionality +pub mod block; +pub mod stream; + +pub use blobby; diff --git a/cipher/src/dev/block.rs b/cipher/src/dev/block.rs index 6a7f90426..7234535fb 100644 --- a/cipher/src/dev/block.rs +++ b/cipher/src/dev/block.rs @@ -1,213 +1,154 @@ -//! Development-related functionality - -/// Define block cipher test -#[macro_export] -macro_rules! block_cipher_test { - ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { - #[test] - fn $name() { - use cipher::{ - BlockSizeUser, KeyInit, - array::Array, - blobby::Blob3Iterator, - block::{BlockCipherDecrypt, BlockCipherEncrypt}, - typenum::Unsigned, - }; - - fn run_test(key: &[u8], pt: &[u8], ct: &[u8]) -> bool { - let mut state = <$cipher as KeyInit>::new_from_slice(key).unwrap(); - - let mut block = Array::try_from(pt).unwrap(); - state.encrypt_block(&mut block); - if ct != block.as_slice() { - return false; - } - - state.decrypt_block(&mut block); - if pt != block.as_slice() { - return false; - } - - true - } - - fn run_par_test(key: &[u8], pt: &[u8]) -> bool { - type Block = cipher::Block<$cipher>; - - let mut state = <$cipher as KeyInit>::new_from_slice(key).unwrap(); - - let block = Block::try_from(pt).unwrap(); - let mut blocks1 = vec![block; 101]; - for (i, b) in blocks1.iter_mut().enumerate() { - *b = block; - b[0] = b[0].wrapping_add(i as u8); - } - let mut blocks2 = blocks1.clone(); +//! Development-related functionality for block ciphers + +use crate::{Block, BlockCipherDecrypt, BlockCipherEncrypt, KeyInit}; + +/// Block cipher test vector +#[derive(Debug, Clone, Copy)] +pub struct TestVector { + /// Initialization key + pub key: &'static [u8], + /// Plaintext block + pub plaintext: &'static [u8], + /// Ciphertext block + pub ciphertext: &'static [u8], +} - // check that `encrypt_blocks` and `encrypt_block` - // result in the same ciphertext - state.encrypt_blocks(&mut blocks1); - for b in blocks2.iter_mut() { - state.encrypt_block(b); - } - if blocks1 != blocks2 { - return false; - } +/// Block cipher encryption test +pub fn block_cipher_enc_test( + tv: &TestVector, +) -> Result<(), &'static str> { + let Ok(state) = C::new_from_slice(tv.key) else { + return Err("cipher initialization failure"); + }; - // check that `encrypt_blocks` and `encrypt_block` - // result in the same plaintext - state.decrypt_blocks(&mut blocks1); - for b in blocks2.iter_mut() { - state.decrypt_block(b); - } - if blocks1 != blocks2 { - return false; - } + let Ok(pt_block) = Block::::try_from(tv.plaintext) else { + return Err("unexpected size of plaintext block"); + }; + let Ok(ct_block) = Block::::try_from(tv.ciphertext) else { + return Err("unexpected size of ciphertext block"); + }; - true - } + let mut block = pt_block.clone(); + state.encrypt_block(&mut block); + if block != ct_block { + return Err("single block encryption failure"); + } + + let mut blocks1: [Block; 101] = core::array::from_fn(|i| { + let mut block = pt_block.clone(); + block[0] ^= i as u8; + block + }); + let mut blocks2 = blocks1.clone(); + + // Check that `encrypt_blocks` and `encrypt_block` result in the same ciphertext + state.encrypt_blocks(&mut blocks1); + for b in blocks2.iter_mut() { + state.encrypt_block(b); + } + if blocks1 != blocks2 { + return Err("multi-block encryption failure"); + } + + Ok(()) +} - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { - let [key, pt, ct] = row.unwrap(); - if !run_test(key, pt, ct) { - panic!( - "\n\ - Failed test №{}\n\ - key:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, key, pt, ct, - ); - } +/// Block cipher encryption test +pub fn block_cipher_dec_test( + tv: &TestVector, +) -> Result<(), &'static str> { + let Ok(state) = C::new_from_slice(tv.key) else { + return Err("cipher initialization failure"); + }; - // test parallel blocks encryption/decryption - if !run_par_test(key, pt) { - panic!( - "\n\ - Failed parallel test №{}\n\ - key:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, key, pt, ct, - ); - } - } - } + let Ok(pt_block) = Block::::try_from(tv.plaintext) else { + return Err("unexpected size of plaintext block"); + }; + let Ok(ct_block) = Block::::try_from(tv.ciphertext) else { + return Err("unexpected size of ciphertext block"); }; + + let mut block = ct_block.clone(); + state.decrypt_block(&mut block); + if block != pt_block { + return Err("single block decryption failure"); + } + + let mut blocks1: [Block; 101] = core::array::from_fn(|i| { + let mut block = ct_block.clone(); + block[0] ^= i as u8; + block + }); + let mut blocks2 = blocks1.clone(); + + // Check that `encrypt_blocks` and `encrypt_block` result in the same ciphertext + state.decrypt_blocks(&mut blocks1); + for b in blocks2.iter_mut() { + state.decrypt_block(b); + } + if blocks1 != blocks2 { + return Err("multi-block decryption failure"); + } + + Ok(()) } -/// Define block mode encryption test +/// Define block cipher test #[macro_export] -macro_rules! block_mode_enc_test { - ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { - #[test] - fn $name() { - use cipher::{ - BlockCipherEncrypt, BlockModeEncrypt, BlockSizeUser, KeyIvInit, array::Array, - blobby::Blob4Iterator, inout::InOutBuf, typenum::Unsigned, - }; - - fn run_test(key: &[u8], iv: &[u8], pt: &[u8], ct: &[u8]) -> bool { - assert_eq!(pt.len(), ct.len()); - // test block-by-block processing - let mut state = <$cipher as KeyIvInit>::new_from_slices(key, iv).unwrap(); - - let mut out = vec![0u8; ct.len()]; - let mut buf = InOutBuf::new(pt, &mut out).unwrap(); - let (blocks, tail) = buf.reborrow().into_chunks(); - assert_eq!(tail.len(), 0); - for block in blocks { - state.encrypt_block_inout(block); - } - if buf.get_out() != ct { - return false; - } - - // test multi-block processing - let mut state = <$cipher as KeyIvInit>::new_from_slices(key, iv).unwrap(); - buf.get_out().iter_mut().for_each(|b| *b = 0); - let (blocks, _) = buf.reborrow().into_chunks(); - state.encrypt_blocks_inout(blocks); - if buf.get_out() != ct { - return false; - } - - true - } - - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - for (i, row) in Blob4Iterator::new(data).unwrap().enumerate() { - let [key, iv, pt, ct] = row.unwrap(); - if !run_test(key, iv, pt, ct) { - panic!( - "\n\ - Failed test №{}\n\ - key:\t{:?}\n\ - iv:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, key, iv, pt, ct, - ); - } - } - } +macro_rules! block_cipher_test { + ( + encdec: $name:ident, $test_name:expr, $cipher:ty $(,)? + ) => { + $crate::block_cipher_test!( + inner: $name, $test_name, $cipher, + "encryption" block_cipher_enc_test, + "decryption" block_cipher_dec_test, + ); + }; + ( + enc: $name:ident, $test_name:expr, $cipher:ty $(,)? + ) => { + $crate::block_cipher_test!( + inner: $name, $test_name, $cipher, + "encryption" block_cipher_enc_test, + ); + }; + ( + dec: $name:ident, $test_name:expr, $cipher:ty $(,)? + ) => { + $crate::block_cipher_test!( + inner: $name, $test_name, $cipher, + "decryption" block_cipher_dec_test, + ); }; -} -/// Define block mode decryption test -#[macro_export] -macro_rules! block_mode_dec_test { - ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { + ( + inner: $name:ident, $test_name:expr, $cipher:ty, + $($test_desc:literal $test_fn:ident,)* + ) => { #[test] fn $name() { - use cipher::{ - BlockCipherDecrypt, BlockModeDecrypt, BlockSizeUser, KeyIvInit, array::Array, - blobby::Blob4Iterator, inout::InOutBuf, typenum::Unsigned, - }; - - fn run_test(key: &[u8], iv: &[u8], pt: &[u8], ct: &[u8]) -> bool { - assert_eq!(pt.len(), ct.len()); - // test block-by-block processing - let mut state = <$cipher as KeyIvInit>::new_from_slices(key, iv).unwrap(); - - let mut out = vec![0u8; pt.len()]; - let mut buf = InOutBuf::new(ct, &mut out).unwrap(); - let (blocks, tail) = buf.reborrow().into_chunks(); - assert_eq!(tail.len(), 0); - for block in blocks { - state.decrypt_block_inout(block); - } - if buf.get_out() != pt { - return false; - } - - // test multi-block processing - let mut state = <$cipher as KeyIvInit>::new_from_slices(key, iv).unwrap(); - buf.get_out().iter_mut().for_each(|b| *b = 0); - let (blocks, _) = buf.reborrow().into_chunks(); - state.decrypt_blocks_inout(blocks); - if buf.get_out() != pt { - return false; - } - - true - } - - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - for (i, row) in Blob4Iterator::new(data).unwrap().enumerate() { - let [key, iv, pt, ct] = row.unwrap(); - if !run_test(key, iv, pt, ct) { - panic!( - "\n\ - Failed test №{}\n\ - key:\t{:?}\n\ - iv:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, key, iv, pt, ct, - ); - } + use $crate::dev::block::TestVector; + + $crate::dev::blobby::parse_into_structs!( + include_bytes!(concat!("data/", $test_name, ".blb")); + static TEST_VECTORS: &[ + TestVector { key, plaintext, ciphertext } + ]; + ); + + for (i, tv) in TEST_VECTORS.iter().enumerate() { + $( + let res = $test_fn(tv); + if let Err(reason) = res { + panic!(concat!( + "\n\ + Failed ", $test_desc, " test #{i}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" + )); + } + )* } } }; diff --git a/cipher/src/dev/stream.rs b/cipher/src/dev/stream.rs index ff530f4cd..50e13b28f 100644 --- a/cipher/src/dev/stream.rs +++ b/cipher/src/dev/stream.rs @@ -1,4 +1,45 @@ -//! Development-related functionality +//! Development-related functionality for stream ciphers +use crate::{KeyIvInit, StreamCipher}; + +/// Stream cipher test vector +#[derive(Clone, Copy, Debug)] +pub struct TestVector { + /// Initialization key + pub key: &'static [u8], + /// Initialization vector + pub iv: &'static [u8], + /// Plaintext + pub plaintext: &'static [u8], + /// Ciphertext + pub ciphertext: &'static [u8], +} + +/// Run stream cipher test +pub fn stream_cipher_test( + tv: &TestVector, +) -> Result<(), &'static str> { + if tv.plaintext.len() != tv.ciphertext.len() { + return Err("mismatch of plaintext and ciphertext lengths"); + } + let mut buf = [0u8; 256]; + for chunk_len in 1..256 { + let Ok(mut mode) = C::new_from_slices(tv.key, tv.iv) else { + return Err("cipher initialization failure"); + }; + let pt_chunks = tv.plaintext.chunks(chunk_len); + let ct_chunks = tv.ciphertext.chunks(chunk_len); + for (pt_chunk, ct_chunk) in pt_chunks.zip(ct_chunks) { + let buf = &mut buf[..pt_chunk.len()]; + buf.copy_from_slice(pt_chunk); + mode.apply_keystream(buf); + + if buf != ct_chunk { + return Err("ciphertext mismatch"); + } + } + } + Ok(()) +} /// Test core functionality of synchronous stream cipher #[macro_export] @@ -6,29 +47,24 @@ macro_rules! stream_cipher_test { ($name:ident, $test_name:expr, $cipher:ty $(,)?) => { #[test] fn $name() { - use cipher::array::Array; - use cipher::{KeyIvInit, StreamCipher, blobby::Blob4Iterator}; + use $crate::dev::stream::TestVector; - let data = include_bytes!(concat!("data/", $test_name, ".blb")); - for (i, row) in Blob4Iterator::new(data).unwrap().enumerate() { - let [key, iv, pt, ct] = row.unwrap(); + $crate::dev::blobby::parse_into_structs!( + include_bytes!(concat!("data/", $test_name, ".blb")); + static TEST_VECTORS: &[ + TestVector { key, iv, plaintext, ciphertext } + ]; + ); - for chunk_n in 1..256 { - let mut mode = <$cipher>::new_from_slices(key, iv).unwrap(); - let mut pt = pt.to_vec(); - for chunk in pt.chunks_mut(chunk_n) { - mode.apply_keystream(chunk); - } - if pt != &ct[..] { - panic!( - "Failed main test №{}, chunk size: {}\n\ - key:\t{:?}\n\ - iv:\t{:?}\n\ - plaintext:\t{:?}\n\ - ciphertext:\t{:?}\n", - i, chunk_n, key, iv, pt, ct, - ); - } + for (i, tv) in TEST_VECTORS.iter().enumerate() { + let res = $crate::dev::stream::stream_cipher_test::<$cipher>(tv); + if let Err(reason) = res { + panic!( + "\n\ + Failed test #{i}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" + ); } } } diff --git a/cipher/src/lib.rs b/cipher/src/lib.rs index 7771e5973..af95d9fc7 100644 --- a/cipher/src/lib.rs +++ b/cipher/src/lib.rs @@ -17,8 +17,9 @@ unused_lifetimes, missing_debug_implementations )] +#![forbid(unsafe_code)] -#[cfg(all(feature = "block-padding", feature = "alloc"))] +#[cfg(feature = "alloc")] extern crate alloc; #[cfg(feature = "dev")] @@ -34,7 +35,7 @@ pub use zeroize; pub mod block; #[cfg(feature = "dev")] -mod dev; +pub mod dev; pub mod stream; pub mod tweak; diff --git a/cipher/src/stream.rs b/cipher/src/stream.rs index a1ba34347..f6169595d 100644 --- a/cipher/src/stream.rs +++ b/cipher/src/stream.rs @@ -9,6 +9,7 @@ use inout::{InOutBuf, NotEqualError}; mod core_api; mod errors; +#[cfg(feature = "stream-wrapper")] mod wrapper; pub use core_api::{ @@ -16,6 +17,7 @@ pub use core_api::{ StreamCipherSeekCore, }; pub use errors::{OverflowError, StreamCipherError}; +#[cfg(feature = "stream-wrapper")] pub use wrapper::StreamCipherCoreWrapper; /// Marker trait for block-level asynchronous stream ciphers diff --git a/cipher/src/stream/wrapper.rs b/cipher/src/stream/wrapper.rs index 3dbc7e1b5..5b9513c37 100644 --- a/cipher/src/stream/wrapper.rs +++ b/cipher/src/stream/wrapper.rs @@ -1,29 +1,22 @@ use super::{ - Block, OverflowError, SeekNum, StreamCipher, StreamCipherCore, StreamCipherSeek, - StreamCipherSeekCore, errors::StreamCipherError, + OverflowError, SeekNum, StreamCipher, StreamCipherCore, StreamCipherSeek, StreamCipherSeekCore, + errors::StreamCipherError, }; +use block_buffer::ReadBuffer; use core::fmt; use crypto_common::{ Iv, IvSizeUser, Key, KeyInit, KeyIvInit, KeySizeUser, array::Array, typenum::Unsigned, }; use inout::InOutBuf; #[cfg(feature = "zeroize")] -use zeroize::{Zeroize, ZeroizeOnDrop}; +use zeroize::ZeroizeOnDrop; /// Buffering wrapper around a [`StreamCipherCore`] implementation. /// /// It handles data buffering and implements the slice-based traits. pub struct StreamCipherCoreWrapper { core: T, - // First byte is used as position - buffer: Block, -} - -impl Default for StreamCipherCoreWrapper { - #[inline] - fn default() -> Self { - Self::from_core(T::default()) - } + buffer: ReadBuffer, } impl Clone for StreamCipherCoreWrapper { @@ -38,61 +31,18 @@ impl Clone for StreamCipherCoreWrapper { impl fmt::Debug for StreamCipherCoreWrapper { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let pos = self.get_pos().into(); - let buf_data = &self.buffer[pos..]; f.debug_struct("StreamCipherCoreWrapper") - .field("core", &self.core) - .field("buffer_data", &buf_data) - .finish() + .finish_non_exhaustive() } } impl StreamCipherCoreWrapper { - /// Return reference to the core type. - pub fn get_core(&self) -> &T { - &self.core - } - - /// Return reference to the core type. + /// Initialize from a [`StreamCipherCore`] instance. pub fn from_core(core: T) -> Self { - let mut buffer: Block = Default::default(); - buffer[0] = T::BlockSize::U8; - Self { core, buffer } - } - - /// Return current cursor position. - #[inline] - fn get_pos(&self) -> u8 { - let pos = self.buffer[0]; - if pos == 0 || pos > T::BlockSize::U8 { - debug_assert!(false); - // SAFETY: `pos` never breaks the invariant - unsafe { - core::hint::unreachable_unchecked(); - } + Self { + core, + buffer: Default::default(), } - pos - } - - /// Set buffer position without checking that it's smaller - /// than buffer size. - /// - /// # Safety - /// `pos` MUST be bigger than zero and smaller or equal to `T::BlockSize::USIZE`. - #[inline] - unsafe fn set_pos_unchecked(&mut self, pos: usize) { - debug_assert!(pos != 0 && pos <= T::BlockSize::USIZE); - // Block size is always smaller than 256 because of the `BlockSizes` bound, - // so if the safety condition is satisfied, the `as` cast does not truncate - // any non-zero bits. - self.buffer[0] = pos as u8; - } - - /// Return number of remaining bytes in the internal buffer. - #[inline] - fn remaining(&self) -> u8 { - // This never underflows because of the safety invariant - T::BlockSize::U8 - self.get_pos() } fn check_remaining(&self, data_len: usize) -> Result<(), StreamCipherError> { @@ -101,7 +51,7 @@ impl StreamCipherCoreWrapper { None => return Ok(()), }; - let buf_rem = usize::from(self.remaining()); + let buf_rem = self.buffer.remaining(); let data_len = match data_len.checked_sub(buf_rem) { Some(0) | None => return Ok(()), Some(res) => res, @@ -121,106 +71,46 @@ impl StreamCipher for StreamCipherCoreWrapper { #[inline] fn try_apply_keystream_inout( &mut self, - mut data: InOutBuf<'_, '_, u8>, + data: InOutBuf<'_, '_, u8>, ) -> Result<(), StreamCipherError> { self.check_remaining(data.len())?; - let pos = usize::from(self.get_pos()); - let rem = usize::from(self.remaining()); - let data_len = data.len(); - - if rem != 0 { - if data_len <= rem { - data.xor_in2out(&self.buffer[pos..][..data_len]); - // SAFETY: we have checked that `data_len` is less or equal to length - // of remaining keystream data, thus `pos + data_len` can not be bigger - // than block size. Since `pos` is never zero, `pos + data_len` can not - // be zero. Thus `pos + data_len` satisfies the safety invariant required - // by `set_pos_unchecked`. - unsafe { - self.set_pos_unchecked(pos + data_len); - } - return Ok(()); - } - let (mut left, right) = data.split_at(rem); - data = right; - left.xor_in2out(&self.buffer[pos..]); - } + let head_ks = self.buffer.read_cached(data.len()); + let (mut head, data) = data.split_at(head_ks.len()); let (blocks, mut tail) = data.into_chunks(); - self.core.apply_keystream_blocks_inout(blocks); - let new_pos = if tail.is_empty() { - T::BlockSize::USIZE - } else { - // Note that we temporarily write a pseudo-random byte into - // the first byte of `self.buffer`. It may break the safety invariant, - // but after XORing keystream block with `tail`, we immediately - // overwrite the first byte with a correct value. - self.core.write_keystream_block(&mut self.buffer); - tail.xor_in2out(&self.buffer[..tail.len()]); - tail.len() - }; + head.xor_in2out(head_ks); + self.core.apply_keystream_blocks_inout(blocks); - // SAFETY: `into_chunks` always returns tail with size - // less than block size. If `tail.len()` is zero, we replace - // it with block size. Thus the invariant required by - // `set_pos_unchecked` is satisfied. - unsafe { - self.set_pos_unchecked(new_pos); - } + self.buffer.write_block( + tail.len(), + |b| self.core.write_keystream_block(b), + |tail_ks| { + tail.xor_in2out(tail_ks); + }, + ); Ok(()) } #[inline] - fn try_write_keystream(&mut self, mut data: &mut [u8]) -> Result<(), StreamCipherError> { + fn try_write_keystream(&mut self, data: &mut [u8]) -> Result<(), StreamCipherError> { self.check_remaining(data.len())?; - let pos = usize::from(self.get_pos()); - let rem = usize::from(self.remaining()); - let data_len = data.len(); - - if rem != 0 { - if data_len <= rem { - data.copy_from_slice(&self.buffer[pos..][..data_len]); - // SAFETY: we have checked that `data_len` is less or equal to length - // of remaining keystream data, thus `pos + data_len` can not be bigger - // than block size. Since `pos` is never zero, `pos + data_len` can not - // be zero. Thus `pos + data_len` satisfies the safety invariant required - // by `set_pos_unchecked`. - unsafe { - self.set_pos_unchecked(pos + data_len); - } - return Ok(()); - } - let (left, right) = data.split_at_mut(rem); - data = right; - left.copy_from_slice(&self.buffer[pos..]); - } + let head_ks = self.buffer.read_cached(data.len()); + let (head, data) = data.split_at_mut(head_ks.len()); let (blocks, tail) = Array::slice_as_chunks_mut(data); - self.core.write_keystream_blocks(blocks); - let new_pos = if tail.is_empty() { - T::BlockSize::USIZE - } else { - // Note that we temporarily write a pseudo-random byte into - // the first byte of `self.buffer`. It may break the safety invariant, - // but after writing keystream block with `tail`, we immediately - // overwrite the first byte with a correct value. - self.core.write_keystream_block(&mut self.buffer); - tail.copy_from_slice(&self.buffer[..tail.len()]); - tail.len() - }; + head.copy_from_slice(head_ks); + self.core.write_keystream_blocks(blocks); - // SAFETY: `into_chunks` always returns tail with size - // less than block size. If `tail.len()` is zero, we replace - // it with block size. Thus the invariant required by - // `set_pos_unchecked` is satisfied. - unsafe { - self.set_pos_unchecked(new_pos); - } + self.buffer.write_block( + tail.len(), + |b| self.core.write_keystream_block(b), + |tail_ks| tail.copy_from_slice(tail_ks), + ); Ok(()) } @@ -228,7 +118,8 @@ impl StreamCipher for StreamCipherCoreWrapper { impl StreamCipherSeek for StreamCipherCoreWrapper { fn try_current_pos(&self) -> Result { - let pos = self.get_pos(); + let pos = u8::try_from(self.buffer.get_pos()) + .expect("buffer position is always smaller than 256"); SN::from_block_byte(self.core.get_block_pos(), pos, T::BlockSize::U8) } @@ -239,19 +130,14 @@ impl StreamCipherSeek for StreamCipherCoreWrapper { assert!(byte_pos < T::BlockSize::U8); self.core.set_block_pos(block_pos); - let new_pos = if byte_pos != 0 { - // See comment in `try_apply_keystream_inout` for use of `write_keystream_block` - self.core.write_keystream_block(&mut self.buffer); - byte_pos.into() - } else { - T::BlockSize::USIZE - }; - // SAFETY: we assert that `byte_pos` is always smaller than block size. - // If `byte_pos` is zero, we replace it with block size. Thus the invariant - // required by `set_pos_unchecked` is satisfied. - unsafe { - self.set_pos_unchecked(new_pos); - } + + self.buffer.reset(); + + self.buffer.write_block( + usize::from(byte_pos), + |b| self.core.write_keystream_block(b), + |_| {}, + ); Ok(()) } } @@ -272,11 +158,9 @@ impl IvSizeUser for StreamCipherCoreWrapper impl KeyIvInit for StreamCipherCoreWrapper { #[inline] fn new(key: &Key, iv: &Iv) -> Self { - let mut buffer = Block::::default(); - buffer[0] = T::BlockSize::U8; Self { core: T::new(key, iv), - buffer, + buffer: Default::default(), } } } @@ -284,22 +168,21 @@ impl KeyIvInit for StreamCipherCoreWrapper { impl KeyInit for StreamCipherCoreWrapper { #[inline] fn new(key: &Key) -> Self { - let mut buffer = Block::::default(); - buffer[0] = T::BlockSize::U8; Self { core: T::new(key), - buffer, + buffer: Default::default(), } } } #[cfg(feature = "zeroize")] -impl Drop for StreamCipherCoreWrapper { - fn drop(&mut self) { - // If present, `core` will be zeroized by its own `Drop`. - self.buffer.zeroize(); - } -} +impl ZeroizeOnDrop for StreamCipherCoreWrapper {} +// Assert that `ReadBuffer` implements `ZeroizeOnDrop` #[cfg(feature = "zeroize")] -impl ZeroizeOnDrop for StreamCipherCoreWrapper {} +const _: () = { + #[allow(dead_code)] + fn check_buffer(v: &ReadBuffer) { + let _ = v as &dyn crate::zeroize::ZeroizeOnDrop; + } +}; diff --git a/crypto-common/Cargo.toml b/crypto-common/Cargo.toml index 5cdf89f25..4ab7e8bf1 100644 --- a/crypto-common/Cargo.toml +++ b/crypto-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crypto-common" -version = "0.2.0-rc.3" +version = "0.2.0-rc.4" authors = ["RustCrypto Developers"] edition = "2024" rust-version = "1.85" @@ -13,7 +13,7 @@ categories = ["cryptography", "no-std"] description = "Common cryptographic traits" [dependencies] -hybrid-array = "0.3" +hybrid-array = "0.4" # optional dependencies rand_core = { version = "0.9", optional = true } diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index fbcf281f1..b7f627262 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -13,7 +13,7 @@ categories = ["cryptography", "no-std"] description = "Facade crate for all of the RustCrypto traits (e.g. `aead`, `cipher`, `digest`)" [dependencies] -crypto-common = { version = "0.2.0-rc.3", path = "../crypto-common", default-features = false } +crypto-common = { version = "0.2.0-rc.4", path = "../crypto-common", default-features = false } # optional dependencies aead = { version = "0.6.0-rc.0", path = "../aead", optional = true } diff --git a/digest/CHANGELOG.md b/digest/CHANGELOG.md index a4fe6b64a..5e2f14e17 100644 --- a/digest/CHANGELOG.md +++ b/digest/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Edition changed to 2024 and MSRV bumped to 1.85 ([#1759]) - `CtVariableCoreWrapper` renamed to `CtOutWrapper` ([#1799]) - Removed the OID type parameter from `CtOutWrapper` ([#1799]) +- Implementations of the `SerializableState` trait ([#1953]) +- `new_test!` and `new_mac_test!` macros ([#1958]) ### Removed - `Mac::new`, `Mac::new_from_slice`, and `Mac::generate_key` methods ([#1173]) @@ -29,6 +31,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#1799]: https://github.com/RustCrypto/traits/pull/1799 [#1809]: https://github.com/RustCrypto/traits/pull/1809 [#1820]: https://github.com/RustCrypto/traits/pull/1820 +[#1953]: https://github.com/RustCrypto/traits/pull/1953 +[#1958]: https://github.com/RustCrypto/traits/pull/1958 ## 0.10.7 (2023-05-19) ### Changed diff --git a/digest/Cargo.toml b/digest/Cargo.toml index 3f527d7d9..4f042b856 100644 --- a/digest/Cargo.toml +++ b/digest/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "digest" -version = "0.11.0-rc.0" +version = "0.11.0-rc.1" authors = ["RustCrypto Developers"] edition = "2024" rust-version = "1.85" @@ -13,10 +13,10 @@ categories = ["cryptography", "no-std"] description = "Traits for cryptographic hash functions and message authentication codes" [dependencies] -crypto-common = { version = "0.2.0-rc.3", path = "../crypto-common" } +crypto-common = { version = "0.2.0-rc.4", path = "../crypto-common" } # optional dependencies -block-buffer = { version = "0.11.0-rc.4", optional = true } +block-buffer = { version = "0.11.0-rc.5", optional = true } subtle = { version = "2.4", default-features = false, optional = true } blobby = { version = "0.4.0-pre.0", optional = true } const-oid = { version = "0.10", optional = true } diff --git a/digest/src/block_api/ct_variable.rs b/digest/src/block_api/ct_variable.rs index 775dd3c34..0e6459932 100644 --- a/digest/src/block_api/ct_variable.rs +++ b/digest/src/block_api/ct_variable.rs @@ -5,16 +5,12 @@ use super::{ #[cfg(feature = "mac")] use crate::MacMarker; use crate::{CollisionResistance, CustomizedInit, HashMarker, VarOutputCustomized}; -use core::{ - fmt, - marker::PhantomData, - ops::{Add, Sub}, -}; +use core::{fmt, marker::PhantomData}; use crypto_common::{ Block, BlockSizeUser, OutputSizeUser, array::{Array, ArraySize}, - hazmat::{DeserializeStateError, SerializableState, SerializedState, SubSerializedStateSize}, - typenum::{IsLess, IsLessOrEqual, Le, NonZero, Sum, True, U1, U256}, + hazmat::{DeserializeStateError, SerializableState, SerializedState}, + typenum::{IsLessOrEqual, True}, }; /// Wrapper around [`VariableOutputCore`] which selects output size at compile time. @@ -177,41 +173,21 @@ where } } -type CtVariableCoreWrapperSerializedStateSize = - Sum<::SerializedStateSize, U1>; - impl SerializableState for CtOutWrapper where T: VariableOutputCore + SerializableState, OutSize: ArraySize + IsLessOrEqual, - T::BlockSize: IsLess, - Le: NonZero, - T::SerializedStateSize: Add, - CtVariableCoreWrapperSerializedStateSize: Sub + ArraySize, - SubSerializedStateSize, T>: ArraySize, { - type SerializedStateSize = CtVariableCoreWrapperSerializedStateSize; + type SerializedStateSize = ::SerializedStateSize; fn serialize(&self) -> SerializedState { - let serialized_inner = self.inner.serialize(); - let serialized_outsize = Array([OutSize::U8]); - - serialized_inner.concat(serialized_outsize) + self.inner.serialize() } fn deserialize( serialized_state: &SerializedState, ) -> Result { - let (serialized_inner, serialized_outsize) = - serialized_state.split_ref::(); - - if serialized_outsize[0] != OutSize::U8 { - return Err(DeserializeStateError); - } - - Ok(Self { - inner: T::deserialize(serialized_inner)?, - _out: PhantomData, - }) + let _out = PhantomData; + T::deserialize(serialized_state).map(|inner| Self { inner, _out }) } } diff --git a/digest/src/buffer_macros/fixed.rs b/digest/src/buffer_macros/fixed.rs index f1e73b641..407581bb3 100644 --- a/digest/src/buffer_macros/fixed.rs +++ b/digest/src/buffer_macros/fixed.rs @@ -429,49 +429,33 @@ macro_rules! buffer_fixed { impl$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $crate::crypto_common::hazmat::SerializableState for $name$(< $( $lt ),+ >)? { type SerializedStateSize = $crate::typenum::Sum< <$core_ty as $crate::crypto_common::hazmat::SerializableState>::SerializedStateSize, - $crate::typenum::Add1< - <$core_ty as $crate::block_api::BlockSizeUser>::BlockSize + $crate::block_buffer::SerializedBufferSize< + <$core_ty as $crate::block_api::BlockSizeUser>::BlockSize, + <$core_ty as $crate::block_api::BufferKindUser>::BufferKind, > >; #[inline] fn serialize(&self) -> $crate::crypto_common::hazmat::SerializedState { - use $crate::{ - array::Array, - consts::U1, - block_buffer::BlockBuffer, - crypto_common::hazmat::SerializableState, - }; - let serialized_core = self.core.serialize(); - let pos = u8::try_from(self.buffer.get_pos()).unwrap(); - let serialized_pos: Array = Array([pos]); - let serialized_data = self.buffer.clone().pad_with_zeros(); - - serialized_core - .concat(serialized_pos) - .concat(serialized_data) + let serialized_buf = self.buffer.serialize(); + serialized_core.concat(serialized_buf) } #[inline] fn deserialize( serialized_state: &$crate::crypto_common::hazmat::SerializedState, ) -> Result { - use $crate::{ - block_buffer::BlockBuffer, - consts::U1, - crypto_common::hazmat::{SerializableState, DeserializeStateError}, - }; + use $crate::crypto_common::hazmat::{SerializableState, DeserializeStateError}; - let (serialized_core, remaining_buffer) = serialized_state + let (serialized_core, serialized_buf) = serialized_state .split_ref::<<$core_ty as SerializableState>::SerializedStateSize>(); - let (serialized_pos, serialized_data) = remaining_buffer.split_ref::(); - Ok(Self { - core: <$core_ty as SerializableState>::deserialize(serialized_core)?, - buffer: BlockBuffer::try_new(&serialized_data[..serialized_pos[0].into()]) - .map_err(|_| DeserializeStateError)?, - }) + let core = SerializableState::deserialize(serialized_core)?; + let buffer = $crate::block_buffer::BlockBuffer::deserialize(serialized_buf) + .map_err(|_| DeserializeStateError)?; + + Ok(Self { core, buffer }) } } diff --git a/digest/src/buffer_macros/variable_ct.rs b/digest/src/buffer_macros/variable_ct.rs index a767a1ca0..b02b69a57 100644 --- a/digest/src/buffer_macros/variable_ct.rs +++ b/digest/src/buffer_macros/variable_ct.rs @@ -176,54 +176,32 @@ macro_rules! buffer_ct_variable { where $out_size: $crate::array::ArraySize + $crate::typenum::IsLessOrEqual<$max_size, Output = $crate::typenum::True>, { - type SerializedStateSize = $crate::typenum::Add1<$crate::typenum::Sum< - < - $crate::block_api::CtOutWrapper<$core_ty, $out_size> - as $crate::crypto_common::hazmat::SerializableState - >::SerializedStateSize, - <$core_ty as $crate::block_api::BlockSizeUser>::BlockSize, - >>; + type SerializedStateSize = $crate::typenum::Sum< + <$core_ty as $crate::crypto_common::hazmat::SerializableState>::SerializedStateSize, + $crate::block_buffer::SerializedBufferSize< + <$core_ty as $crate::block_api::BlockSizeUser>::BlockSize, + <$core_ty as $crate::block_api::BufferKindUser>::BufferKind, + > + >; #[inline] fn serialize(&self) -> $crate::crypto_common::hazmat::SerializedState { - use $crate::{ - array::Array, - consts::U1, - block_buffer::BlockBuffer, - crypto_common::hazmat::SerializableState, - }; - let serialized_core = self.core.serialize(); - let pos = u8::try_from(self.buffer.get_pos()).unwrap(); - let serialized_pos: Array = Array([pos]); - let serialized_data = self.buffer.clone().pad_with_zeros(); - - serialized_core - .concat(serialized_pos) - .concat(serialized_data) + let serialized_buf = self.buffer.serialize(); + serialized_core.concat(serialized_buf) } #[inline] fn deserialize( serialized_state: &$crate::crypto_common::hazmat::SerializedState, ) -> Result { - use $crate::{ - block_buffer::BlockBuffer, - consts::U1, - block_api::CtOutWrapper, - crypto_common::hazmat::{SerializableState, DeserializeStateError}, - }; - - let (serialized_core, remaining_buffer) = serialized_state - .split_ref::<< - CtOutWrapper<$core_ty, $out_size> - as SerializableState - >::SerializedStateSize>(); - let (serialized_pos, serialized_data) = remaining_buffer.split_ref::(); + use $crate::crypto_common::hazmat::{SerializableState, DeserializeStateError}; + + let (serialized_core, serialized_buf) = serialized_state + .split_ref::<<$core_ty as SerializableState>::SerializedStateSize>(); let core = SerializableState::deserialize(serialized_core)?; - let pos = usize::from(serialized_pos[0]); - let buffer = BlockBuffer::try_new(&serialized_data[..pos]) + let buffer = $crate::block_buffer::BlockBuffer::deserialize(serialized_buf) .map_err(|_| DeserializeStateError)?; Ok(Self { core, buffer }) diff --git a/digest/src/buffer_macros/xof.rs b/digest/src/buffer_macros/xof.rs index 8687c18b4..c8cbe72c7 100644 --- a/digest/src/buffer_macros/xof.rs +++ b/digest/src/buffer_macros/xof.rs @@ -284,49 +284,33 @@ macro_rules! buffer_xof { impl $crate::crypto_common::hazmat::SerializableState for $name { type SerializedStateSize = $crate::typenum::Sum< <$core_ty as $crate::crypto_common::hazmat::SerializableState>::SerializedStateSize, - $crate::typenum::Add1< - <$core_ty as $crate::block_api::BlockSizeUser>::BlockSize + $crate::block_buffer::SerializedBufferSize< + <$core_ty as $crate::block_api::BlockSizeUser>::BlockSize, + <$core_ty as $crate::block_api::BufferKindUser>::BufferKind, > >; #[inline] fn serialize(&self) -> $crate::crypto_common::hazmat::SerializedState { - use $crate::{ - array::Array, - consts::U1, - block_buffer::BlockBuffer, - crypto_common::hazmat::SerializableState, - }; - let serialized_core = self.core.serialize(); - let pos = u8::try_from(self.buffer.get_pos()).unwrap(); - let serialized_pos: Array = Array([pos]); - let serialized_data = self.buffer.clone().pad_with_zeros(); - - serialized_core - .concat(serialized_pos) - .concat(serialized_data) + let serialized_buf = self.buffer.serialize(); + serialized_core.concat(serialized_buf) } #[inline] fn deserialize( serialized_state: &$crate::crypto_common::hazmat::SerializedState, ) -> Result { - use $crate::{ - block_buffer::BlockBuffer, - consts::U1, - crypto_common::hazmat::{SerializableState, DeserializeStateError}, - }; + use $crate::crypto_common::hazmat::{SerializableState, DeserializeStateError}; - let (serialized_core, remaining_buffer) = serialized_state + let (serialized_core, serialized_buf) = serialized_state .split_ref::<<$core_ty as SerializableState>::SerializedStateSize>(); - let (serialized_pos, serialized_data) = remaining_buffer.split_ref::(); - Ok(Self { - core: <$core_ty as SerializableState>::deserialize(serialized_core)?, - buffer: BlockBuffer::try_new(&serialized_data[..serialized_pos[0].into()]) - .map_err(|_| DeserializeStateError)?, - }) + let core = SerializableState::deserialize(serialized_core)?; + let buffer = $crate::block_buffer::BlockBuffer::deserialize(serialized_buf) + .map_err(|_| DeserializeStateError)?; + + Ok(Self { core, buffer }) } } diff --git a/digest/src/dev.rs b/digest/src/dev.rs index b3d0203dc..da08409d5 100644 --- a/digest/src/dev.rs +++ b/digest/src/dev.rs @@ -3,33 +3,47 @@ pub use blobby; mod fixed; +#[cfg(feature = "mac")] mod mac; mod rng; mod variable; mod xof; pub use fixed::*; +#[cfg(feature = "mac")] +pub use mac::*; pub use variable::*; pub use xof::*; +/// Test vector for hash functions +#[derive(Debug, Clone, Copy)] +pub struct TestVector { + /// Input data + pub input: &'static [u8], + /// Output hash + pub output: &'static [u8], +} + /// Define hash function test #[macro_export] macro_rules! new_test { - ($name:ident, $test_name:expr, $hasher:ty, $test_func:ident $(,)?) => { + ($name:ident, $hasher:ty, $test_fn:ident $(,)?) => { #[test] fn $name() { - use digest::dev::blobby::Blob2Iterator; - let data = include_bytes!(concat!("data/", $test_name, ".blb")); + use $crate::dev::TestVector; + + $crate::dev::blobby::parse_into_structs!( + include_bytes!(concat!("data/", stringify!($name), ".blb")); + static TEST_VECTORS: &[TestVector { input, output }]; + ); - for (i, row) in Blob2Iterator::new(data).unwrap().enumerate() { - let [input, output] = row.unwrap(); - if let Some(desc) = $test_func::<$hasher>(input, output) { + for (i, tv) in TEST_VECTORS.iter().enumerate() { + if let Err(reason) = $test_fn::<$hasher>(tv) { panic!( "\n\ - Failed test №{}: {}\n\ - input:\t{:?}\n\ - output:\t{:?}\n", - i, desc, input, output, + Failed test #{i}:\n\ + reason:\t{reason}\n\ + test vector:\t{tv:?}\n" ); } } @@ -40,7 +54,7 @@ macro_rules! new_test { /// Define hash function serialization test #[macro_export] macro_rules! hash_serialization_test { - ($name:ident, $hasher:ty, $expected_serialized_state:expr) => { + ($name:ident, $hasher:ty $(,)?) => { #[test] fn $name() { use digest::{ @@ -54,7 +68,8 @@ macro_rules! hash_serialization_test { h.update(&[0x13; <$hasher as BlockSizeUser>::BlockSize::USIZE + 1]); let serialized_state = h.serialize(); - assert_eq!(serialized_state.as_slice(), $expected_serialized_state); + let expected = include_bytes!(concat!("data/", stringify!($name), ".bin")); + assert_eq!(serialized_state.as_slice(), expected); let mut h = <$hasher>::deserialize(&serialized_state).unwrap(); diff --git a/digest/src/dev/fixed.rs b/digest/src/dev/fixed.rs index 24f380112..ad57e7741 100644 --- a/digest/src/dev/fixed.rs +++ b/digest/src/dev/fixed.rs @@ -1,24 +1,22 @@ -use crate::{Digest, FixedOutput, FixedOutputReset, HashMarker, Update}; -use core::fmt::Debug; +use crate::{Digest, FixedOutput, FixedOutputReset, HashMarker, dev::TestVector}; /// Fixed-output resettable digest test via the `Digest` trait -pub fn fixed_reset_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: FixedOutputReset + Debug + Clone + Default + Update + HashMarker, -{ +pub fn fixed_reset_test( + &TestVector { input, output }: &TestVector, +) -> Result<(), &'static str> { let mut hasher = D::new(); // Test that it works when accepting the message all at once hasher.update(input); let mut hasher2 = hasher.clone(); if hasher.finalize()[..] != output[..] { - return Some("whole message"); + return Err("whole message"); } // Test if reset works correctly hasher2.reset(); hasher2.update(input); if hasher2.finalize_reset()[..] != output[..] { - return Some("whole message after reset"); + return Err("whole message after reset"); } // Test that it works when accepting the message in chunks @@ -29,26 +27,25 @@ where hasher2.update(chunk); } if hasher.finalize()[..] != output[..] { - return Some("message in chunks"); + return Err("message in chunks"); } if hasher2.finalize_reset()[..] != output[..] { - return Some("message in chunks"); + return Err("message in chunks"); } } - None + Ok(()) } /// Variable-output resettable digest test -pub fn fixed_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: FixedOutput + Default + Debug + Clone, -{ +pub fn fixed_test( + &TestVector { input, output }: &TestVector, +) -> Result<(), &'static str> { let mut hasher = D::default(); // Test that it works when accepting the message all at once hasher.update(input); if hasher.finalize_fixed()[..] != output[..] { - return Some("whole message"); + return Err("whole message"); } // Test that it works when accepting the message in chunks @@ -58,8 +55,8 @@ where hasher.update(chunk); } if hasher.finalize_fixed()[..] != output[..] { - return Some("message in chunks"); + return Err("message in chunks"); } } - None + Ok(()) } diff --git a/digest/src/dev/mac.rs b/digest/src/dev/mac.rs index 969a18b03..f33a0bb66 100644 --- a/digest/src/dev/mac.rs +++ b/digest/src/dev/mac.rs @@ -1,154 +1,149 @@ -/// Define MAC test -#[macro_export] -#[cfg(feature = "mac")] -macro_rules! new_mac_test { - ($name:ident, $test_name:expr, $mac:ty $(,)?) => { - digest::new_mac_test!($name, $test_name, $mac, ""); - }; - ($name:ident, $test_name:expr, $mac:ty, trunc_left $(,)?) => { - digest::new_mac_test!($name, $test_name, $mac, "left"); +use crate::{FixedOutputReset, Mac, crypto_common::KeyInit}; + +/// Tag truncation side used in MAC tests +#[derive(Clone, Copy, Debug)] +pub enum MacTruncSide { + /// Tag truncated from left (i.e. `tag[..n]`) + Left, + /// Tag truncated from right (i.e. `tag[n..]`) + Right, + /// Tag is not truncated + None, +} + +/// MAC test vector +#[derive(Debug, Clone, Copy)] +pub struct MacTestVector { + /// Initialization key + pub key: &'static [u8], + /// Input message + pub input: &'static [u8], + /// Output tag + pub tag: &'static [u8], +} + +/// Run MAC test +pub fn mac_test( + &MacTestVector { key, input, tag }: &MacTestVector, + trunc_side: MacTruncSide, +) -> Result<(), &'static str> { + let Ok(mac0) = ::new_from_slice(key) else { + return Err("Failed to initialize MAC instance"); }; - ($name:ident, $test_name:expr, $mac:ty, trunc_right $(,)?) => { - digest::new_mac_test!($name, $test_name, $mac, "right"); + + let mut mac = mac0.clone(); + mac.update(input); + let result = mac.finalize().into_bytes(); + let n = tag.len(); + let result_bytes = match trunc_side { + MacTruncSide::Left => &result[..n], + MacTruncSide::Right => &result[result.len() - n..], + MacTruncSide::None => &result[..], }; - ($name:ident, $test_name:expr, $mac:ty, $trunc:expr $(,)?) => { - #[test] - fn $name() { - use core::cmp::min; - use digest::dev::blobby::Blob3Iterator; - use digest::{KeyInit, Mac}; + if result_bytes != tag { + return Err("whole message"); + } - fn run_test(key: &[u8], input: &[u8], tag: &[u8]) -> Option<&'static str> { - let mac0 = <$mac as KeyInit>::new_from_slice(key).unwrap(); + // test reading different chunk sizes + for chunk_size in 1..core::cmp::min(64, input.len()) { + let mut mac = mac0.clone(); + for chunk in input.chunks(chunk_size) { + mac.update(chunk); + } + let res = match trunc_side { + MacTruncSide::Left => mac.verify_truncated_left(tag), + MacTruncSide::Right => mac.verify_truncated_right(tag), + MacTruncSide::None => mac.verify_slice(tag), + }; + if res.is_err() { + return Err("chunked message"); + } + } - let mut mac = mac0.clone(); - mac.update(input); - let result = mac.finalize().into_bytes(); - let n = tag.len(); - let result_bytes = match $trunc { - "left" => &result[..n], - "right" => &result[result.len() - n..], - _ => &result[..], - }; - if result_bytes != tag { - return Some("whole message"); - } + Ok(()) +} - // test reading different chunk sizes - for chunk_size in 1..min(64, input.len()) { - let mut mac = mac0.clone(); - for chunk in input.chunks(chunk_size) { - mac.update(chunk); - } - let res = match $trunc { - "left" => mac.verify_truncated_left(tag), - "right" => mac.verify_truncated_right(tag), - _ => mac.verify_slice(tag), - }; - if res.is_err() { - return Some("chunked message"); - } - } +/// Run resettable MAC test +pub fn reset_mac_test( + &MacTestVector { key, input, tag }: &MacTestVector, + trunc_side: MacTruncSide, +) -> Result<(), &'static str> { + let Ok(mac0) = ::new_from_slice(key) else { + return Err("Failed to initialize MAC instance"); + }; - None - } + let mut mac = mac0.clone(); + Mac::update(&mut mac, input); + let result = mac.finalize_reset().into_bytes(); + let n = tag.len(); + let result_bytes = match trunc_side { + MacTruncSide::Left => &result[..n], + MacTruncSide::Right => &result[result.len() - n..], + MacTruncSide::None => &result[..], + }; + if result_bytes != tag { + return Err("whole message"); + } - let data = include_bytes!(concat!("data/", $test_name, ".blb")); + // test if reset worked correctly + Mac::update(&mut mac, input); + let res = match trunc_side { + MacTruncSide::Left => mac.verify_truncated_left(tag), + MacTruncSide::Right => mac.verify_truncated_right(tag), + MacTruncSide::None => mac.verify_slice(tag), + }; + if res.is_err() { + return Err("after reset"); + } - for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { - let [key, input, tag] = row.unwrap(); - if let Some(desc) = run_test(key, input, tag) { - panic!( - "\n\ - Failed test №{}: {}\n\ - key:\t{:?}\n\ - input:\t{:?}\n\ - tag:\t{:?}\n", - i, desc, key, input, tag, - ); - } - } + // test reading different chunk sizes + for chunk_size in 1..core::cmp::min(64, input.len()) { + let mut mac = mac0.clone(); + for chunk in input.chunks(chunk_size) { + Mac::update(&mut mac, chunk); } - }; + let res = match trunc_side { + MacTruncSide::Left => mac.verify_truncated_left(tag), + MacTruncSide::Right => mac.verify_truncated_right(tag), + MacTruncSide::None => mac.verify_slice(tag), + }; + if res.is_err() { + return Err("chunked message"); + } + } + + Ok(()) } -/// Define resettable MAC test +/// Define MAC test #[macro_export] -#[cfg(feature = "mac")] -macro_rules! new_resettable_mac_test { - ($name:ident, $test_name:expr, $mac:ty $(,)?) => { - digest::new_resettable_mac_test!($name, $test_name, $mac, ""); +macro_rules! new_mac_test { + ($name:ident, $mac:ty, $test_fn:ident $(,)?) => { + digest::new_mac_test!($name, $mac, $test_fn, $crate::dev::MacTruncSide::None); }; - ($name:ident, $test_name:expr, $mac:ty, trunc_left $(,)?) => { - digest::new_resettable_mac_test!($name, $test_name, $mac, "left"); + ($name:ident, $mac:ty, $test_fn:ident, trunc_left $(,)?) => { + digest::new_mac_test!($name, $mac, $test_fn, $crate::dev::MacTruncSide::Left); }; - ($name:ident, $test_name:expr, $mac:ty, trunc_right $(,)?) => { - digest::new_resettable_mac_test!($name, $test_name, $mac, "right"); + ($name:ident, $mac:ty, $test_fn:ident, trunc_right $(,)?) => { + digest::new_mac_test!($name, $mac, $test_fn, $crate::dev::MacTruncSide::Right); }; - ($name:ident, $test_name:expr, $mac:ty, $trunc:expr $(,)?) => { + ($name:ident, $mac:ty, $test_fn:ident, $trunc:expr $(,)?) => { #[test] fn $name() { - use core::cmp::min; - use digest::dev::blobby::Blob3Iterator; - use digest::{KeyInit, Mac}; - - fn run_test(key: &[u8], input: &[u8], tag: &[u8]) -> Option<&'static str> { - let mac0 = <$mac as KeyInit>::new_from_slice(key).unwrap(); - - let mut mac = mac0.clone(); - mac.update(input); - let result = mac.finalize_reset().into_bytes(); - let n = tag.len(); - let result_bytes = match $trunc { - "left" => &result[..n], - "right" => &result[result.len() - n..], - _ => &result[..], - }; - if result_bytes != tag { - return Some("whole message"); - } - - // test if reset worked correctly - mac.update(input); - let res = match $trunc { - "left" => mac.verify_truncated_left(tag), - "right" => mac.verify_truncated_right(tag), - _ => mac.verify_slice(tag), - }; - if res.is_err() { - return Some("after reset"); - } - - // test reading different chunk sizes - for chunk_size in 1..min(64, input.len()) { - let mut mac = mac0.clone(); - for chunk in input.chunks(chunk_size) { - mac.update(chunk); - } - let res = match $trunc { - "left" => mac.verify_truncated_left(tag), - "right" => mac.verify_truncated_right(tag), - _ => mac.verify_slice(tag), - }; - if res.is_err() { - return Some("chunked message"); - } - } - None - } + use digest::dev::MacTestVector; - let data = include_bytes!(concat!("data/", $test_name, ".blb")); + $crate::dev::blobby::parse_into_structs!( + include_bytes!(concat!("data/", stringify!($name), ".blb")); + static TEST_VECTORS: &[MacTestVector { key, input, tag }]; + ); - for (i, row) in Blob3Iterator::new(data).unwrap().enumerate() { - let [key, input, tag] = row.unwrap(); - if let Some(desc) = run_test(key, input, tag) { + for (i, tv) in TEST_VECTORS.iter().enumerate() { + if let Err(reason) = $test_fn::<$mac>(tv, $trunc) { panic!( "\n\ - Failed test №{}: {}\n\ - key:\t{:?}\n\ - input:\t{:?}\n\ - tag:\t{:?}\n", - i, desc, key, input, tag, + Failed test #{i}\n\ + reason:\t{reason:?}\n\ + test vector:\t{tv:?}\n" ); } } diff --git a/digest/src/dev/rng.rs b/digest/src/dev/rng.rs index d34a1cf31..038a6f6ee 100644 --- a/digest/src/dev/rng.rs +++ b/digest/src/dev/rng.rs @@ -10,7 +10,7 @@ pub(crate) const RNG: XorShiftRng = XorShiftRng { w: Wrapping(0xB0AD_B4F3), }; -/// Xorshift RNG instance/ +/// Xorshift RNG instance pub(crate) struct XorShiftRng { x: Wrapping, y: Wrapping, diff --git a/digest/src/dev/variable.rs b/digest/src/dev/variable.rs index ed8ff8828..aab77a38b 100644 --- a/digest/src/dev/variable.rs +++ b/digest/src/dev/variable.rs @@ -1,11 +1,9 @@ -use crate::{VariableOutput, VariableOutputReset}; -use core::fmt::Debug; +use crate::{VariableOutput, VariableOutputReset, dev::TestVector}; /// Variable-output resettable digest test -pub fn variable_reset_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: VariableOutputReset + Debug + Clone, -{ +pub fn variable_reset_test( + &TestVector { input, output }: &TestVector, +) -> Result<(), &'static str> { let mut hasher = D::new(output.len()).unwrap(); let mut buf = [0u8; 128]; let buf = &mut buf[..output.len()]; @@ -14,7 +12,7 @@ where let mut hasher2 = hasher.clone(); hasher.finalize_variable(buf).unwrap(); if buf != output { - return Some("whole message"); + return Err("whole message"); } buf.iter_mut().for_each(|b| *b = 0); @@ -23,7 +21,7 @@ where hasher2.update(input); hasher2.finalize_variable_reset(buf).unwrap(); if buf != output { - return Some("whole message after reset"); + return Err("whole message after reset"); } buf.iter_mut().for_each(|b| *b = 0); @@ -36,25 +34,24 @@ where } hasher.finalize_variable(buf).unwrap(); if buf != output { - return Some("message in chunks"); + return Err("message in chunks"); } buf.iter_mut().for_each(|b| *b = 0); hasher2.finalize_variable_reset(buf).unwrap(); if buf != output { - return Some("message in chunks"); + return Err("message in chunks"); } buf.iter_mut().for_each(|b| *b = 0); } - None + Ok(()) } /// Variable-output resettable digest test -pub fn variable_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: VariableOutput + Debug + Clone, -{ +pub fn variable_test( + &TestVector { input, output }: &TestVector, +) -> Result<(), &'static str> { let mut hasher = D::new(output.len()).unwrap(); let mut buf = [0u8; 128]; let buf = &mut buf[..output.len()]; @@ -62,7 +59,7 @@ where hasher.update(input); hasher.finalize_variable(buf).unwrap(); if buf != output { - return Some("whole message"); + return Err("whole message"); } buf.iter_mut().for_each(|b| *b = 0); @@ -74,9 +71,9 @@ where } hasher.finalize_variable(buf).unwrap(); if buf != output { - return Some("message in chunks"); + return Err("message in chunks"); } buf.iter_mut().for_each(|b| *b = 0); } - None + Ok(()) } diff --git a/digest/src/dev/xof.rs b/digest/src/dev/xof.rs index 9e5d07a09..acd4c8769 100644 --- a/digest/src/dev/xof.rs +++ b/digest/src/dev/xof.rs @@ -1,11 +1,10 @@ -use crate::ExtendableOutputReset; +use crate::{ExtendableOutputReset, dev::TestVector}; use core::fmt::Debug; /// Resettable XOF test -pub fn xof_reset_test(input: &[u8], output: &[u8]) -> Option<&'static str> -where - D: ExtendableOutputReset + Default + Debug + Clone, -{ +pub fn xof_reset_test( + &TestVector { input, output }: &TestVector, +) -> Result<(), &'static str> { let mut hasher = D::default(); let mut buf = [0u8; 1024]; let buf = &mut buf[..output.len()]; @@ -14,7 +13,7 @@ where let mut hasher2 = hasher.clone(); hasher.finalize_xof_into(buf); if buf != output { - return Some("whole message"); + return Err("whole message"); } buf.iter_mut().for_each(|b| *b = 0); @@ -23,7 +22,7 @@ where hasher2.update(input); hasher2.finalize_xof_reset_into(buf); if buf != output { - return Some("whole message after reset"); + return Err("whole message after reset"); } buf.iter_mut().for_each(|b| *b = 0); @@ -36,16 +35,16 @@ where } hasher.finalize_xof_into(buf); if buf != output { - return Some("message in chunks"); + return Err("message in chunks"); } buf.iter_mut().for_each(|b| *b = 0); hasher2.finalize_xof_reset_into(buf); if buf != output { - return Some("message in chunks"); + return Err("message in chunks"); } buf.iter_mut().for_each(|b| *b = 0); } - None + Ok(()) } diff --git a/digest/tests/data/fixed_hash_serialization.bin b/digest/tests/data/fixed_hash_serialization.bin new file mode 100644 index 0000000000000000000000000000000000000000..2a034968a1c142c68888ae9fbf5600cd01a655a4 GIT binary patch literal 16 PcmWd@h5$xk1~32s2nhhK literal 0 HcmV?d00001 diff --git a/digest/tests/dummy_fixed.rs b/digest/tests/dummy_fixed.rs index cce734f59..e66b430a0 100644 --- a/digest/tests/dummy_fixed.rs +++ b/digest/tests/dummy_fixed.rs @@ -111,6 +111,9 @@ digest::buffer_fixed!( impl: FixedHashTraits; ); +#[cfg(feature = "dev")] +digest::hash_serialization_test!(fixed_hash_serialization, FixedHashWithSer,); + #[cfg(feature = "zeroize")] /// check for `ZeroizeOnDrop` implementations const _: () = { diff --git a/elliptic-curve/Cargo.toml b/elliptic-curve/Cargo.toml index dc2cfef16..2317ad846 100644 --- a/elliptic-curve/Cargo.toml +++ b/elliptic-curve/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "elliptic-curve" -version = "0.14.0-rc.10" +version = "0.14.0-rc.12" authors = ["RustCrypto Developers"] edition = "2024" rust-version = "1.85" @@ -17,30 +17,26 @@ and public/secret keys composed thereof. """ [dependencies] -base16ct = "0.2" -crypto-bigint = { version = "=0.7.0-pre.7", default-features = false, features = ["rand_core", "hybrid-array", "zeroize"] } -hybrid-array = { version = "0.3", default-features = false, features = ["zeroize"] } +base16ct = "0.3" +crypto-bigint = { version = "0.7.0-rc.3", default-features = false, features = ["rand_core", "hybrid-array", "zeroize"] } +hybrid-array = { version = "0.4", default-features = false, features = ["zeroize"] } rand_core = { version = "0.9.0", default-features = false } subtle = { version = "2.6", default-features = false } zeroize = { version = "1.7", default-features = false } # optional dependencies -base64ct = { version = "1", optional = true, default-features = false, features = ["alloc"] } -digest = { version = "0.11.0-rc.0", optional = true } +digest = { version = "0.11.0-rc.1", optional = true } ff = { version = "=0.14.0-pre.0", optional = true, default-features = false } group = { version = "=0.14.0-pre.0", optional = true, default-features = false } hkdf = { version = "0.13.0-rc.0", optional = true, default-features = false } hex-literal = { version = "1", optional = true } pem-rfc7468 = { version = "1.0.0-rc.2", optional = true, features = ["alloc"] } pkcs8 = { version = "0.11.0-rc.6", optional = true, default-features = false } -sec1 = { version = "0.8.0-rc.8", optional = true, features = ["subtle", "zeroize"] } -serdect = { version = "0.3", optional = true, default-features = false, features = ["alloc"] } -serde_json = { version = "1.0.121", optional = true, default-features = false, features = ["alloc"] } +sec1 = { version = "0.8.0-rc.9", optional = true, features = ["subtle", "zeroize"] } +serdect = { version = "0.4", optional = true, default-features = false, features = ["alloc"] } [dev-dependencies] hex-literal = "1" -sha2 = "0.11.0-rc.0" -sha3 = "0.11.0-rc.0" [features] default = ["arithmetic"] @@ -48,6 +44,7 @@ alloc = [ "base16ct/alloc", "ff?/alloc", "group?/alloc", + "hybrid-array/alloc", "pkcs8?/alloc", "sec1?/alloc", "zeroize/alloc" @@ -64,10 +61,9 @@ bits = ["arithmetic", "ff/bits"] dev = ["arithmetic", "dep:hex-literal", "pem", "pkcs8"] ecdh = ["arithmetic", "digest", "dep:hkdf"] group = ["dep:group", "ff"] -jwk = ["dep:base64ct", "dep:serde_json", "alloc", "serde", "zeroize/alloc"] pkcs8 = ["dep:pkcs8", "sec1"] pem = ["dep:pem-rfc7468", "alloc", "arithmetic", "pkcs8/pem", "sec1/pem"] serde = ["dep:serdect", "alloc", "pkcs8", "sec1/serde"] [package.metadata.docs.rs] -features = ["bits", "ecdh", "jwk", "pem", "std"] +features = ["bits", "ecdh", "pem", "std"] diff --git a/elliptic-curve/src/arithmetic.rs b/elliptic-curve/src/arithmetic.rs index d955f02f3..e61ac45c8 100644 --- a/elliptic-curve/src/arithmetic.rs +++ b/elliptic-curve/src/arithmetic.rs @@ -80,7 +80,8 @@ pub trait CurveArithmetic: Curve { + Mul + for<'a> Mul<&'a Self::ProjectivePoint, Output = Self::ProjectivePoint> + PartialOrd - + Reduce> + + Reduce + + Reduce> + TryInto, Error = Error> + ff::Field + ff::PrimeField>; diff --git a/elliptic-curve/src/dev.rs b/elliptic-curve/src/dev.rs index e6f6c1418..05f391ec5 100644 --- a/elliptic-curve/src/dev.rs +++ b/elliptic-curve/src/dev.rs @@ -6,7 +6,7 @@ use crate::{ BatchNormalize, Curve, CurveArithmetic, CurveGroup, FieldBytesEncoding, PrimeCurve, array::typenum::U32, - bigint::{Limb, U256}, + bigint::{Limb, NonZero, U256}, error::{Error, Result}, ops::{Invert, LinearCombination, Reduce, ShrAssign}, point::{AffineCoordinates, NonIdentity}, @@ -31,9 +31,6 @@ use alloc::vec::Vec; #[cfg(feature = "bits")] use ff::PrimeFieldBits; -#[cfg(feature = "jwk")] -use crate::JwkParameters; - /// Pseudo-coordinate for fixed-based scalar mult output pub const PSEUDO_COORDINATE_FIXED_BASE_MUL: [u8; 32] = hex!("deadbeef00000000000000000000000000000000000000000000000000000001"); @@ -73,8 +70,9 @@ impl Curve for MockCurve { type FieldBytesSize = U32; type Uint = U256; - const ORDER: U256 = - U256::from_be_hex("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"); + const ORDER: NonZero = NonZero::::from_be_hex( + "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + ); } impl PrimeCurve for MockCurve {} @@ -90,11 +88,6 @@ impl AssociatedOid for MockCurve { const OID: pkcs8::ObjectIdentifier = pkcs8::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); } -#[cfg(feature = "jwk")] -impl JwkParameters for MockCurve { - const CRV: &'static str = "P-256"; -} - /// Example scalar type #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] pub struct Scalar(ScalarPrimitive); @@ -366,16 +359,16 @@ impl Invert for Scalar { } impl Reduce for Scalar { - type Bytes = FieldBytes; - - fn reduce(w: U256) -> Self { + fn reduce(w: &U256) -> Self { let (r, underflow) = w.borrowing_sub(&MockCurve::ORDER, Limb::ZERO); let underflow = Choice::from((underflow.0 >> (Limb::BITS - 1)) as u8); - let reduced = U256::conditional_select(&w, &r, !underflow); + let reduced = U256::conditional_select(w, &r, !underflow); Self(ScalarPrimitive::new(reduced).unwrap()) } +} - fn reduce_bytes(_: &FieldBytes) -> Self { +impl Reduce for Scalar { + fn reduce(_w: &FieldBytes) -> Self { todo!() } } diff --git a/elliptic-curve/src/jwk.rs b/elliptic-curve/src/jwk.rs deleted file mode 100644 index b85ac90e1..000000000 --- a/elliptic-curve/src/jwk.rs +++ /dev/null @@ -1,676 +0,0 @@ -//! JSON Web Key (JWK) Support. -//! -//! Specified in RFC 7518 Section 6: Cryptographic Algorithms for Keys: -//! - -use crate::{ - Curve, Error, FieldBytes, FieldBytesSize, Result, - sec1::{Coordinates, EncodedPoint, ModulusSize, ValidatePublicKey}, - secret_key::SecretKey, -}; -use alloc::{ - borrow::ToOwned, - format, - string::{String, ToString}, -}; -use base64ct::{Base64UrlUnpadded as Base64Url, Encoding}; -use core::{ - fmt::{self, Debug}, - marker::PhantomData, - str::{self, FromStr}, -}; -use serdect::serde::{Deserialize, Serialize, de, ser}; -use zeroize::{Zeroize, ZeroizeOnDrop}; - -#[cfg(feature = "arithmetic")] -use crate::{ - AffinePoint, CurveArithmetic, - public_key::PublicKey, - sec1::{FromEncodedPoint, ToEncodedPoint}, -}; - -/// Key Type (`kty`) for elliptic curve keys. -pub const EC_KTY: &str = "EC"; - -/// Deserialization error message. -const DE_ERROR_MSG: &str = "struct JwkEcKey with 5 elements"; - -/// Name of the JWK type -const JWK_TYPE_NAME: &str = "JwkEcKey"; - -/// Field names -const FIELDS: &[&str] = &["kty", "crv", "x", "y", "d"]; - -/// Elliptic curve parameters used by JSON Web Keys. -pub trait JwkParameters: Curve { - /// The `crv` parameter which identifies a particular elliptic curve - /// as defined in RFC 7518 Section 6.2.1.1: - /// - /// - /// Curve values are registered in the IANA "JSON Web Key Elliptic Curve" - /// registry defined in RFC 7518 Section 7.6: - /// - const CRV: &'static str; -} - -/// JSON Web Key (JWK) with a `kty` of `"EC"` (elliptic curve). -/// -/// Specified in [RFC 7518 Section 6: Cryptographic Algorithms for Keys][1]. -/// -/// This type can represent either a public/private keypair, or just a -/// public key, depending on whether or not the `d` parameter is present. -/// -/// [1]: https://tools.ietf.org/html/rfc7518#section-6 -// TODO(tarcieri): eagerly decode or validate `x`, `y`, and `d` as Base64 -#[derive(Clone)] -pub struct JwkEcKey { - /// The `crv` parameter which identifies a particular elliptic curve - /// as defined in RFC 7518 Section 6.2.1.1: - /// - crv: String, - - /// The x-coordinate of the elliptic curve point which is the public key - /// value associated with this JWK as defined in RFC 7518 6.2.1.2: - /// - x: String, - - /// The y-coordinate of the elliptic curve point which is the public key - /// value associated with this JWK as defined in RFC 7518 6.2.1.3: - /// - y: String, - - /// The `d` ECC private key parameter as described in RFC 7518 6.2.2.1: - /// - /// - /// Value is optional and if omitted, this JWK represents a private key. - /// - /// Inner value is encoded according to the `Integer-to-Octet-String` - /// conversion as defined in SEC1 section 2.3.7: - /// - d: Option, -} - -impl JwkEcKey { - /// Get the `crv` parameter for this JWK. - pub fn crv(&self) -> &str { - &self.crv - } - - /// Is this JWK a keypair that includes a private key? - pub fn is_keypair(&self) -> bool { - self.d.is_some() - } - - /// Does this JWK contain only a public key? - pub fn is_public_key(&self) -> bool { - self.d.is_none() - } - - /// Decode a JWK into a [`PublicKey`]. - #[cfg(feature = "arithmetic")] - pub fn to_public_key(&self) -> Result> - where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - PublicKey::from_sec1_bytes(self.to_encoded_point::()?.as_bytes()) - } - - /// Create a JWK from a SEC1 [`EncodedPoint`]. - pub fn from_encoded_point(point: &EncodedPoint) -> Option - where - C: Curve + JwkParameters, - FieldBytesSize: ModulusSize, - { - match point.coordinates() { - Coordinates::Uncompressed { x, y } => Some(JwkEcKey { - crv: C::CRV.to_owned(), - x: Base64Url::encode_string(x), - y: Base64Url::encode_string(y), - d: None, - }), - _ => None, - } - } - - /// Get the public key component of this JWK as a SEC1 [`EncodedPoint`]. - pub fn to_encoded_point(&self) -> Result> - where - C: Curve + JwkParameters, - FieldBytesSize: ModulusSize, - { - if self.crv != C::CRV { - return Err(Error); - } - - let x = decode_base64url_fe::(&self.x)?; - let y = decode_base64url_fe::(&self.y)?; - Ok(EncodedPoint::::from_affine_coordinates(&x, &y, false)) - } - - /// Decode a JWK into a [`SecretKey`]. - #[cfg(feature = "arithmetic")] - pub fn to_secret_key(&self) -> Result> - where - C: Curve + JwkParameters + ValidatePublicKey, - FieldBytesSize: ModulusSize, - { - self.try_into() - } -} - -impl FromStr for JwkEcKey { - type Err = Error; - - fn from_str(s: &str) -> Result { - serde_json::from_str(s).map_err(|_| Error) - } -} - -#[allow(clippy::to_string_trait_impl)] -impl ToString for JwkEcKey { - fn to_string(&self) -> String { - serde_json::to_string(self).expect("JWK encoding error") - } -} - -impl TryFrom for SecretKey -where - C: Curve + JwkParameters + ValidatePublicKey, - FieldBytesSize: ModulusSize, -{ - type Error = Error; - - fn try_from(jwk: JwkEcKey) -> Result> { - (&jwk).try_into() - } -} - -impl TryFrom<&JwkEcKey> for SecretKey -where - C: Curve + JwkParameters + ValidatePublicKey, - FieldBytesSize: ModulusSize, -{ - type Error = Error; - - fn try_from(jwk: &JwkEcKey) -> Result> { - if let Some(d_base64) = &jwk.d { - let pk = jwk.to_encoded_point::()?; - let mut d_bytes = decode_base64url_fe::(d_base64)?; - let result = SecretKey::from_slice(&d_bytes); - d_bytes.zeroize(); - - result.and_then(|secret_key| { - C::validate_public_key(&secret_key, &pk)?; - Ok(secret_key) - }) - } else { - Err(Error) - } - } -} - -#[cfg(feature = "arithmetic")] -impl From> for JwkEcKey -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - fn from(sk: SecretKey) -> JwkEcKey { - (&sk).into() - } -} - -#[cfg(feature = "arithmetic")] -impl From<&SecretKey> for JwkEcKey -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - fn from(sk: &SecretKey) -> JwkEcKey { - let mut jwk = sk.public_key().to_jwk(); - let mut d = sk.to_bytes(); - jwk.d = Some(Base64Url::encode_string(&d)); - d.zeroize(); - jwk - } -} - -#[cfg(feature = "arithmetic")] -impl TryFrom for PublicKey -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - type Error = Error; - - fn try_from(jwk: JwkEcKey) -> Result> { - (&jwk).try_into() - } -} - -#[cfg(feature = "arithmetic")] -impl TryFrom<&JwkEcKey> for PublicKey -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - type Error = Error; - - fn try_from(jwk: &JwkEcKey) -> Result> { - PublicKey::from_sec1_bytes(jwk.to_encoded_point::()?.as_bytes()) - } -} - -#[cfg(feature = "arithmetic")] -impl From> for JwkEcKey -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - fn from(pk: PublicKey) -> JwkEcKey { - (&pk).into() - } -} - -#[cfg(feature = "arithmetic")] -impl From<&PublicKey> for JwkEcKey -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - fn from(pk: &PublicKey) -> JwkEcKey { - Self::from_encoded_point::(&pk.to_encoded_point(false)).expect("JWK encoding error") - } -} - -impl Debug for JwkEcKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let d = if self.d.is_some() { - "Some(...)" - } else { - "None" - }; - - // NOTE: this implementation omits the `d` private key parameter - f.debug_struct(JWK_TYPE_NAME) - .field("crv", &self.crv) - .field("x", &self.x) - .field("y", &self.y) - .field("d", &d) - .finish() - } -} - -impl PartialEq for JwkEcKey { - fn eq(&self, other: &Self) -> bool { - use subtle::ConstantTimeEq; - - // Compare private key in constant time - let d_eq = match &self.d { - Some(d1) => match &other.d { - Some(d2) => d1.as_bytes().ct_eq(d2.as_bytes()).into(), - None => other.d.is_none(), - }, - None => other.d.is_none(), - }; - - self.crv == other.crv && self.x == other.x && self.y == other.y && d_eq - } -} - -impl Eq for JwkEcKey {} - -impl ZeroizeOnDrop for JwkEcKey {} - -impl Drop for JwkEcKey { - fn drop(&mut self) { - self.zeroize(); - } -} - -impl Zeroize for JwkEcKey { - fn zeroize(&mut self) { - if let Some(d) = &mut self.d { - d.zeroize(); - } - } -} - -impl<'de> Deserialize<'de> for JwkEcKey { - fn deserialize(deserializer: D) -> core::result::Result - where - D: de::Deserializer<'de>, - { - /// Field positions - enum Field { - Kty, - Crv, - X, - Y, - D, - } - - /// Field visitor - struct FieldVisitor; - - impl de::Visitor<'_> for FieldVisitor { - type Value = Field; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Formatter::write_str(formatter, "field identifier") - } - - fn visit_u64(self, value: u64) -> core::result::Result - where - E: de::Error, - { - match value { - 0 => Ok(Field::Kty), - 1 => Ok(Field::Crv), - 2 => Ok(Field::X), - 3 => Ok(Field::Y), - 4 => Ok(Field::D), - _ => Err(de::Error::invalid_value( - de::Unexpected::Unsigned(value), - &"field index 0 <= i < 5", - )), - } - } - - fn visit_str(self, value: &str) -> core::result::Result - where - E: de::Error, - { - self.visit_bytes(value.as_bytes()) - } - - fn visit_bytes(self, value: &[u8]) -> core::result::Result - where - E: de::Error, - { - match value { - b"kty" => Ok(Field::Kty), - b"crv" => Ok(Field::Crv), - b"x" => Ok(Field::X), - b"y" => Ok(Field::Y), - b"d" => Ok(Field::D), - _ => Err(de::Error::unknown_field( - &String::from_utf8_lossy(value), - FIELDS, - )), - } - } - } - - impl<'de> Deserialize<'de> for Field { - #[inline] - fn deserialize(__deserializer: D) -> core::result::Result - where - D: de::Deserializer<'de>, - { - de::Deserializer::deserialize_identifier(__deserializer, FieldVisitor) - } - } - - struct Visitor<'de> { - marker: PhantomData, - lifetime: PhantomData<&'de ()>, - } - - impl<'de> de::Visitor<'de> for Visitor<'de> { - type Value = JwkEcKey; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Formatter::write_str(formatter, "struct JwkEcKey") - } - - #[inline] - fn visit_seq(self, mut seq: A) -> core::result::Result - where - A: de::SeqAccess<'de>, - { - let kty = de::SeqAccess::next_element::(&mut seq)? - .ok_or_else(|| de::Error::invalid_length(0, &DE_ERROR_MSG))?; - - if kty != EC_KTY { - return Err(de::Error::custom(format!("unsupported JWK kty: {kty:?}"))); - } - - let crv = de::SeqAccess::next_element::(&mut seq)? - .ok_or_else(|| de::Error::invalid_length(1, &DE_ERROR_MSG))?; - - let x = de::SeqAccess::next_element::(&mut seq)? - .ok_or_else(|| de::Error::invalid_length(2, &DE_ERROR_MSG))?; - - let y = de::SeqAccess::next_element::(&mut seq)? - .ok_or_else(|| de::Error::invalid_length(3, &DE_ERROR_MSG))?; - - let d = de::SeqAccess::next_element::>(&mut seq)? - .ok_or_else(|| de::Error::invalid_length(4, &DE_ERROR_MSG))?; - - Ok(JwkEcKey { crv, x, y, d }) - } - - #[inline] - fn visit_map(self, mut map: A) -> core::result::Result - where - A: de::MapAccess<'de>, - { - let mut kty: Option = None; - let mut crv: Option = None; - let mut x: Option = None; - let mut y: Option = None; - let mut d: Option = None; - - while let Some(key) = de::MapAccess::next_key::(&mut map)? { - match key { - Field::Kty => { - if kty.is_none() { - kty = Some(de::MapAccess::next_value::(&mut map)?); - } else { - return Err(de::Error::duplicate_field(FIELDS[0])); - } - } - Field::Crv => { - if crv.is_none() { - crv = Some(de::MapAccess::next_value::(&mut map)?); - } else { - return Err(de::Error::duplicate_field(FIELDS[1])); - } - } - Field::X => { - if x.is_none() { - x = Some(de::MapAccess::next_value::(&mut map)?); - } else { - return Err(de::Error::duplicate_field(FIELDS[2])); - } - } - Field::Y => { - if y.is_none() { - y = Some(de::MapAccess::next_value::(&mut map)?); - } else { - return Err(de::Error::duplicate_field(FIELDS[3])); - } - } - Field::D => { - if d.is_none() { - d = de::MapAccess::next_value::>(&mut map)?; - } else { - return Err(de::Error::duplicate_field(FIELDS[4])); - } - } - } - } - - let kty = kty.ok_or_else(|| de::Error::missing_field("kty"))?; - - if kty != EC_KTY { - return Err(de::Error::custom(format!("unsupported JWK kty: {kty}"))); - } - - let crv = crv.ok_or_else(|| de::Error::missing_field("crv"))?; - let x = x.ok_or_else(|| de::Error::missing_field("x"))?; - let y = y.ok_or_else(|| de::Error::missing_field("y"))?; - - Ok(JwkEcKey { crv, x, y, d }) - } - } - - de::Deserializer::deserialize_struct( - deserializer, - JWK_TYPE_NAME, - FIELDS, - Visitor { - marker: PhantomData::, - lifetime: PhantomData, - }, - ) - } -} - -impl Serialize for JwkEcKey { - fn serialize(&self, serializer: S) -> core::result::Result - where - S: ser::Serializer, - { - use ser::SerializeStruct; - - let mut state = serializer.serialize_struct(JWK_TYPE_NAME, 5)?; - - for (i, field) in [EC_KTY, &self.crv, &self.x, &self.y].iter().enumerate() { - state.serialize_field(FIELDS[i], field)?; - } - - if let Some(d) = &self.d { - state.serialize_field("d", d)?; - } - - SerializeStruct::end(state) - } -} - -/// Decode a Base64url-encoded field element -fn decode_base64url_fe(s: &str) -> Result> { - let mut result = FieldBytes::::default(); - Base64Url::decode(s, &mut result).map_err(|_| Error)?; - Ok(result) -} - -#[cfg(test)] -mod tests { - #![allow(clippy::unwrap_used, clippy::panic)] - use super::*; - - #[cfg(feature = "dev")] - use crate::dev::MockCurve; - - /// Example private key. From RFC 7518 Appendix C: - /// - const JWK_PRIVATE_KEY: &str = r#" - { - "kty":"EC", - "crv":"P-256", - "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", - "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps", - "d":"0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo" - } - "#; - - /// Example public key. - const JWK_PUBLIC_KEY: &str = r#" - { - "kty":"EC", - "crv":"P-256", - "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", - "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps" - } - "#; - - /// Example unsupported JWK (RSA key) - const UNSUPPORTED_JWK: &str = r#" - { - "kty":"RSA", - "kid":"cc34c0a0-bd5a-4a3c-a50d-a2a7db7643df", - "use":"sig", - "n":"pjdss8ZaDfEH6K6U7GeW2nxDqR4IP049fk1fK0lndimbMMVBdPv_hSpm8T8EtBDxrUdi1OHZfMhUixGaut-3nQ4GG9nM249oxhCtxqqNvEXrmQRGqczyLxuh-fKn9Fg--hS9UpazHpfVAFnB5aCfXoNhPuI8oByyFKMKaOVgHNqP5NBEqabiLftZD3W_lsFCPGuzr4Vp0YS7zS2hDYScC2oOMu4rGU1LcMZf39p3153Cq7bS2Xh6Y-vw5pwzFYZdjQxDn8x8BG3fJ6j8TGLXQsbKH1218_HcUJRvMwdpbUQG5nvA2GXVqLqdwp054Lzk9_B_f1lVrmOKuHjTNHq48w", - "e":"AQAB", - "d":"ksDmucdMJXkFGZxiomNHnroOZxe8AmDLDGO1vhs-POa5PZM7mtUPonxwjVmthmpbZzla-kg55OFfO7YcXhg-Hm2OWTKwm73_rLh3JavaHjvBqsVKuorX3V3RYkSro6HyYIzFJ1Ek7sLxbjDRcDOj4ievSX0oN9l-JZhaDYlPlci5uJsoqro_YrE0PRRWVhtGynd-_aWgQv1YzkfZuMD-hJtDi1Im2humOWxA4eZrFs9eG-whXcOvaSwO4sSGbS99ecQZHM2TcdXeAs1PvjVgQ_dKnZlGN3lTWoWfQP55Z7Tgt8Nf1q4ZAKd-NlMe-7iqCFfsnFwXjSiaOa2CRGZn-Q", - "p":"4A5nU4ahEww7B65yuzmGeCUUi8ikWzv1C81pSyUKvKzu8CX41hp9J6oRaLGesKImYiuVQK47FhZ--wwfpRwHvSxtNU9qXb8ewo-BvadyO1eVrIk4tNV543QlSe7pQAoJGkxCia5rfznAE3InKF4JvIlchyqs0RQ8wx7lULqwnn0", - "q":"ven83GM6SfrmO-TBHbjTk6JhP_3CMsIvmSdo4KrbQNvp4vHO3w1_0zJ3URkmkYGhz2tgPlfd7v1l2I6QkIh4Bumdj6FyFZEBpxjE4MpfdNVcNINvVj87cLyTRmIcaGxmfylY7QErP8GFA-k4UoH_eQmGKGK44TRzYj5hZYGWIC8", - "dp":"lmmU_AG5SGxBhJqb8wxfNXDPJjf__i92BgJT2Vp4pskBbr5PGoyV0HbfUQVMnw977RONEurkR6O6gxZUeCclGt4kQlGZ-m0_XSWx13v9t9DIbheAtgVJ2mQyVDvK4m7aRYlEceFh0PsX8vYDS5o1txgPwb3oXkPTtrmbAGMUBpE", - "dq":"mxRTU3QDyR2EnCv0Nl0TCF90oliJGAHR9HJmBe__EjuCBbwHfcT8OG3hWOv8vpzokQPRl5cQt3NckzX3fs6xlJN4Ai2Hh2zduKFVQ2p-AF2p6Yfahscjtq-GY9cB85NxLy2IXCC0PF--Sq9LOrTE9QV988SJy_yUrAjcZ5MmECk", - "qi":"ldHXIrEmMZVaNwGzDF9WG8sHj2mOZmQpw9yrjLK9hAsmsNr5LTyqWAqJIYZSwPTYWhY4nu2O0EY9G9uYiqewXfCKw_UngrJt8Xwfq1Zruz0YY869zPN4GiE9-9rzdZB33RBw8kIOquY3MK74FMwCihYx_LiU2YTHkaoJ3ncvtvg" - } - "#; - - #[test] - fn parse_private_key() { - let jwk = JwkEcKey::from_str(JWK_PRIVATE_KEY).unwrap(); - assert_eq!(jwk.crv, "P-256"); - assert_eq!(jwk.x, "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0"); - assert_eq!(jwk.y, "SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps"); - assert_eq!( - jwk.d.as_ref().unwrap(), - "0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo" - ); - } - - #[test] - fn parse_public_key() { - let jwk = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap(); - assert_eq!(jwk.crv, "P-256"); - assert_eq!(jwk.x, "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0"); - assert_eq!(jwk.y, "SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps"); - assert_eq!(jwk.d, None); - } - - #[test] - fn parse_unsupported() { - assert_eq!(JwkEcKey::from_str(UNSUPPORTED_JWK), Err(Error)); - } - - #[test] - fn serialize_private_key() { - let actual = JwkEcKey::from_str(JWK_PRIVATE_KEY).unwrap().to_string(); - let expected: String = JWK_PRIVATE_KEY.split_whitespace().collect(); - assert_eq!(actual, expected); - } - - #[test] - fn serialize_public_key() { - let actual = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap().to_string(); - let expected: String = JWK_PUBLIC_KEY.split_whitespace().collect(); - assert_eq!(actual, expected); - } - - #[cfg(feature = "dev")] - #[test] - fn jwk_into_encoded_point() { - let jwk = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap(); - let point = jwk.to_encoded_point::().unwrap(); - let (x, y) = match point.coordinates() { - Coordinates::Uncompressed { x, y } => (x, y), - other => panic!("unexpected coordinates: {other:?}"), - }; - - assert_eq!(&decode_base64url_fe::(&jwk.x).unwrap(), x); - assert_eq!(&decode_base64url_fe::(&jwk.y).unwrap(), y); - } - - #[cfg(feature = "dev")] - #[test] - fn encoded_point_into_jwk() { - let jwk = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap(); - let point = jwk.to_encoded_point::().unwrap(); - let jwk2 = JwkEcKey::from_encoded_point::(&point).unwrap(); - assert_eq!(jwk, jwk2); - } -} diff --git a/elliptic-curve/src/lib.rs b/elliptic-curve/src/lib.rs index bd880be88..3bf084788 100644 --- a/elliptic-curve/src/lib.rs +++ b/elliptic-curve/src/lib.rs @@ -61,7 +61,6 @@ //! When the `serde` feature of this crate is enabled, `Serialize` and //! `Deserialize` impls are provided for the following types: //! -//! - [`JwkEcKey`] //! - [`PublicKey`] //! - [`ScalarPrimitive`] //! @@ -108,9 +107,6 @@ mod arithmetic; #[cfg(feature = "arithmetic")] mod public_key; -#[cfg(feature = "jwk")] -mod jwk; - pub use crate::{ error::{Error, Result}, field::{FieldBytes, FieldBytesEncoding, FieldBytesSize}, @@ -136,12 +132,10 @@ pub use { group::{self, Curve as CurveGroup, Group}, }; -#[cfg(feature = "jwk")] -pub use crate::jwk::{JwkEcKey, JwkParameters}; - #[cfg(feature = "pkcs8")] pub use pkcs8; +use bigint::NonZero; use core::{ fmt::Debug, ops::{Add, ShrAssign}, @@ -186,7 +180,9 @@ pub trait Curve: 'static + Copy + Clone + Debug + Default + Eq + Ord + Send + Sy /// Order of this elliptic curve, i.e. number of elements in the scalar /// field. - const ORDER: Self::Uint; + // TODO(tarcieri): make `Odd`? the prime order subgroup should always have an odd number of + // elements, even if there is a cofactor + const ORDER: NonZero; } /// Marker trait for elliptic curves with prime order. diff --git a/elliptic-curve/src/ops.rs b/elliptic-curve/src/ops.rs index 2e7252d65..93abe7028 100644 --- a/elliptic-curve/src/ops.rs +++ b/elliptic-curve/src/ops.rs @@ -1,11 +1,10 @@ //! Traits for arithmetic operations on elliptic curve field elements. pub use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Shr, ShrAssign, Sub, SubAssign}; -pub use crypto_bigint::Invert; +pub use crypto_bigint::{Invert, Reduce}; use crate::CurveGroup; use core::iter; -use crypto_bigint::Integer; use ff::Field; use subtle::{Choice, CtOption}; @@ -175,18 +174,6 @@ where } } -/// Modular reduction. -pub trait Reduce: Sized { - /// Bytes used as input to [`Reduce::reduce_bytes`]. - type Bytes: AsRef<[u8]>; - - /// Perform a modular reduction, returning a field element. - fn reduce(n: Uint) -> Self; - - /// Interpret the given bytes as an integer and perform a modular reduction. - fn reduce_bytes(bytes: &Self::Bytes) -> Self; -} - /// Modular reduction to a non-zero output. /// /// This trait is primarily intended for use by curve implementations such @@ -194,11 +181,7 @@ pub trait Reduce: Sized { /// /// End users should use the [`Reduce`] impl on /// [`NonZeroScalar`][`crate::NonZeroScalar`] instead. -pub trait ReduceNonZero: Reduce + Sized { +pub trait ReduceNonZero: Reduce { /// Perform a modular reduction, returning a field element. - fn reduce_nonzero(n: Uint) -> Self; - - /// Interpret the given bytes as an integer and perform a modular reduction - /// to a non-zero output. - fn reduce_nonzero_bytes(bytes: &Self::Bytes) -> Self; + fn reduce_nonzero(n: &T) -> Self; } diff --git a/elliptic-curve/src/point/non_identity.rs b/elliptic-curve/src/point/non_identity.rs index 6617e2c9a..6380b3a68 100644 --- a/elliptic-curve/src/point/non_identity.rs +++ b/elliptic-curve/src/point/non_identity.rs @@ -3,7 +3,7 @@ use core::ops::{Deref, Mul}; use group::{Group, GroupEncoding, prime::PrimeCurveAffine}; -use rand_core::CryptoRng; +use rand_core::{CryptoRng, TryCryptoRng}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; #[cfg(feature = "alloc")] @@ -94,6 +94,15 @@ where } } + /// Generate a random `NonIdentity`. + pub fn try_from_rng(rng: &mut R) -> Result { + loop { + if let Some(point) = Self::new(P::try_from_rng(rng)?).into() { + break Ok(point); + } + } + } + /// Converts this element into its affine representation. pub fn to_affine(self) -> NonIdentity { NonIdentity { diff --git a/elliptic-curve/src/public_key.rs b/elliptic-curve/src/public_key.rs index 6995b2568..f0e4a1637 100644 --- a/elliptic-curve/src/public_key.rs +++ b/elliptic-curve/src/public_key.rs @@ -7,14 +7,14 @@ use crate::{ use core::fmt::Debug; use group::Group; -#[cfg(feature = "jwk")] -use crate::{JwkEcKey, JwkParameters}; - #[cfg(feature = "pkcs8")] use pkcs8::spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier, ObjectIdentifier}; #[cfg(feature = "pem")] -use core::str::FromStr; +use { + alloc::string::{String, ToString}, + core::str::FromStr, +}; #[cfg(feature = "sec1")] use { @@ -27,18 +27,15 @@ use { subtle::{Choice, CtOption}, }; +#[cfg(feature = "serde")] +use serdect::serde::{Deserialize, Serialize, de, ser}; + #[cfg(all(feature = "alloc", feature = "pkcs8"))] use pkcs8::EncodePublicKey; #[cfg(all(feature = "alloc", feature = "sec1"))] use alloc::boxed::Box; -#[cfg(any(feature = "jwk", feature = "pem"))] -use alloc::string::{String, ToString}; - -#[cfg(feature = "serde")] -use serdect::serde::{Deserialize, Serialize, de, ser}; - #[cfg(any(feature = "pem", feature = "serde"))] use pkcs8::DecodePublicKey; @@ -82,8 +79,6 @@ use { /// /// The serialization is binary-oriented and supports ASN.1 DER /// Subject Public Key Info (SPKI) as the encoding format. -/// -/// For a more text-friendly encoding of public keys, use [`JwkEcKey`] instead. #[derive(Clone, Debug, Eq, PartialEq)] pub struct PublicKey where @@ -162,50 +157,6 @@ where pub fn to_nonidentity(&self) -> NonIdentity> { NonIdentity::new_unchecked(self.point) } - - /// Parse a [`JwkEcKey`] JSON Web Key (JWK) into a [`PublicKey`]. - #[cfg(feature = "jwk")] - pub fn from_jwk(jwk: &JwkEcKey) -> Result - where - C: JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - jwk.to_public_key::() - } - - /// Parse a string containing a JSON Web Key (JWK) into a [`PublicKey`]. - #[cfg(feature = "jwk")] - pub fn from_jwk_str(jwk: &str) -> Result - where - C: JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - jwk.parse::().and_then(|jwk| Self::from_jwk(&jwk)) - } - - /// Serialize this public key as [`JwkEcKey`] JSON Web Key (JWK). - #[cfg(feature = "jwk")] - pub fn to_jwk(&self) -> JwkEcKey - where - C: JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - self.into() - } - - /// Serialize this public key as JSON Web Key (JWK) string. - #[cfg(feature = "jwk")] - pub fn to_jwk_string(&self) -> String - where - C: JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - self.to_jwk().to_string() - } } impl AsRef> for PublicKey diff --git a/elliptic-curve/src/scalar/nonzero.rs b/elliptic-curve/src/scalar/nonzero.rs index d0dd1768b..fd9e5315c 100644 --- a/elliptic-curve/src/scalar/nonzero.rs +++ b/elliptic-curve/src/scalar/nonzero.rs @@ -12,7 +12,6 @@ use core::{ ops::{Deref, Mul, MulAssign, Neg}, str, }; -use crypto_bigint::{ArrayEncoding, Integer}; use ff::{Field, PrimeField}; use rand_core::{CryptoRng, TryCryptoRng}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; @@ -391,42 +390,25 @@ where } } -/// Note: this is a non-zero reduction, as it's impl'd for [`NonZeroScalar`]. -impl Reduce for NonZeroScalar +impl Reduce for NonZeroScalar where C: CurveArithmetic, - I: Integer + ArrayEncoding, - Scalar: Reduce + ReduceNonZero, + Scalar: ReduceNonZero, { - type Bytes = as Reduce>::Bytes; - - fn reduce(n: I) -> Self { - let scalar = Scalar::::reduce_nonzero(n); - debug_assert!(!bool::from(scalar.is_zero())); - Self { scalar } - } - - fn reduce_bytes(bytes: &Self::Bytes) -> Self { - let scalar = Scalar::::reduce_nonzero_bytes(bytes); - debug_assert!(!bool::from(scalar.is_zero())); - Self { scalar } + fn reduce(n: &T) -> Self { + >::reduce_nonzero(n) } } -/// Note: forwards to the [`Reduce`] impl. -impl ReduceNonZero for NonZeroScalar +impl ReduceNonZero for NonZeroScalar where - Self: Reduce, C: CurveArithmetic, - I: Integer + ArrayEncoding, - Scalar: Reduce + ReduceNonZero, + Scalar: ReduceNonZero, { - fn reduce_nonzero(n: I) -> Self { - >::reduce(n) - } - - fn reduce_nonzero_bytes(bytes: &Self::Bytes) -> Self { - >::reduce_bytes(bytes) + fn reduce_nonzero(n: &T) -> Self { + let scalar = Scalar::::reduce_nonzero(n); + debug_assert!(!bool::from(scalar.is_zero())); + Self { scalar } } } diff --git a/elliptic-curve/src/scalar/primitive.rs b/elliptic-curve/src/scalar/primitive.rs index 3772cb81f..adfcf1cec 100644 --- a/elliptic-curve/src/scalar/primitive.rs +++ b/elliptic-curve/src/scalar/primitive.rs @@ -62,12 +62,12 @@ where }; /// Scalar modulus. - pub const MODULUS: C::Uint = C::ORDER; + pub const MODULUS: NonZero = C::ORDER; /// Generate a random [`ScalarPrimitive`]. pub fn random(rng: &mut R) -> Self { Self { - inner: C::Uint::random_mod(rng, &NonZero::new(Self::MODULUS).unwrap()), + inner: C::Uint::random_mod(rng, &Self::MODULUS), } } @@ -357,7 +357,7 @@ where C: Curve, { fn is_high(&self) -> Choice { - let n_2 = C::ORDER >> 1u32; + let n_2 = Self::MODULUS.get() >> 1u32; self.inner.ct_gt(&n_2) } } diff --git a/elliptic-curve/src/secret_key.rs b/elliptic-curve/src/secret_key.rs index 1559f62bc..dbc435605 100644 --- a/elliptic-curve/src/secret_key.rs +++ b/elliptic-curve/src/secret_key.rs @@ -20,9 +20,6 @@ use crate::{ rand_core::{CryptoRng, TryCryptoRng}, }; -#[cfg(feature = "jwk")] -use crate::jwk::{JwkEcKey, JwkParameters}; - #[cfg(feature = "pem")] use pem_rfc7468::{self as pem, PemLabel}; @@ -45,12 +42,9 @@ use { sec1::der::Encode, }; -#[cfg(all(feature = "arithmetic", any(feature = "jwk", feature = "pem")))] +#[cfg(all(feature = "arithmetic", feature = "pem"))] use alloc::string::String; -#[cfg(all(feature = "arithmetic", feature = "jwk"))] -use alloc::string::ToString; - #[cfg(all(doc, feature = "pkcs8"))] use {crate::pkcs8::DecodePrivateKey, core::str::FromStr}; @@ -278,48 +272,6 @@ where .map(Zeroizing::new) .ok_or(Error) } - - /// Parse a [`JwkEcKey`] JSON Web Key (JWK) into a [`SecretKey`]. - #[cfg(feature = "jwk")] - pub fn from_jwk(jwk: &JwkEcKey) -> Result - where - C: JwkParameters + ValidatePublicKey, - FieldBytesSize: ModulusSize, - { - Self::try_from(jwk) - } - - /// Parse a string containing a JSON Web Key (JWK) into a [`SecretKey`]. - #[cfg(feature = "jwk")] - pub fn from_jwk_str(jwk: &str) -> Result - where - C: JwkParameters + ValidatePublicKey, - FieldBytesSize: ModulusSize, - { - jwk.parse::().and_then(|jwk| Self::from_jwk(&jwk)) - } - - /// Serialize this secret key as [`JwkEcKey`] JSON Web Key (JWK). - #[cfg(all(feature = "arithmetic", feature = "jwk"))] - pub fn to_jwk(&self) -> JwkEcKey - where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - self.into() - } - - /// Serialize this secret key as JSON Web Key (JWK) string. - #[cfg(all(feature = "arithmetic", feature = "jwk"))] - pub fn to_jwk_string(&self) -> Zeroizing - where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - Zeroizing::new(self.to_jwk().to_string()) - } } impl ConstantTimeEq for SecretKey diff --git a/password-hash/src/salt.rs b/password-hash/src/salt.rs index c42f8b6ef..2d1975407 100644 --- a/password-hash/src/salt.rs +++ b/password-hash/src/salt.rs @@ -146,21 +146,6 @@ impl<'a> Salt<'a> { pub fn len(&self) -> usize { self.as_str().len() } - - /// Create a [`Salt`] from the given B64-encoded input string, validating - /// [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions. - #[deprecated(since = "0.5.0", note = "use `from_b64` instead")] - pub fn new(input: &'a str) -> Result { - Self::from_b64(input) - } - - /// Attempt to decode a B64-encoded [`Salt`] into bytes, writing the - /// decoded output into the provided buffer, and returning a slice of the - /// portion of the buffer containing the decoded result on success. - #[deprecated(since = "0.5.0", note = "use `decode_b64` instead")] - pub fn b64_decode<'b>(&self, buf: &'b mut [u8]) -> Result<&'b [u8]> { - self.decode_b64(buf) - } } impl AsRef for Salt<'_> { @@ -268,27 +253,6 @@ impl SaltString { pub fn len(&self) -> usize { self.as_str().len() } - - /// Create a new [`SaltString`] from the given B64-encoded input string, - /// validating [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions. - #[deprecated(since = "0.5.0", note = "use `from_b64` instead")] - pub fn new(s: &str) -> Result { - Self::from_b64(s) - } - - /// Decode this [`SaltString`] from B64 into the provided output buffer. - #[deprecated(since = "0.5.0", note = "use `decode_b64` instead")] - pub fn b64_decode<'a>(&self, buf: &'a mut [u8]) -> Result<&'a [u8]> { - self.decode_b64(buf) - } - - /// Encode the given byte slice as B64 into a new [`SaltString`]. - /// - /// Returns `Error` if the slice is too long. - #[deprecated(since = "0.5.0", note = "use `encode_b64` instead")] - pub fn b64_encode(input: &[u8]) -> Result { - Self::encode_b64(input) - } } impl AsRef for SaltString { diff --git a/signature/Cargo.toml b/signature/Cargo.toml index 4e70a09fd..9b8b6d5b4 100644 --- a/signature/Cargo.toml +++ b/signature/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "signature" -version = "3.0.0-rc.2" +version = "3.0.0-rc.3" authors = ["RustCrypto Developers"] edition = "2024" rust-version = "1.85" @@ -13,7 +13,7 @@ categories = ["cryptography", "no-std"] description = "Traits for cryptographic signature algorithms (e.g. ECDSA, Ed25519)" [dependencies] -digest = { version = "0.11.0-rc.0", optional = true, default-features = false } +digest = { version = "0.11.0-rc.1", optional = true, default-features = false } rand_core = { version = "0.9", optional = true, default-features = false } [features] diff --git a/universal-hash/Cargo.toml b/universal-hash/Cargo.toml index 164fa7df7..62a3b3b1c 100644 --- a/universal-hash/Cargo.toml +++ b/universal-hash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "universal-hash" -version = "0.6.0-rc.1" +version = "0.6.0-rc.2" authors = ["RustCrypto Developers"] edition = "2024" rust-version = "1.85" @@ -13,7 +13,7 @@ categories = ["cryptography", "no-std"] description = "Traits which describe the functionality of universal hash functions (UHFs)" [dependencies] -crypto-common = { version = "0.2.0-rc.3", path = "../crypto-common" } +crypto-common = { version = "0.2.0-rc.4", path = "../crypto-common" } subtle = { version = "2.4", default-features = false } [package.metadata.docs.rs] From abbe68a8526ce7ece8fba1ef146fb842afa3bf86 Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Sun, 7 Sep 2025 21:10:20 -0400 Subject: [PATCH 11/12] update dependencies --- vrf/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vrf/Cargo.toml b/vrf/Cargo.toml index f4eeb45e0..32be883ff 100644 --- a/vrf/Cargo.toml +++ b/vrf/Cargo.toml @@ -4,5 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] -digest = "=0.11.0-pre.10" -signature = "3.0.0-rc.1" +digest = "0.11.0-rc.1" +signature = "3.0.0-rc.3" From bf40f5776c2a17fa28ca690128cf99d2668c91e6 Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Sun, 7 Sep 2025 21:10:39 -0400 Subject: [PATCH 12/12] remove comment on `Verifier` --- Cargo.lock | 27 +++++++++------------------ vrf/src/lib.rs | 6 ------ 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1fa924650..5420171aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,7 +20,7 @@ dependencies = [ "arrayvec", "blobby", "bytes", - "crypto-common 0.2.0-rc.3", + "crypto-common", "heapless", "inout", ] @@ -132,8 +132,8 @@ version = "0.6.0-pre" dependencies = [ "aead", "cipher", - "crypto-common 0.2.0-rc.3", - "digest 0.11.0-rc.0", + "crypto-common", + "digest", "elliptic-curve", "password-hash", "signature", @@ -161,15 +161,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "crypto-common" -version = "0.2.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a23fa214dea9efd4dacee5a5614646b30216ae0f05d4bb51bafb50e9da1c5be" -dependencies = [ - "hybrid-array", -] - [[package]] name = "der" version = "0.8.0-rc.8" @@ -188,7 +179,7 @@ dependencies = [ "blobby", "block-buffer", "const-oid", - "crypto-common 0.2.0-rc.3", + "crypto-common", "subtle", "zeroize", ] @@ -199,7 +190,7 @@ version = "0.14.0-rc.12" dependencies = [ "base16ct", "crypto-bigint", - "digest 0.11.0-rc.0", + "digest", "ff", "group", "hex-literal", @@ -310,7 +301,7 @@ version = "0.13.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dc6a2fcc35ab09136c6df2cdf9ca49790701420a3a6b5db0987dddbabc79b21" dependencies = [ - "digest 0.11.0-rc.0", + "digest", ] [[package]] @@ -491,7 +482,7 @@ dependencies = [ name = "signature" version = "3.0.0-rc.3" dependencies = [ - "digest 0.11.0-rc.0", + "digest", "rand_core", ] @@ -561,7 +552,7 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" name = "universal-hash" version = "0.6.0-rc.2" dependencies = [ - "crypto-common 0.2.0-rc.3", + "crypto-common", "subtle", ] @@ -569,7 +560,7 @@ dependencies = [ name = "vrf" version = "0.1.0" dependencies = [ - "digest 0.11.0-pre.10", + "digest", "signature", ] diff --git a/vrf/src/lib.rs b/vrf/src/lib.rs index e5c5be0ef..71dbd4eeb 100644 --- a/vrf/src/lib.rs +++ b/vrf/src/lib.rs @@ -20,12 +20,6 @@ //! //! Traits are defined to match the functionality of verifiable random functions in //! [RFC9381](https://www.rfc-editor.org/rfc/rfc9381.pdf). -//! -//! ## Verifying Proofs -//! -//! Trait based proof verification is delegated to the [`signature::Verifier`] trait, defined in -//! the `signature` crate and re-exported here. The message corresponds to the `alpha` or -//! `alpha_string` in RFC9381 (see section 1.2), and the signature corresponds to the [`Proof`]. use digest::{Output, OutputSizeUser};