mirror of
https://github.com/RustCrypto/signatures
synced 2026-06-21 13:45:42 +00:00
lms: import into RustCrypto/signatures (#801)
Leighton-Micali Hash-Based Signatures Co-authored-by: Tjaden Hess <tjade273@gmail.com>
This commit is contained in:
@@ -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
|
||||
@@ -5,6 +5,7 @@ members = [
|
||||
"ecdsa",
|
||||
"ed448",
|
||||
"ed25519",
|
||||
"lms",
|
||||
"rfc6979"
|
||||
]
|
||||
|
||||
|
||||
@@ -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).
|
||||
Generated
+225
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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];
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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 {}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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>>();
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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>>();
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -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>>();
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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[..]);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
Reference in New Issue
Block a user