lms: import into RustCrypto/signatures (#801)

Leighton-Micali Hash-Based Signatures

Co-authored-by: Tjaden Hess <tjade273@gmail.com>
This commit is contained in:
Will Song
2024-03-06 14:30:50 -05:00
committed by GitHub
parent c2f3ee6497
commit 3e4e1aa270
27 changed files with 3090 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
name: lms
on:
pull_request:
paths:
- ".github/workflows/lms.yml"
- "lms/**"
- "Cargo.*"
push:
branches: master
defaults:
run:
working-directory: lms
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-Dwarnings"
jobs:
clippy:
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- 1.73.0 # MSRV
- stable
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
components: clippy
- run: cargo clippy --no-default-features
- run: cargo clippy
- run: cargo clippy --all-features
test:
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- 1.73.0 # MSRV
- stable
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- run: cargo check --all-features
- run: cargo test --no-default-features
- run: cargo test
- run: cargo test --all-features
+1
View File
@@ -5,6 +5,7 @@ members = [
"ecdsa",
"ed448",
"ed25519",
"lms",
"rfc6979"
]
+5
View File
@@ -0,0 +1,5 @@
# 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.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+225
View File
@@ -0,0 +1,225 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cpufeatures"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "crypto-common"
version = "0.2.0-pre.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc17eb697364b18256ec92675ebe6b7b153d2f1041e568d74533c5d0fc1ca162"
dependencies = [
"hybrid-array",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common 0.1.6",
]
[[package]]
name = "digest"
version = "0.11.0-pre.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb3be3c52e023de5662dc05a32f747d09a1d6024fdd1f64b0850e373269efb43"
dependencies = [
"crypto-common 0.2.0-pre.3",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
"zeroize",
]
[[package]]
name = "getrandom"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hex-literal"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
[[package]]
name = "hybrid-array"
version = "0.2.0-pre.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27fbaf242418fe980caf09ed348d5a6aeabe71fc1bd8bebad641f4591ae0a46d"
dependencies = [
"typenum",
]
[[package]]
name = "libc"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "lms"
version = "0.1.0"
dependencies = [
"anyhow",
"digest 0.10.7",
"generic-array",
"hex",
"hex-literal",
"rand",
"rand_core",
"sha2",
"signature",
"static_assertions",
"typenum",
"zeroize",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.10.7",
]
[[package]]
name = "signature"
version = "2.3.0-pre.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a9b544e1447a755027450a51bc29aa51772e071bd839aca18136a7dab1b93e"
dependencies = [
"digest 0.11.0-pre.3",
"rand_core",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "zeroize"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
+38
View File
@@ -0,0 +1,38 @@
[package]
name = "lms-signature"
version = "0.0.0"
edition = "2021"
license = "Apache-2.0 OR MIT"
repository = "https://github.com/RustCrypto/signatures/tree/master/lms"
readme = "README.md"
rust-version = "1.73"
categories = ["cryptography"]
keywords = ["crypto", "signature"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "lms"
path = "src/lib.rs"
edition = "2021"
[dependencies]
digest = "0.10.7"
generic-array = {version = "0.14.4", features = ["zeroize"]}
rand = "0.8.5"
sha2 = "0.10.8"
static_assertions = "1.1.0"
rand_core = "0.6.4"
zeroize = "1.7.0"
[dependencies.typenum]
version = "1.17.0"
features = ["const-generics"]
[dependencies.signature]
version = "2.3.0-pre.0"
features = ["digest", "std", "rand_core"]
[dev-dependencies]
hex = "0.4.3"
hex-literal = "0.4.1"
+201
View File
@@ -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 2024 Trail of Bits
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.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2024 Trail of Bits
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.
+70
View File
@@ -0,0 +1,70 @@
# Leighton-Micali Hash-Based Signatures
This repository contains implementations of [Leighton-Micali Hash-Based
Signatures (RFC 8554)](https://datatracker.ietf.org/doc/html/rfc8554).
## Security Notice
LMS signatures are stateful: Users must take care to never sign more than one
message with the same internal LM-OTS private key. To avoid catastrophe, state
must be maintained across multiple invocations of the signing algorithm.
When using our LMS implementations, the internal counter (`q`) will be
incremented before each signature is returned.
If the LMS private key is persisted to storage, you **MUST** update the
persistent storage after each signature is generated and before it is released
to the rest of the application. Failure to adhere to this requirement is a
security vulnerability in your application.
For a stateless hash-based signature algorithm, see
[SPHINCS+](https://sphincs.org).
NOTE: this project has not been externally audited, but the entire codebase
was internally reviewed by cryptographers at Trail of Bits.
## Installation
```terminal
cargo install
```
## Usage
Our implementation uses strongly typed private and public key types.
```rust
let mut rng = thread_rng();
let mut seckey = lms::lms::PrivateKey::new::<LmsSha256M32H10<LmsOtsSha256N32W4>>(&mut rng);
let pubkey = seckey.public(); // of type lms::lms::PublicKey<LmsSha256M32H10>
let sig = seckey.try_sign_with_rng(&mut rng, "example".as_bytes()).unwrap();
let sig_valid = pubkey.verify("example".as_bytes(), &sig).is_ok();
```
We can generate LMOTS signatures in the same way using `lms::ots::PrivateKey`
instead.
### Key Management
We do not require much from the user in terms of key management. Any internal
state changing operation uses mutable reference to update the internal state.
When persisting private keys to long term storage, users must be very careful
that **the same private key is never read from disk twice**. This would create
two private keys in the same state and thus when they are both used to sign a
message, the LMOTS private keys will have been reused, which is considered **not
good**.
## License
All crates 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.
+13
View File
@@ -0,0 +1,13 @@
//! Constants as defined in RFC 8554
/// The length of the identifier `I`
pub const ID_LEN: usize = 16;
/// `D_PBLC`
pub const D_PBLC: [u8; 2] = [0x80, 0x80];
/// `D_MESG`
pub const D_MESG: [u8; 2] = [0x81, 0x81];
/// `D_LEAF`
pub const D_LEAF: [u8; 2] = [0x82, 0x82];
/// `D_INTR`
pub const D_INTR: [u8; 2] = [0x83, 0x83];
+19
View File
@@ -0,0 +1,19 @@
//! Error types
// TODO: review errors and make sure they are appropriate
// I expect it does not make sense to use a single error type for both
// LMS and OTS parsing, as we are currently doing.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
/// The error returned by `TryFrom<&[u8]>` impls
pub enum LmsDeserializeError {
/// Length of the slice was `< 4` and no algorithm can be parsed
NoAlgorithm,
/// The parsed algorithm does not match the requested deserialization
WrongAlgorithm,
/// The slice did not contain enough data
TooShort,
/// The slice contained too much data
TooLong,
/// The parsed `q` value was too large
InvalidQ,
}
+14
View File
@@ -0,0 +1,14 @@
//! LMS in Rust
//!
//! This is a strongly typed implementation of Leighton-Micali signatures. You
//! can find the private key, public key, and signature struct documentations in
//! their respective crates. See [lms] for anything LMS related and [ots] for
//! anything LM-OTS related.
pub mod error;
pub mod lms;
pub mod ots;
// TODO: do we need to expose these?
pub(crate) mod constants;
pub(crate) mod types;
+15
View File
@@ -0,0 +1,15 @@
//! LMS Signing error
use std::error::Error;
use std::fmt::{Display, Formatter, Result};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LmsOutOfPrivateKeys {}
impl Display for LmsOutOfPrivateKeys {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "private key has been exhausted")
}
}
impl Error for LmsOutOfPrivateKeys {}
+13
View File
@@ -0,0 +1,13 @@
use crate::lms::modes::LmsMode;
use crate::lms::private::SigningKey;
use crate::lms::public::VerifyingKey;
use signature::Keypair;
// implements the Keypair trait for PrivateKey
impl<Mode: LmsMode> Keypair for SigningKey<Mode> {
type VerifyingKey = VerifyingKey<Mode>;
fn verifying_key(&self) -> Self::VerifyingKey {
self.public()
}
}
+47
View File
@@ -0,0 +1,47 @@
//! Everything related to LMS (and not LM-OTS)
pub mod error;
mod keypair;
pub(crate) mod modes;
mod private;
mod public;
pub mod signature;
pub use modes::{
LmsMode, LmsSha256M32H10, LmsSha256M32H15, LmsSha256M32H20, LmsSha256M32H25, LmsSha256M32H5,
};
pub use private::SigningKey;
pub use public::VerifyingKey;
pub use signature::Signature;
#[cfg(test)]
mod tests {
use ::signature::{RandomizedSignerMut, Verifier};
use super::*;
use crate::{lms::SigningKey, ots::LmsOtsSha256N32W4};
fn test_sign_and_verify<Mode: LmsMode>() {
let mut rng = rand::thread_rng();
// Generate a fresh keypair
let mut sk = SigningKey::<Mode>::new(&mut rng);
let pk = sk.public();
let msg = "this is a test message".as_bytes();
// Sign the message
let sig = sk.try_sign_with_rng(&mut rng, msg);
let sig = sig.unwrap();
// Verify the signature
assert!(pk.verify(msg, &sig).is_ok());
}
// TODO: macro-generate these exhaustively
#[test]
fn test_sign_and_verify_lms_sha256_m32_h5_lmsots_sha256_n32_w4() {
test_sign_and_verify::<LmsSha256M32H5<LmsOtsSha256N32W4>>();
}
}
+118
View File
@@ -0,0 +1,118 @@
//! LMS modes
use crate::ots::modes::LmsOtsMode;
use crate::types::Typecode;
use digest::Digest;
use digest::Output;
use generic_array::ArrayLength;
use std::ops::Add;
use std::{
marker::PhantomData,
ops::{Shl, Sub},
};
use typenum::{bit::B1, Add1, Shleft, Sub1, U1, U10, U15, U20, U25, U5};
/// The basic trait that must be implemented for any valid LMS mode
pub trait LmsMode: Typecode + Clone {
/// The underlying hash function
type Hasher: Digest;
/// The underlying LM-OTS mode
type OtsMode: LmsOtsMode;
/// Length of the internal Merkle tree, computed as `2^(h+1)-1`
type TreeLen: ArrayLength<Output<Self::Hasher>>;
/// `h` as a type
type HLen: ArrayLength<Output<Self::Hasher>>;
/// The length of the hash function output as a type
const M: usize;
/// `h` as a [usize]
const H: usize;
/// The number of leaves as a [u32], computed as `2^h`
const LEAVES: u32; // precomputed
/// `TreeLen` as a [u32], `2^(h+1)-1`
const TREE_NODES: u32; // precomputed
}
#[derive(Debug)]
pub struct LmsModeInternal<
OtsMode: LmsOtsMode,
Hasher: Digest,
HLen: ArrayLength<Output<Hasher>>,
const M: usize,
const H: usize,
const TC: u32,
> {
_ots_mode: PhantomData<OtsMode>,
_hasher: PhantomData<Hasher>,
_h_len: PhantomData<HLen>,
}
impl<
OtsMode: LmsOtsMode,
Hasher: Digest,
TreeLen: ArrayLength<Output<Hasher>>,
const M: usize,
const H: usize,
const TC: u32,
> Clone for LmsModeInternal<OtsMode, Hasher, TreeLen, M, H, TC>
{
fn clone(&self) -> Self {
*self
}
}
impl<
OtsMode: LmsOtsMode,
Hasher: Digest,
TreeLen: ArrayLength<Output<Hasher>>,
const M: usize,
const H: usize,
const TC: u32,
> Copy for LmsModeInternal<OtsMode, Hasher, TreeLen, M, H, TC>
{
}
impl<
OtsMode: LmsOtsMode,
Hasher: Digest,
HLen: ArrayLength<Output<Hasher>>,
const M: usize,
const H: usize,
const TC: u32,
> LmsMode for LmsModeInternal<OtsMode, Hasher, HLen, M, H, TC>
where
HLen: Add<typenum::B1>,
U1: Shl<<HLen as Add<B1>>::Output>,
Shleft<U1, <HLen as Add<B1>>::Output>: Sub<B1>,
Sub1<Shleft<U1, <HLen as Add<B1>>::Output>>: ArrayLength<Output<Hasher>>,
{
type OtsMode = OtsMode;
type Hasher = Hasher;
type TreeLen = Sub1<Shleft<U1, Add1<HLen>>>;
type HLen = HLen;
const M: usize = M;
const H: usize = H;
const LEAVES: u32 = 1 << H; // precomputed as 2 to the H power
const TREE_NODES: u32 = (1 << (H + 1)) - 1; // 2^(H+1)-1
}
impl<
Hasher: Digest,
OtsMode: LmsOtsMode,
TreeLen: ArrayLength<Output<Hasher>>,
const M: usize,
const H: usize,
const TC: u32,
> Typecode for LmsModeInternal<OtsMode, Hasher, TreeLen, M, H, TC>
{
const TYPECODE: u32 = TC;
}
/// LMS_SHA256_M32_H5
pub type LmsSha256M32H5<OtsMode> = LmsModeInternal<OtsMode, sha2::Sha256, U5, 32, 5, 5>;
/// LMS_SHA256_M32_H10
pub type LmsSha256M32H10<OtsMode> = LmsModeInternal<OtsMode, sha2::Sha256, U10, 32, 10, 6>;
/// LMS_SHA256_M32_H15
pub type LmsSha256M32H15<OtsMode> = LmsModeInternal<OtsMode, sha2::Sha256, U15, 32, 15, 7>;
/// LMS_SHA256_M32_H20
pub type LmsSha256M32H20<OtsMode> = LmsModeInternal<OtsMode, sha2::Sha256, U20, 32, 20, 8>;
/// LMS_SHA256_M32_H25
pub type LmsSha256M32H25<OtsMode> = LmsModeInternal<OtsMode, sha2::Sha256, U25, 32, 25, 9>;
+359
View File
@@ -0,0 +1,359 @@
use crate::constants::{D_INTR, D_LEAF, ID_LEN};
use crate::error::LmsDeserializeError;
use crate::lms::error::LmsOutOfPrivateKeys;
use crate::lms::{LmsMode, Signature, VerifyingKey};
use crate::ots::SigningKey as OtsPrivateKey;
use crate::types::{Identifier, Typecode};
use digest::{Digest, Output, OutputSizeUser};
use generic_array::{ArrayLength, GenericArray};
use rand::{CryptoRng, Rng};
use signature::{Error, RandomizedSignerMut};
use std::cmp::Ordering;
use std::ops::Add;
use typenum::{Sum, U28};
/// Opaque struct representing a LMS private key
///
/// Note: there is no requirement to map specific LMS algorithms to specific
/// LM-OTS algorithms so it must be parametrized. With the algorithms provided
/// by this crate, this is done via
/// [LmsSha256M32H10](crate::lms::LmsSha256M32H10)<[LmsOtsSha256N32W4](crate::ots::LmsOtsSha256N32W4)>.
pub struct SigningKey<Mode: LmsMode> {
id: Identifier,
seed: Output<Mode::Hasher>, // Re-generate the leaf privkeys as-needed from a seed
auth_tree: GenericArray<Output<Mode::Hasher>, Mode::TreeLen>, // TODO: Decide whether/when to precompute
q: u32,
}
impl<Mode: LmsMode> SigningKey<Mode> {
/// Creates a new private key with a random identifier using
/// algorithm 5 from <https://datatracker.ietf.org/doc/html/rfc8554#section-5.2>
pub fn new(mut rng: impl Rng + CryptoRng) -> Self {
let mut id = Identifier::default();
rng.fill_bytes(id.as_mut());
let mut seed = Output::<Mode::Hasher>::default();
rng.fill_bytes(seed.as_mut());
Self::new_from_seed(id, seed)
}
// Returns a new LMS private key generated pseudorandomly from an identifier
// and secret seed. The seed must be equal to the hash output length of the
// LMS mode ([Mode::M])
//
// TODO: Return error rather than panic? Or just make the input a
// GenericArray? This is the algorithm from Appendix A of
// <https://datatracker.ietf.org/doc/html/rfc8554#appendix-A>
pub fn new_from_seed(id: Identifier, seed: impl AsRef<[u8]>) -> Self {
//let seed = seed.as_ref();
let seed = GenericArray::clone_from_slice(seed.as_ref());
let mut sk = Self {
id,
seed,
auth_tree: GenericArray::default(),
q: 0, // we set q = 0 when generating keys; it will change
};
sk.gen_pk_tree(); // TODO: Use lazy generation / MTT
sk
}
/// Generates a Merkle tree of OTS public key hashes, using the indexing scheme of RFC 8554 offset by 1
/// auth_tree[i] == T[i+1]
/// (i.e. the root is at index 0, the internal nodes are at indices 1..MODE_LEAVES-1, and the leaves are at indices MODE_LEAVES-1..2*MODE_LEAVES-1)
fn gen_pk_tree(&mut self) {
for i in 0..Mode::LEAVES {
let mut r: u32 = i + Mode::LEAVES;
let ots_priv = OtsPrivateKey::<Mode::OtsMode>::new_from_seed(i, self.id, &self.seed);
Mode::Hasher::new()
.chain_update(self.id)
.chain_update(r.to_be_bytes())
.chain_update(D_LEAF)
.chain_update(ots_priv.public().k)
.finalize_into(&mut self.auth_tree[(r - 1) as usize]);
let mut j: u32 = i;
while (j % 2) == 1 {
r = (r - 1) >> 1;
j = (j - 1) >> 1;
Mode::Hasher::new()
.chain_update(self.id)
.chain_update(r.to_be_bytes())
.chain_update(D_INTR)
.chain_update(self.auth_tree[(2 * r - 1) as usize].clone())
.chain_update(self.auth_tree[(2 * r) as usize].clone())
.finalize_into(&mut self.auth_tree[(r - 1) as usize]);
}
}
}
/// this implements algorithm 1 from <https://datatracker.ietf.org/doc/html/rfc8554#section-4.3>
pub fn public(&self) -> VerifyingKey<Mode> {
VerifyingKey::<Mode>::new(self.id, self.auth_tree[0].clone())
}
/// Returns the 16-byte identifier of the key pair
pub fn id(&self) -> &Identifier {
&self.id
}
/// Returns the current value of the signing index q
pub fn q(&self) -> u32 {
self.q
}
}
// this implements the algorithm from Appendix D in <https://datatracker.ietf.org/doc/html/rfc8554#appendix-D>
impl<Mode: LmsMode> RandomizedSignerMut<Signature<Mode>> for SigningKey<Mode> {
fn try_sign_with_rng(
&mut self,
rng: &mut impl rand_core::CryptoRngCore,
msg: &[u8],
) -> Result<Signature<Mode>, Error> {
if self.q >= Mode::LEAVES {
return Err(Error::from_source(LmsOutOfPrivateKeys {}));
}
let mut ots_priv_key =
OtsPrivateKey::<Mode::OtsMode>::new_from_seed(self.q, self.id, &self.seed);
let ots_sig = ots_priv_key.try_sign_with_rng(rng, msg)?;
let r = (1 << Mode::H) + self.q;
let auth_path = (0..Mode::H).map(|i| self.auth_tree[(((r >> i) ^ 1) - 1) as usize].clone());
// increment q
self.q += 1;
Ok(Signature::<Mode> {
q: self.q - 1,
lmots_sig: ots_sig,
path: auth_path.collect(),
})
}
}
/// Converts a [PrivateKey] into its byte representation
impl<Mode: LmsMode> From<SigningKey<Mode>>
for GenericArray<u8, Sum<<Mode::Hasher as OutputSizeUser>::OutputSize, U28>>
where
<Mode::Hasher as OutputSizeUser>::OutputSize: Add<U28>,
Sum<<Mode::Hasher as OutputSizeUser>::OutputSize, U28>: ArrayLength<u8>,
{
fn from(pk: SigningKey<Mode>) -> Self {
// Return u32(type) || u32(otstype) || u32(q) || id || seed
GenericArray::from_exact_iter(
std::iter::empty()
.chain(Mode::TYPECODE.to_be_bytes())
.chain(Mode::OtsMode::TYPECODE.to_be_bytes())
.chain(pk.q.to_be_bytes())
.chain(pk.id)
.chain(pk.seed),
)
.unwrap()
}
}
/// Tries to parse a [PrivateKey] from an exact slice
impl<'a, Mode: LmsMode> TryFrom<&'a [u8]> for SigningKey<Mode> {
type Error = LmsDeserializeError;
fn try_from(pk: &'a [u8]) -> Result<Self, Self::Error> {
if pk.len() < 4 {
return Err(LmsDeserializeError::NoAlgorithm);
}
let (alg, pk) = pk.split_at(4);
let expected = Mode::M + ID_LEN + 8;
// will never panic because alg is a 4 byte slice
if u32::from_be_bytes(alg.try_into().unwrap()) != Mode::TYPECODE {
return Err(LmsDeserializeError::WrongAlgorithm);
}
match pk.len().cmp(&expected) {
Ordering::Less => Err(LmsDeserializeError::TooShort),
Ordering::Greater => Err(LmsDeserializeError::TooLong),
Ordering::Equal => {
// pk is now guaranteed to be of the form otstype || q || id || seed
let (otstype, qk) = pk.split_at(ID_LEN);
let (q, idseed) = qk.split_at(4);
let (id, seed) = idseed.split_at(ID_LEN);
// check the OTS type
if u32::from_be_bytes(otstype.try_into().unwrap()) != Mode::OtsMode::TYPECODE {
return Err(LmsDeserializeError::WrongAlgorithm);
}
let mut key = Self {
q: u32::from_be_bytes(q.try_into().expect("ok")),
id: id.try_into().expect("ok"),
seed: GenericArray::clone_from_slice(seed),
auth_tree: GenericArray::default(),
};
key.gen_pk_tree();
Ok(key)
}
}
}
}
#[cfg(test)]
mod tests {
use super::SigningKey;
use crate::lms::modes::{LmsSha256M32H10, LmsSha256M32H5};
use crate::ots::modes::{LmsOtsSha256N32W4, LmsOtsSha256N32W8};
use hex_literal::hex;
use signature::{RandomizedSignerMut, SignatureEncoding};
// Known-Answer Test vectors from <https://datatracker.ietf.org/doc/html/rfc8554#appendix-F>
#[test]
// Generate Test Case 2 top-level LMS public key
// LM_SHA256_M32_H10 / LMOTS_SHA256_N32_W4
fn test_pk_gen_rfc8554_testcase_2_top_level() {
let seed = hex!("558b8966c48ae9cb898b423c83443aae014a72f1b1ab5cc85cf1d892903b5439");
let id = hex!("d08fabd4a2091ff0a8cb4ed834e74534");
let expected_k = hex!("32a58885cd9ba0431235466bff9651c6c92124404d45fa53cf161c28f1ad5a8e");
let lms_priv = SigningKey::<LmsSha256M32H10<LmsOtsSha256N32W4>>::new_from_seed(id, seed);
let lms_pub = lms_priv.public();
assert_eq!(lms_pub.k(), expected_k);
assert_eq!(lms_pub.id(), &id);
}
#[test]
// Generate Test Case 2 leaf-level LMS public key
// LM_SHA256_M32_H5 / LMOTS_SHA256_N32_W8
fn test_pk_gen_rfc8554_testcase_2_leaf_level() {
let seed = hex!("a1c4696e2608035a886100d05cd99945eb3370731884a8235e2fb3d4d71f2547");
let id = hex!("215f83b7ccb9acbcd08db97b0d04dc2b");
let expected_k = hex!("a1cd035833e0e90059603f26e07ad2aad152338e7a5e5984bcd5f7bb4eba40b7");
let lms_priv = SigningKey::<LmsSha256M32H5<LmsOtsSha256N32W8>>::new_from_seed(id, seed);
let lms_pub = lms_priv.public();
assert_eq!(lms_pub.k(), expected_k);
assert_eq!(lms_pub.id(), &id);
}
#[test]
// Byte-for-byte signature equivalence test with RFC 8554 Test Case 2
// Leaf-level LMS signature. LM_SHA256_M32_H5 / LMOTS_SHA256_N32_W8
fn test_sign_rfc8554_testcase_2() {
let expected_signature = [
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x0e, 0xb1, 0xed, 0x54, 0xa2, 0x46,
0x0d, 0x51, 0x23, 0x88, 0xca, 0xd5, 0x33, 0x13, 0x8d, 0x24, 0x05, 0x34, 0xe9, 0x7b,
0x1e, 0x82, 0xd3, 0x3b, 0xd9, 0x27, 0xd2, 0x01, 0xdf, 0xc2, 0x4e, 0xbb, 0x11, 0xb3,
0x64, 0x90, 0x23, 0x69, 0x6f, 0x85, 0x15, 0x0b, 0x18, 0x9e, 0x50, 0xc0, 0x0e, 0x98,
0x85, 0x0a, 0xc3, 0x43, 0xa7, 0x7b, 0x36, 0x38, 0x31, 0x9c, 0x34, 0x7d, 0x73, 0x10,
0x26, 0x9d, 0x3b, 0x77, 0x14, 0xfa, 0x40, 0x6b, 0x8c, 0x35, 0xb0, 0x21, 0xd5, 0x4d,
0x4f, 0xda, 0xda, 0x7b, 0x9c, 0xe5, 0xd4, 0xba, 0x5b, 0x06, 0x71, 0x9e, 0x72, 0xaa,
0xf5, 0x8c, 0x5a, 0xae, 0x7a, 0xca, 0x05, 0x7a, 0xa0, 0xe2, 0xe7, 0x4e, 0x7d, 0xcf,
0xd1, 0x7a, 0x08, 0x23, 0x42, 0x9d, 0xb6, 0x29, 0x65, 0xb7, 0xd5, 0x63, 0xc5, 0x7b,
0x4c, 0xec, 0x94, 0x2c, 0xc8, 0x65, 0xe2, 0x9c, 0x1d, 0xad, 0x83, 0xca, 0xc8, 0xb4,
0xd6, 0x1a, 0xac, 0xc4, 0x57, 0xf3, 0x36, 0xe6, 0xa1, 0x0b, 0x66, 0x32, 0x3f, 0x58,
0x87, 0xbf, 0x35, 0x23, 0xdf, 0xca, 0xde, 0xe1, 0x58, 0x50, 0x3b, 0xfa, 0xa8, 0x9d,
0xc6, 0xbf, 0x59, 0xda, 0xa8, 0x2a, 0xfd, 0x2b, 0x5e, 0xbb, 0x2a, 0x9c, 0xa6, 0x57,
0x2a, 0x60, 0x67, 0xce, 0xe7, 0xc3, 0x27, 0xe9, 0x03, 0x9b, 0x3b, 0x6e, 0xa6, 0xa1,
0xed, 0xc7, 0xfd, 0xc3, 0xdf, 0x92, 0x7a, 0xad, 0xe1, 0x0c, 0x1c, 0x9f, 0x2d, 0x5f,
0xf4, 0x46, 0x45, 0x0d, 0x2a, 0x39, 0x98, 0xd0, 0xf9, 0xf6, 0x20, 0x2b, 0x5e, 0x07,
0xc3, 0xf9, 0x7d, 0x24, 0x58, 0xc6, 0x9d, 0x3c, 0x81, 0x90, 0x64, 0x39, 0x78, 0xd7,
0xa7, 0xf4, 0xd6, 0x4e, 0x97, 0xe3, 0xf1, 0xc4, 0xa0, 0x8a, 0x7c, 0x5b, 0xc0, 0x3f,
0xd5, 0x56, 0x82, 0xc0, 0x17, 0xe2, 0x90, 0x7e, 0xab, 0x07, 0xe5, 0xbb, 0x2f, 0x19,
0x01, 0x43, 0x47, 0x5a, 0x60, 0x43, 0xd5, 0xe6, 0xd5, 0x26, 0x34, 0x71, 0xf4, 0xee,
0xcf, 0x6e, 0x25, 0x75, 0xfb, 0xc6, 0xff, 0x37, 0xed, 0xfa, 0x24, 0x9d, 0x6c, 0xda,
0x1a, 0x09, 0xf7, 0x97, 0xfd, 0x5a, 0x3c, 0xd5, 0x3a, 0x06, 0x67, 0x00, 0xf4, 0x58,
0x63, 0xf0, 0x4b, 0x6c, 0x8a, 0x58, 0xcf, 0xd3, 0x41, 0x24, 0x1e, 0x00, 0x2d, 0x0d,
0x2c, 0x02, 0x17, 0x47, 0x2b, 0xf1, 0x8b, 0x63, 0x6a, 0xe5, 0x47, 0xc1, 0x77, 0x13,
0x68, 0xd9, 0xf3, 0x17, 0x83, 0x5c, 0x9b, 0x0e, 0xf4, 0x30, 0xb3, 0xdf, 0x40, 0x34,
0xf6, 0xaf, 0x00, 0xd0, 0xda, 0x44, 0xf4, 0xaf, 0x78, 0x00, 0xbc, 0x7a, 0x5c, 0xf8,
0xa5, 0xab, 0xdb, 0x12, 0xdc, 0x71, 0x8b, 0x55, 0x9b, 0x74, 0xca, 0xb9, 0x09, 0x0e,
0x33, 0xcc, 0x58, 0xa9, 0x55, 0x30, 0x09, 0x81, 0xc4, 0x20, 0xc4, 0xda, 0x8f, 0xfd,
0x67, 0xdf, 0x54, 0x08, 0x90, 0xa0, 0x62, 0xfe, 0x40, 0xdb, 0xa8, 0xb2, 0xc1, 0xc5,
0x48, 0xce, 0xd2, 0x24, 0x73, 0x21, 0x9c, 0x53, 0x49, 0x11, 0xd4, 0x8c, 0xca, 0xab,
0xfb, 0x71, 0xbc, 0x71, 0x86, 0x2f, 0x4a, 0x24, 0xeb, 0xd3, 0x76, 0xd2, 0x88, 0xfd,
0x4e, 0x6f, 0xb0, 0x6e, 0xd8, 0x70, 0x57, 0x87, 0xc5, 0xfe, 0xdc, 0x81, 0x3c, 0xd2,
0x69, 0x7e, 0x5b, 0x1a, 0xac, 0x1c, 0xed, 0x45, 0x76, 0x7b, 0x14, 0xce, 0x88, 0x40,
0x9e, 0xae, 0xbb, 0x60, 0x1a, 0x93, 0x55, 0x9a, 0xae, 0x89, 0x3e, 0x14, 0x3d, 0x1c,
0x39, 0x5b, 0xc3, 0x26, 0xda, 0x82, 0x1d, 0x79, 0xa9, 0xed, 0x41, 0xdc, 0xfb, 0xe5,
0x49, 0x14, 0x7f, 0x71, 0xc0, 0x92, 0xf4, 0xf3, 0xac, 0x52, 0x2b, 0x5c, 0xc5, 0x72,
0x90, 0x70, 0x66, 0x50, 0x48, 0x7b, 0xae, 0x9b, 0xb5, 0x67, 0x1e, 0xcc, 0x9c, 0xcc,
0x2c, 0xe5, 0x1e, 0xad, 0x87, 0xac, 0x01, 0x98, 0x52, 0x68, 0x52, 0x12, 0x22, 0xfb,
0x90, 0x57, 0xdf, 0x7e, 0xd4, 0x18, 0x10, 0xb5, 0xef, 0x0d, 0x4f, 0x7c, 0xc6, 0x73,
0x68, 0xc9, 0x0f, 0x57, 0x3b, 0x1a, 0xc2, 0xce, 0x95, 0x6c, 0x36, 0x5e, 0xd3, 0x8e,
0x89, 0x3c, 0xe7, 0xb2, 0xfa, 0xe1, 0x5d, 0x36, 0x85, 0xa3, 0xdf, 0x2f, 0xa3, 0xd4,
0xcc, 0x09, 0x8f, 0xa5, 0x7d, 0xd6, 0x0d, 0x2c, 0x97, 0x54, 0xa8, 0xad, 0xe9, 0x80,
0xad, 0x0f, 0x93, 0xf6, 0x78, 0x70, 0x75, 0xc3, 0xf6, 0x80, 0xa2, 0xba, 0x19, 0x36,
0xa8, 0xc6, 0x1d, 0x1a, 0xf5, 0x2a, 0xb7, 0xe2, 0x1f, 0x41, 0x6b, 0xe0, 0x9d, 0x2a,
0x8d, 0x64, 0xc3, 0xd3, 0xd8, 0x58, 0x29, 0x68, 0xc2, 0x83, 0x99, 0x02, 0x22, 0x9f,
0x85, 0xae, 0xe2, 0x97, 0xe7, 0x17, 0xc0, 0x94, 0xc8, 0xdf, 0x4a, 0x23, 0xbb, 0x5d,
0xb6, 0x58, 0xdd, 0x37, 0x7b, 0xf0, 0xf4, 0xff, 0x3f, 0xfd, 0x8f, 0xba, 0x5e, 0x38,
0x3a, 0x48, 0x57, 0x48, 0x02, 0xed, 0x54, 0x5b, 0xbe, 0x7a, 0x6b, 0x47, 0x53, 0x53,
0x33, 0x53, 0xd7, 0x37, 0x06, 0x06, 0x76, 0x40, 0x13, 0x5a, 0x7c, 0xe5, 0x17, 0x27,
0x9c, 0xd6, 0x83, 0x03, 0x97, 0x47, 0xd2, 0x18, 0x64, 0x7c, 0x86, 0xe0, 0x97, 0xb0,
0xda, 0xa2, 0x87, 0x2d, 0x54, 0xb8, 0xf3, 0xe5, 0x08, 0x59, 0x87, 0x62, 0x95, 0x47,
0xb8, 0x30, 0xd8, 0x11, 0x81, 0x61, 0xb6, 0x50, 0x79, 0xfe, 0x7b, 0xc5, 0x9a, 0x99,
0xe9, 0xc3, 0xc7, 0x38, 0x0e, 0x3e, 0x70, 0xb7, 0x13, 0x8f, 0xe5, 0xd9, 0xbe, 0x25,
0x51, 0x50, 0x2b, 0x69, 0x8d, 0x09, 0xae, 0x19, 0x39, 0x72, 0xf2, 0x7d, 0x40, 0xf3,
0x8d, 0xea, 0x26, 0x4a, 0x01, 0x26, 0xe6, 0x37, 0xd7, 0x4a, 0xe4, 0xc9, 0x2a, 0x62,
0x49, 0xfa, 0x10, 0x34, 0x36, 0xd3, 0xeb, 0x0d, 0x40, 0x29, 0xac, 0x71, 0x2b, 0xfc,
0x7a, 0x5e, 0xac, 0xbd, 0xd7, 0x51, 0x8d, 0x6d, 0x4f, 0xe9, 0x03, 0xa5, 0xae, 0x65,
0x52, 0x7c, 0xd6, 0x5b, 0xb0, 0xd4, 0xe9, 0x92, 0x5c, 0xa2, 0x4f, 0xd7, 0x21, 0x4d,
0xc6, 0x17, 0xc1, 0x50, 0x54, 0x4e, 0x42, 0x3f, 0x45, 0x0c, 0x99, 0xce, 0x51, 0xac,
0x80, 0x05, 0xd3, 0x3a, 0xcd, 0x74, 0xf1, 0xbe, 0xd3, 0xb1, 0x7b, 0x72, 0x66, 0xa4,
0xa3, 0xbb, 0x86, 0xda, 0x7e, 0xba, 0x80, 0xb1, 0x01, 0xe1, 0x5c, 0xb7, 0x9d, 0xe9,
0xa2, 0x07, 0x85, 0x2c, 0xf9, 0x12, 0x49, 0xef, 0x48, 0x06, 0x19, 0xff, 0x2a, 0xf8,
0xca, 0xbc, 0xa8, 0x31, 0x25, 0xd1, 0xfa, 0xa9, 0x4c, 0xbb, 0x0a, 0x03, 0xa9, 0x06,
0xf6, 0x83, 0xb3, 0xf4, 0x7a, 0x97, 0xc8, 0x71, 0xfd, 0x51, 0x3e, 0x51, 0x0a, 0x7a,
0x25, 0xf2, 0x83, 0xb1, 0x96, 0x07, 0x57, 0x78, 0x49, 0x61, 0x52, 0xa9, 0x1c, 0x2b,
0xf9, 0xda, 0x76, 0xeb, 0xe0, 0x89, 0xf4, 0x65, 0x48, 0x77, 0xf2, 0xd5, 0x86, 0xae,
0x71, 0x49, 0xc4, 0x06, 0xe6, 0x63, 0xea, 0xde, 0xb2, 0xb5, 0xc7, 0xe8, 0x24, 0x29,
0xb9, 0xe8, 0xcb, 0x48, 0x34, 0xc8, 0x34, 0x64, 0xf0, 0x79, 0x99, 0x53, 0x32, 0xe4,
0xb3, 0xc8, 0xf5, 0xa7, 0x2b, 0xb4, 0xb8, 0xc6, 0xf7, 0x4b, 0x0d, 0x45, 0xdc, 0x6c,
0x1f, 0x79, 0x95, 0x2c, 0x0b, 0x74, 0x20, 0xdf, 0x52, 0x5e, 0x37, 0xc1, 0x53, 0x77,
0xb5, 0xf0, 0x98, 0x43, 0x19, 0xc3, 0x99, 0x39, 0x21, 0xe5, 0xcc, 0xd9, 0x7e, 0x09,
0x75, 0x92, 0x06, 0x45, 0x30, 0xd3, 0x3d, 0xe3, 0xaf, 0xad, 0x57, 0x33, 0xcb, 0xe7,
0x70, 0x3c, 0x52, 0x96, 0x26, 0x3f, 0x77, 0x34, 0x2e, 0xfb, 0xf5, 0xa0, 0x47, 0x55,
0xb0, 0xb3, 0xc9, 0x97, 0xc4, 0x32, 0x84, 0x63, 0xe8, 0x4c, 0xaa, 0x2d, 0xe3, 0xff,
0xdc, 0xd2, 0x97, 0xba, 0xaa, 0xac, 0xd7, 0xae, 0x64, 0x6e, 0x44, 0xb5, 0xc0, 0xf1,
0x60, 0x44, 0xdf, 0x38, 0xfa, 0xbd, 0x29, 0x6a, 0x47, 0xb3, 0xa8, 0x38, 0xa9, 0x13,
0x98, 0x2f, 0xb2, 0xe3, 0x70, 0xc0, 0x78, 0xed, 0xb0, 0x42, 0xc8, 0x4d, 0xb3, 0x4c,
0xe3, 0x6b, 0x46, 0xcc, 0xb7, 0x64, 0x60, 0xa6, 0x90, 0xcc, 0x86, 0xc3, 0x02, 0x45,
0x7d, 0xd1, 0xcd, 0xe1, 0x97, 0xec, 0x80, 0x75, 0xe8, 0x2b, 0x39, 0x3d, 0x54, 0x20,
0x75, 0x13, 0x4e, 0x2a, 0x17, 0xee, 0x70, 0xa5, 0xe1, 0x87, 0x07, 0x5d, 0x03, 0xae,
0x3c, 0x85, 0x3c, 0xff, 0x60, 0x72, 0x9b, 0xa4, 0x00, 0x00, 0x00, 0x05, 0x4d, 0xe1,
0xf6, 0x96, 0x5b, 0xda, 0xbc, 0x67, 0x6c, 0x5a, 0x4d, 0xc7, 0xc3, 0x5f, 0x97, 0xf8,
0x2c, 0xb0, 0xe3, 0x1c, 0x68, 0xd0, 0x4f, 0x1d, 0xad, 0x96, 0x31, 0x4f, 0xf0, 0x9e,
0x6b, 0x3d, 0xe9, 0x6a, 0xee, 0xe3, 0x00, 0xd1, 0xf6, 0x8b, 0xf1, 0xbc, 0xa9, 0xfc,
0x58, 0xe4, 0x03, 0x23, 0x36, 0xcd, 0x81, 0x9a, 0xaf, 0x57, 0x87, 0x44, 0xe5, 0x0d,
0x13, 0x57, 0xa0, 0xe4, 0x28, 0x67, 0x04, 0xd3, 0x41, 0xaa, 0x0a, 0x33, 0x7b, 0x19,
0xfe, 0x4b, 0xc4, 0x3c, 0x2e, 0x79, 0x96, 0x4d, 0x4f, 0x35, 0x10, 0x89, 0xf2, 0xe0,
0xe4, 0x1c, 0x7c, 0x43, 0xae, 0x0d, 0x49, 0xe7, 0xf4, 0x04, 0xb0, 0xf7, 0x5b, 0xe8,
0x0e, 0xa3, 0xaf, 0x09, 0x8c, 0x97, 0x52, 0x42, 0x0a, 0x8a, 0xc0, 0xea, 0x2b, 0xbb,
0x1f, 0x4e, 0xeb, 0xa0, 0x52, 0x38, 0xae, 0xf0, 0xd8, 0xce, 0x63, 0xf0, 0xc6, 0xe5,
0xe4, 0x04, 0x1d, 0x95, 0x39, 0x8a, 0x6f, 0x7f, 0x3e, 0x0e, 0xe9, 0x7c, 0xc1, 0x59,
0x18, 0x49, 0xd4, 0xed, 0x23, 0x63, 0x38, 0xb1, 0x47, 0xab, 0xde, 0x9f, 0x51, 0xef,
0x9f, 0xd4, 0xe1, 0xc1,
];
let seed = hex!("a1c4696e2608035a886100d05cd99945eb3370731884a8235e2fb3d4d71f2547");
let id = hex!("215f83b7ccb9acbcd08db97b0d04dc2b");
let _expected_k = hex!("a1cd035833e0e90059603f26e07ad2aad152338e7a5e5984bcd5f7bb4eba40b7");
let mut lms_priv = SigningKey::<LmsSha256M32H5<LmsOtsSha256N32W8>>::new_from_seed(id, seed);
lms_priv.q = 4;
let _lms_pub = lms_priv.public();
let msg = "The enumeration in the Constitution, of certain rights, shall not be construed to deny or disparage others retained by the people.\n".as_bytes();
use crate::ots::tests::ConstantRng;
let c = hex!("0eb1ed54a2460d512388cad533138d240534e97b1e82d33bd927d201dfc24ebb");
let mut rng = ConstantRng(&c);
let sig = lms_priv
.try_sign_with_rng(&mut rng, msg)
.unwrap()
.to_bytes();
assert_eq!(sig.len(), expected_signature.len());
assert_eq!(sig, expected_signature)
}
}
+299
View File
@@ -0,0 +1,299 @@
use std::cmp::Ordering;
use std::ops::Add;
use crate::constants::{D_LEAF, ID_LEN};
use crate::error::LmsDeserializeError;
use crate::lms::Signature;
use crate::types::Typecode;
use crate::{constants::D_INTR, lms::LmsMode};
use digest::{Digest, OutputSizeUser};
use generic_array::{ArrayLength, GenericArray};
use signature::{Error, Verifier};
use typenum::{Sum, U24};
//use crate::signature::Signature as Signature;
use crate::types::Identifier;
use digest::Output;
#[derive(Debug)]
/// Opaque struct representing a LMS public key
pub struct VerifyingKey<Mode: LmsMode> {
pub(crate) id: Identifier,
pub(crate) k: Output<Mode::Hasher>,
}
impl<Mode: LmsMode> Clone for VerifyingKey<Mode> {
fn clone(&self) -> Self {
Self {
id: self.id,
k: self.k.clone(),
}
}
}
impl<Mode: LmsMode> PartialEq for VerifyingKey<Mode> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.k == other.k
}
}
impl<Mode: LmsMode> VerifyingKey<Mode> {
pub fn new(id: Identifier, k: Output<Mode::Hasher>) -> Self {
Self { id, k }
}
/// Returns the 16-byte identifier of the public key
pub fn id(&self) -> &Identifier {
&self.id
}
/// Returns the N-byte public key as a byte slice
pub fn k(&self) -> &[u8] {
&self.k
}
}
impl<Mode: LmsMode> Verifier<Signature<Mode>> for VerifyingKey<Mode> {
fn verify(&self, msg: &[u8], signature: &Signature<Mode>) -> Result<(), Error> {
// Compute the LMS Public Key Candidate Tc from the signature,
// message, identifier, pubtype, and ots_typecode, using
// Algorithm 6a.
let key_candidate = signature
.lmots_sig
.recover_pubkey(self.id, signature.q, msg);
let mut node_num = signature.q + Mode::LEAVES;
let mut tmp = Mode::Hasher::new()
.chain_update(self.id)
.chain_update(node_num.to_be_bytes())
.chain_update(D_LEAF)
.chain_update(key_candidate.k)
.finalize();
for i in 0..Mode::H {
// Tc = H(I || u32str(node_num/2) || u16str(D_INTR) || path[i] || tmp)
let mut hasher = Mode::Hasher::new()
.chain_update(self.id)
.chain_update((node_num / 2).to_be_bytes())
.chain_update(D_INTR);
if node_num % 2 == 1 {
hasher.update(&signature.path[i]);
hasher.update(&tmp);
} else {
// Tc = H(I || u32str(node_num/2) || u16str(D_INTR) || tmp || path[i])
hasher.update(&tmp);
hasher.update(&signature.path[i]);
}
hasher.finalize_into(&mut tmp);
node_num /= 2;
}
if self.k == tmp {
Ok(())
} else {
Err(Error::new())
}
}
}
/// Converts a [`VerifyingKey`] into its byte representation
impl<Mode: LmsMode> From<VerifyingKey<Mode>>
for GenericArray<u8, Sum<<Mode::Hasher as OutputSizeUser>::OutputSize, U24>>
where
<Mode::Hasher as OutputSizeUser>::OutputSize: Add<U24>,
Sum<<Mode::Hasher as OutputSizeUser>::OutputSize, U24>: ArrayLength<u8>,
{
fn from(pk: VerifyingKey<Mode>) -> Self {
// Return u32(type) || u32(otstype) || id || k
GenericArray::from_exact_iter(
std::iter::empty()
.chain(Mode::TYPECODE.to_be_bytes())
.chain(Mode::OtsMode::TYPECODE.to_be_bytes())
.chain(pk.id)
.chain(pk.k),
)
.unwrap()
}
}
/// Tries to parse a [`VerifyingKey`] from an exact slice
impl<'a, Mode: LmsMode> TryFrom<&'a [u8]> for VerifyingKey<Mode> {
type Error = LmsDeserializeError;
fn try_from(pk: &'a [u8]) -> Result<Self, Self::Error> {
let expected_len = Mode::M + ID_LEN + 8;
match pk.len().cmp(&expected_len) {
Ordering::Less => return Err(LmsDeserializeError::TooShort),
Ordering::Greater => return Err(LmsDeserializeError::TooLong),
Ordering::Equal => (),
};
let (alg, pk) = pk.split_at(4);
// will never panic because we already checked the length
if u32::from_be_bytes(alg.try_into().unwrap()) != Mode::TYPECODE {
return Err(LmsDeserializeError::WrongAlgorithm);
}
// pk is now guaranteed to be of the form u32(otstype) || ID || K
let (otstype, id_k) = pk.split_at(4);
// Check that otstype is correct
if u32::from_be_bytes(otstype.try_into().unwrap()) != Mode::OtsMode::TYPECODE {
return Err(LmsDeserializeError::WrongAlgorithm);
}
let (id, k) = id_k.split_at(ID_LEN);
Ok(Self {
id: id.try_into().unwrap(),
k: GenericArray::clone_from_slice(k),
})
}
}
#[cfg(test)]
mod tests {
use std::ops::Add;
use crate::{
lms::modes::*,
lms::SigningKey,
lms::VerifyingKey,
ots::{LmsOtsSha256N32W4, LmsOtsSha256N32W8},
};
use digest::OutputSizeUser;
use generic_array::{ArrayLength, GenericArray};
use hex_literal::hex;
use typenum::{Sum, U24};
// RFC 8554 Appendix F. Test Case 1
// Top-level LMS Public Key
// LM_SHA256_M32_H5 / LMOTS_SHA256_N32_W8
const KAT1: [u8; 56] = hex!(
"
00000005
00000004
61a5d57d37f5e46bfb7520806b07a1b8
50650e3b31fe4a773ea29a07f09cf2ea
30e579f0df58ef8e298da0434cb2b878"
);
#[test]
fn test_pubkey_deserialize_kat1() {
let pk = VerifyingKey::<LmsSha256M32H5<LmsOtsSha256N32W8>>::try_from(&KAT1[..]).unwrap();
let expected = VerifyingKey::<LmsSha256M32H5<LmsOtsSha256N32W8>> {
id: hex!("61a5d57d37f5e46bfb7520806b07a1b8"),
k: hex!("50650e3b31fe4a773ea29a07f09cf2ea30e579f0df58ef8e298da0434cb2b878").into(),
};
assert_eq!(pk, expected);
}
#[test]
fn test_pubkey_deserialize_kat1_wrong_lms_mode() {
let pk = VerifyingKey::<LmsSha256M32H10<LmsOtsSha256N32W8>>::try_from(&KAT1[..]);
assert_eq!(pk, Err(crate::error::LmsDeserializeError::WrongAlgorithm));
}
#[test]
fn test_pubkey_deserialize_kat1_wrong_otsmode() {
let pk = VerifyingKey::<LmsSha256M32H5<LmsOtsSha256N32W4>>::try_from(&KAT1[..]);
assert_eq!(pk, Err(crate::error::LmsDeserializeError::WrongAlgorithm));
}
#[test]
fn test_pubkey_deserialize_kat1_too_short() {
let pk_bytes = &KAT1[..(KAT1.len() - 4)];
let pk = VerifyingKey::<LmsSha256M32H5<LmsOtsSha256N32W8>>::try_from(pk_bytes);
assert_eq!(pk, Err(crate::error::LmsDeserializeError::TooShort));
}
#[test]
fn test_pubkey_deserialize_kat1_too_long() {
let mut pk_bytes = vec![42; 4];
pk_bytes.extend_from_slice(&KAT1[..]);
let pk = VerifyingKey::<LmsSha256M32H5<LmsOtsSha256N32W8>>::try_from(&pk_bytes[..]);
assert_eq!(pk, Err(crate::error::LmsDeserializeError::TooLong));
}
#[test]
fn test_kat1_round_trip() {
let pk = VerifyingKey::<LmsSha256M32H5<LmsOtsSha256N32W8>>::try_from(&KAT1[..]).unwrap();
let pk_serialized: GenericArray<u8, _> = pk.clone().into();
let bytes = pk_serialized.as_slice();
assert_eq!(bytes, &KAT1[..]);
}
// RFC 8554 Appendix F. Test Case 2
// Top-level LMS Public Key
// LM_SHA256_M32_H10 / LMOTS_SHA256_N32_W4
#[test]
fn test_kat2() {
// Tests that the serialized public key from RFC seed matches the expected value
let seed = hex!("558b8966c48ae9cb898b423c83443aae014a72f1b1ab5cc85cf1d892903b5439");
let id = hex!("d08fabd4a2091ff0a8cb4ed834e74534");
let expected_pubkey = hex!(
"
00000006
00000003
d08fabd4a2091ff0a8cb4ed834e74534
32a58885cd9ba0431235466bff9651c6
c92124404d45fa53cf161c28f1ad5a8e
"
);
let lms_priv = SigningKey::<LmsSha256M32H10<LmsOtsSha256N32W4>>::new_from_seed(id, seed);
let lms_pub = lms_priv.public();
let lms_pub_serialized: GenericArray<u8, _> = lms_pub.into();
let bytes = lms_pub_serialized.as_slice();
assert_eq!(bytes, &expected_pubkey[..]);
}
fn test_serialize_deserialize_random<Mode: LmsMode>()
where
VerifyingKey<Mode>: std::fmt::Debug,
<Mode::Hasher as OutputSizeUser>::OutputSize: Add<U24>,
Sum<<Mode::Hasher as OutputSizeUser>::OutputSize, U24>: ArrayLength<u8>,
{
let rng = rand::thread_rng();
let lms_priv = SigningKey::<Mode>::new(rng);
let lms_pub = lms_priv.public();
let lms_pub_serialized: GenericArray<
u8,
Sum<<Mode::Hasher as OutputSizeUser>::OutputSize, U24>,
> = lms_pub.clone().into();
let bytes = lms_pub_serialized.as_slice();
let lms_pub_deserialized = VerifyingKey::<Mode>::try_from(bytes).unwrap();
assert_eq!(lms_pub, lms_pub_deserialized);
}
#[test]
fn test_serialize_deserialize_random_lms_sha256_m32_h5_lms_ots_sha256_n32_w8() {
test_serialize_deserialize_random::<LmsSha256M32H5<LmsOtsSha256N32W8>>();
}
#[test]
fn test_serialize_deserialize_random_lms_sha256_m32_h10_lms_ots_sha256_n32_w8() {
test_serialize_deserialize_random::<LmsSha256M32H10<LmsOtsSha256N32W8>>();
}
// These tests use too much memory and overflow the stack
/*
#[test]
fn test_serialize_deserialize_random_lms_sha256_m32_h15_lms_ots_sha256_n32_w8(){
test_serialize_deserialize_random::<LmsSha256M32H15<LmsOtsSha256N32W8>>();
}
#[test]
fn test_serialize_deserialize_random_lms_sha256_m32_h20_lms_ots_sha256_n32_w8(){
test_serialize_deserialize_random::<LmsSha256M32H20<LmsOtsSha256N32W8>>();
}
#[test]
fn test_serialize_deserialize_random_lms_sha256_m32_h25_lms_ots_sha256_n32_w8(){
test_serialize_deserialize_random::<LmsSha256M32H25<LmsOtsSha256N32W8>>();
}
*/
}
+318
View File
@@ -0,0 +1,318 @@
//! Contains the [`Signature`] type
use crate::error::LmsDeserializeError;
use crate::lms::LmsMode;
use crate::ots::modes::LmsOtsMode;
use crate::ots::Signature as OtsSignature;
use generic_array::{ArrayLength, GenericArray};
use signature::SignatureEncoding;
use std::{
cmp::Ordering,
ops::{Add, Mul},
};
use typenum::{Prod, Sum, U1, U4};
/// Opaque struct representing a LMS signature
pub struct Signature<Mode: LmsMode> {
pub(crate) q: u32, // TODO: do these really need to be public?
pub(crate) lmots_sig: OtsSignature<Mode::OtsMode>,
pub(crate) path: GenericArray<digest::Output<Mode::Hasher>, Mode::HLen>,
}
// manual implementation is required to not require bounds on Mode
impl<Mode: LmsMode> Clone for Signature<Mode> {
fn clone(&self) -> Self {
Self {
q: self.q,
lmots_sig: self.lmots_sig.clone(),
path: self.path.clone(),
}
}
}
// manual implementation is required to not require bounds on Mode
impl<Mode: LmsMode> PartialEq for Signature<Mode> {
fn eq(&self, other: &Self) -> bool {
self.q == other.q && self.lmots_sig == other.lmots_sig && self.path == other.path
}
}
impl<Mode: LmsMode> SignatureEncoding for Signature<Mode>
where
<Mode::OtsMode as LmsOtsMode>::PLen: Add<U1>,
<Mode::OtsMode as LmsOtsMode>::NLen: Mul<Sum<<Mode::OtsMode as LmsOtsMode>::PLen, U1>>,
Prod<<Mode::OtsMode as LmsOtsMode>::NLen, Sum<<Mode::OtsMode as LmsOtsMode>::PLen, U1>>:
Add<U4>,
Sum<
Prod<<Mode::OtsMode as LmsOtsMode>::NLen, Sum<<Mode::OtsMode as LmsOtsMode>::PLen, U1>>,
U4,
>: ArrayLength<u8>,
{
type Repr = Vec<u8>; // TODO: GenericArray
}
impl<Mode: LmsMode> From<Signature<Mode>> for Vec<u8>
where
<Mode::OtsMode as LmsOtsMode>::PLen: Add<U1>,
<Mode::OtsMode as LmsOtsMode>::NLen: Mul<Sum<<Mode::OtsMode as LmsOtsMode>::PLen, U1>>,
Prod<<Mode::OtsMode as LmsOtsMode>::NLen, Sum<<Mode::OtsMode as LmsOtsMode>::PLen, U1>>:
Add<U4>,
Sum<
Prod<<Mode::OtsMode as LmsOtsMode>::NLen, Sum<<Mode::OtsMode as LmsOtsMode>::PLen, U1>>,
U4,
>: ArrayLength<u8>,
{
fn from(val: Signature<Mode>) -> Self {
let mut sig = Vec::new();
sig.extend_from_slice(&val.q.to_be_bytes());
let lms_sig: GenericArray<u8, _> = val.lmots_sig.into();
sig.extend_from_slice(&lms_sig);
sig.extend_from_slice(&Mode::TYPECODE.to_be_bytes());
for node in val.path {
sig.extend_from_slice(&node);
}
sig
}
}
/// Tries to parse a [`Signature`] from an exact slice
impl<Mode: LmsMode> TryFrom<&[u8]> for Signature<Mode> {
type Error = LmsDeserializeError;
fn try_from(sig: &[u8]) -> Result<Self, Self::Error> {
// Follows the validations in algorithm 6a of RFC 8554
// Fully check signature length up-front. Removes need for checks as we go.
match sig
.len()
.cmp(&(8 + Mode::OtsMode::SIG_LEN + Mode::M * Mode::H))
{
Ordering::Less => return Err(LmsDeserializeError::TooShort),
Ordering::Greater => return Err(LmsDeserializeError::TooLong),
Ordering::Equal => (),
};
// 6a.2.a: Get q
let (q_bytes, sig) = sig.split_at(4);
let q = u32::from_be_bytes(q_bytes.try_into().unwrap());
// 6a.2.i: If q >= 2^H, return INVALID.
if q >= 1 << Mode::H {
return Err(LmsDeserializeError::InvalidQ);
}
// We already checked that this won't panic
let (lmots_sig, sig) = sig.split_at(Mode::OtsMode::SIG_LEN);
// Checks that the OTS mode is correct happen inside `try_from`
let ots_signature = OtsSignature::<Mode::OtsMode>::try_from(lmots_sig)?;
// 6a.2.f: Get LMS typecode (sigtype)
let (sigtype, path) = sig.split_at(4);
// 6a.2.g: If the OTS typecode is not equal to the typecode of the
// expected LM-OTS Mode, return INVALID.
if u32::from_be_bytes(sigtype.try_into().unwrap()) != Mode::TYPECODE {
return Err(LmsDeserializeError::WrongAlgorithm);
}
// Path length is already validated by initial length check
let path = path
.chunks_exact(Mode::M)
.map(GenericArray::clone_from_slice)
.collect();
Ok(Self {
q,
lmots_sig: ots_signature,
path,
})
}
}
#[cfg(test)]
mod tests {
use std::ops::{Add, Mul};
use crate::lms::modes::*;
use crate::lms::{Signature, SigningKey, VerifyingKey};
use crate::ots::modes::*;
use generic_array::ArrayLength;
use hex_literal::hex;
use rand::thread_rng;
use signature::{RandomizedSignerMut, Verifier};
use typenum::{Prod, Sum, U1, U4};
#[test]
fn test_deserialize_kat1() {
let pk_bytes = hex!("0000000500000004d2f14ff6346af964569f7d6cb880a1b66c5004917da6eafe4d9ef6c6407b3db0e5485b122d9ebe15cda93cfec582d7ab");
let sig_bytes = hex!(
"
0000000a
00000004
0703c491e7558b35011ece3592eaa5da
4d918786771233e8353bc4f62323185c
95cae05b899e35dffd71705470620998
8ebfdf6e37960bb5c38d7657e8bffeef
9bc042da4b4525650485c66d0ce19b31
7587c6ba4bffcc428e25d08931e72dfb
6a120c5612344258b85efdb7db1db9e1
865a73caf96557eb39ed3e3f426933ac
9eeddb03a1d2374af7bf771855774562
37f9de2d60113c23f846df26fa942008
a698994c0827d90e86d43e0df7f4bfcd
b09b86a373b98288b7094ad81a0185ac
100e4f2c5fc38c003c1ab6fea479eb2f
5ebe48f584d7159b8ada03586e65ad9c
969f6aecbfe44cf356888a7b15a3ff07
4f771760b26f9c04884ee1faa329fbf4
e61af23aee7fa5d4d9a5dfcf43c4c26c
e8aea2ce8a2990d7ba7b57108b47dabf
beadb2b25b3cacc1ac0cef346cbb90fb
044beee4fac2603a442bdf7e507243b7
319c9944b1586e899d431c7f91bcccc8
690dbf59b28386b2315f3d36ef2eaa3c
f30b2b51f48b71b003dfb08249484201
043f65f5a3ef6bbd61ddfee81aca9ce6
0081262a00000480dcbc9a3da6fbef5c
1c0a55e48a0e729f9184fcb1407c3152
9db268f6fe50032a363c9801306837fa
fabdf957fd97eafc80dbd165e435d0e2
dfd836a28b354023924b6fb7e48bc0b3
ed95eea64c2d402f4d734c8dc26f3ac5
91825daef01eae3c38e3328d00a77dc6
57034f287ccb0f0e1c9a7cbdc828f627
205e4737b84b58376551d44c12c3c215
c812a0970789c83de51d6ad787271963
327f0a5fbb6b5907dec02c9a90934af5
a1c63b72c82653605d1dcce51596b3c2
b45696689f2eb382007497557692caac
4d57b5de9f5569bc2ad0137fd47fb47e
664fcb6db4971f5b3e07aceda9ac130e
9f38182de994cff192ec0e82fd6d4cb7
f3fe00812589b7a7ce51544045643301
6b84a59bec6619a1c6c0b37dd1450ed4
f2d8b584410ceda8025f5d2d8dd0d217
6fc1cf2cc06fa8c82bed4d944e71339e
ce780fd025bd41ec34ebff9d4270a322
4e019fcb444474d482fd2dbe75efb203
89cc10cd600abb54c47ede93e08c114e
db04117d714dc1d525e11bed8756192f
929d15462b939ff3f52f2252da2ed64d
8fae88818b1efa2c7b08c8794fb1b214
aa233db3162833141ea4383f1a6f120b
e1db82ce3630b3429114463157a64e91
234d475e2f79cbf05e4db6a9407d72c6
bff7d1198b5c4d6aad2831db61274993
715a0182c7dc8089e32c8531deed4f74
31c07c02195eba2ef91efb5613c37af7
ae0c066babc69369700e1dd26eddc0d2
16c781d56e4ce47e3303fa73007ff7b9
49ef23be2aa4dbf25206fe45c20dd888
395b2526391a724996a44156beac8082
12858792bf8e74cba49dee5e8812e019
da87454bff9e847ed83db07af3137430
82f880a278f682c2bd0ad6887cb59f65
2e155987d61bbf6a88d36ee93b6072e6
656d9ccbaae3d655852e38deb3a2dcf8
058dc9fb6f2ab3d3b3539eb77b248a66
1091d05eb6e2f297774fe6053598457c
c61908318de4b826f0fc86d4bb117d33
e865aa805009cc2918d9c2f840c4da43
a703ad9f5b5806163d7161696b5a0adc
00000005
d5c0d1bebb06048ed6fe2ef2c6cef305
b3ed633941ebc8b3bec9738754cddd60
e1920ada52f43d055b5031cee6192520
d6a5115514851ce7fd448d4a39fae2ab
2335b525f484e9b40d6a4a969394843b
dcf6d14c48e8015e08ab92662c05c6e9
f90b65a7a6201689999f32bfd368e5e3
ec9cb70ac7b8399003f175c40885081a
09ab3034911fe125631051df0408b394
6b0bde790911e8978ba07dd56c73e7ee
"
);
let msg = hex!(
"
54686520706f77657273206e6f742064
656c65676174656420746f2074686520
556e6974656420537461746573206279
2074686520436f6e737469747574696f
6e2c206e6f722070726f686962697465
6420627920697420746f207468652053
74617465732c20617265207265736572
76656420746f20746865205374617465
7320726573706563746976656c792c20
6f7220746f207468652070656f706c65
2e0a"
);
let pk =
VerifyingKey::<LmsSha256M32H5<LmsOtsSha256N32W8>>::try_from(&pk_bytes[..]).unwrap();
let sig = Signature::<LmsSha256M32H5<LmsOtsSha256N32W8>>::try_from(&sig_bytes[..]).unwrap();
assert!(pk.verify(&msg[..], &sig).is_ok());
}
fn test_serialize_deserialize_random<Mode: LmsMode>()
where
<Mode::OtsMode as LmsOtsMode>::PLen: Add<U1>,
<Mode::OtsMode as LmsOtsMode>::NLen: Mul<Sum<<Mode::OtsMode as LmsOtsMode>::PLen, U1>>,
Prod<<Mode::OtsMode as LmsOtsMode>::NLen, Sum<<Mode::OtsMode as LmsOtsMode>::PLen, U1>>:
Add<U4>,
Sum<
Prod<<Mode::OtsMode as LmsOtsMode>::NLen, Sum<<Mode::OtsMode as LmsOtsMode>::PLen, U1>>,
U4,
>: ArrayLength<u8>,
{
let mut rng = thread_rng();
let mut sk = SigningKey::<Mode>::new(&mut rng);
let pk = sk.public();
let msg = b"Hello, world!";
let sig = sk.sign_with_rng(&mut rng, msg);
let sig_bytes: Vec<_> = sig.clone().into();
let sig2 = Signature::<Mode>::try_from(&sig_bytes[..]).unwrap();
assert!(pk.verify(msg, &sig2).is_ok());
}
#[test]
fn test_serialize_deserialize_random_h5_w1() {
test_serialize_deserialize_random::<LmsSha256M32H5<LmsOtsSha256N32W1>>();
}
#[test]
fn test_serialize_deserialize_random_h5_w2() {
test_serialize_deserialize_random::<LmsSha256M32H5<LmsOtsSha256N32W2>>();
}
#[test]
fn test_serialize_deserialize_random_h5_w4() {
test_serialize_deserialize_random::<LmsSha256M32H5<LmsOtsSha256N32W4>>();
}
#[test]
fn test_serialize_deserialize_random_h5_w8() {
test_serialize_deserialize_random::<LmsSha256M32H5<LmsOtsSha256N32W8>>();
}
#[test]
fn test_serialize_deserialize_random_h10_w1() {
test_serialize_deserialize_random::<LmsSha256M32H10<LmsOtsSha256N32W1>>();
}
#[test]
fn test_serialize_deserialize_random_h10_w2() {
test_serialize_deserialize_random::<LmsSha256M32H10<LmsOtsSha256N32W2>>();
}
#[test]
fn test_serialize_deserialize_random_h10_w4() {
test_serialize_deserialize_random::<LmsSha256M32H10<LmsOtsSha256N32W4>>();
}
#[test]
fn test_serialize_deserialize_random_h10_w8() {
test_serialize_deserialize_random::<LmsSha256M32H10<LmsOtsSha256N32W8>>();
}
}
+15
View File
@@ -0,0 +1,15 @@
//! LM-OTS Signing error
use std::error::Error;
use std::fmt::{Display, Formatter, Result};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LmsOtsInvalidPrivateKey {}
impl Display for LmsOtsInvalidPrivateKey {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "private key is no longer valid")
}
}
impl Error for LmsOtsInvalidPrivateKey {}
+13
View File
@@ -0,0 +1,13 @@
use crate::ots::modes::LmsOtsMode;
use crate::ots::private::SigningKey;
use crate::ots::public::VerifyingKey;
use signature::Keypair;
// implements the Keypair trait for PrivateKey
impl<Mode: LmsOtsMode> Keypair for SigningKey<Mode> {
type VerifyingKey = VerifyingKey<Mode>;
fn verifying_key(&self) -> Self::VerifyingKey {
self.public()
}
}
+204
View File
@@ -0,0 +1,204 @@
//! Everything related to LM-OTS
pub mod error;
mod keypair;
pub(crate) mod modes;
mod private;
mod public;
pub mod signature;
mod util;
pub use modes::{
LmsOtsMode, LmsOtsSha256N32W1, LmsOtsSha256N32W2, LmsOtsSha256N32W4, LmsOtsSha256N32W8,
};
pub use private::SigningKey;
pub use public::VerifyingKey;
pub use signature::Signature;
#[cfg(test)]
pub mod tests {
use crate::constants::ID_LEN;
use crate::ots::modes::{
LmsOtsMode, LmsOtsSha256N32W1, LmsOtsSha256N32W2, LmsOtsSha256N32W4, LmsOtsSha256N32W8,
};
use crate::ots::private::SigningKey;
use digest::Digest;
use digest::OutputSizeUser;
use generic_array::{ArrayLength, GenericArray};
use hex_literal::hex;
use rand::thread_rng;
use rand_core::{CryptoRng, RngCore};
use signature::RandomizedSignerMut;
use signature::Verifier;
use std::matches;
use std::ops::Add;
use typenum::{Sum, U2};
// tests that a signature signed with a private key verifies under
// its public key
fn test_sign<Mode: LmsOtsMode>()
where
<Mode::Hasher as OutputSizeUser>::OutputSize: Add<U2>,
Sum<<Mode::Hasher as OutputSizeUser>::OutputSize, U2>: ArrayLength<u8>,
{
let mut rng = thread_rng();
let mut sk = SigningKey::<Mode>::new(0, [0xcc; ID_LEN], &mut rng);
let pk = sk.public();
let msg = "this is a test message".as_bytes();
assert!(sk.is_valid());
let sig = sk.try_sign_with_rng(&mut rng, msg);
assert!(!sk.is_valid());
assert!(sig.is_ok());
let sig = sig.unwrap();
let result = pk.verify(msg, &sig);
assert!(matches!(result, Ok(())));
}
// tests that a signature signed with a private key does not verify under
// a different public key
fn test_sign_fail_verify<Mode: LmsOtsMode>()
where
<Mode::Hasher as OutputSizeUser>::OutputSize: Add<U2>,
Sum<<Mode::Hasher as OutputSizeUser>::OutputSize, U2>: ArrayLength<u8>,
{
let mut rng = thread_rng();
let mut sk = SigningKey::<Mode>::new(0, [0xcc; ID_LEN], &mut rng);
let mut pk = sk.public();
let msg = "this is a test message".as_bytes();
assert!(sk.is_valid());
let sig = sk.try_sign_with_rng(&mut rng, msg);
assert!(!sk.is_valid());
assert!(sig.is_ok());
let sig = sig.unwrap();
// modify q to get the wrong public key
pk.q = 1;
let result = pk.verify(msg, &sig);
assert!(result.is_err());
}
#[test]
fn test_signverify_sha256_n32_w1() {
test_sign::<LmsOtsSha256N32W1>();
}
#[test]
fn test_signverify_sha256_n32_w2() {
test_sign::<LmsOtsSha256N32W2>();
}
#[test]
fn test_signverify_sha256_n32_w4() {
test_sign::<LmsOtsSha256N32W4>();
}
#[test]
fn test_signverify_sha256_n32_w8() {
test_sign::<LmsOtsSha256N32W8>();
}
#[test]
fn test_sign_fail_verify_sha256_n32_w1() {
test_sign_fail_verify::<LmsOtsSha256N32W1>();
}
#[test]
fn test_sign_fail_verify_sha256_n32_w2() {
test_sign_fail_verify::<LmsOtsSha256N32W2>();
}
#[test]
fn test_sign_fail_verify_sha256_n32_w4() {
test_sign_fail_verify::<LmsOtsSha256N32W4>();
}
#[test]
fn test_sign_fail_verify_sha256_n32_w8() {
test_sign_fail_verify::<LmsOtsSha256N32W8>();
}
/// Constant RNG for testing purposes only.
pub struct ConstantRng<'a>(pub &'a [u8]);
impl<'a> RngCore for ConstantRng<'a> {
fn next_u32(&mut self) -> u32 {
let (head, tail) = self.0.split_at(4);
self.0 = tail;
u32::from_be_bytes(head.try_into().unwrap())
}
fn next_u64(&mut self) -> u64 {
let (head, tail) = self.0.split_at(8);
self.0 = tail;
u64::from_be_bytes(head.try_into().unwrap())
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
let (hd, tl) = self.0.split_at(dest.len());
dest.copy_from_slice(hd);
self.0 = tl;
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
if dest.len() > self.0.len() {
return Err(rand_core::Error::new("not enough bytes"));
}
let (hd, tl) = self.0.split_at(dest.len());
dest.copy_from_slice(hd);
self.0 = tl;
Ok(())
}
}
/// WARNING: This is not a secure cryptographic RNG. It is only used for testing.
impl CryptoRng for ConstantRng<'_> {}
#[test]
/// Test Case 2, Appendix F. LMS level 2. https://datatracker.ietf.org/doc/html/rfc8554#appendix-F
fn test_sign_kat1() {
let seed = hex!("a1c4696e2608035a886100d05cd99945eb3370731884a8235e2fb3d4d71f2547");
let id = hex!("215f83b7ccb9acbcd08db97b0d04dc2b");
let q = 4;
let y0 = hex!("11b3649023696f85150b189e50c00e98850ac343a77b3638319c347d7310269d");
let mut sk = SigningKey::<LmsOtsSha256N32W8>::new_from_seed(q, id, seed);
let _ = sk.public();
let c = hex!("0eb1ed54a2460d512388cad533138d240534e97b1e82d33bd927d201dfc24ebb");
let mut rng = ConstantRng(&c);
let msg = "The enumeration in the Constitution, of certain rights, shall not be construed to deny or disparage others retained by the people.\n".as_bytes();
let sig = sk.try_sign_with_rng(&mut rng, msg).unwrap();
assert_eq!(&sig.c, GenericArray::from_slice(&c));
assert_eq!(&sig.y[0], GenericArray::from_slice(&y0));
}
#[test]
// Tests that the public key generated from a given seed matches the expected value.
fn test_keygen_kat() {
let seed = hex!("a1c4696e2608035a886100d05cd99945eb3370731884a8235e2fb3d4d71f2547");
let id = hex!("215f83b7ccb9acbcd08db97b0d04dc2b");
let q = 5;
// Test Case 2, Appendix F. final signature. path[0]
// https://datatracker.ietf.org/doc/html/rfc8554#appendix-F
let k = hex!("4de1f6965bdabc676c5a4dc7c35f97f82cb0e31c68d04f1dad96314ff09e6b3d");
let sk = SigningKey::<LmsOtsSha256N32W8>::new_from_seed(q, id, seed);
let pk = sk.public();
// H(I||u32str(r)||u16str(D_LEAF)||OTS_PUB_HASH[r-2^h])
let x = <LmsOtsSha256N32W8 as LmsOtsMode>::Hasher::new()
.chain_update(pk.id)
.chain_update((pk.q + (1 << 5)).to_be_bytes())
.chain_update(crate::constants::D_LEAF)
.chain_update(pk.k)
.finalize();
assert_eq!(&x[..], &k[..]);
}
}
+219
View File
@@ -0,0 +1,219 @@
use crate::ots::util::coefs;
use crate::types::Typecode;
use digest::{Digest, Output};
use generic_array::{ArrayLength, GenericArray};
use sha2::Sha256;
use static_assertions::const_assert_eq;
use std::marker::PhantomData;
use typenum::consts::{U133, U265, U34, U67};
use typenum::Unsigned;
/// The basic trait that must be implemented by any OTS mode.
pub trait LmsOtsMode: Typecode {
/// The underlying hash function
type Hasher: Digest;
/// The length of the hash function output as a type
type NLen: ArrayLength<u8>;
/// The value of P as a type
type PLen: ArrayLength<Output<Self::Hasher>> + ArrayLength<u8>;
/// The length of the hash function output as a [usize]
const N: usize;
/// The Winternitz window, which should be a value that divides 8
const W: usize;
/// The number of `W` bit fields required to contain the hash of the message
const U: usize; // internal value calculated as https://datatracker.ietf.org/doc/html/rfc8554#appendix-B
/// The number of `W` bit fields required to contain the checksum
const V: usize; // see above
/// Computed as `U` + `V`
const P: usize;
/// The left shift required to get the checksum bits
const LS: usize;
/// The total length of the signature
const SIG_LEN: usize;
/// Expands a message into its Winternitz coefficients and checksum
fn expand(message: &Output<Self::Hasher>) -> GenericArray<u8, Self::PLen> {
// Returns an array containing Coefs(message, w, U) || Coefs(checksum, w, V)
// where Coefs(M, w, L) is an array containing coef(M, i, w) for each i in 0..L
// See RFC 8554 section 3.1.3.
// Expand the message into its coefficients
// Immediately allocates full expanded length, but only the first U coefficients are used
// in this step
let mut arr: GenericArray<u8, <Self as LmsOtsMode>::PLen> = GenericArray::default();
for (i, c) in coefs(message, Self::W).enumerate().take(Self::U) {
arr[i] = c;
}
// Compute the checksum as described in RFC 8554 section 4.4
// The checksum is the sum of all "negated" chunks
// This means that if every chunk of message `a` is <= each corresponding chunk of message ``b``,
// then the checksum of `a` is greater than the checksum of `b`
// The negation is done by subtracting the chunk value from 2^W - 1
let cksum = (&arr)
.into_iter()
.take(Self::U)
.map(|&x| ((1u16 << Self::W) - 1 - (x as u16)))
.sum::<u16>()
<< Self::LS;
// The checksum itself in then expanded into its coefficients and appended to the message coefficients
let cksum_bytes = cksum.to_be_bytes();
let cksum_chunks = coefs(&cksum_bytes, Self::W).take(Self::V);
for (i, c) in cksum_chunks.enumerate() {
arr[Self::U + i] = c;
}
arr
}
}
#[derive(Debug)]
pub struct LmsOtsModeInternal<
Hasher: Digest,
const W: usize,
PP: ArrayLength<GenericArray<u8, Hasher::OutputSize>> + ArrayLength<u8>,
const TC: u32,
> {
_phantomdata: PhantomData<(Hasher, PP)>,
}
impl<
Hasher: Digest,
const W: usize,
PP: ArrayLength<GenericArray<u8, Hasher::OutputSize>> + ArrayLength<u8>,
const TC: u32,
> Typecode for LmsOtsModeInternal<Hasher, W, PP, TC>
{
const TYPECODE: u32 = TC;
}
/// because trait associated consts cannot be used as generic values, we work around this by passing in an additional
/// type representing the array length P used for private keys, which gets checked via some static asserts
///
/// NLen and N are calculated using the associated OutputSize of the given Digest, as specified by
/// https://datatracker.ietf.org/doc/html/rfc8554#section-4.1
impl<
Hasher: Digest,
const W: usize,
PP: ArrayLength<GenericArray<u8, Hasher::OutputSize>> + ArrayLength<u8>,
const TC: u32,
> LmsOtsMode for LmsOtsModeInternal<Hasher, W, PP, TC>
{
type Hasher = Hasher;
type NLen = Hasher::OutputSize;
type PLen = PP;
const N: usize = Hasher::OutputSize::USIZE;
const W: usize = W;
const U: usize = (8 * Self::N + W - 1) / W;
const V: usize = ((((1 << W) - 1) * Self::U).ilog2() as usize / W) + 1;
const P: usize = Self::U + Self::V;
const LS: usize = 16 - Self::V * W;
const SIG_LEN: usize = 4 + Self::N * (Self::P + 1);
}
/// `LMOTS_SHA256_N32_W1`
pub type LmsOtsSha256N32W1 = LmsOtsModeInternal<Sha256, 1, U265, 1>;
/// `LMOTS_SHA256_N32_W2`
pub type LmsOtsSha256N32W2 = LmsOtsModeInternal<Sha256, 2, U133, 2>;
/// `LMOTS_SHA256_N32_W4`
pub type LmsOtsSha256N32W4 = LmsOtsModeInternal<Sha256, 4, U67, 3>;
/// `LMOTS_SHA256_N32_W8`
pub type LmsOtsSha256N32W8 = LmsOtsModeInternal<Sha256, 8, U34, 4>;
// make sure that the auto generated N, P, LS, SIG_LEN values are correct
const_assert_eq!(
<LmsOtsSha256N32W1 as LmsOtsMode>::NLen::USIZE,
LmsOtsSha256N32W1::N
);
const_assert_eq!(
<LmsOtsSha256N32W1 as LmsOtsMode>::PLen::USIZE,
LmsOtsSha256N32W1::P
);
const_assert_eq!(LmsOtsSha256N32W1::N, 32);
const_assert_eq!(LmsOtsSha256N32W1::P, 265);
const_assert_eq!(LmsOtsSha256N32W1::LS, 7);
const_assert_eq!(LmsOtsSha256N32W1::SIG_LEN, 8516);
const_assert_eq!(
<LmsOtsSha256N32W2 as LmsOtsMode>::NLen::USIZE,
LmsOtsSha256N32W2::N
);
const_assert_eq!(
<LmsOtsSha256N32W2 as LmsOtsMode>::PLen::USIZE,
LmsOtsSha256N32W2::P
);
const_assert_eq!(LmsOtsSha256N32W2::N, 32);
const_assert_eq!(LmsOtsSha256N32W2::P, 133);
const_assert_eq!(LmsOtsSha256N32W2::LS, 6);
const_assert_eq!(LmsOtsSha256N32W2::SIG_LEN, 4292);
const_assert_eq!(
<LmsOtsSha256N32W4 as LmsOtsMode>::NLen::USIZE,
LmsOtsSha256N32W4::N
);
const_assert_eq!(
<LmsOtsSha256N32W4 as LmsOtsMode>::PLen::USIZE,
LmsOtsSha256N32W4::P
);
const_assert_eq!(LmsOtsSha256N32W4::N, 32);
const_assert_eq!(LmsOtsSha256N32W4::P, 67);
const_assert_eq!(LmsOtsSha256N32W4::LS, 4);
const_assert_eq!(LmsOtsSha256N32W4::SIG_LEN, 2180);
const_assert_eq!(
<LmsOtsSha256N32W8 as LmsOtsMode>::NLen::USIZE,
LmsOtsSha256N32W8::N
);
const_assert_eq!(
<LmsOtsSha256N32W8 as LmsOtsMode>::PLen::USIZE,
LmsOtsSha256N32W8::P
);
const_assert_eq!(LmsOtsSha256N32W8::N, 32);
const_assert_eq!(LmsOtsSha256N32W8::P, 34);
const_assert_eq!(LmsOtsSha256N32W8::LS, 0);
const_assert_eq!(LmsOtsSha256N32W8::SIG_LEN, 1124);
#[cfg(test)]
mod test {
use generic_array::GenericArray;
use super::LmsOtsMode;
#[test]
fn test_checksum_zero_w1() {
let arr = [0u8; super::LmsOtsSha256N32W1::N];
let cksm = super::LmsOtsSha256N32W1::expand(GenericArray::from_slice(&arr));
assert_eq!(
&cksm[super::LmsOtsSha256N32W1::U..],
&[1, 0, 0, 0, 0, 0, 0, 0, 0]
);
}
#[test]
fn test_checksum_ones_w1() {
let arr = [255u8; super::LmsOtsSha256N32W1::N];
let cksm = super::LmsOtsSha256N32W1::expand(GenericArray::from_slice(&arr));
assert_eq!(
&cksm[super::LmsOtsSha256N32W1::U..],
&[0, 0, 0, 0, 0, 0, 0, 0, 0]
);
}
#[test]
fn test_checksum_ten_w4() {
let arr = [0xaa; super::LmsOtsSha256N32W4::N];
let cksm = super::LmsOtsSha256N32W4::expand(GenericArray::from_slice(&arr));
assert_eq!(&cksm[super::LmsOtsSha256N32W4::U..], &[0x01, 0x04, 0x00]);
}
#[test]
fn test_expand_zero_w8() {
let arr = [0u8; super::LmsOtsSha256N32W8::N];
let expanded = super::LmsOtsSha256N32W8::expand(GenericArray::from_slice(&arr));
let mut expected = [0u8; super::LmsOtsSha256N32W8::P];
expected[super::LmsOtsSha256N32W8::U] = 0x1f;
expected[super::LmsOtsSha256N32W8::U + 1] = 0xe0;
assert_eq!(&expanded.as_slice(), &expected);
}
}
+149
View File
@@ -0,0 +1,149 @@
use crate::constants::{D_MESG, D_PBLC};
use crate::ots::error::LmsOtsInvalidPrivateKey;
use crate::ots::modes::LmsOtsMode;
use crate::ots::public::VerifyingKey;
use crate::ots::signature::Signature;
use crate::types::Identifier;
use digest::{Digest, Output};
use generic_array::sequence::GenericSequence;
use generic_array::GenericArray;
use rand_core::CryptoRngCore;
use signature::{Error, RandomizedSignerMut};
use zeroize::Zeroize;
//use std::mem::MaybeUninit;
#[derive(Debug)]
/// Opaque struct representing an LM-OTS private key. Does not implement
/// [Clone] because OTS keys are supposed to be one time use.
pub struct SigningKey<Mode: LmsOtsMode> {
q: u32,
id: Identifier,
x: GenericArray<Output<Mode::Hasher>, Mode::PLen>,
valid: bool,
}
impl<Mode: LmsOtsMode> SigningKey<Mode> {
/// Generate a private key, expanded pseudorandomly from a seed generated by `rng`.
/// Uses the algorithm from appendix A <https://datatracker.ietf.org/doc/html/rfc8554#appendix-A>
// a key part of this code working is the DerefMut impl for GenericArray which we abuse in a similar manner to
// generic_array::ArrayBuilder's internal implementation
/// If LM-OTS is being used directly, q MUST be set to the all-zero value
/// <https://datatracker.ietf.org/doc/html/rfc8554#section-4>
pub fn new(q: u32, id: Identifier, rng: &mut impl CryptoRngCore) -> Self {
let mut seed: GenericArray<u8, Mode::NLen> = GenericArray::default();
rng.fill_bytes(&mut seed);
Self::new_from_seed(q, id, seed)
}
/// Returns a private key generated pseudorandomly from a seed
/// according to Appendix A of <https://datatracker.ietf.org/doc/html/rfc8554#appendix-A>
pub fn new_from_seed(q: u32, id: Identifier, seed: impl AsRef<[u8]>) -> Self {
let seed = seed.as_ref();
let x = GenericArray::generate(|i| {
Mode::Hasher::new()
.chain_update(id)
.chain_update(q.to_be_bytes())
.chain_update((i as u16).to_be_bytes())
.chain_update([0xff])
.chain_update(seed)
.finalize()
});
Self {
q,
id,
x,
valid: true,
}
}
/// this implements algorithm 1 from <https://datatracker.ietf.org/doc/html/rfc8554#section-4.3>
pub fn public(&self) -> VerifyingKey<Mode> {
let mut hasher = Mode::Hasher::new()
.chain_update(self.id)
.chain_update(self.q.to_be_bytes())
.chain_update(D_PBLC);
let mut tmp = Output::<Mode::Hasher>::default();
for i in 0..Mode::P {
//let mut tmp = self.x[i].clone();
tmp.clone_from(&self.x[i]);
for j in 0..((1u32 << Mode::W) - 1) {
Mode::Hasher::new()
.chain_update(self.id)
.chain_update(self.q.to_be_bytes())
.chain_update((i as u16).to_be_bytes())
.chain_update((j as u8).to_be_bytes())
.chain_update(&tmp)
.finalize_into(&mut tmp);
}
hasher.update(&tmp);
}
VerifyingKey {
id: self.id,
q: self.q,
k: hasher.finalize(),
}
}
/// `true` if the private key can be used for signing operations
///
/// `false` if it has already been used
pub fn is_valid(&self) -> bool {
self.valid
}
}
impl<Mode: LmsOtsMode> RandomizedSignerMut<Signature<Mode>> for SigningKey<Mode> {
fn try_sign_with_rng(
&mut self,
rng: &mut impl CryptoRngCore,
msg: &[u8],
) -> Result<Signature<Mode>, Error> {
if !self.valid {
return Err(Error::from_source(LmsOtsInvalidPrivateKey {}));
}
// Generate the message randomizer C
let mut c = <Output<Mode::Hasher>>::default();
rng.fill_bytes(&mut c);
// Q is the randomized message hash
let q = Mode::Hasher::new()
.chain_update(self.id)
.chain_update(self.q.to_be_bytes())
.chain_update(D_MESG)
.chain_update(&c)
.chain_update(msg)
.finalize();
// Y is the signature. We iterate over the message hash and checksum expanded into Winternitz coefficients
let y = Mode::expand(&q).into_iter().enumerate().map(|(i, a)| {
let a = a as u32;
let mut tmp = self.x[i].clone();
for j in 0..a {
Mode::Hasher::new()
.chain_update(self.id)
.chain_update(self.q.to_be_bytes())
.chain_update((i as u16).to_be_bytes())
.chain_update((j as u8).to_be_bytes())
.chain_update(&tmp)
.finalize_into(&mut tmp);
}
tmp
});
let y = GenericArray::from_iter(y);
let sig = Signature { c, y };
// zero out fields so we can't use the private key a second time
self.q.zeroize();
self.id.zeroize();
self.x.zeroize();
self.valid = false;
Ok(sig)
}
}
+150
View File
@@ -0,0 +1,150 @@
//! Contains the [`VerifyingKey`] type
use crate::constants::ID_LEN;
use crate::error::LmsDeserializeError;
use crate::ots::modes::LmsOtsMode;
use crate::ots::signature::Signature;
use crate::types::Identifier;
use digest::{Output, OutputSizeUser};
use generic_array::{ArrayLength, GenericArray};
use signature::{Error, Verifier};
use std::cmp::Ordering;
use std::ops::Add;
use typenum::{Sum, U2, U24};
#[derive(Debug)]
/// Opaque struct representing a LM-OTS public key
pub struct VerifyingKey<Mode: LmsOtsMode> {
pub(crate) q: u32,
pub(crate) id: Identifier,
pub(crate) k: Output<Mode::Hasher>,
}
// manual Clone impl because Mode is not Clone
impl<Mode: LmsOtsMode> Clone for VerifyingKey<Mode> {
fn clone(&self) -> Self {
Self {
q: self.q,
id: self.id,
k: self.k.clone(),
}
}
}
// manual PartialEq impl because Mode is not PartialEq
impl<Mode: LmsOtsMode> PartialEq for VerifyingKey<Mode> {
fn eq(&self, other: &Self) -> bool {
self.q == other.q && self.id == other.id && self.k == other.k
}
}
impl<Mode: LmsOtsMode> Verifier<Signature<Mode>> for VerifyingKey<Mode>
where
// required to concat Q and cksm(Q)
<Mode::Hasher as OutputSizeUser>::OutputSize: Add<U2>,
Sum<<Mode::Hasher as OutputSizeUser>::OutputSize, U2>: ArrayLength<u8>,
{
// this implements algorithm 4a of https://datatracker.ietf.org/doc/html/rfc8554#section-4.6
fn verify(&self, msg: &[u8], signature: &Signature<Mode>) -> Result<(), Error> {
// If the public key is not at least four bytes long, return INVALID.
// We are calling this method on a valid public key so there's no worry here.
let kc = signature.recover_pubkey(self.id, self.q, msg);
// 4. If Kc is equal to K, return VALID; otherwise, return INVALID.
if self.k == kc.k {
Ok(())
} else {
Err(Error::new())
}
}
}
/// Converts a [`VerifyingKey`] into its byte representation
impl<Mode: LmsOtsMode> From<VerifyingKey<Mode>>
for GenericArray<u8, Sum<<Mode::Hasher as OutputSizeUser>::OutputSize, U24>>
where
<Mode::Hasher as OutputSizeUser>::OutputSize: Add<U24>,
Sum<<Mode::Hasher as OutputSizeUser>::OutputSize, U24>: ArrayLength<u8>,
{
fn from(pk: VerifyingKey<Mode>) -> Self {
// Return u32str(type) || I || u32str(q) || K
GenericArray::from_exact_iter(
std::iter::empty()
.chain(Mode::TYPECODE.to_be_bytes())
.chain(pk.id)
.chain(pk.q.to_be_bytes())
.chain(pk.k),
)
.expect("ok")
}
}
/// Tries to parse a [`VerifyingKey`] from an exact slice
impl<'a, Mode: LmsOtsMode> TryFrom<&'a [u8]> for VerifyingKey<Mode> {
type Error = LmsDeserializeError;
fn try_from(pk: &'a [u8]) -> Result<Self, Self::Error> {
if pk.len() < 4 {
return Err(LmsDeserializeError::NoAlgorithm);
}
let (alg, pk) = pk.split_at(4);
let expected = Mode::N + ID_LEN + 4;
// will never panic because alg is a 4 byte slice
if u32::from_be_bytes(alg.try_into().unwrap()) != Mode::TYPECODE {
return Err(LmsDeserializeError::WrongAlgorithm);
}
match pk.len().cmp(&expected) {
Ordering::Less => Err(LmsDeserializeError::TooShort),
Ordering::Greater => Err(LmsDeserializeError::TooLong),
Ordering::Equal => {
// pk is now guaranteed to be of the form I || q || K
let (i, qk) = pk.split_at(ID_LEN);
let (q, k) = qk.split_at(4);
Ok(Self {
q: u32::from_be_bytes(q.try_into().expect("ok")),
id: i.try_into().expect("ok"),
k: GenericArray::clone_from_slice(k),
})
}
}
}
}
#[cfg(test)]
mod tests {
use crate::constants::ID_LEN;
use crate::error::LmsDeserializeError;
use crate::ots::modes::{LmsOtsSha256N32W4, LmsOtsSha256N32W8};
use crate::ots::private::SigningKey;
use crate::ots::public::VerifyingKey;
use generic_array::GenericArray;
use rand::thread_rng;
#[test]
fn test_serde() {
let pk =
SigningKey::<LmsOtsSha256N32W8>::new(0, [0xbb; ID_LEN], &mut thread_rng()).public();
let pk_serialized: GenericArray<u8, _> = pk.clone().into();
let bytes = pk_serialized.as_slice();
let pk_deserialized = VerifyingKey::<LmsOtsSha256N32W8>::try_from(bytes);
assert!(pk_deserialized.is_ok());
let pk_deserialized = pk_deserialized.unwrap();
assert_eq!(pk, pk_deserialized);
let pk_wrongalgo = VerifyingKey::<LmsOtsSha256N32W4>::try_from(bytes);
let pk_short = VerifyingKey::<LmsOtsSha256N32W8>::try_from(&bytes[0..(bytes.len() - 1)]);
let mut long_bytes = pk_serialized.into_iter().collect::<Vec<_>>();
long_bytes.push(0);
let pk_long = VerifyingKey::<LmsOtsSha256N32W8>::try_from(long_bytes.as_slice());
assert_eq!(pk_wrongalgo, Err(LmsDeserializeError::WrongAlgorithm));
assert_eq!(pk_short, Err(LmsDeserializeError::TooShort));
assert_eq!(pk_long, Err(LmsDeserializeError::TooLong));
}
}
+439
View File
@@ -0,0 +1,439 @@
//! Contains the [`Signature`] type
use crate::constants::{D_MESG, D_PBLC};
use crate::error::LmsDeserializeError;
use crate::ots::modes::LmsOtsMode;
use crate::types::Identifier;
use digest::Digest;
use generic_array::{ArrayLength, GenericArray};
use signature::SignatureEncoding;
use std::cmp::Ordering;
use std::ops::{Add, Mul};
use typenum::{Prod, Sum, U1, U4};
use super::VerifyingKey;
#[derive(Debug, Eq)]
/// Opaque struct representing a LM-OTS signature
pub struct Signature<Mode: LmsOtsMode> {
pub(crate) c: digest::Output<Mode::Hasher>,
pub(crate) y: GenericArray<digest::Output<Mode::Hasher>, Mode::PLen>,
}
// manual implementation is required to not require bounds on Mode
impl<Mode: LmsOtsMode> Clone for Signature<Mode> {
fn clone(&self) -> Self {
Self {
c: self.c.clone(),
y: self.y.clone(),
}
}
}
// manual implementation is required to not require bounds on Mode
impl<Mode: LmsOtsMode> PartialEq for Signature<Mode> {
fn eq(&self, other: &Self) -> bool {
self.c == other.c && self.y == other.y
}
}
/// Useful type alias to get the [`GenericArray`] representation
pub type Output<Mode> = GenericArray<
u8,
Sum<Prod<<Mode as LmsOtsMode>::NLen, Sum<<Mode as LmsOtsMode>::PLen, U1>>, U4>,
>;
/// Converts a [`Signature`] into its byte representation
impl<Mode: LmsOtsMode> From<Signature<Mode>> for Output<Mode>
where
// required for Output<Mode>
Mode::PLen: Add<U1>,
Mode::NLen: Mul<Sum<Mode::PLen, U1>>,
Prod<Mode::NLen, Sum<Mode::PLen, U1>>: Add<U4>,
Sum<Prod<Mode::NLen, Sum<Mode::PLen, U1>>, U4>: ArrayLength<u8>,
{
fn from(sig: Signature<Mode>) -> Self {
GenericArray::from_exact_iter(
std::iter::empty()
.chain(Mode::TYPECODE.to_be_bytes())
.chain(sig.c.clone())
.chain(sig.y.iter().flatten().cloned()),
)
.expect("ok")
}
}
/// Tries to parse a [`Signature`] from an exact slice
impl<'a, Mode: LmsOtsMode> TryFrom<&'a [u8]> for Signature<Mode> {
type Error = LmsDeserializeError;
fn try_from(sig: &'a [u8]) -> Result<Self, Self::Error> {
if sig.len() < 4 {
return Err(LmsDeserializeError::NoAlgorithm);
}
let (alg, sig) = sig.split_at(4);
// will never panic because alg is a 4 byte slice
if u32::from_be_bytes(alg.try_into().unwrap()) != Mode::TYPECODE {
return Err(LmsDeserializeError::WrongAlgorithm);
}
let expected = Mode::N * (Mode::P + 1);
match sig.len().cmp(&expected) {
Ordering::Less => Err(LmsDeserializeError::TooShort),
Ordering::Greater => Err(LmsDeserializeError::TooLong),
Ordering::Equal => {
// sig is now guaranteed to be of the form C || y[0] || ... || y[p - 1]
let (c, y) = sig.split_at(Mode::N);
let c = GenericArray::clone_from_slice(c);
// ys is an iterator over GenericArray<u8, Mode::NLen> of length p
let ys = y.chunks_exact(Mode::N).map(GenericArray::clone_from_slice);
debug_assert!(ys.len() == Mode::P);
let y = GenericArray::from_iter(ys);
Ok(Self { c, y })
}
}
}
}
impl<Mode: LmsOtsMode> Signature<Mode> {
/// Returns a public key candidate for this signature as defined by
/// algorithm 4b of the LMS RFC. The signature will always be valid for
/// the returned public key candidate.
pub fn recover_pubkey(&self, id: Identifier, q: u32, msg: &[u8]) -> VerifyingKey<Mode> {
// algorithm 4b
// Q = H(I || u32str(q) || u16str(D_MESG) || C || message)
let msg_hash = Mode::Hasher::new()
.chain_update(id)
.chain_update(q.to_be_bytes())
.chain_update(D_MESG)
.chain_update(&self.c)
.chain_update(msg)
.finalize();
// first part of
// Kc = H(I || u32str(q) || u16str(D_PBLC) || z[0] || z[1] || ... || z[p-1])
let mut hasher = Mode::Hasher::new()
.chain_update(id)
.chain_update(q.to_be_bytes())
.chain_update(D_PBLC);
let mut tmp = GenericArray::default();
for (i, a) in Mode::expand(&msg_hash).into_iter().enumerate() {
tmp.clone_from(&self.y[i]);
// for ( j = a; j < 2^w - 1; j = j + 1 )
for j in a..((1usize << Mode::W) - 1) as u8 {
// tmp = H(I || u32str(q) || u16str(i) || u8str(j) || tmp)
Mode::Hasher::new()
.chain_update(id)
.chain_update(q.to_be_bytes())
.chain_update((i as u16).to_be_bytes())
.chain_update(j.to_be_bytes())
.chain_update(&tmp)
.finalize_into(&mut tmp);
}
// z[i] = tmp
// except we directly absorb into the hash function to finish off the computation of
// Kc = H(I || u32str(q) || u16str(D_PBLC) || z[0] || z[1] || ... || z[p-1])
hasher.update(&tmp);
}
VerifyingKey {
id,
q,
k: hasher.finalize(),
}
}
}
impl<Mode: LmsOtsMode> SignatureEncoding for Signature<Mode>
where
// required for Output<Mode>
Mode::PLen: Add<U1>,
Mode::NLen: Mul<Sum<Mode::PLen, U1>>,
Prod<Mode::NLen, Sum<Mode::PLen, U1>>: Add<U4>,
Sum<Prod<Mode::NLen, Sum<Mode::PLen, U1>>, U4>: ArrayLength<u8>,
{
type Repr = Output<Mode>;
}
#[cfg(test)]
mod tests {
use crate::ots::modes::{LmsOtsSha256N32W4, LmsOtsSha256N32W8};
use crate::ots::signature::{LmsDeserializeError, Signature};
#[test]
fn test_deserialize_sha256_n32_w8() {
let sig_str = "00000004\
0703c491e7558b35011ece3592eaa5da4d918786771233e8353bc4f62323185c\
95cae05b899e35dffd717054706209988ebfdf6e37960bb5c38d7657e8bffeef\
9bc042da4b4525650485c66d0ce19b317587c6ba4bffcc428e25d08931e72dfb\
6a120c5612344258b85efdb7db1db9e1865a73caf96557eb39ed3e3f426933ac\
9eeddb03a1d2374af7bf77185577456237f9de2d60113c23f846df26fa942008\
a698994c0827d90e86d43e0df7f4bfcdb09b86a373b98288b7094ad81a0185ac\
100e4f2c5fc38c003c1ab6fea479eb2f5ebe48f584d7159b8ada03586e65ad9c\
969f6aecbfe44cf356888a7b15a3ff074f771760b26f9c04884ee1faa329fbf4\
e61af23aee7fa5d4d9a5dfcf43c4c26ce8aea2ce8a2990d7ba7b57108b47dabf\
beadb2b25b3cacc1ac0cef346cbb90fb044beee4fac2603a442bdf7e507243b7\
319c9944b1586e899d431c7f91bcccc8690dbf59b28386b2315f3d36ef2eaa3c\
f30b2b51f48b71b003dfb08249484201043f65f5a3ef6bbd61ddfee81aca9ce6\
0081262a00000480dcbc9a3da6fbef5c1c0a55e48a0e729f9184fcb1407c3152\
9db268f6fe50032a363c9801306837fafabdf957fd97eafc80dbd165e435d0e2\
dfd836a28b354023924b6fb7e48bc0b3ed95eea64c2d402f4d734c8dc26f3ac5\
91825daef01eae3c38e3328d00a77dc657034f287ccb0f0e1c9a7cbdc828f627\
205e4737b84b58376551d44c12c3c215c812a0970789c83de51d6ad787271963\
327f0a5fbb6b5907dec02c9a90934af5a1c63b72c82653605d1dcce51596b3c2\
b45696689f2eb382007497557692caac4d57b5de9f5569bc2ad0137fd47fb47e\
664fcb6db4971f5b3e07aceda9ac130e9f38182de994cff192ec0e82fd6d4cb7\
f3fe00812589b7a7ce515440456433016b84a59bec6619a1c6c0b37dd1450ed4\
f2d8b584410ceda8025f5d2d8dd0d2176fc1cf2cc06fa8c82bed4d944e71339e\
ce780fd025bd41ec34ebff9d4270a3224e019fcb444474d482fd2dbe75efb203\
89cc10cd600abb54c47ede93e08c114edb04117d714dc1d525e11bed8756192f\
929d15462b939ff3f52f2252da2ed64d8fae88818b1efa2c7b08c8794fb1b214\
aa233db3162833141ea4383f1a6f120be1db82ce3630b3429114463157a64e91\
234d475e2f79cbf05e4db6a9407d72c6bff7d1198b5c4d6aad2831db61274993\
715a0182c7dc8089e32c8531deed4f7431c07c02195eba2ef91efb5613c37af7\
ae0c066babc69369700e1dd26eddc0d216c781d56e4ce47e3303fa73007ff7b9\
49ef23be2aa4dbf25206fe45c20dd888395b2526391a724996a44156beac8082\
12858792bf8e74cba49dee5e8812e019da87454bff9e847ed83db07af3137430\
82f880a278f682c2bd0ad6887cb59f652e155987d61bbf6a88d36ee93b6072e6\
656d9ccbaae3d655852e38deb3a2dcf8058dc9fb6f2ab3d3b3539eb77b248a66\
1091d05eb6e2f297774fe6053598457cc61908318de4b826f0fc86d4bb117d33\
e865aa805009cc2918d9c2f840c4da43a703ad9f5b5806163d7161696b5a0adc";
let sig_bytes = hex::decode(sig_str).unwrap();
let mut long_bytes = sig_bytes.clone();
long_bytes.push(0);
let sig = Signature::<LmsOtsSha256N32W8>::try_from(&sig_bytes[..]);
let sig_wrongalgo = Signature::<LmsOtsSha256N32W4>::try_from(&sig_bytes[..]);
let sig_short = Signature::<LmsOtsSha256N32W8>::try_from(&sig_bytes[..sig_bytes.len() - 1]);
let sig_long = Signature::<LmsOtsSha256N32W8>::try_from(&long_bytes[..]);
assert!(sig.is_ok());
assert_eq!(sig_wrongalgo, Err(LmsDeserializeError::WrongAlgorithm));
assert_eq!(sig_short, Err(LmsDeserializeError::TooShort));
assert_eq!(sig_long, Err(LmsDeserializeError::TooLong));
let ec: [u8; 32] = [
0x07, 0x03, 0xc4, 0x91, 0xe7, 0x55, 0x8b, 0x35, 0x01, 0x1e, 0xce, 0x35, 0x92, 0xea,
0xa5, 0xda, 0x4d, 0x91, 0x87, 0x86, 0x77, 0x12, 0x33, 0xe8, 0x35, 0x3b, 0xc4, 0xf6,
0x23, 0x23, 0x18, 0x5c,
];
let ey: [[u8; 32]; 34] = [
[
0x95, 0xca, 0xe0, 0x5b, 0x89, 0x9e, 0x35, 0xdf, 0xfd, 0x71, 0x70, 0x54, 0x70, 0x62,
0x09, 0x98, 0x8e, 0xbf, 0xdf, 0x6e, 0x37, 0x96, 0x0b, 0xb5, 0xc3, 0x8d, 0x76, 0x57,
0xe8, 0xbf, 0xfe, 0xef,
],
[
0x9b, 0xc0, 0x42, 0xda, 0x4b, 0x45, 0x25, 0x65, 0x04, 0x85, 0xc6, 0x6d, 0x0c, 0xe1,
0x9b, 0x31, 0x75, 0x87, 0xc6, 0xba, 0x4b, 0xff, 0xcc, 0x42, 0x8e, 0x25, 0xd0, 0x89,
0x31, 0xe7, 0x2d, 0xfb,
],
[
0x6a, 0x12, 0x0c, 0x56, 0x12, 0x34, 0x42, 0x58, 0xb8, 0x5e, 0xfd, 0xb7, 0xdb, 0x1d,
0xb9, 0xe1, 0x86, 0x5a, 0x73, 0xca, 0xf9, 0x65, 0x57, 0xeb, 0x39, 0xed, 0x3e, 0x3f,
0x42, 0x69, 0x33, 0xac,
],
[
0x9e, 0xed, 0xdb, 0x03, 0xa1, 0xd2, 0x37, 0x4a, 0xf7, 0xbf, 0x77, 0x18, 0x55, 0x77,
0x45, 0x62, 0x37, 0xf9, 0xde, 0x2d, 0x60, 0x11, 0x3c, 0x23, 0xf8, 0x46, 0xdf, 0x26,
0xfa, 0x94, 0x20, 0x08,
],
[
0xa6, 0x98, 0x99, 0x4c, 0x08, 0x27, 0xd9, 0x0e, 0x86, 0xd4, 0x3e, 0x0d, 0xf7, 0xf4,
0xbf, 0xcd, 0xb0, 0x9b, 0x86, 0xa3, 0x73, 0xb9, 0x82, 0x88, 0xb7, 0x09, 0x4a, 0xd8,
0x1a, 0x01, 0x85, 0xac,
],
[
0x10, 0x0e, 0x4f, 0x2c, 0x5f, 0xc3, 0x8c, 0x00, 0x3c, 0x1a, 0xb6, 0xfe, 0xa4, 0x79,
0xeb, 0x2f, 0x5e, 0xbe, 0x48, 0xf5, 0x84, 0xd7, 0x15, 0x9b, 0x8a, 0xda, 0x03, 0x58,
0x6e, 0x65, 0xad, 0x9c,
],
[
0x96, 0x9f, 0x6a, 0xec, 0xbf, 0xe4, 0x4c, 0xf3, 0x56, 0x88, 0x8a, 0x7b, 0x15, 0xa3,
0xff, 0x07, 0x4f, 0x77, 0x17, 0x60, 0xb2, 0x6f, 0x9c, 0x04, 0x88, 0x4e, 0xe1, 0xfa,
0xa3, 0x29, 0xfb, 0xf4,
],
[
0xe6, 0x1a, 0xf2, 0x3a, 0xee, 0x7f, 0xa5, 0xd4, 0xd9, 0xa5, 0xdf, 0xcf, 0x43, 0xc4,
0xc2, 0x6c, 0xe8, 0xae, 0xa2, 0xce, 0x8a, 0x29, 0x90, 0xd7, 0xba, 0x7b, 0x57, 0x10,
0x8b, 0x47, 0xda, 0xbf,
],
[
0xbe, 0xad, 0xb2, 0xb2, 0x5b, 0x3c, 0xac, 0xc1, 0xac, 0x0c, 0xef, 0x34, 0x6c, 0xbb,
0x90, 0xfb, 0x04, 0x4b, 0xee, 0xe4, 0xfa, 0xc2, 0x60, 0x3a, 0x44, 0x2b, 0xdf, 0x7e,
0x50, 0x72, 0x43, 0xb7,
],
[
0x31, 0x9c, 0x99, 0x44, 0xb1, 0x58, 0x6e, 0x89, 0x9d, 0x43, 0x1c, 0x7f, 0x91, 0xbc,
0xcc, 0xc8, 0x69, 0x0d, 0xbf, 0x59, 0xb2, 0x83, 0x86, 0xb2, 0x31, 0x5f, 0x3d, 0x36,
0xef, 0x2e, 0xaa, 0x3c,
],
[
0xf3, 0x0b, 0x2b, 0x51, 0xf4, 0x8b, 0x71, 0xb0, 0x03, 0xdf, 0xb0, 0x82, 0x49, 0x48,
0x42, 0x01, 0x04, 0x3f, 0x65, 0xf5, 0xa3, 0xef, 0x6b, 0xbd, 0x61, 0xdd, 0xfe, 0xe8,
0x1a, 0xca, 0x9c, 0xe6,
],
[
0x00, 0x81, 0x26, 0x2a, 0x00, 0x00, 0x04, 0x80, 0xdc, 0xbc, 0x9a, 0x3d, 0xa6, 0xfb,
0xef, 0x5c, 0x1c, 0x0a, 0x55, 0xe4, 0x8a, 0x0e, 0x72, 0x9f, 0x91, 0x84, 0xfc, 0xb1,
0x40, 0x7c, 0x31, 0x52,
],
[
0x9d, 0xb2, 0x68, 0xf6, 0xfe, 0x50, 0x03, 0x2a, 0x36, 0x3c, 0x98, 0x01, 0x30, 0x68,
0x37, 0xfa, 0xfa, 0xbd, 0xf9, 0x57, 0xfd, 0x97, 0xea, 0xfc, 0x80, 0xdb, 0xd1, 0x65,
0xe4, 0x35, 0xd0, 0xe2,
],
[
0xdf, 0xd8, 0x36, 0xa2, 0x8b, 0x35, 0x40, 0x23, 0x92, 0x4b, 0x6f, 0xb7, 0xe4, 0x8b,
0xc0, 0xb3, 0xed, 0x95, 0xee, 0xa6, 0x4c, 0x2d, 0x40, 0x2f, 0x4d, 0x73, 0x4c, 0x8d,
0xc2, 0x6f, 0x3a, 0xc5,
],
[
0x91, 0x82, 0x5d, 0xae, 0xf0, 0x1e, 0xae, 0x3c, 0x38, 0xe3, 0x32, 0x8d, 0x00, 0xa7,
0x7d, 0xc6, 0x57, 0x03, 0x4f, 0x28, 0x7c, 0xcb, 0x0f, 0x0e, 0x1c, 0x9a, 0x7c, 0xbd,
0xc8, 0x28, 0xf6, 0x27,
],
[
0x20, 0x5e, 0x47, 0x37, 0xb8, 0x4b, 0x58, 0x37, 0x65, 0x51, 0xd4, 0x4c, 0x12, 0xc3,
0xc2, 0x15, 0xc8, 0x12, 0xa0, 0x97, 0x07, 0x89, 0xc8, 0x3d, 0xe5, 0x1d, 0x6a, 0xd7,
0x87, 0x27, 0x19, 0x63,
],
[
0x32, 0x7f, 0x0a, 0x5f, 0xbb, 0x6b, 0x59, 0x07, 0xde, 0xc0, 0x2c, 0x9a, 0x90, 0x93,
0x4a, 0xf5, 0xa1, 0xc6, 0x3b, 0x72, 0xc8, 0x26, 0x53, 0x60, 0x5d, 0x1d, 0xcc, 0xe5,
0x15, 0x96, 0xb3, 0xc2,
],
[
0xb4, 0x56, 0x96, 0x68, 0x9f, 0x2e, 0xb3, 0x82, 0x00, 0x74, 0x97, 0x55, 0x76, 0x92,
0xca, 0xac, 0x4d, 0x57, 0xb5, 0xde, 0x9f, 0x55, 0x69, 0xbc, 0x2a, 0xd0, 0x13, 0x7f,
0xd4, 0x7f, 0xb4, 0x7e,
],
[
0x66, 0x4f, 0xcb, 0x6d, 0xb4, 0x97, 0x1f, 0x5b, 0x3e, 0x07, 0xac, 0xed, 0xa9, 0xac,
0x13, 0x0e, 0x9f, 0x38, 0x18, 0x2d, 0xe9, 0x94, 0xcf, 0xf1, 0x92, 0xec, 0x0e, 0x82,
0xfd, 0x6d, 0x4c, 0xb7,
],
[
0xf3, 0xfe, 0x00, 0x81, 0x25, 0x89, 0xb7, 0xa7, 0xce, 0x51, 0x54, 0x40, 0x45, 0x64,
0x33, 0x01, 0x6b, 0x84, 0xa5, 0x9b, 0xec, 0x66, 0x19, 0xa1, 0xc6, 0xc0, 0xb3, 0x7d,
0xd1, 0x45, 0x0e, 0xd4,
],
[
0xf2, 0xd8, 0xb5, 0x84, 0x41, 0x0c, 0xed, 0xa8, 0x02, 0x5f, 0x5d, 0x2d, 0x8d, 0xd0,
0xd2, 0x17, 0x6f, 0xc1, 0xcf, 0x2c, 0xc0, 0x6f, 0xa8, 0xc8, 0x2b, 0xed, 0x4d, 0x94,
0x4e, 0x71, 0x33, 0x9e,
],
[
0xce, 0x78, 0x0f, 0xd0, 0x25, 0xbd, 0x41, 0xec, 0x34, 0xeb, 0xff, 0x9d, 0x42, 0x70,
0xa3, 0x22, 0x4e, 0x01, 0x9f, 0xcb, 0x44, 0x44, 0x74, 0xd4, 0x82, 0xfd, 0x2d, 0xbe,
0x75, 0xef, 0xb2, 0x03,
],
[
0x89, 0xcc, 0x10, 0xcd, 0x60, 0x0a, 0xbb, 0x54, 0xc4, 0x7e, 0xde, 0x93, 0xe0, 0x8c,
0x11, 0x4e, 0xdb, 0x04, 0x11, 0x7d, 0x71, 0x4d, 0xc1, 0xd5, 0x25, 0xe1, 0x1b, 0xed,
0x87, 0x56, 0x19, 0x2f,
],
[
0x92, 0x9d, 0x15, 0x46, 0x2b, 0x93, 0x9f, 0xf3, 0xf5, 0x2f, 0x22, 0x52, 0xda, 0x2e,
0xd6, 0x4d, 0x8f, 0xae, 0x88, 0x81, 0x8b, 0x1e, 0xfa, 0x2c, 0x7b, 0x08, 0xc8, 0x79,
0x4f, 0xb1, 0xb2, 0x14,
],
[
0xaa, 0x23, 0x3d, 0xb3, 0x16, 0x28, 0x33, 0x14, 0x1e, 0xa4, 0x38, 0x3f, 0x1a, 0x6f,
0x12, 0x0b, 0xe1, 0xdb, 0x82, 0xce, 0x36, 0x30, 0xb3, 0x42, 0x91, 0x14, 0x46, 0x31,
0x57, 0xa6, 0x4e, 0x91,
],
[
0x23, 0x4d, 0x47, 0x5e, 0x2f, 0x79, 0xcb, 0xf0, 0x5e, 0x4d, 0xb6, 0xa9, 0x40, 0x7d,
0x72, 0xc6, 0xbf, 0xf7, 0xd1, 0x19, 0x8b, 0x5c, 0x4d, 0x6a, 0xad, 0x28, 0x31, 0xdb,
0x61, 0x27, 0x49, 0x93,
],
[
0x71, 0x5a, 0x01, 0x82, 0xc7, 0xdc, 0x80, 0x89, 0xe3, 0x2c, 0x85, 0x31, 0xde, 0xed,
0x4f, 0x74, 0x31, 0xc0, 0x7c, 0x02, 0x19, 0x5e, 0xba, 0x2e, 0xf9, 0x1e, 0xfb, 0x56,
0x13, 0xc3, 0x7a, 0xf7,
],
[
0xae, 0x0c, 0x06, 0x6b, 0xab, 0xc6, 0x93, 0x69, 0x70, 0x0e, 0x1d, 0xd2, 0x6e, 0xdd,
0xc0, 0xd2, 0x16, 0xc7, 0x81, 0xd5, 0x6e, 0x4c, 0xe4, 0x7e, 0x33, 0x03, 0xfa, 0x73,
0x00, 0x7f, 0xf7, 0xb9,
],
[
0x49, 0xef, 0x23, 0xbe, 0x2a, 0xa4, 0xdb, 0xf2, 0x52, 0x06, 0xfe, 0x45, 0xc2, 0x0d,
0xd8, 0x88, 0x39, 0x5b, 0x25, 0x26, 0x39, 0x1a, 0x72, 0x49, 0x96, 0xa4, 0x41, 0x56,
0xbe, 0xac, 0x80, 0x82,
],
[
0x12, 0x85, 0x87, 0x92, 0xbf, 0x8e, 0x74, 0xcb, 0xa4, 0x9d, 0xee, 0x5e, 0x88, 0x12,
0xe0, 0x19, 0xda, 0x87, 0x45, 0x4b, 0xff, 0x9e, 0x84, 0x7e, 0xd8, 0x3d, 0xb0, 0x7a,
0xf3, 0x13, 0x74, 0x30,
],
[
0x82, 0xf8, 0x80, 0xa2, 0x78, 0xf6, 0x82, 0xc2, 0xbd, 0x0a, 0xd6, 0x88, 0x7c, 0xb5,
0x9f, 0x65, 0x2e, 0x15, 0x59, 0x87, 0xd6, 0x1b, 0xbf, 0x6a, 0x88, 0xd3, 0x6e, 0xe9,
0x3b, 0x60, 0x72, 0xe6,
],
[
0x65, 0x6d, 0x9c, 0xcb, 0xaa, 0xe3, 0xd6, 0x55, 0x85, 0x2e, 0x38, 0xde, 0xb3, 0xa2,
0xdc, 0xf8, 0x05, 0x8d, 0xc9, 0xfb, 0x6f, 0x2a, 0xb3, 0xd3, 0xb3, 0x53, 0x9e, 0xb7,
0x7b, 0x24, 0x8a, 0x66,
],
[
0x10, 0x91, 0xd0, 0x5e, 0xb6, 0xe2, 0xf2, 0x97, 0x77, 0x4f, 0xe6, 0x05, 0x35, 0x98,
0x45, 0x7c, 0xc6, 0x19, 0x08, 0x31, 0x8d, 0xe4, 0xb8, 0x26, 0xf0, 0xfc, 0x86, 0xd4,
0xbb, 0x11, 0x7d, 0x33,
],
[
0xe8, 0x65, 0xaa, 0x80, 0x50, 0x09, 0xcc, 0x29, 0x18, 0xd9, 0xc2, 0xf8, 0x40, 0xc4,
0xda, 0x43, 0xa7, 0x03, 0xad, 0x9f, 0x5b, 0x58, 0x06, 0x16, 0x3d, 0x71, 0x61, 0x69,
0x6b, 0x5a, 0x0a, 0xdc,
],
];
// test value decoding
let sig = sig.unwrap();
assert_eq!(sig.c.as_slice(), &ec);
assert_eq!(sig.y[0].as_slice(), &ey[0]);
assert_eq!(sig.y[1].as_slice(), &ey[1]);
assert_eq!(sig.y[2].as_slice(), &ey[2]);
assert_eq!(sig.y[3].as_slice(), &ey[3]);
assert_eq!(sig.y[4].as_slice(), &ey[4]);
assert_eq!(sig.y[5].as_slice(), &ey[5]);
assert_eq!(sig.y[6].as_slice(), &ey[6]);
assert_eq!(sig.y[7].as_slice(), &ey[7]);
assert_eq!(sig.y[8].as_slice(), &ey[8]);
assert_eq!(sig.y[9].as_slice(), &ey[9]);
assert_eq!(sig.y[10].as_slice(), &ey[10]);
assert_eq!(sig.y[11].as_slice(), &ey[11]);
assert_eq!(sig.y[12].as_slice(), &ey[12]);
assert_eq!(sig.y[13].as_slice(), &ey[13]);
assert_eq!(sig.y[14].as_slice(), &ey[14]);
assert_eq!(sig.y[15].as_slice(), &ey[15]);
assert_eq!(sig.y[16].as_slice(), &ey[16]);
assert_eq!(sig.y[17].as_slice(), &ey[17]);
assert_eq!(sig.y[18].as_slice(), &ey[18]);
assert_eq!(sig.y[19].as_slice(), &ey[19]);
assert_eq!(sig.y[20].as_slice(), &ey[20]);
assert_eq!(sig.y[21].as_slice(), &ey[21]);
assert_eq!(sig.y[22].as_slice(), &ey[22]);
assert_eq!(sig.y[23].as_slice(), &ey[23]);
assert_eq!(sig.y[24].as_slice(), &ey[24]);
assert_eq!(sig.y[25].as_slice(), &ey[25]);
assert_eq!(sig.y[26].as_slice(), &ey[26]);
assert_eq!(sig.y[27].as_slice(), &ey[27]);
assert_eq!(sig.y[28].as_slice(), &ey[28]);
assert_eq!(sig.y[29].as_slice(), &ey[29]);
assert_eq!(sig.y[30].as_slice(), &ey[30]);
assert_eq!(sig.y[31].as_slice(), &ey[31]);
assert_eq!(sig.y[32].as_slice(), &ey[32]);
assert_eq!(sig.y[33].as_slice(), &ey[33]);
}
}
+56
View File
@@ -0,0 +1,56 @@
use std::iter::IntoIterator;
/// Returns an iterator over the w-bit Winternitz coefficients of the inout bytes
/// Implements the Coef function from section 3.1.3 of RFC8554
/// https://datatracker.ietf.org/doc/html/rfc8554#section-3.1.3
pub(crate) fn coefs<'a>(
bytes: impl IntoIterator<Item = &'a u8>,
w: usize,
) -> impl Iterator<Item = u8> {
let mask: u8 = match w {
1 => 0x01,
2 => 0x03,
4 => 0x0f,
8 => 0xff,
_ => panic!("invalid bit width: {}", w),
};
let entries_per_byte: usize = 8 / w;
bytes
.into_iter()
.cloned()
.flat_map(move |byte| (0..entries_per_byte).map(move |i| (byte >> (8 - w - i * w)) & mask))
}
#[cfg(test)]
mod tests {
use crate::ots::util::coefs;
#[test]
fn coef_test_w1() {
let s = [0x12, 0x34];
let cs = coefs(&s, 1).collect::<Vec<_>>();
assert_eq!(cs, vec![0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0]);
}
#[test]
fn coef_test_w2() {
let s = [0x12, 0x34];
let cs: Vec<u8> = coefs(&s, 2).collect::<Vec<_>>();
assert_eq!(cs, vec![0, 1, 0, 2, 0, 3, 1, 0]);
}
#[test]
fn coef_test_w4() {
let s = [0x12, 0x34];
let cs: Vec<u8> = coefs(&s, 4).collect::<Vec<_>>();
assert_eq!(cs, vec![1, 2, 3, 4]);
}
#[test]
fn coef_test_w8() {
let s = [0x12, 0x34];
let cs: Vec<u8> = coefs(&s, 8).collect::<Vec<_>>();
assert_eq!(cs, vec![0x12, 0x34]);
}
}
+13
View File
@@ -0,0 +1,13 @@
//! All types related to LMS
use crate::constants::ID_LEN;
/// Anything that has a corresponding `lmots_algorithm_type` or
/// `lms_algorithm_type` will implement this trait.
pub trait Typecode {
/// The associated enum value for the algorithm type.
const TYPECODE: u32;
}
/// The 16 byte identifier I from the LM-OTS algorithm.
pub type Identifier = [u8; ID_LEN];