Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"e841835a9f77b7aecb92146d176ebd6846fe806b7d82ffddb2fb98ebcffd79f5","CHANGELOG.md":"2bb5415886f2cc2dcd54b64486758ebf98581e57ab928de80a830902146f3159","Cargo.lock":"22a24745516fe76c1c8fa79de8e9eb363d32e6275f763285d6d1cbd0a3fa5fe1","Cargo.toml":"aafad7883015f7b4daf7e2ba40416a0176663c47167ca1a132614db11dcb8639","Cargo.toml.orig":"25ed9e515750da59d1beeae3a71ec1dfa50640a98899bdb30377d7d02d7f9e08","LICENSE-APACHE":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","LICENSE-MIT":"91585c36e4fb9ab4ca0d3dfac5d66d3c0c62cc51f640a0e1196542daf2267eae","README.md":"e018081981623bce603050b320ea085ceb0adaf121791c3bba3e4fcb30dd7a8a","src/choice.rs":"cb0cdfa8c7154c0a646945abf7784475f48291f378fd12a8227e2cc05a3cff88","src/ct_option.rs":"f47576d494f4b0d1073416b00bccae7c9d6bed83d6b17d8f641db8fb8006e9d1","src/lib.rs":"f87435c79405bc1e8cca99394613d297498759b13b36fbace31366adb938f1a9","src/traits.rs":"12955969208f4ab71f9ed07d84763a3a9915cbcc5544fcf8e947dcb7b8982a25","src/traits/ct_assign.rs":"547c3456c64f429087923c1c8be12a059d623de82be60b6c749254d97a17dcb3","src/traits/ct_eq.rs":"dfd4ea492d3f34909fe3028e12055c432ba2ba8e2f5377bc6fdf87a378f32238","src/traits/ct_find.rs":"90278cf8778db2e8c3f294c6f87e368450711ce815e55ead5d460fe7cd350e87","src/traits/ct_gt.rs":"195c554f035d0f40c12283ec3fdb3e1b3fb10e43df675aba538b187bf7d6a8a3","src/traits/ct_lookup.rs":"e412cefd054ae4a9c2980ddc937a45331ead46fc917cef1fa7aa886496e17611","src/traits/ct_lt.rs":"f5ce691b68c3ce996a9700bb67a766aa7f228e4d253da6a7cee32fb8caf891ab","src/traits/ct_neg.rs":"d22f138b33455971e1a70faf3a25ad67fd43e1c9cac5a5ab8d8ed75e295bef2f","src/traits/ct_select.rs":"e0832740578f42dcd6d0234dd6cd022620e9c70f180014291e1de534f78500a8","tests/proptests.rs":"682b21f0d9ab787cc07651a22721cf0c41df617f8615d577dcc6bf260fc55876"},"package":"7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "53f7fc3fa806e6d4e9650675e7b97d3621cff340"
},
"path_in_vcs": "ctutils"
}
+157
View File
@@ -0,0 +1,157 @@
# 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).
## 0.4.2 (2026-04-02)
### Fixed
- `cmov` v0.5.3 version requirement ([#1467])
[#1467]: https://github.com/RustCrypto/utils/pull/1467
## 0.4.1 (2026-04-02)
### Added
- `isize`/`usize` and `NonZero*` trait impls ([#1416])
- `subtle` migration guide ([#1465])
[#1416]: https://github.com/RustCrypto/utils/pull/1416
[#1465]: https://github.com/RustCrypto/utils/pull/1465
## 0.4.0 (2026-01-19)
### Added
- `core::num::NonZero<T>` support ([#1368])
- Pluggable trait impls for `[T]` and `[T; N]` using helper traits: ([#1388])
- `CtAssign`: `CtAssignSlice`
- `CtEq`: `CtEqSlice`
- `CtSelect`: `CtSelectArray`
- `CtSelectUsingCtAssign` marker trait ([#1391])
### Changed
- Split `CtAssign` out of `CtSelect` ([#1363])
- Bump `cmov` to v0.5 ([#1386])
### Removed
- `BytesCtEq`/`BytesCtSelect` no longer needed because default `[u8]` impls are fast ([#1376])
- `target_pointer_width` gating ([#1389])
- `unsafe` code ([#1405])
[#1363]: https://github.com/RustCrypto/utils/pull/1363
[#1368]: https://github.com/RustCrypto/utils/pull/1368
[#1376]: https://github.com/RustCrypto/utils/pull/1376
[#1386]: https://github.com/RustCrypto/utils/pull/1386
[#1388]: https://github.com/RustCrypto/utils/pull/1388
[#1389]: https://github.com/RustCrypto/utils/pull/1389
[#1391]: https://github.com/RustCrypto/utils/pull/1391
[#1405]: https://github.com/RustCrypto/utils/pull/1405
## 0.3.2 (2026-01-16)
### Added
- `BytesCtEq` and `BytesCtSelect` traits ([#1359])
- `CtFind` trait ([#1361])
- `CtLookup` trait ([#1362])
### Changed
- Bump `cmov` crate dependency to v0.5.0-pre.0 ([#1357])
[#1357]: https://github.com/RustCrypto/utils/pull/1357
[#1359]: https://github.com/RustCrypto/utils/pull/1359
[#1361]: https://github.com/RustCrypto/utils/pull/1361
[#1362]: https://github.com/RustCrypto/utils/pull/1362
## 0.3.1 (2026-01-03)
### Added
- `Choice::to_u8_mask`/`to_u16_mask` ([#1322])
- `Choice::select_u8`/`select_u16` ([#1324])
[#1322]: https://github.com/RustCrypto/utils/pull/1322
[#1324]: https://github.com/RustCrypto/utils/pull/1324
## 0.3.0 (2025-12-29)
### Removed
- `Choice::new` ([#1314])
- `(Partial)Eq` impls for `Choice` ([#1315])
[#1314]: https://github.com/RustCrypto/utils/pull/1314
[#1315]: https://github.com/RustCrypto/utils/pull/1315
## 0.2.3 (2025-12-29)
### Added
- Impl `From<u8>` for `Choice` ([#1309])
- `Choice::from_u8*` and `from_u16*` ([#1311])
### Changed
- Deprecate `Choice::new` ([#1312])
[#1309]: https://github.com/RustCrypto/utils/pull/1309
[#1311]: https://github.com/RustCrypto/utils/pull/1311
[#1312]: https://github.com/RustCrypto/utils/pull/1312
## 0.2.2 (2025-12-28)
### Added
- Unsigned `CtNeg` impls ([#1306])
[#1306]: https://github.com/RustCrypto/utils/pull/1306
## 0.2.1 (2025-12-27)
### Added
- Enhanced `subtle` interop ([#1289])
### Security
- Pin to `cmov` v0.4.3+ - includes important security fixes ([#1304])
[#1289]: https://github.com/RustCrypto/utils/pull/1304
[#1304]: https://github.com/RustCrypto/utils/pull/1304
## 0.2.0 (2025-12-27)
### Added
- Additional `Choice::from_u128*` constructors ([#1285])
- `CtNeg` trait ([#1286])
### Changed
- Renamed `Choice::from_*_nonzero` => `from_*_nz` ([#1287])
[#1285]: https://github.com/RustCrypto/utils/pull/1285
[#1286]: https://github.com/RustCrypto/utils/pull/1286
[#1287]: https://github.com/RustCrypto/utils/pull/1287
## 0.1.4 (2025-12-26)
### Added
- Impl `CtEq`/`CtSelect` for `isize` ([#1283])
[#1283]: https://github.com/RustCrypto/utils/pull/1283
## 0.1.3 (2025-12-26)
### Fixed
- Rustdoc syntax for variable-time-related warning text ([#1278])
[#1278]: https://github.com/RustCrypto/utils/pull/1278
## 0.1.2 (2025-12-26)
### Added
- Additional methods for `CtOption` ([#1274]):
- `some`
- `none`
- `into_option_copied`
- `filter_by`
- `as_inner_unchecked`
- `to_inner_unchecked`
- `Default` impl for `CtOption` ([#1274])
- `map!` and `unwrap_or!` macros ([#1274])
- `u128` methods for `Choice` ([#1277]):
- `from_u128_le`
- `from_u128_lsb`
- `select_u128`
[#1274]: https://github.com/RustCrypto/utils/pull/1274
[#1277]: https://github.com/RustCrypto/utils/pull/1277
## 0.1.1 (2025-12-26)
### Added
- Additional `const fn` constructor and predication methods for `Choice` ([#1266], [#1272])
[#1266]: https://github.com/RustCrypto/utils/pull/1266
[#1272]: https://github.com/RustCrypto/utils/pull/1272
## 0.1.0 (2025-12-19)
- Initial release
+635
View File
@@ -0,0 +1,635 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anyhow"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cmov"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746"
[[package]]
name = "ctutils"
version = "0.4.2"
dependencies = [
"cmov",
"proptest",
"subtle",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
]
[[package]]
name = "getrandom"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
"wasip3",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
[[package]]
name = "itoa"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "proptest"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
dependencies = [
"bit-set",
"bit-vec",
"bitflags",
"num-traits",
"rand",
"rand_chacha",
"rand_xorshift",
"regex-syntax",
"rusty-fork",
"tempfile",
"unarray",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
[[package]]
name = "rand_xorshift"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
dependencies = [
"rand_core",
]
[[package]]
name = "regex-syntax"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "rusty-fork"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
dependencies = [
"fnv",
"quick-error",
"tempfile",
"wait-timeout",
]
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
dependencies = [
"fastrand",
"getrandom 0.4.1",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "unarray"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "wait-timeout"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
dependencies = [
"libc",
]
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen 0.46.0",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen 0.51.0",
]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "zerocopy"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+107
View File
@@ -0,0 +1,107 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
rust-version = "1.85"
name = "ctutils"
version = "0.4.2"
authors = ["RustCrypto Developers"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = """
Constant-time utility library with selection and equality testing support targeting cryptographic
applications. Supports `const fn` where appropriate. Built on the `cmov` crate which provides
architecture-specific predication intrinsics. Heavily inspired by the `subtle` crate.
"""
homepage = "https://github.com/RustCrypto/utils/tree/master/ctselect"
readme = "README.md"
keywords = [
"constant-time",
"crypto",
"intrinsics",
]
categories = [
"cryptography",
"no-std",
]
license = "Apache-2.0 OR MIT"
repository = "https://github.com/RustCrypto/utils"
[package.metadata.docs.rs]
all-features = true
[features]
alloc = []
subtle = ["dep:subtle"]
[lib]
name = "ctutils"
path = "src/lib.rs"
[[test]]
name = "proptests"
path = "tests/proptests.rs"
[dependencies.cmov]
version = "0.5.3"
[dependencies.subtle]
version = "2"
optional = true
default-features = false
[dev-dependencies.proptest]
version = "1.11"
[lints.clippy]
borrow_as_ptr = "warn"
cast_lossless = "warn"
cast_possible_truncation = "warn"
cast_possible_wrap = "warn"
cast_precision_loss = "warn"
cast_sign_loss = "warn"
checked_conversions = "warn"
doc_markdown = "warn"
from_iter_instead_of_collect = "warn"
implicit_saturating_sub = "warn"
manual_assert = "warn"
map_unwrap_or = "warn"
missing_errors_doc = "warn"
missing_panics_doc = "warn"
mod_module_files = "warn"
must_use_candidate = "warn"
needless_range_loop = "allow"
ptr_as_ptr = "warn"
redundant_closure_for_method_calls = "warn"
ref_as_ptr = "warn"
return_self_not_must_use = "warn"
semicolon_if_nothing_returned = "warn"
std_instead_of_alloc = "warn"
std_instead_of_core = "warn"
trivially_copy_pass_by_ref = "warn"
undocumented_unsafe_blocks = "warn"
unnecessary_safety_comment = "warn"
unwrap_in_result = "warn"
unwrap_used = "warn"
[lints.rust]
missing_copy_implementations = "warn"
missing_debug_implementations = "warn"
missing_docs = "warn"
trivial_casts = "warn"
trivial_numeric_casts = "warn"
unused_lifetimes = "warn"
unused_qualifications = "warn"
+36
View File
@@ -0,0 +1,36 @@
[package]
name = "ctutils"
description = """
Constant-time utility library with selection and equality testing support targeting cryptographic
applications. Supports `const fn` where appropriate. Built on the `cmov` crate which provides
architecture-specific predication intrinsics. Heavily inspired by the `subtle` crate.
"""
version = "0.4.2"
authors = ["RustCrypto Developers"]
license = "Apache-2.0 OR MIT"
homepage = "https://github.com/RustCrypto/utils/tree/master/ctselect"
repository = "https://github.com/RustCrypto/utils"
categories = ["cryptography", "no-std"]
keywords = ["constant-time", "crypto", "intrinsics"]
readme = "README.md"
edition = "2024"
rust-version = "1.85"
[dependencies]
cmov = "0.5.3"
# optional dependencies
subtle = { version = "2", optional = true, default-features = false }
[dev-dependencies]
proptest = "1.11"
[features]
alloc = []
subtle = ["dep:subtle"]
[lints]
workspace = true
[package.metadata.docs.rs]
all-features = true
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2025-2026 The RustCrypto Project Developers
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+93
View File
@@ -0,0 +1,93 @@
# [RustCrypto]: Constant-Time Utilities
[![Crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link]
[![Build Status][build-image]][build-link]
![Apache 2.0/MIT Licensed][license-image]
![MSRV][msrv-image]
[![Project Chat][chat-image]][chat-link]
Constant-time utility library with selection and equality testing support targeting cryptographic
applications. Supports `const fn` where appropriate. Built on the [`cmov`] crate which provides
architecture-specific predication intrinsics. Heavily inspired by the [`subtle`] crate.
## About
This crate contains constant-time equivalents of the `bool` and `Option` types (`Choice` and
`CtOption` respectively), along with traits that can be used in combination with them.
The `CtOption` type notably provides eagerly evaluated combinator methods (as opposed to the lazily
evaluated combinators on `Option`) which make it possible to write constant-time code using
an idiomatic Rust style.
This is an experimental next-generation constant-time library inspired by `subtle`, but for now we
recommend you continue to stick with `subtle`. We may attempt to get some of the changes in this
library incorporated into `subtle` for a potential v3.0.
## What makes this crate different from `subtle`?
- Pervasive `const fn` support
- Almost all constructors/methods on `Choice` are `const fn`
- `Choice` can be constructed using various `const fn` predicates on integer types, enabling
writing constant-time `const fn` logic
- `CtOption` supports `const fn` constructors and `*_copied` methods to access the inner value
when it's a `Copy` type
- Macros to act as `CtOption` pseudo-combinators: `map!` and `unwrap_or!`
- Expanded selection of `CtOption` combinators that more closely mirrors `std::option::Option`
- Guaranteed constant-time equality testing and conditional selection on `x86(_64)` and `aarch64`
using `asm!` implementations in the `cmov` crate which call special constant-time CPU instructions
with a portable "best effort" fallback on other platforms using bitwise arithmetic and `black_box`
- No `Copy` bounds, which means all functionality can work with heap-allocated types in addition to
stack-allocated
- Expanded selection of traits: `CtFind` and `CtLookup` for arrays and slices
Many features of this crate are extractions from the [`crypto-bigint`] crate, where we implement all
core logic as `const fn` and needed solutions for implementing constant-time code despite the
unique constraints it imposes.
## ⚠️ Security Warning
The implementation contained in this crate has never been independently audited!
USE AT YOUR OWN RISK!
## Minimum Supported Rust Version (MSRV) Policy
MSRV increases are not considered breaking changes and can happen in patch releases.
The crate MSRV accounts for all supported targets and crate feature combinations.
## License
Licensed under either of:
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
* [MIT license](http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
[//]: # (badges)
[crate-image]: https://img.shields.io/crates/v/ctutils.svg
[crate-link]: https://crates.io/crates/ctutils
[docs-image]: https://docs.rs/ctutils/badge.svg
[docs-link]: https://docs.rs/ctutils/
[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
[msrv-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg
[build-image]: https://github.com/RustCrypto/utils/actions/workflows/ctutils.yml/badge.svg
[build-link]: https://github.com/RustCrypto/utils/actions/workflows/ctutils.yml
[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils
[//]: # (links)
[RustCrypto]: https://github.com/RustCrypto
[`cmov`]: https://docs.rs/cmov
[`subtle`]: https://docs.rs/subtle
[`crypto-bigint`]: https://docs.rs/crypto-bigint
+961
View File
@@ -0,0 +1,961 @@
use crate::{CtAssign, CtAssignSlice, CtEq, CtEqSlice, CtSelectUsingCtAssign};
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};
#[cfg(feature = "subtle")]
use crate::CtSelect;
/// Bitwise less-than-or equal: returns `1` if `x <= y`, and otherwise returns `0`.
///
/// See "Hacker's Delight" 2nd edition, section 2-12 (Comparison predicates)
macro_rules! bitle {
($x:expr, $y:expr, $bits:expr) => {
(((!$x) | $y) & (($x ^ $y) | !($y.wrapping_sub($x)))) >> ($bits - 1)
};
}
/// Bitwise less-than: returns `1` if `x < y`, and otherwise returns `0`.
///
/// See "Hacker's Delight" 2nd edition, section 2-12 (Comparison predicates)
macro_rules! bitlt {
($x:expr, $y:expr, $bits:expr) => {
(((!$x) & $y) | (((!$x) | $y) & $x.wrapping_sub($y))) >> ($bits - 1)
};
}
/// Bitwise non-zero: returns `1` if `x != 0`, and otherwise returns `0`.
macro_rules! bitnz {
($value:expr, $bits:expr) => {
($value | $value.wrapping_neg()) >> ($bits - 1)
};
}
/// Constant-time analogue of `bool` providing a "best effort" optimization barrier.
///
/// This type attempts to hint to the compiler and its codegen backends that optimizations should
/// not be applied which depend on specific values of this type.
///
/// This is used as a "belt-and-suspenders" defense in addition to mechanisms like
/// constant-time predication intrinsics provided by the [`cmov`] crate, and is never expected to be
/// the only line of defense.
// NOTE: we deliberately do NOT impl `Eq`, `Hash`, `PartialEq`, etc. See #1315
#[derive(Copy, Clone, Debug)]
pub struct Choice(pub(crate) u8);
impl Choice {
/// Equivalent of [`false`].
pub const FALSE: Self = Self(0);
/// Equivalent of [`true`].
pub const TRUE: Self = Self(1);
//
// `const fn` bitwise ops
//
/// Apply an `and` conditional to the given [`Choice`]s.
#[inline]
#[must_use]
pub const fn and(self, rhs: Choice) -> Choice {
Self(self.0 & rhs.0)
}
/// Apply an `or` conditional to the given [`Choice`]s.
#[inline]
#[must_use]
pub const fn or(self, rhs: Choice) -> Choice {
Self(self.0 | rhs.0)
}
/// Apply an `xor` conditional to the given [`Choice`]s.
#[inline]
#[must_use]
pub const fn xor(self, rhs: Choice) -> Choice {
Self(self.0 ^ rhs.0)
}
/// Compute the boolean inverse of `self`.
#[inline]
#[must_use]
pub const fn not(self) -> Choice {
// NOTE: assumes self.0 is `0` or `1` as checked in constructor
Self(self.0 ^ 1)
}
//
// `const fn` comparison ops
//
/// `const fn` equality operation.
#[inline]
#[must_use]
pub const fn eq(self, other: Self) -> Self {
Self::ne(self, other).not()
}
/// `const fn` not equal operation.
#[inline]
#[must_use]
pub const fn ne(self, other: Self) -> Self {
Self::xor(self, other)
}
//
// `const fn` constructor methods
//
// i64
/// Returns [`Choice::TRUE`] if `x == y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
#[allow(clippy::cast_sign_loss)]
pub const fn from_i64_eq(x: i64, y: i64) -> Self {
// TODO(tarcieri): use `cast_unsigned` when MSRV is 1.87
Self::from_u64_nz(x as u64 ^ y as u64).not()
}
// u8
/// Returns [`Choice::TRUE`] if `x == y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u8_eq(x: u8, y: u8) -> Self {
Self::from_u8_nz(x ^ y).not()
}
/// Returns [`Choice::TRUE`] if `x <= y` and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u8_le(x: u8, y: u8) -> Self {
Self::from_u8_lsb(bitle!(x, y, u8::BITS))
}
/// Initialize from the least significant bit of a `u8`.
#[inline]
#[must_use]
pub const fn from_u8_lsb(value: u8) -> Self {
Self(value & 0x1)
}
/// Returns [`Choice::TRUE`] if `x < y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u8_lt(x: u8, y: u8) -> Self {
Self::from_u8_lsb(bitlt!(x, y, u8::BITS))
}
/// Returns [`Choice::TRUE`] if `value != 0`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u8_nz(value: u8) -> Self {
Self::from_u8_lsb(bitnz!(value, u8::BITS))
}
// u16
/// Returns [`Choice::TRUE`] if `x == y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u16_eq(x: u16, y: u16) -> Self {
Self::from_u16_nz(x ^ y).not()
}
/// Returns [`Choice::TRUE`] if `x <= y` and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u16_le(x: u16, y: u16) -> Self {
Self::from_u16_lsb(bitle!(x, y, u16::BITS))
}
/// Initialize from the least significant bit of a `u16`.
#[inline]
#[must_use]
pub const fn from_u16_lsb(value: u16) -> Self {
Self((value & 0x1) as u8)
}
/// Returns [`Choice::TRUE`] if `x < y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u16_lt(x: u16, y: u16) -> Self {
Self::from_u16_lsb(bitlt!(x, y, u16::BITS))
}
/// Returns [`Choice::TRUE`] if `value != 0`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u16_nz(value: u16) -> Self {
Self::from_u16_lsb(bitnz!(value, u16::BITS))
}
// u32
/// Returns [`Choice::TRUE`] if `x == y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u32_eq(x: u32, y: u32) -> Self {
Self::from_u32_nz(x ^ y).not()
}
/// Returns [`Choice::TRUE`] if `x <= y` and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u32_le(x: u32, y: u32) -> Self {
Self::from_u32_lsb(bitle!(x, y, u32::BITS))
}
/// Initialize from the least significant bit of a `u32`.
#[inline]
#[must_use]
pub const fn from_u32_lsb(value: u32) -> Self {
Self((value & 0x1) as u8)
}
/// Returns [`Choice::TRUE`] if `x < y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u32_lt(x: u32, y: u32) -> Self {
Self::from_u32_lsb(bitlt!(x, y, u32::BITS))
}
/// Returns [`Choice::TRUE`] if `value != 0`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u32_nz(value: u32) -> Self {
Self::from_u32_lsb(bitnz!(value, u32::BITS))
}
// u64
/// Returns [`Choice::TRUE`] if `x == y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u64_eq(x: u64, y: u64) -> Self {
Self::from_u64_nz(x ^ y).not()
}
/// Returns [`Choice::TRUE`] if `x <= y` and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u64_le(x: u64, y: u64) -> Self {
Self::from_u64_lsb(bitle!(x, y, u64::BITS))
}
/// Initialize from the least significant bit of a `u64`.
#[inline]
#[must_use]
pub const fn from_u64_lsb(value: u64) -> Self {
Self((value & 0x1) as u8)
}
/// Returns [`Choice::TRUE`] if `x < y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u64_lt(x: u64, y: u64) -> Self {
Self::from_u64_lsb(bitlt!(x, y, u64::BITS))
}
/// Returns [`Choice::TRUE`] if `value != 0`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u64_nz(value: u64) -> Self {
Self::from_u64_lsb(bitnz!(value, u64::BITS))
}
// u128
/// Returns [`Choice::TRUE`] if `x == y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u128_eq(x: u128, y: u128) -> Self {
Self::from_u128_nz(x ^ y).not()
}
/// Returns [`Choice::TRUE`] if `x <= y` and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u128_le(x: u128, y: u128) -> Self {
Self::from_u128_lsb(bitle!(x, y, u128::BITS))
}
/// Initialize from the least significant bit of a `u128`.
#[inline]
#[must_use]
pub const fn from_u128_lsb(value: u128) -> Self {
Self((value & 1) as u8)
}
/// Returns [`Choice::TRUE`] if `x < y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u128_lt(x: u128, y: u128) -> Self {
Self::from_u128_lsb(bitlt!(x, y, u128::BITS))
}
/// Returns [`Choice::TRUE`] if `value != 0`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u128_nz(value: u128) -> Self {
Self::from_u128_lsb(bitnz!(value, u128::BITS))
}
//
// `const fn` predication methods
//
/// `const fn` helper: return `b` if `self` is [`Choice::TRUE`], otherwise return `a`.
///
/// Only use this instead of the [`CtSelect`] trait in the event you're in a `const fn` context
/// and can't use the trait. The former will provide better constant-time assurances.
#[inline]
#[must_use]
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
pub const fn select_i64(self, a: i64, b: i64) -> i64 {
// TODO(tarcieri): use `cast_signed` when MSRV is 1.87
self.select_u64(a as u64, b as u64) as i64
}
/// `const fn` helper: return `b` if `self` is [`Choice::TRUE`], otherwise return `a`.
///
/// Only use this instead of the [`CtSelect`] trait in the event you're in a `const fn` context
/// and can't use the trait. The former will provide better constant-time assurances.
#[inline]
#[must_use]
pub const fn select_u8(self, a: u8, b: u8) -> u8 {
a ^ (self.to_u8_mask() & (a ^ b))
}
/// `const fn` helper: return `b` if `self` is [`Choice::TRUE`], otherwise return `a`.
///
/// Only use this instead of the [`CtSelect`] trait in the event you're in a `const fn` context
/// and can't use the trait. The former will provide better constant-time assurances.
#[inline]
#[must_use]
pub const fn select_u16(self, a: u16, b: u16) -> u16 {
a ^ (self.to_u16_mask() & (a ^ b))
}
/// `const fn` helper: return `b` if `self` is [`Choice::TRUE`], otherwise return `a`.
///
/// Only use this instead of the [`CtSelect`] trait in the event you're in a `const fn` context
/// and can't use the trait. The former will provide better constant-time assurances.
#[inline]
#[must_use]
pub const fn select_u32(self, a: u32, b: u32) -> u32 {
a ^ (self.to_u32_mask() & (a ^ b))
}
/// `const fn` helper: return `b` if `self` is [`Choice::TRUE`], otherwise return `a`.
///
/// Only use this instead of the [`CtSelect`] trait in the event you're in a `const fn` context
/// and can't use the trait. The former will provide better constant-time assurances.
#[inline]
#[must_use]
pub const fn select_u64(self, a: u64, b: u64) -> u64 {
a ^ (self.to_u64_mask() & (a ^ b))
}
/// `const fn` helper: return `b` if `self` is [`Choice::TRUE`], otherwise return `a`.
///
/// Only use this instead of the [`CtSelect`] trait in the event you're in a `const fn` context
/// and can't use the trait. The former will provide better constant-time assurances.
#[inline]
#[must_use]
pub const fn select_u128(self, a: u128, b: u128) -> u128 {
a ^ (self.to_u128_mask() & (a ^ b))
}
//
// Output conversion methods
//
/// Convert `Choice` into a `bool`.
///
/// <div class = "warning">
/// <b>Security Warning</b>
///
/// Using this function will introduce timing variability, since computing this at all currently
/// requires a branch.
///
/// This is intended to be used as either the one and only branch at the end of a constant-time
/// operation to e.g. differentiate between success and failure, or in contexts where
/// constant-time doesn't matter, e.g. variable-time code that operates on "maybe secret" types
/// which aren't secrets in a particular context.
///
/// If you are trying to use this in the context of a constant-time operation, be warned that
/// the small amount of timing variability it introduces can potentially be exploited. Whenever
/// possible, prefer fully constant-time approaches instead.
/// </div>
// TODO(tarcieri): `const fn` when MSRV 1.86
#[must_use]
pub fn to_bool(self) -> bool {
self.to_u8() != 0
}
/// Convert [`Choice`] to a `u8`, attempting to apply a "best effort" optimization barrier.
// TODO(tarcieri): `const fn` when MSRV 1.86
#[must_use]
pub fn to_u8(self) -> u8 {
// `black_box` is documented as working on a "best effort" basis. That's fine, this type is
// likewise documented as only working on a "best effort" basis itself. The only way we
// rely on `black_box` for correctness is it behaving as the identity function.
core::hint::black_box(self.0)
}
/// HACK: workaround to allow `const fn` boolean support on Rust 1.85.
///
/// This does not apply `black_box` to the output.
///
/// <div class = "warning">
/// <b>Security Warning</b>
///
/// See the security warnings for [`Choice::to_bool`].
/// </div>
// TODO(tarcieri): deprecate/remove this in favor of `to_bool` when MSRV is Rust 1.86
#[must_use]
pub const fn to_bool_vartime(self) -> bool {
self.0 != 0
}
/// HACK: workaround to allow `const fn` boolean support on Rust 1.85.
///
/// This does not apply `black_box` to the output.
// TODO(tarcieri): deprecate/remove this in favor of `to_u8` when MSRV is Rust 1.86
#[must_use]
pub const fn to_u8_vartime(self) -> u8 {
self.0
}
/// Create a `u8` bitmask.
///
/// # Returns
/// - `0` for `Choice::FALSE`
/// - `u8::MAX` for `Choice::TRUE`
#[inline]
#[must_use]
pub const fn to_u8_mask(self) -> u8 {
self.0.wrapping_neg()
}
/// Create a `u16` bitmask.
///
/// # Returns
/// - `0` for `Choice::FALSE`
/// - `u16::MAX` for `Choice::TRUE`
#[inline]
#[must_use]
pub const fn to_u16_mask(self) -> u16 {
(self.0 as u16).wrapping_neg()
}
/// Create a `u32` bitmask.
///
/// # Returns
/// - `0` for `Choice::FALSE`
/// - `u32::MAX` for `Choice::TRUE`
#[inline]
#[must_use]
pub const fn to_u32_mask(self) -> u32 {
(self.0 as u32).wrapping_neg()
}
/// Create a `u64` bitmask.
///
/// # Returns
/// - `0` for `Choice::FALSE`
/// - `u64::MAX` for `Choice::TRUE`
#[inline]
#[must_use]
pub const fn to_u64_mask(self) -> u64 {
(self.0 as u64).wrapping_neg()
}
/// Create a `u128` bitmask.
///
/// # Returns
/// - `0` for `Choice::FALSE`
/// - `u128::MAX` for `Choice::TRUE`
#[inline]
#[must_use]
pub const fn to_u128_mask(self) -> u128 {
(self.0 as u128).wrapping_neg()
}
}
impl BitAnd for Choice {
type Output = Choice;
#[inline]
fn bitand(self, rhs: Choice) -> Choice {
self.and(rhs)
}
}
impl BitAndAssign for Choice {
#[inline]
fn bitand_assign(&mut self, rhs: Choice) {
*self = *self & rhs;
}
}
impl BitOr for Choice {
type Output = Choice;
#[inline]
fn bitor(self, rhs: Choice) -> Choice {
self.or(rhs)
}
}
impl BitOrAssign for Choice {
#[inline]
fn bitor_assign(&mut self, rhs: Choice) {
*self = *self | rhs;
}
}
impl BitXor for Choice {
type Output = Choice;
#[inline]
fn bitxor(self, rhs: Choice) -> Choice {
Choice(self.0 ^ rhs.0)
}
}
impl BitXorAssign for Choice {
#[inline]
fn bitxor_assign(&mut self, rhs: Choice) {
*self = *self ^ rhs;
}
}
impl CtAssign for Choice {
#[inline]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
self.0.ct_assign(&other.0, choice);
}
}
impl CtAssignSlice for Choice {}
impl CtSelectUsingCtAssign for Choice {}
impl CtEq for Choice {
#[inline]
fn ct_eq(&self, other: &Self) -> Self {
self.0.ct_eq(&other.0)
}
}
impl CtEqSlice for Choice {}
/// DEPRECATED: this exists to aid migrating code from `subtle`. Use `Choice::from_u8_lsb` instead.
///
/// <div class="warning">
/// <b>Note</b>
///
/// Rust doesn't actually let us deprecate an impl block, however this comment is here to
/// discourage future use and warn that this will be removed in a future release.
/// </div>
impl From<u8> for Choice {
fn from(value: u8) -> Self {
Choice::from_u8_lsb(value)
}
}
impl From<Choice> for u8 {
fn from(choice: Choice) -> u8 {
choice.to_u8()
}
}
/// Convert `Choice` into a `bool`.
///
/// <div class = "warning">
/// <b>Security Warning</b>
///
/// Using this function will introduce timing variability, since computing this at all currently
/// requires a branch.
///
/// See the security warnings for [`Choice::to_bool`].
/// </div>
impl From<Choice> for bool {
fn from(choice: Choice) -> bool {
choice.to_bool()
}
}
impl Not for Choice {
type Output = Choice;
#[inline]
fn not(self) -> Choice {
self.not()
}
}
#[cfg(feature = "subtle")]
impl From<subtle::Choice> for Choice {
#[inline]
fn from(choice: subtle::Choice) -> Choice {
Choice(choice.unwrap_u8())
}
}
#[cfg(feature = "subtle")]
impl From<Choice> for subtle::Choice {
#[inline]
fn from(choice: Choice) -> subtle::Choice {
subtle::Choice::from(choice.0)
}
}
#[cfg(feature = "subtle")]
impl subtle::ConditionallySelectable for Choice {
#[inline]
fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
CtSelect::ct_select(a, b, choice.into())
}
}
#[cfg(feature = "subtle")]
impl subtle::ConstantTimeEq for Choice {
#[inline]
fn ct_eq(&self, other: &Self) -> subtle::Choice {
CtEq::ct_eq(self, other).into()
}
}
#[cfg(test)]
mod tests {
use super::Choice;
use crate::{CtEq, CtSelect};
#[test]
fn ct_eq() {
let a = Choice::TRUE;
let b = Choice::TRUE;
let c = Choice::FALSE;
assert!(a.ct_eq(&b).to_bool());
assert!(!a.ct_eq(&c).to_bool());
assert!(!b.ct_eq(&c).to_bool());
assert!(!a.ct_ne(&b).to_bool());
assert!(a.ct_ne(&c).to_bool());
assert!(b.ct_ne(&c).to_bool());
}
#[test]
fn ct_select() {
let a = Choice::FALSE;
let b = Choice::TRUE;
assert_eq!(a.ct_select(&b, Choice::FALSE).to_bool(), a.to_bool());
assert_eq!(a.ct_select(&b, Choice::TRUE).to_bool(), b.to_bool());
}
#[test]
fn and() {
assert_eq!((Choice::FALSE & Choice::FALSE).to_u8(), 0);
assert_eq!((Choice::TRUE & Choice::FALSE).to_u8(), 0);
assert_eq!((Choice::FALSE & Choice::TRUE).to_u8(), 0);
assert_eq!((Choice::TRUE & Choice::TRUE).to_u8(), 1);
}
#[test]
fn or() {
assert_eq!((Choice::FALSE | Choice::FALSE).to_u8(), 0);
assert_eq!((Choice::TRUE | Choice::FALSE).to_u8(), 1);
assert_eq!((Choice::FALSE | Choice::TRUE).to_u8(), 1);
assert_eq!((Choice::TRUE | Choice::TRUE).to_u8(), 1);
}
#[test]
fn xor() {
assert_eq!((Choice::FALSE ^ Choice::FALSE).to_u8(), 0);
assert_eq!((Choice::TRUE ^ Choice::FALSE).to_u8(), 1);
assert_eq!((Choice::FALSE ^ Choice::TRUE).to_u8(), 1);
assert_eq!((Choice::TRUE ^ Choice::TRUE).to_u8(), 0);
}
#[test]
fn not() {
assert_eq!(Choice::FALSE.not().to_u8(), 1);
assert_eq!(Choice::TRUE.not().to_u8(), 0);
}
#[test]
fn from_i64_eq() {
assert!(Choice::from_i64_eq(0, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_i64_eq(1, 1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u8_eq() {
assert!(Choice::from_u8_eq(0, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_eq(1, 1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u8_le() {
assert!(Choice::from_u8_le(0, 0).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u8_le(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_le(1, 1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u8_le(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u8_lsb() {
assert!(Choice::from_u8_lsb(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_lsb(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u8_lsb(2).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_lsb(3).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u8_lt() {
assert!(Choice::from_u8_lt(0, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_lt(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_lt(1, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_lt(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u8_nz() {
assert!(Choice::from_u8_nz(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_nz(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u8_nz(2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u16_eq() {
assert!(Choice::from_u16_eq(0, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_eq(1, 1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u16_le() {
assert!(Choice::from_u16_le(0, 0).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u16_le(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_le(1, 1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u16_le(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u16_lsb() {
assert!(Choice::from_u16_lsb(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_lsb(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u16_lsb(2).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_lsb(3).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u16_lt() {
assert!(Choice::from_u16_lt(0, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_lt(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_lt(1, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_lt(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u16_nz() {
assert!(Choice::from_u16_nz(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_nz(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u16_nz(2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u32_eq() {
assert!(Choice::from_u32_eq(0, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_eq(1, 1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u32_le() {
assert!(Choice::from_u32_le(0, 0).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u32_le(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_le(1, 1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u32_le(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u32_lsb() {
assert!(Choice::from_u32_lsb(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_lsb(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u32_lsb(2).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_lsb(3).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u32_lt() {
assert!(Choice::from_u32_lt(0, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_lt(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_lt(1, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_lt(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u32_nz() {
assert!(Choice::from_u32_nz(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_nz(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u32_nz(2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u64_eq() {
assert!(Choice::from_u64_eq(0, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_eq(1, 1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u64_le() {
assert!(Choice::from_u64_le(0, 0).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u64_le(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_le(1, 1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u64_le(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u64_lsb() {
assert!(Choice::from_u64_lsb(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_lsb(1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u64_lt() {
assert!(Choice::from_u64_lt(0, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_lt(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_lt(1, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_lt(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u64_nz() {
assert!(Choice::from_u64_nz(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_nz(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u64_nz(2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u128_eq() {
assert!(Choice::from_u128_eq(0, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_eq(1, 1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u128_le() {
assert!(Choice::from_u128_le(0, 0).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u128_le(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_le(1, 1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u128_le(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u128_lsb() {
assert!(Choice::from_u128_lsb(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_lsb(1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u128_lt() {
assert!(Choice::from_u128_lt(0, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_lt(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_lt(1, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_lt(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u128_nz() {
assert!(Choice::from_u128_nz(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_nz(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u128_nz(2).eq(Choice::TRUE).to_bool());
}
#[test]
fn select_i64() {
let a: i64 = 1;
let b: i64 = 2;
assert_eq!(Choice::TRUE.select_i64(a, b), b);
assert_eq!(Choice::FALSE.select_i64(a, b), a);
}
#[test]
fn select_u8() {
let a: u8 = 1;
let b: u8 = 2;
assert_eq!(Choice::TRUE.select_u8(a, b), b);
assert_eq!(Choice::FALSE.select_u8(a, b), a);
}
#[test]
fn select_u16() {
let a: u16 = 1;
let b: u16 = 2;
assert_eq!(Choice::TRUE.select_u16(a, b), b);
assert_eq!(Choice::FALSE.select_u16(a, b), a);
}
#[test]
fn select_u32() {
let a: u32 = 1;
let b: u32 = 2;
assert_eq!(Choice::TRUE.select_u32(a, b), b);
assert_eq!(Choice::FALSE.select_u32(a, b), a);
}
#[test]
fn select_u64() {
let a: u64 = 1;
let b: u64 = 2;
assert_eq!(Choice::TRUE.select_u64(a, b), b);
assert_eq!(Choice::FALSE.select_u64(a, b), a);
}
#[test]
fn select_u128() {
let a: u128 = 1;
let b: u128 = 2;
assert_eq!(Choice::TRUE.select_u128(a, b), b);
assert_eq!(Choice::FALSE.select_u128(a, b), a);
}
#[test]
fn to_bool() {
assert!(!Choice::FALSE.to_bool());
assert!(Choice::TRUE.to_bool());
}
#[test]
fn to_u8() {
assert_eq!(Choice::FALSE.to_u8(), 0);
assert_eq!(Choice::TRUE.to_u8(), 1);
}
#[test]
fn to_u8_mask() {
assert_eq!(Choice::FALSE.to_u8_mask(), 0);
assert_eq!(Choice::TRUE.to_u8_mask(), u8::MAX);
}
#[test]
fn to_u16_mask() {
assert_eq!(Choice::FALSE.to_u16_mask(), 0);
assert_eq!(Choice::TRUE.to_u16_mask(), u16::MAX);
}
#[test]
fn to_u32_mask() {
assert_eq!(Choice::FALSE.to_u32_mask(), 0);
assert_eq!(Choice::TRUE.to_u32_mask(), u32::MAX);
}
#[test]
fn to_u64_mask() {
assert_eq!(Choice::FALSE.to_u64_mask(), 0);
assert_eq!(Choice::TRUE.to_u64_mask(), u64::MAX);
}
#[test]
fn to_u128_mask() {
assert_eq!(Choice::FALSE.to_u128_mask(), 0);
assert_eq!(Choice::TRUE.to_u128_mask(), u128::MAX);
}
}
+918
View File
@@ -0,0 +1,918 @@
use crate::{Choice, CtAssign, CtAssignSlice, CtEq, CtEqSlice, CtSelect};
use core::ops::{Deref, DerefMut};
/// Helper macro for providing behavior like the [`CtOption::map`] combinator that works in
/// `const fn` contexts.
///
/// Requires a provided `$mapper` function to convert from one type to another, e.g.
///
/// ```ignore
/// const fn mapper(value: T) -> U
/// ```
#[macro_export]
macro_rules! map {
($opt:expr, $mapper:path) => {{ $crate::CtOption::new($mapper($opt.to_inner_unchecked()), $opt.is_some()) }};
}
/// Helper macro for providing behavior like the [`CtOption::unwrap_or`] combinator that works in
/// `const fn` contexts.
///
/// Requires a provided selector function `$select` to perform constant-time selection which takes
/// two `T` values by reference along with a [`Choice`], returning the first `T` for
/// [`Choice::FALSE`], and the second for [`Choice::TRUE`], e.g.:
///
/// ```ignore
/// const fn ct_select(a: &T, b: &T, condition: Choice) -> T
/// ```
#[macro_export]
macro_rules! unwrap_or {
($opt:expr, $default:expr, $select:path) => {
$select(&$default, $opt.as_inner_unchecked(), $opt.is_some())
};
}
/// Equivalent of [`Option`] but predicated on a [`Choice`] with combinators that allow for
/// constant-time operations which always perform the same sequence of instructions regardless of
/// the value of `is_some`.
///
/// Unlike [`Option`], [`CtOption`] always contains a value, and will use the contained value when
/// e.g. evaluating the callbacks of combinator methods, which unlike `core` it does unconditionally
/// in order to ensure constant-time operation. This approach stands in contrast to the lazy
/// evaluation similar methods on [`Option`] provide.
#[derive(Clone, Copy, Debug)]
pub struct CtOption<T> {
value: T,
is_some: Choice,
}
impl<T> CtOption<T> {
/// Construct a new [`CtOption`], with a [`Choice`] parameter `is_some` as a stand-in for
/// `Some` or `None` enum variants of a typical [`Option`] type.
#[inline]
#[must_use]
pub const fn new(value: T, is_some: Choice) -> CtOption<T> {
Self { value, is_some }
}
/// Construct a new [`CtOption`] where `self.is_some()` is [`Choice::TRUE`].
#[inline]
#[must_use]
pub const fn some(value: T) -> CtOption<T> {
Self::new(value, Choice::TRUE)
}
/// Construct a new [`CtOption`] with the [`Default`] value, and where `self.is_some()` is
/// [`Choice::FALSE`].
#[inline]
#[must_use]
pub fn none() -> CtOption<T>
where
T: Default,
{
Self::new(Default::default(), Choice::FALSE)
}
/// Convert from a `&mut CtOption<T>` to `CtOption<&mut T>`.
#[inline]
#[must_use]
pub const fn as_mut(&mut self) -> CtOption<&mut T> {
CtOption {
value: &mut self.value,
is_some: self.is_some,
}
}
/// Convert from a `&CtOption<T>` to `CtOption<&T>`.
#[inline]
#[must_use]
pub const fn as_ref(&self) -> CtOption<&T> {
CtOption {
value: &self.value,
is_some: self.is_some,
}
}
/// Convert from `CtOption<T>` (or `&CtOption<T>`) to `CtOption<&T::Target>`, for types which
/// impl the [`Deref`] trait.
#[inline]
#[must_use]
pub fn as_deref(&self) -> CtOption<&T::Target>
where
T: Deref,
{
self.as_ref().map(Deref::deref)
}
/// Convert from `CtOption<T>` (or `&mut CtOption<T>`) to `CtOption<&mut T::Target>`, for types
/// which impl the [`DerefMut`] trait.
#[inline]
#[must_use]
pub fn as_deref_mut(&mut self) -> CtOption<&mut T::Target>
where
T: DerefMut,
{
self.as_mut().map(DerefMut::deref_mut)
}
/// Return the contained value, consuming the `self` value.
///
/// # Panics
/// In the event `self.is_some()` is [`Choice::FALSE`], panics with a custom panic message
/// provided as the `msg` argument.
#[inline]
#[must_use]
#[track_caller]
pub fn expect(self, msg: &str) -> T {
assert!(self.is_some().to_bool(), "{}", msg);
self.value
}
/// Return the contained value, consuming the `self` value, with `const fn` support.
///
/// Relies on a `Copy` bound which implies `!Drop` which is needed to be able to move out of
/// `self` in a `const fn` without `feature(const_precise_live_drops)`.
///
/// # Panics
/// In the event `self.is_some()` is [`Choice::FALSE`], panics with a custom panic message
/// provided as the `msg` argument.
// TODO(tarcieri): get rid of this when we can make `expect` a `const fn`
// (needs `const_precise_live_drops`)
#[inline]
#[must_use]
#[track_caller]
pub const fn expect_copied(self, msg: &str) -> T
where
T: Copy,
{
*self.expect_ref(msg)
}
/// Borrow the contained value.
///
/// # Panics
/// In the event `self.is_some()` is [`Choice::FALSE`], panics with a custom panic message
/// provided as the `msg` argument.
// TODO(tarcieri): get rid of this when we can make `expect` a `const fn`
// (needs `const_precise_live_drops`)
#[inline]
#[must_use]
#[track_caller]
pub const fn expect_ref(&self, msg: &str) -> &T {
// TODO(tarcieri): use `self.is_some().to_bool()` when MSRV is 1.86
assert!(self.is_some.to_bool_vartime(), "{}", msg);
self.as_inner_unchecked()
}
/// Inserts `value` into the [`CtOption`], then returns a mutable reference to it.
///
/// If the option already contains a value, the old value is dropped.
pub fn insert(&mut self, value: T) -> &mut T {
self.value = value;
self.is_some = Choice::TRUE;
&mut self.value
}
/// Conditionally inserts `value` into the [`CtOption`] if the given condition holds.
pub fn insert_if(&mut self, value: &T, condition: Choice)
where
T: CtAssign,
{
self.value.ct_assign(value, condition);
self.is_some.ct_assign(&Choice::TRUE, condition);
}
/// Convert the [`CtOption`] wrapper into an [`Option`], depending on whether
/// [`CtOption::is_some`] is a truthy or falsy [`Choice`].
///
/// This function exists to avoid ending up with ugly, verbose and/or bad handled conversions
/// from the [`CtOption`] wraps to an [`Option`] or [`Result`].
///
/// It's equivalent to the corresponding [`From`] impl, however this version is friendlier for
/// type inference.
///
/// <div class="warning">
/// <b>Warning: variable-time!</b>
///
/// This implementation doesn't intend to be constant-time nor try to protect the leakage of the
/// `T` value since the [`Option`] will do it anyway.
/// </div>
#[inline]
pub fn into_option(self) -> Option<T> {
if self.is_some.to_bool() {
Some(self.value)
} else {
None
}
}
/// Convert the [`CtOption`] wrapper into an [`Option`] in a `const fn`-friendly manner.
///
/// This is the equivalent of [`CtOption::into_option`] but is `const fn`-friendly by only
/// allowing `Copy` types which are implicitly `!Drop` and don't run into problems with
/// `const fn` and destructors.
///
/// <div class="warning">
/// <b>Warning: variable-time!</b>
///
/// This implementation doesn't intend to be constant-time nor try to protect the leakage of the
/// `T` value since the [`Option`] will do it anyway.
/// </div>
#[inline]
pub const fn into_option_copied(self) -> Option<T>
where
T: Copy,
{
// TODO(tarcieri): use `self.is_some().to_bool()` when MSRV is 1.86
if self.is_some.to_bool_vartime() {
Some(self.value)
} else {
None
}
}
/// Returns [`Choice::TRUE`] if the option is the equivalent of a `Some`.
#[inline]
#[must_use]
pub const fn is_some(&self) -> Choice {
self.is_some
}
/// Returns [`Choice::TRUE`] if the option is the equivalent of a `None`.
#[inline]
#[must_use]
pub const fn is_none(&self) -> Choice {
self.is_some.not()
}
/// Returns `optb` if `self.is_some()` is [`Choice::TRUE`], otherwise returns a [`CtOption`]
/// where `self.is_some()` is [`Choice::FALSE`].
#[inline]
#[must_use]
pub fn and<U>(self, mut optb: CtOption<U>) -> CtOption<U> {
optb.is_some &= self.is_some;
optb
}
/// Calls the provided callback with the wrapped inner value, returning the resulting
/// [`CtOption`] value in the event that `self.is_some()` is [`Choice::TRUE`], or if not
/// returns a [`CtOption`] with `self.is_none()`.
///
/// Unlike [`Option`], the provided callback `f` is unconditionally evaluated to ensure
/// constant-time operation. This requires evaluating the function with "dummy" value of `T`
/// (e.g. if the [`CtOption`] was constructed with a supplied placeholder value and
/// [`Choice::FALSE`], the placeholder value will be provided).
#[inline]
#[must_use]
pub fn and_then<U, F>(self, f: F) -> CtOption<U>
where
F: FnOnce(T) -> CtOption<U>,
{
let mut ret = f(self.value);
ret.is_some &= self.is_some;
ret
}
/// Obtain a reference to the inner value without first checking that `self.is_some()` is
/// [`Choice::TRUE`].
///
/// This method is primarily intended for use in `const fn` scenarios where it's not yet
/// possible to use the safe combinator methods, and returns a reference to avoid issues with
/// `const fn` destructors.
///
/// <div class="warning">
/// <b>Use with care!</b>
///
/// This method does not ensure the `value` is actually valid. Callers of this method should
/// take great care to ensure that `self.is_some()` is checked elsewhere.
/// </div>
#[inline]
#[must_use]
pub const fn as_inner_unchecked(&self) -> &T {
&self.value
}
/// Calls the provided callback with the wrapped inner value, which computes a [`Choice`],
/// and updates `self.is_some()`.
///
/// It updates it to be [`Choice::FALSE`] in the event the returned choice is also false.
/// If it was [`Choice::FALSE`] to begin with, it will unconditionally remain that way.
#[inline]
#[must_use]
pub fn filter<P>(mut self, predicate: P) -> Self
where
P: FnOnce(&T) -> Choice,
{
self.is_some &= predicate(&self.value);
self
}
/// Apply an additional [`Choice`] requirement to `is_some`.
#[inline]
#[must_use]
pub const fn filter_by(mut self, is_some: Choice) -> Self {
self.is_some = self.is_some.and(is_some);
self
}
/// Maps a `CtOption<T>` to a `CtOption<U>` by unconditionally applying a function to the
/// contained `value`, but returning a new option value which inherits `self.is_some()`.
#[inline]
#[must_use]
pub fn map<U, F>(self, f: F) -> CtOption<U>
where
F: FnOnce(T) -> U,
{
CtOption::new(f(self.value), self.is_some)
}
/// Maps a `CtOption<T>` to a `U` value, eagerly evaluating the provided function, and returning
/// the supplied `default` in the event `self.is_some()` is [`Choice::FALSE`].
#[inline]
#[must_use = "if you don't need the returned value, use `if let` instead"]
pub fn map_or<U, F>(self, default: U, f: F) -> U
where
U: CtSelect,
F: FnOnce(T) -> U,
{
self.map(f).unwrap_or(default)
}
/// Maps a `CtOption<T>` to a `U` value, eagerly evaluating the provided function, precomputing
/// `U::default()` using the [`Default`] trait, and returning it in the event `self.is_some()`
/// is [`Choice::FALSE`].
#[inline]
#[must_use]
pub fn map_or_default<U, F>(self, f: F) -> U
where
U: CtSelect + Default,
F: FnOnce(T) -> U,
{
self.map_or(U::default(), f)
}
/// Transforms a `CtOption<T>` into a `Result<T, E>`, mapping to `Ok(T)` if `self.is_some()` is
/// [`Choice::TRUE`], or mapping to the provided `err` in the event `self.is_some()` is
/// [`Choice::FALSE`].
///
/// <div class="warning">
/// <b>Warning: variable-time!</b>
///
/// This implementation doesn't intend to be constant-time nor try to protect the leakage of the
/// `T` value since the [`Result`] will do it anyway.
/// </div>
///
/// # Errors
/// - Returns `err` in the event `self.is_some()` is [`Choice::FALSE`].
#[inline]
pub fn ok_or<E>(self, err: E) -> Result<T, E> {
self.into_option().ok_or(err)
}
/// Transforms a `CtOption<T>` into a `Result<T, E>` by unconditionally calling the provided
/// callback value and using its result in the event `self.is_some()` is [`Choice::FALSE`].
///
/// <div class="warning">
/// <b>Warning: variable-time!</b>
///
/// This implementation doesn't intend to be constant-time nor try to protect the leakage of the
/// `T` value since the [`Result`] will do it anyway.
/// </div>
///
/// # Errors
/// - Returns `err` in the event `self.is_some()` is [`Choice::FALSE`].
#[inline]
#[allow(clippy::missing_errors_doc)]
pub fn ok_or_else<E, F>(self, err: F) -> Result<T, E>
where
F: FnOnce() -> E,
{
self.ok_or(err())
}
/// Returns `self` if `self.is_some()` is [`Choice::TRUE`], otherwise returns `optb`.
#[inline]
#[must_use]
pub fn or(self, optb: CtOption<T>) -> CtOption<T>
where
T: CtSelect,
{
CtOption {
value: self.value.ct_select(&optb.value, self.is_none()),
is_some: self.is_some | optb.is_some,
}
}
/// Obtain a copy of the inner value without first checking that `self.is_some()` is
/// [`Choice::TRUE`].
///
/// This method is primarily intended for use in `const fn` scenarios where it's not yet
/// possible to use the safe combinator methods, and uses a `Copy` bound to avoid issues with
/// `const fn` destructors.
///
/// <div class="warning">
/// <b>Use with care!</b>
///
/// This method does not ensure the `value` is actually valid. Callers of this method should
/// take great care to ensure that `self.is_some()` is checked elsewhere.
/// </div>
#[inline]
#[must_use]
pub const fn to_inner_unchecked(self) -> T
where
T: Copy,
{
self.value
}
/// Return the contained value, consuming the `self` value.
///
/// Use of this function is discouraged due to panic potential. Instead, prefer non-panicking
/// alternatives such as `unwrap_or` or `unwrap_or_default` which operate in constant-time.
///
/// As the final step of a sequence of constant-time operations, or in the event you are dealing
/// with a [`CtOption`] in a non-secret context where constant-time does not matter, you can
/// also convert to [`Option`] using `into_option` or the [`From`] impl on [`Option`]. Note
/// this introduces a branch and with it a small amount of timing variability. If possible try
/// to avoid this branch when writing constant-time code (e.g. use implicit rejection instead
/// of `Option`/`Result` to handle errors)
///
/// # Panics
/// In the event `self.is_some()` is [`Choice::FALSE`].
#[inline]
#[must_use]
#[track_caller]
pub fn unwrap(self) -> T {
assert!(
self.is_some.to_bool(),
"called `CtOption::unwrap()` on a value with `is_some` set to `Choice::FALSE`"
);
self.value
}
/// Return the contained value in the event `self.is_some()` is [`Choice::TRUE`], or if not,
/// uses a provided default.
#[inline]
#[must_use]
pub fn unwrap_or(self, default: T) -> T
where
T: CtSelect,
{
default.ct_select(&self.value, self.is_some)
}
/// Unconditionally computes `T::default()` using the [`Default`] trait, then returns either
/// the contained value if `self.is_some()` is [`Choice::TRUE`], or if it's [`Choice::FALSE`]
/// returns the previously computed default.
#[inline]
#[must_use]
pub fn unwrap_or_default(self) -> T
where
T: CtSelect + Default,
{
self.unwrap_or(T::default())
}
/// Returns an "is some" [`CtOption`] with the contained value from either `self` or `optb` in
/// the event exactly one of them has `self.is_some()` set to [`Choice::TRUE`], or else returns
/// a [`CtOption`] with `self.is_some()` set to [`Choice::FALSE`].
#[inline]
#[must_use]
pub fn xor(self, optb: CtOption<T>) -> CtOption<T>
where
T: CtSelect,
{
CtOption {
value: self.value.ct_select(&optb.value, self.is_none()),
is_some: self.is_some ^ optb.is_some,
}
}
/// Zips `self` with another [`CtOption`].
///
/// If `self.is_some() && other.is_some()`, this method returns a new [`CtOption`] for a 2-tuple
/// of their contents where `is_some()` is [`Choice::TRUE`].
///
/// Otherwise, a [`CtOption`] where `is_some()` is [`Choice::FALSE`] is returned.
pub fn zip<U>(self, other: CtOption<U>) -> CtOption<(T, U)> {
CtOption {
value: (self.value, other.value),
is_some: self.is_some & other.is_some,
}
}
/// Zips `self` and another `CtOption` with function `f`.
///
/// If `self.is_some() && other.is_some()`, this method returns a new [`CtOption`] for
/// the result of `f` applied to their inner values where `is_some()` is [`Choice::TRUE`].
///
/// Otherwise, a [`CtOption`] where `is_some()` is [`Choice::FALSE`] is returned.
pub fn zip_with<U, F, R>(self, other: CtOption<U>, f: F) -> CtOption<R>
where
F: FnOnce(T, U) -> R,
{
self.zip(other).map(|(a, b)| f(a, b))
}
}
impl<T> CtOption<&T> {
/// Maps a `CtOption<&T>` to `CtOption<T>` by copying the contents of the option.
#[must_use = "`self` will be dropped if the result is not used"]
pub const fn copied(self) -> CtOption<T>
where
T: Copy,
{
CtOption {
value: *self.value,
is_some: self.is_some,
}
}
/// Maps a `CtOption<&T>` to `CtOption<T>` by cloning the contents of the option.
#[must_use = "`self` will be dropped if the result is not used"]
pub fn cloned(self) -> CtOption<T>
where
T: Clone,
{
CtOption {
value: self.value.clone(),
is_some: self.is_some,
}
}
}
impl<T> CtOption<&mut T> {
/// Maps a `CtOption<&mut T>` to `CtOption<T>` by copying the contents of the option.
#[must_use = "`self` will be dropped if the result is not used"]
pub const fn copied(self) -> CtOption<T>
where
T: Copy,
{
CtOption {
value: *self.value,
is_some: self.is_some,
}
}
/// Maps a `CtOption<&mut T>` to `CtOption<T>` by cloning the contents of the option.
#[must_use = "`self` will be dropped if the result is not used"]
pub fn cloned(self) -> CtOption<T>
where
T: Clone,
{
CtOption {
value: self.value.clone(),
is_some: self.is_some,
}
}
}
impl<T: CtAssign> CtAssign for CtOption<T> {
fn ct_assign(&mut self, other: &Self, choice: Choice) {
self.value.ct_assign(&other.value, choice);
self.is_some.ct_assign(&other.is_some, choice);
}
}
impl<T: CtAssign> CtAssignSlice for CtOption<T> {}
impl<T: CtEq> CtEq for CtOption<T> {
#[inline]
fn ct_eq(&self, other: &CtOption<T>) -> Choice {
(self.is_some & other.is_some & self.value.ct_eq(&other.value))
| (self.is_none() & other.is_none())
}
}
impl<T: CtEq> CtEqSlice for CtOption<T> {}
impl<T: CtSelect> CtSelect for CtOption<T> {
fn ct_select(&self, other: &Self, choice: Choice) -> Self {
Self {
value: self.value.ct_select(&other.value, choice),
is_some: self.is_some.ct_select(&other.is_some, choice),
}
}
}
impl<T: Default> Default for CtOption<T> {
fn default() -> Self {
Self::none()
}
}
/// Convert the [`CtOption`] wrapper into an [`Option`], depending on whether
/// [`CtOption::is_some`] is a truthy or falsy [`Choice`].
///
/// <div class="warning">
/// <b>Warning: variable-time!</b>
///
/// This implementation doesn't intend to be constant-time nor try to protect the leakage of the
/// `T` value since the `Option` will do it anyway.
/// </div>
impl<T> From<CtOption<T>> for Option<T> {
fn from(src: CtOption<T>) -> Option<T> {
src.into_option()
}
}
/// NOTE: in order to be able to unwrap the `subtle::CtOption` we rely on a `Default` bound in
/// order to have a placeholder value, and `ConditionallySelectable` to be able to use `unwrap_or`.
#[cfg(feature = "subtle")]
impl<T> From<subtle::CtOption<T>> for CtOption<T>
where
T: subtle::ConditionallySelectable + Default,
{
#[inline]
fn from(src: subtle::CtOption<T>) -> CtOption<T> {
let is_some = src.is_some();
CtOption {
value: src.unwrap_or(Default::default()),
is_some: is_some.into(),
}
}
}
#[cfg(feature = "subtle")]
impl<T> From<CtOption<T>> for subtle::CtOption<T> {
#[inline]
fn from(src: CtOption<T>) -> subtle::CtOption<T> {
subtle::CtOption::new(src.value, src.is_some.into())
}
}
#[cfg(feature = "subtle")]
impl<T> subtle::ConditionallySelectable for CtOption<T>
where
T: Copy, // `ConditionallySelectable` supertrait bound
Self: CtSelect,
{
#[inline]
fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
CtSelect::ct_select(a, b, choice.into())
}
}
#[cfg(feature = "subtle")]
impl<T> subtle::ConstantTimeEq for CtOption<T>
where
Self: CtEq,
{
#[inline]
fn ct_eq(&self, other: &Self) -> subtle::Choice {
CtEq::ct_eq(self, other).into()
}
}
#[cfg(test)]
mod tests {
use crate::{Choice, CtEq, CtOption, CtSelect};
/// Example wrapped value for testing
const VALUE: u8 = 42;
/// Example option which is like `Option::Some`
const SOME: CtOption<u8> = CtOption::new(VALUE, Choice::TRUE);
/// Example option which is like `Option::None`
const NONE: CtOption<u8> = CtOption::new(VALUE, Choice::FALSE);
/// Another option containing a different value
const OTHER: CtOption<u8> = CtOption::new(VALUE + 1, Choice::TRUE);
/// Dummy error type
#[derive(Debug, Eq, PartialEq)]
struct Error;
#[test]
fn map_macro() {
assert!(map!(NONE, u16::from).is_none().to_bool());
assert_eq!(map!(SOME, u16::from).unwrap(), u16::from(VALUE));
}
#[test]
fn unwrap_or_macro() {
// Don't actually use this! It's just a test function implemented in variable-time
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn select_vartime(a: &u8, b: &u8, choice: Choice) -> u8 {
if choice.to_bool_vartime() { *b } else { *a }
}
assert_eq!(
unwrap_or!(NONE, OTHER.unwrap(), select_vartime),
OTHER.unwrap()
);
assert_eq!(unwrap_or!(SOME, OTHER.unwrap(), select_vartime), VALUE);
}
#[test]
fn ct_eq() {
assert!(NONE.ct_eq(&NONE).to_bool());
assert!(NONE.ct_ne(&SOME).to_bool());
assert!(SOME.ct_ne(&NONE).to_bool());
assert!(SOME.ct_eq(&SOME).to_bool());
assert!(SOME.ct_ne(&OTHER).to_bool());
}
#[test]
fn ct_select() {
assert!(NONE.ct_select(&SOME, Choice::FALSE).is_none().to_bool());
assert!(NONE.ct_select(&SOME, Choice::TRUE).ct_eq(&SOME).to_bool());
assert!(SOME.ct_select(&NONE, Choice::FALSE).ct_eq(&SOME).to_bool());
assert!(SOME.ct_select(&NONE, Choice::TRUE).is_none().to_bool());
}
#[test]
fn default() {
assert!(NONE.ct_eq(&CtOption::default()).to_bool());
}
#[test]
fn expect_some() {
assert_eq!(SOME.expect("should succeed"), VALUE);
}
#[test]
#[should_panic]
fn expect_none() {
let _ = NONE.expect("should panic");
}
#[test]
fn into_option() {
assert_eq!(SOME.into_option(), Some(VALUE));
assert_eq!(NONE.into_option(), None);
}
#[test]
fn into_option_copied() {
assert_eq!(SOME.into_option_copied(), Some(VALUE));
assert_eq!(NONE.into_option_copied(), None);
}
#[test]
fn is_some() {
assert!(SOME.is_some().to_bool());
assert!(!NONE.is_some().to_bool());
}
#[test]
fn is_none() {
assert!(!SOME.is_none().to_bool());
assert!(NONE.is_none().to_bool());
}
#[test]
fn and() {
assert!(SOME.and(NONE).is_none().to_bool());
assert_eq!(SOME.and(OTHER).unwrap(), OTHER.unwrap());
}
#[test]
fn and_then() {
assert!(NONE.and_then(|_| NONE).is_none().to_bool());
assert!(NONE.and_then(|_| SOME).is_none().to_bool());
let ret = SOME.and_then(|value| {
assert_eq!(VALUE, value);
OTHER
});
assert!(ret.ct_eq(&OTHER).to_bool());
}
#[test]
fn filter() {
assert!(NONE.filter(|_| Choice::TRUE).ct_eq(&NONE).to_bool());
assert!(NONE.filter(|_| Choice::FALSE).ct_eq(&NONE).to_bool());
assert!(SOME.filter(|_| Choice::FALSE).ct_eq(&NONE).to_bool());
let ret = SOME.filter(|&value| {
assert_eq!(VALUE, value);
Choice::TRUE
});
assert_eq!(ret.unwrap(), VALUE);
}
#[test]
fn filter_by() {
assert!(NONE.filter_by(Choice::FALSE).is_none().to_bool());
assert!(NONE.filter_by(Choice::TRUE).is_none().to_bool());
assert!(SOME.filter_by(Choice::FALSE).ct_eq(&NONE).to_bool());
assert_eq!(SOME.filter_by(Choice::TRUE).unwrap(), VALUE);
}
#[test]
fn insert() {
let mut example = NONE;
assert!(example.is_none().to_bool());
let ret = example.insert(42);
assert_eq!(ret, &42);
assert!(example.is_some().to_bool());
}
#[test]
fn insert_if() {
let mut example = NONE;
assert!(example.is_none().to_bool());
example.insert_if(&42, Choice::FALSE);
assert!(example.is_none().to_bool());
example.insert_if(&42, Choice::TRUE);
assert_eq!(example.unwrap(), 42);
}
#[test]
fn map() {
assert!(NONE.map(|value| value + 1).ct_eq(&NONE).to_bool());
assert!(SOME.map(|value| value + 1).ct_eq(&OTHER).to_bool());
}
#[test]
fn map_or() {
let example = 52;
assert_eq!(NONE.map_or(example, |value| value + 1), example);
assert_eq!(SOME.map_or(example, |value| value + 1), VALUE + 1);
}
#[test]
fn map_or_default() {
assert_eq!(NONE.map_or_default(|value| value + 1), Default::default());
assert_eq!(SOME.map_or_default(|value| value + 1), VALUE + 1);
}
#[test]
fn ok_or() {
assert_eq!(NONE.ok_or(Error), Err(Error));
assert_eq!(SOME.ok_or(Error), Ok(VALUE));
}
#[test]
fn ok_or_else() {
assert_eq!(NONE.ok_or_else(|| Error), Err(Error));
assert_eq!(SOME.ok_or_else(|| Error), Ok(VALUE));
}
#[test]
fn or() {
assert!(NONE.or(NONE).is_none().to_bool());
assert!(SOME.or(NONE).ct_eq(&SOME).to_bool());
assert!(NONE.or(SOME).ct_eq(&SOME).to_bool());
assert!(SOME.or(OTHER).ct_eq(&SOME).to_bool());
}
#[test]
fn some() {
assert!(CtOption::some(VALUE).ct_eq(&SOME).to_bool());
}
#[test]
fn unwrap_some() {
assert_eq!(SOME.unwrap(), VALUE);
}
#[test]
#[should_panic]
fn unwrap_none() {
let _ = NONE.unwrap();
}
#[test]
fn unwrap_or() {
let example = 52;
assert_eq!(NONE.unwrap_or(example), example);
assert_eq!(SOME.unwrap_or(example), VALUE);
}
#[test]
fn unwrap_or_default() {
assert_eq!(NONE.unwrap_or_default(), Default::default());
assert_eq!(SOME.unwrap_or_default(), VALUE);
}
#[test]
fn xor() {
assert!(NONE.xor(NONE).is_none().to_bool());
assert!(SOME.xor(NONE).ct_eq(&SOME).to_bool());
assert!(NONE.xor(SOME).ct_eq(&SOME).to_bool());
assert!(SOME.xor(OTHER).is_none().to_bool());
}
#[test]
fn zip() {
assert!(NONE.zip(NONE).is_none().to_bool());
assert!(NONE.zip(SOME).is_none().to_bool());
assert!(SOME.zip(NONE).is_none().to_bool());
assert_eq!(SOME.zip(OTHER).unwrap(), (SOME.unwrap(), OTHER.unwrap()));
}
#[test]
fn zip_with() {
assert!(NONE.zip_with(NONE, |a, b| a + b).is_none().to_bool());
assert!(NONE.zip_with(SOME, |a, b| a + b).is_none().to_bool());
assert!(SOME.zip_with(NONE, |a, b| a + b).is_none().to_bool());
assert_eq!(
SOME.zip_with(OTHER, |a, b| a + b).unwrap(),
SOME.unwrap() + OTHER.unwrap()
);
}
}
+140
View File
@@ -0,0 +1,140 @@
#![no_std]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
)]
#![forbid(unsafe_code)] // `unsafe` should go in `cmov`
#![warn(
clippy::arithmetic_side_effects,
clippy::integer_division_remainder_used,
clippy::panic
)]
//! # API Design
//!
//! ## [`Choice`]: constant-time analogue for [`bool`]
//! Values of this type are one of either [`Choice::FALSE`] or [`Choice::TRUE`].
//!
//! To achieve constant-time operation, `Choice` is ultimately used in combination with special
//! CPU-specific constant-time predication instructions implemented by the [`cmov`] crate
//! (with a portable "best effort" fallback that cannot provide guarantees).
//!
//! It additionally uses various methods to hint to the compiler that it should avoid inserting
//! branches based on its value where it otherwise would if `bool` were used instead, but cannot
//! provide guarantees in this regard.
//!
//! ## [`CtOption`]: constant-time analogue for [`Option`]
//! The core `Option` type is typically great for representing the conditional absence or presence
//! of a value, and provides a number of handy combinators for operating on them.
//!
//! However, it has a rather fundamental flaw when constant-time is desirable: its combinators are
//! lazily evaluated. To ensure constant-time operation, all combinators must be eagerly evaluated
//! so they aren't conditionally executed based on the value's presence.
//!
//! `CtOption` instead carries a `Choice` along with a value, which makes it possible to do
//! something it isn't with `Option`: evaluate combinators eagerly instead of lazily, running the
//! same functions regardless of the value's effective presence or absence.
//!
//! ## [`CtAssign`]: constant-time additional assignment using [predication]
//! Support for conditionally assigning to a type or slices thereof (for types which impl the
//! [`CtAssignSlice`] trait) based on a provided condition value.
//!
//! Uses predication instructions or a portable simulation thereof to perform constant-time
//! conditional assignment based ona [`Choice`].
//!
//! *NOTE: for `subtle` users, this trait provides the equivalent of the
//! `ConditionallySelectable::conditional_assign` method, but as its own trait without a `Sized`
//! bound so it can also be impl'd for slices*
//!
//! ## [`CtEq`]: constant-time analogue for [`PartialEq`]/[`Eq`]
//! Equality testing often short circuits for performance reasons, but when comparing values in
//! constant-time such short-circuiting is forbidden.
//!
//! The `CtEq` trait is a replacement for these scenarios. It's impl'd for several core types
//! including unsigned and signed integers as well as slices and arrays. It returns a `Choice`
//! as opposed to a `bool`], following the standard practice in this crate.
//!
//! *NOTE: for `subtle` users, this is the equivalent of the `ConstantTimeEq` trait*
//!
//! ## [`CtSelect`]: constant-time [predication]
//! Predication in computer architecture describes methods for conditionally modifying state
//! using non-branch instructions which perform conditional modifications based on a *predicate*
//! or boolean value, in the design of this library a `Choice`.
//!
//! The `CtSelect` trait provides methods for performing conditional selection between two
//! different inputs and returning a new one.
//!
//! *NOTE: for `subtle` users, this is the equivalent of the `ConditionallySelectable` trait*
//!
//! [predication]: https://en.wikipedia.org/wiki/Predication_(computer_architecture)
//!
//! # [`subtle`] interop
//!
//! When the `subtle` feature of this crate is enabled, bidirectional [`From`] impls are available
//! for the following types:
//!
//! - [`Choice`] <=> [`subtle::Choice`]
//! - [`CtOption`] <=> [`subtle::CtOption`]
//!
//! This makes it possible to use `ctutils` in a codebase where other dependencies are using
//! `subtle`.
//!
//! # [`subtle`] migration guide
//!
//! This library presents an API which is largely the same shape as `subtle` and amenable to mostly
//! mechanical find-and-replace updates. Using the above `subtle` interop, you can also migrate
//! incrementally by converting `ctutils::Choice` <=> `subtle::Choice` and `ctutils::CtOption`
//! <=> `subtle::CtOption`.
//!
//! The following substitutions can be used to perform the migration:
//!
//! 1. `subtle` => `ctutils`
//! 2. `ConstantTimeEq` => `CtEq`, `ConstantTimeGreater` => `CtGt`, `ConstantTimeLess` => `CtLt`.
//! - These all use the same `ct_eq`/`ct_gt`/`ct_lt` method names as `subtle` with the same type
//! signatures, so only the trait names need to be changed.
//! 3. `ConditionallySelectable` => `CtSelect`, `conditional_select` => `ct_select`.
//! - Note that `ct_select` has a slightly different type signature in that it accepts `&self`
//! as the LHS argument. This needs to be changed in the `impl` blocks, but call sites are
//! compatible if you update the method name alone because it's valid "fully qualified syntax".
//! Changing them from `T::conditional_select(&a, &b, choice)` => `a.ct_select(&b, choice)`
//! may still be nice for brevity.
//! - `conditional_assign` => `CtAssign::ct_assign`: this one will require some manual work as
//! this method has been split out of `ConditionallySelectable` into its own `CtAssign` trait,
//! which makes it possible to impl on DSTs like slices which can't be returned from a select
//! operation because they're `!Sized`.
//! 4. `ConditionallyNegatable` => `CtNeg`, `conditional_negate` => `ct_neg`
//!
//! ## `CtOption` notes
//!
//! A notable semantic change from `subtle` is combinators like `CtOption::map` no longer have a
//! `Default` bound and will call the provided function with the contained value unconditionally.
//!
//! This means whatever value was provided at the time the `CtOption` was constructed now needs to
//! uphold whatever invariants the provided function is expecting.
//!
//! Code which previously constructed a `CtOption` with an invalid inner value that worked with
//! `subtle` because the `Default` value upheld these invariants might break when the provided
//! function is now called with the invalid inner value.
//!
//! See also: [dalek-cryptography/subtle#63](https://github.com/dalek-cryptography/subtle/issues/63)
#[cfg(feature = "alloc")]
extern crate alloc;
mod choice;
mod ct_option;
mod traits;
pub use choice::Choice;
pub use ct_option::CtOption;
pub use traits::{
ct_assign::{CtAssign, CtAssignSlice},
ct_eq::{CtEq, CtEqSlice},
ct_find::CtFind,
ct_gt::CtGt,
ct_lookup::CtLookup,
ct_lt::CtLt,
ct_neg::CtNeg,
ct_select::{CtSelect, CtSelectArray, CtSelectUsingCtAssign},
};
+13
View File
@@ -0,0 +1,13 @@
//! Trait definitions.
//!
//! These are each in their own module so we can also define tests for the core types they're impl'd
//! on in the same module.
pub(crate) mod ct_assign;
pub(crate) mod ct_eq;
pub(crate) mod ct_find;
pub(crate) mod ct_gt;
pub(crate) mod ct_lookup;
pub(crate) mod ct_lt;
pub(crate) mod ct_neg;
pub(crate) mod ct_select;
+213
View File
@@ -0,0 +1,213 @@
use crate::Choice;
use cmov::Cmov;
use core::{
cmp,
num::{
NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
},
};
#[cfg(feature = "subtle")]
use crate::CtSelect;
#[cfg(doc)]
use core::num::NonZero;
/// Constant-time conditional assignment: assign a given value to another based on a [`Choice`].
///
/// This crate provides built-in implementations for the following types:
/// - [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`isize`]
/// - [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`]
/// - [`NonZeroI8`], [`NonZeroI16`], [`NonZeroI32`], [`NonZeroI64`], [`NonZeroI128`], [`NonZeroI128`]
/// - [`NonZeroU8`], [`NonZeroU16`], [`NonZeroU32`], [`NonZeroU64`], [`NonZeroU128`],, [`NonZeroUsize`]
/// - [`cmp::Ordering`]
/// - [`Choice`]
/// - `[T]` and `[T; N]` where `T` impls [`CtAssignSlice`], which the previously mentioned
/// types all do.
pub trait CtAssign<Rhs: ?Sized = Self> {
/// Conditionally assign `src` to `self` if `choice` is [`Choice::TRUE`].
fn ct_assign(&mut self, src: &Rhs, choice: Choice);
}
/// Implementing this trait enables use of the [`CtAssign`] trait for `[T]` where `T` is the
/// `Self` type implementing the trait, via a blanket impl.
///
/// It needs to be a separate trait from [`CtAssign`] because we need to be able to impl
/// [`CtAssign`] for `[T]` which is `?Sized`.
pub trait CtAssignSlice: CtAssign + Sized {
/// Conditionally assign `src` to `dst` if `choice` is [`Choice::TRUE`], or leave it unchanged
/// for [`Choice::FALSE`].
fn ct_assign_slice(dst: &mut [Self], src: &[Self], choice: Choice) {
assert_eq!(
dst.len(),
src.len(),
"source slice length ({}) does not match destination slice length ({})",
src.len(),
dst.len()
);
for (a, b) in dst.iter_mut().zip(src) {
a.ct_assign(b, choice);
}
}
}
impl<T: CtAssignSlice> CtAssign for [T] {
fn ct_assign(&mut self, src: &[T], choice: Choice) {
T::ct_assign_slice(self, src, choice);
}
}
/// Impl `CtAssign` using the `cmov::Cmov` trait
macro_rules! impl_ct_assign_with_cmov {
( $($ty:ty),+ ) => {
$(
impl CtAssign for $ty {
#[inline]
fn ct_assign(&mut self, rhs: &Self, choice: Choice) {
self.cmovnz(rhs, choice.into());
}
}
)+
};
}
/// Impl `CtAssign` and `CtAssignSlice` using the `cmov::Cmov` trait
macro_rules! impl_ct_assign_slice_with_cmov {
( $($ty:ty),+ ) => {
$(
impl_ct_assign_with_cmov!($ty);
impl CtAssignSlice for $ty {
#[inline]
fn ct_assign_slice(dst: &mut [Self], src: &[Self], choice: Choice) {
dst.cmovnz(src, choice.into());
}
}
)+
};
}
// NOTE: impls `CtAssign` and `CtAssignSlice`
impl_ct_assign_slice_with_cmov!(
i8,
i16,
i32,
i64,
i128,
u8,
u16,
u32,
u64,
u128,
NonZeroI8,
NonZeroI16,
NonZeroI32,
NonZeroI64,
NonZeroI128,
NonZeroIsize,
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroUsize,
cmp::Ordering
);
impl_ct_assign_with_cmov!(isize, usize);
impl CtAssignSlice for isize {}
impl CtAssignSlice for usize {}
impl<T, const N: usize> CtAssign for [T; N]
where
T: CtAssignSlice,
{
#[inline]
fn ct_assign(&mut self, rhs: &Self, choice: Choice) {
self.as_mut_slice().ct_assign(rhs, choice);
}
}
impl<T, const N: usize> CtAssignSlice for [T; N] where T: CtAssignSlice {}
#[cfg(feature = "subtle")]
impl CtAssign for subtle::Choice {
#[inline]
fn ct_assign(&mut self, rhs: &Self, choice: Choice) {
*self = Self::ct_select(self, rhs, choice);
}
}
#[cfg(feature = "subtle")]
impl<T> CtAssign for subtle::CtOption<T>
where
T: Default + subtle::ConditionallySelectable,
{
#[inline]
fn ct_assign(&mut self, rhs: &Self, choice: Choice) {
use subtle::ConditionallySelectable as _;
self.conditional_assign(rhs, choice.into());
}
}
#[cfg(feature = "alloc")]
mod alloc {
use super::{Choice, CtAssign, CtAssignSlice};
use ::alloc::{boxed::Box, vec::Vec};
impl<T> CtAssign for Box<T>
where
T: CtAssign,
{
#[inline]
#[track_caller]
fn ct_assign(&mut self, rhs: &Self, choice: Choice) {
(**self).ct_assign(rhs, choice);
}
}
impl<T> CtAssign for Box<[T]>
where
T: CtAssignSlice,
{
#[inline]
#[track_caller]
fn ct_assign(&mut self, rhs: &Self, choice: Choice) {
self.ct_assign(&**rhs, choice);
}
}
impl<T> CtAssign<[T]> for Box<[T]>
where
T: CtAssignSlice,
{
#[inline]
#[track_caller]
fn ct_assign(&mut self, rhs: &[T], choice: Choice) {
(**self).ct_assign(rhs, choice);
}
}
impl<T> CtAssign for Vec<T>
where
T: CtAssignSlice,
{
#[inline]
#[track_caller]
fn ct_assign(&mut self, rhs: &Self, choice: Choice) {
self.ct_assign(rhs.as_slice(), choice);
}
}
impl<T> CtAssign<[T]> for Vec<T>
where
T: CtAssignSlice,
{
#[inline]
#[track_caller]
fn ct_assign(&mut self, rhs: &[T], choice: Choice) {
self.as_mut_slice().ct_assign(rhs, choice);
}
}
}
+327
View File
@@ -0,0 +1,327 @@
use crate::Choice;
use cmov::CmovEq;
use core::{
cmp,
num::{
NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroU8, NonZeroU16,
NonZeroU32, NonZeroU64, NonZeroU128,
},
};
#[cfg(feature = "subtle")]
use crate::CtOption;
/// Constant-time equality: like `(Partial)Eq` with [`Choice`] instead of [`bool`].
///
/// Impl'd for: [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`], [`cmp::Ordering`],
/// [`Choice`], and arrays/slices of any type which also impls [`CtEq`].
///
/// This crate provides built-in implementations for the following types:
/// - [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`isize`]
/// - [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`]
/// - [`NonZeroI8`], [`NonZeroI16`], [`NonZeroI32`], [`NonZeroI64`], [`NonZeroI128`]
/// - [`NonZeroU8`], [`NonZeroU16`], [`NonZeroU32`], [`NonZeroU64`], [`NonZeroU128`]
/// - [`cmp::Ordering`]
/// - [`Choice`]
/// - `[T]` and `[T; N]` where `T` impls [`CtEqSlice`], which the previously mentioned types all do.
pub trait CtEq<Rhs = Self>
where
Rhs: ?Sized,
{
/// Determine if `self` is equal to `other` in constant-time.
#[must_use]
fn ct_eq(&self, other: &Rhs) -> Choice;
/// Determine if `self` is NOT equal to `other` in constant-time.
#[must_use]
fn ct_ne(&self, other: &Rhs) -> Choice {
!self.ct_eq(other)
}
}
/// Implementing this trait enables use of the [`CtEq`] trait for `[T]` where `T` is the
/// `Self` type implementing the trait, via a blanket impl.
///
/// It needs to be a separate trait from [`CtEq`] because we need to be able to impl
/// [`CtEq`] for `[T]` which is `?Sized`.
pub trait CtEqSlice: CtEq + Sized {
/// Determine if `a` is equal to `b` in constant-time.
#[must_use]
fn ct_eq_slice(a: &[Self], b: &[Self]) -> Choice {
let mut ret = a.len().ct_eq(&b.len());
for (a, b) in a.iter().zip(b.iter()) {
ret &= a.ct_eq(b);
}
ret
}
/// Determine if `a` is NOT equal to `b` in constant-time.
#[must_use]
fn ct_ne_slice(a: &[Self], b: &[Self]) -> Choice {
!Self::ct_eq_slice(a, b)
}
}
impl<T: CtEqSlice> CtEq for [T] {
fn ct_eq(&self, other: &Self) -> Choice {
T::ct_eq_slice(self, other)
}
fn ct_ne(&self, other: &Self) -> Choice {
T::ct_ne_slice(self, other)
}
}
/// Impl `CtEq` using the `cmov::CmovEq` trait
macro_rules! impl_ct_eq_with_cmov_eq {
( $($ty:ty),+ ) => {
$(
impl CtEq for $ty {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
let mut ret = Choice::FALSE;
self.cmoveq(other, 1, &mut ret.0);
ret
}
}
)+
};
}
/// Impl `CtEq` and `CtEqSlice` using the `cmov::CmovEq` trait
macro_rules! impl_ct_eq_slice_with_cmov_eq {
( $($ty:ty),+ ) => {
$(
impl_ct_eq_with_cmov_eq!($ty);
impl CtEqSlice for $ty {
#[inline]
fn ct_eq_slice(a: &[Self], b: &[Self]) -> Choice {
let mut ret = Choice::FALSE;
a.cmoveq(b, 1, &mut ret.0);
ret
}
}
)+
};
}
impl_ct_eq_slice_with_cmov_eq!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
impl_ct_eq_with_cmov_eq!(isize, usize);
impl CtEqSlice for isize {}
impl CtEqSlice for usize {}
/// Impl `CtEq` for `NonZero<T>` by calling `NonZero::get`.
macro_rules! impl_ct_eq_for_nonzero_integer {
( $($ty:ty),+ ) => {
$(
impl CtEq for $ty {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
self.get().ct_eq(&other.get())
}
}
impl CtEqSlice for $ty {}
)+
};
}
impl_ct_eq_for_nonzero_integer!(
NonZeroI8,
NonZeroI16,
NonZeroI32,
NonZeroI64,
NonZeroI128,
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128
);
impl CtEq for cmp::Ordering {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
// `Ordering` is `repr(i8)`, which has a `CtEq` impl
(*self as i8).ct_eq(&(*other as i8))
}
}
impl CtEqSlice for cmp::Ordering {}
impl<T, const N: usize> CtEq for [T; N]
where
T: CtEqSlice,
{
#[inline]
fn ct_eq(&self, other: &[T; N]) -> Choice {
self.as_slice().ct_eq(other.as_slice())
}
}
impl<T, const N: usize> CtEqSlice for [T; N] where T: CtEqSlice {}
#[cfg(feature = "subtle")]
impl CtEq for subtle::Choice {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
self.unwrap_u8().ct_eq(&other.unwrap_u8())
}
}
#[cfg(feature = "subtle")]
impl<T> CtEq for subtle::CtOption<T>
where
T: CtEq + Default + subtle::ConditionallySelectable,
{
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
CtOption::from(*self).ct_eq(&CtOption::from(*other))
}
}
#[cfg(feature = "alloc")]
mod alloc {
use super::{Choice, CtEq, CtEqSlice};
use ::alloc::{boxed::Box, vec::Vec};
impl<T> CtEq for Box<T>
where
T: CtEq,
{
#[inline]
#[track_caller]
fn ct_eq(&self, rhs: &Self) -> Choice {
(**self).ct_eq(rhs)
}
}
impl<T> CtEq for Box<[T]>
where
T: CtEqSlice,
{
#[inline]
#[track_caller]
fn ct_eq(&self, rhs: &Self) -> Choice {
self.ct_eq(&**rhs)
}
}
impl<T> CtEq<[T]> for Box<[T]>
where
T: CtEqSlice,
{
#[inline]
#[track_caller]
fn ct_eq(&self, rhs: &[T]) -> Choice {
(**self).ct_eq(rhs)
}
}
impl<T> CtEq for Vec<T>
where
T: CtEqSlice,
{
#[inline]
#[track_caller]
fn ct_eq(&self, rhs: &Self) -> Choice {
self.ct_eq(rhs.as_slice())
}
}
impl<T> CtEq<[T]> for Vec<T>
where
T: CtEqSlice,
{
#[inline]
#[track_caller]
fn ct_eq(&self, rhs: &[T]) -> Choice {
self.as_slice().ct_eq(rhs)
}
}
}
#[cfg(test)]
mod tests {
use super::CtEq;
use core::cmp::Ordering;
macro_rules! truth_table {
($a:expr, $b:expr, $c:expr) => {
assert!($a.ct_eq(&$b).to_bool());
assert!(!$a.ct_eq(&$c).to_bool());
assert!(!$b.ct_eq(&$c).to_bool());
assert!(!$a.ct_ne(&$b).to_bool());
assert!($a.ct_ne(&$c).to_bool());
assert!($b.ct_ne(&$c).to_bool());
};
}
macro_rules! ct_eq_test_unsigned {
($ty:ty, $name:ident) => {
#[test]
fn $name() {
let a = <$ty>::MAX;
let b = <$ty>::MAX;
let c = <$ty>::MIN;
truth_table!(a, b, c);
}
};
}
macro_rules! ct_eq_test_signed {
($ty:ty, $name:ident) => {
#[test]
fn $name() {
let a = <$ty>::MAX;
let b = <$ty>::MAX;
let c = <$ty>::MIN;
truth_table!(a, b, c);
}
};
}
ct_eq_test_unsigned!(u8, u8_ct_eq);
ct_eq_test_unsigned!(u16, u16_ct_eq);
ct_eq_test_unsigned!(u32, u32_ct_eq);
ct_eq_test_unsigned!(u64, u64_ct_eq);
ct_eq_test_unsigned!(u128, u128_ct_eq);
ct_eq_test_unsigned!(usize, usize_ct_eq);
ct_eq_test_signed!(i8, i8_ct_eq);
ct_eq_test_signed!(i16, i16_ct_eq);
ct_eq_test_signed!(i32, i32_ct_eq);
ct_eq_test_signed!(i64, i64_ct_eq);
ct_eq_test_signed!(i128, i128_ct_eq);
ct_eq_test_signed!(isize, isize_ct_eq);
#[test]
fn array_ct_eq() {
let a = [1u64, 2, 3];
let b = [1u64, 2, 3];
let c = [1u64, 2, 4];
truth_table!(a, b, c);
}
#[test]
fn ordering_ct_eq() {
let a = Ordering::Greater;
let b = Ordering::Greater;
let c = Ordering::Less;
truth_table!(a, b, c);
}
#[test]
fn slice_ct_eq() {
let a: &[u64] = &[1, 2, 3];
let b: &[u64] = &[1, 2, 3];
let c: &[u64] = &[1, 2, 4];
truth_table!(a, b, c);
// Length mismatches
assert!(a.ct_ne(&[]).to_bool());
assert!(a.ct_ne(&[1, 2]).to_bool());
}
}
+127
View File
@@ -0,0 +1,127 @@
use crate::{Choice, CtAssign, CtOption};
#[cfg(doc)]
use core::iter::Iterator;
/// Constant-time equivalent of [`Iterator::find`], which can search a collection by iterating over
/// every element and applying the given predicate to each item, then selecting the first matching
/// entry.
pub trait CtFind<T: CtAssign> {
/// Iterate through every `T` item in `&self`, applying the given `predicate` which can select
/// a specific item by returning [`Choice::TRUE`].
///
/// The first item where `predicate` returns [`Choice::TRUE`] is selected, or the [`CtOption`]
/// equivalent of `None` is returned if the `predicate` returns [`Choice::FALSE`] for all items.
#[must_use]
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
where
P: Fn(&T) -> Choice;
}
impl<T> CtFind<T> for [T]
where
T: CtAssign + Default,
{
#[inline]
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
where
P: Fn(&T) -> Choice,
{
let mut ret = CtOption::none();
for item in self {
ret.insert_if(item, predicate(item) & ret.is_none());
}
ret
}
}
impl<T, const N: usize> CtFind<T> for [T; N]
where
T: CtAssign + Default,
{
#[inline]
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
where
P: Fn(&T) -> Choice,
{
self.as_slice().ct_find(predicate)
}
}
#[cfg(feature = "alloc")]
mod alloc {
use super::{Choice, CtAssign, CtFind, CtOption};
use ::alloc::{boxed::Box, vec::Vec};
impl<T> CtFind<T> for Box<[T]>
where
T: CtAssign + Default,
{
#[inline]
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
where
P: Fn(&T) -> Choice,
{
(**self).ct_find(predicate)
}
}
#[cfg(feature = "alloc")]
impl<T> CtFind<T> for Vec<T>
where
T: CtAssign + Default,
{
#[inline]
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
where
P: Fn(&T) -> Choice,
{
self.as_slice().ct_find(predicate)
}
}
}
#[cfg(test)]
mod tests {
use super::CtFind;
mod array {
use super::*;
use crate::{CtEq, CtGt};
const ARRAY: [u8; 6] = [0, 0, 0, 1, 2, 3];
#[test]
fn ct_find() {
// Find the first nonzero even number
assert_eq!(
ARRAY.ct_find(|n| n.ct_ne(&0) & (n & 1).ct_eq(&0)).unwrap(),
2
);
// Predicate where nothing matches
assert!(ARRAY.ct_find(|n| n.ct_gt(&3)).is_none().to_bool());
}
}
mod slice {
use super::*;
use crate::{CtEq, CtGt};
const SLICE: &[u8] = &[0, 0, 0, 1, 2, 3];
#[test]
fn ct_find() {
// Find the first nonzero even number
assert_eq!(
SLICE.ct_find(|n| n.ct_ne(&0) & (n & 1).ct_eq(&0)).unwrap(),
2
);
// Predicate where nothing matches
assert!(SLICE.ct_find(|n| n.ct_gt(&3)).is_none().to_bool());
}
}
}
+102
View File
@@ -0,0 +1,102 @@
use crate::Choice;
use core::{
cmp,
num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize},
};
/// Constant time greater than.
pub trait CtGt {
/// Compute whether `self > other` in constant time.
#[must_use]
fn ct_gt(&self, other: &Self) -> Choice;
}
// Impl `CtGt` using overflowing subtraction
macro_rules! impl_unsigned_ct_gt {
( $($uint:ty),+ ) => {
$(
impl CtGt for $uint {
#[inline]
fn ct_gt(&self, other: &Self) -> Choice {
let (_, overflow) = other.overflowing_sub(*self);
Choice(overflow.into())
}
}
)+
};
}
impl_unsigned_ct_gt!(u8, u16, u32, u64, u128, usize);
/// Impl `CtGt` for `NonZero<T>` by calling `NonZero::get`.
macro_rules! impl_ct_gt_for_nonzero_integer {
( $($ty:ty),+ ) => {
$(
impl CtGt for $ty {
#[inline]
fn ct_gt(&self, other: &Self) -> Choice {
self.get().ct_gt(&other.get())
}
}
)+
};
}
impl_ct_gt_for_nonzero_integer!(
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroUsize
);
impl CtGt for cmp::Ordering {
#[inline]
#[allow(clippy::arithmetic_side_effects, clippy::cast_sign_loss)]
fn ct_gt(&self, other: &Self) -> Choice {
// No impl of `CtGt` for `i8`, so use `u8`
// TODO(tarcieri): use `cast_signed` when MSRV is 1.87
let a = (*self as i8) + 1;
let b = (*other as i8) + 1;
// TODO(tarcieri): use `cast_unsigned` when MSRV is 1.87
(a as u8).ct_gt(&(b as u8))
}
}
#[cfg(test)]
mod tests {
use super::CtGt;
use core::cmp::Ordering;
/// Test `CtGt`
macro_rules! ct_gt_tests {
( $($int:ident),+ ) => {
$(
mod $int {
use super::CtGt;
#[test]
fn ct_gt() {
let a = <$int>::MIN;
let b = <$int>::MAX;
assert!(!a.ct_gt(&a).to_bool());
assert!(!a.ct_gt(&b).to_bool());
assert!(b.ct_gt(&a).to_bool());
}
}
)+
};
}
ct_gt_tests!(u8, u16, u32, u64, u128, usize);
#[test]
fn ordering() {
assert!(!Ordering::Equal.ct_gt(&Ordering::Equal).to_bool());
assert!(!Ordering::Less.ct_gt(&Ordering::Greater).to_bool());
assert!(Ordering::Greater.ct_gt(&Ordering::Less).to_bool());
}
}
+137
View File
@@ -0,0 +1,137 @@
use crate::{CtAssign, CtEq, CtOption};
use core::ops::AddAssign;
#[cfg(doc)]
use core::ops::Index;
/// Constant-time lookup by index, similar to the [`Index`] trait, but returning an owned result in
/// constant-time.
pub trait CtLookup<Idx> {
/// Output type returned by the lookup operation.
type Output: CtAssign;
/// Attempt to retrieve the item at the given `index`, either returning it or the [`CtOption`]
/// equivalent of [`None`] if the `index` was out-of-bounds.
#[must_use]
fn ct_lookup(&self, index: Idx) -> CtOption<Self::Output>;
}
impl<T, Idx> CtLookup<Idx> for [T]
where
T: CtAssign + Default,
Idx: AddAssign + CtEq + Default + From<u8>,
{
type Output = T;
#[inline]
#[allow(clippy::arithmetic_side_effects)]
fn ct_lookup(&self, index: Idx) -> CtOption<T> {
let mut ret = CtOption::none();
let mut i = Idx::default();
for item in self {
ret.insert_if(item, i.ct_eq(&index));
// TODO(tarcieri): ideally we'd prevent overflow here but there's no core `CheckedAdd`
i += Idx::from(1u8);
}
ret
}
}
impl<T, Idx, const N: usize> CtLookup<Idx> for [T; N]
where
T: CtAssign + Default,
Idx: AddAssign + CtEq + Default + From<u8>,
{
type Output = T;
#[inline]
fn ct_lookup(&self, index: Idx) -> CtOption<T> {
self.as_slice().ct_lookup(index)
}
}
#[cfg(feature = "alloc")]
mod alloc {
use super::{AddAssign, CtAssign, CtEq, CtLookup, CtOption};
use ::alloc::{boxed::Box, vec::Vec};
impl<T, Idx> CtLookup<Idx> for Box<[T]>
where
T: CtAssign + Default,
Idx: AddAssign + CtEq + Default + From<u8>,
{
type Output = T;
#[inline]
fn ct_lookup(&self, index: Idx) -> CtOption<T> {
(**self).ct_lookup(index)
}
}
impl<T, Idx> CtLookup<Idx> for Vec<T>
where
T: CtAssign + Default,
Idx: AddAssign + CtEq + Default + From<u8>,
{
type Output = T;
#[inline]
fn ct_lookup(&self, index: Idx) -> CtOption<T> {
self.as_slice().ct_lookup(index)
}
}
}
#[cfg(test)]
mod tests {
mod array {
use crate::CtLookup;
const EXAMPLE: [u8; 3] = [1, 2, 3];
#[test]
fn ct_lookup_u32() {
assert_eq!(EXAMPLE.ct_lookup(0u32).unwrap(), 1);
assert_eq!(EXAMPLE.ct_lookup(1u32).unwrap(), 2);
assert_eq!(EXAMPLE.ct_lookup(2u32).unwrap(), 3);
assert!(EXAMPLE.ct_lookup(3u32).is_none().to_bool());
assert!(EXAMPLE.ct_lookup(4u32).is_none().to_bool());
}
#[test]
fn ct_lookup_usize() {
assert_eq!(EXAMPLE.ct_lookup(0usize).unwrap(), 1);
assert_eq!(EXAMPLE.ct_lookup(1usize).unwrap(), 2);
assert_eq!(EXAMPLE.ct_lookup(2usize).unwrap(), 3);
assert!(EXAMPLE.ct_lookup(3usize).is_none().to_bool());
assert!(EXAMPLE.ct_lookup(4usize).is_none().to_bool());
}
}
mod slice {
use crate::CtLookup;
const EXAMPLE: &[u8] = &[1, 2, 3];
#[test]
fn ct_lookup_u32() {
assert_eq!(EXAMPLE.ct_lookup(0u32).unwrap(), 1);
assert_eq!(EXAMPLE.ct_lookup(1u32).unwrap(), 2);
assert_eq!(EXAMPLE.ct_lookup(2u32).unwrap(), 3);
assert!(EXAMPLE.ct_lookup(3u32).is_none().to_bool());
assert!(EXAMPLE.ct_lookup(4u32).is_none().to_bool());
}
#[test]
fn ct_lookup_usize() {
assert_eq!(EXAMPLE.ct_lookup(0usize).unwrap(), 1);
assert_eq!(EXAMPLE.ct_lookup(1usize).unwrap(), 2);
assert_eq!(EXAMPLE.ct_lookup(2usize).unwrap(), 3);
assert!(EXAMPLE.ct_lookup(3usize).is_none().to_bool());
assert!(EXAMPLE.ct_lookup(4usize).is_none().to_bool());
}
}
}
+111
View File
@@ -0,0 +1,111 @@
use crate::Choice;
use core::{
cmp,
num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize},
};
/// Constant time less than.
pub trait CtLt {
/// Compute whether `self < other` in constant time.
#[must_use]
fn ct_lt(&self, other: &Self) -> Choice;
}
// Impl `CtLt` using overflowing subtraction
macro_rules! impl_unsigned_ct_lt {
( $($uint:ty),+ ) => {
$(
impl CtLt for $uint {
#[inline]
fn ct_lt(&self, other: &Self) -> Choice {
let (_, overflow) = self.overflowing_sub(*other);
Choice(overflow.into())
}
}
)+
};
}
impl_unsigned_ct_lt!(u8, u16, u32, u64, u128, usize);
/// Impl `CtLt` for `NonZero<T>` by calling `NonZero::get`.
macro_rules! impl_ct_lt_for_nonzero_integer {
( $($ty:ty),+ ) => {
$(
impl CtLt for $ty {
#[inline]
fn ct_lt(&self, other: &Self) -> Choice {
self.get().ct_lt(&other.get())
}
}
)+
};
}
impl_ct_lt_for_nonzero_integer!(
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroUsize
);
impl CtLt for cmp::Ordering {
#[inline]
#[allow(clippy::arithmetic_side_effects, clippy::cast_sign_loss)]
fn ct_lt(&self, other: &Self) -> Choice {
// No impl of `CtLt` for `i8`, so use `u8`
// TODO(tarcieri): use `cast_signed` when MSRV is 1.87
let a = (*self as i8) + 1;
let b = (*other as i8) + 1;
// TODO(tarcieri): use `cast_unsigned` when MSRV is 1.87
(a as u8).ct_lt(&(b as u8))
}
}
#[cfg(test)]
mod tests {
use super::CtLt;
use core::cmp::Ordering;
#[test]
fn ct_lt() {
let a = 42u64;
let b = 43u64;
assert!(!a.ct_lt(&a).to_bool());
assert!(a.ct_lt(&b).to_bool());
assert!(!b.ct_lt(&a).to_bool());
}
/// Test `CtLt`
macro_rules! ct_lt_tests {
( $($int:ident),+ ) => {
$(
mod $int {
use super::CtLt;
#[test]
fn ct_gt() {
let a = <$int>::MIN;
let b = <$int>::MAX;
assert!(!a.ct_lt(&a).to_bool());
assert!(a.ct_lt(&b).to_bool());
assert!(!b.ct_lt(&a).to_bool());
}
}
)+
};
}
ct_lt_tests!(u8, u16, u32, u64, u128, usize);
#[test]
fn ordering() {
assert!(!Ordering::Equal.ct_lt(&Ordering::Equal).to_bool());
assert!(Ordering::Less.ct_lt(&Ordering::Greater).to_bool());
assert!(!Ordering::Greater.ct_lt(&Ordering::Less).to_bool());
}
}
+167
View File
@@ -0,0 +1,167 @@
use crate::{Choice, CtAssign, CtSelect};
use core::num::{
NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
};
/// Constant-time conditional negation: negates a value when `choice` is [`Choice::TRUE`].
pub trait CtNeg: Sized {
/// Conditionally negate `self`, returning `-self` if `choice` is [`Choice::TRUE`], or `self`
/// otherwise.
#[must_use]
fn ct_neg(&self, choice: Choice) -> Self;
/// Conditionally negate `self` in-place, replacing it with `-self` if `choice` is
/// [`Choice::TRUE`].
fn ct_neg_assign(&mut self, choice: Choice) {
*self = self.ct_neg(choice);
}
}
// Impl `CtNeg` for a signed integer (`i*`) type which impls `CtSelect`
macro_rules! impl_signed_ct_neg {
( $($int:ty),+ ) => {
$(
impl CtNeg for $int {
#[inline]
#[allow(clippy::arithmetic_side_effects)]
fn ct_neg(&self, choice: Choice) -> Self {
self.ct_select(&-*self, choice)
}
#[inline]
#[allow(clippy::arithmetic_side_effects)]
fn ct_neg_assign(&mut self, choice: Choice) {
self.ct_assign(&-*self, choice)
}
}
)+
};
}
// Impl `CtNeg` for an unsigned integer (`u*`) type which impls `CtSelect`
macro_rules! impl_unsigned_ct_neg {
( $($uint:ty),+ ) => {
$(
impl CtNeg for $uint {
#[inline]
fn ct_neg(&self, choice: Choice) -> Self {
self.ct_select(&self.wrapping_neg(), choice)
}
#[inline]
fn ct_neg_assign(&mut self, choice: Choice) {
self.ct_assign(&self.wrapping_neg(), choice)
}
}
)+
};
}
impl_signed_ct_neg!(
i8,
i16,
i32,
i64,
i128,
isize,
NonZeroI8,
NonZeroI16,
NonZeroI32,
NonZeroI64,
NonZeroI128,
NonZeroIsize
);
impl_unsigned_ct_neg!(u8, u16, u32, u64, u128, usize);
/// Unfortunately `NonZeroU*` doesn't support `wrapping_neg` for some reason (but `NonZeroI*` does),
/// even though the wrapping negation of any non-zero integer should also be non-zero.
///
/// So we need a special case just for `NonZeroU*`, at least for now.
macro_rules! impl_ct_neg_for_unsigned_nonzero {
( $($nzuint:ident),+ ) => {
$(
impl CtNeg for $nzuint {
#[inline]
fn ct_neg(&self, choice: Choice) -> Self {
// TODO(tarcieri): use `NonZero::wrapping_neg` if it becomes available
let n = self.get().ct_select(&self.get().wrapping_neg(), choice);
$nzuint::new(n).expect("should be non-zero")
}
}
)+
};
}
impl_ct_neg_for_unsigned_nonzero!(
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroUsize
);
#[cfg(test)]
mod tests {
/// Test `CtNeg` impl on `i*`
macro_rules! signed_ct_neg_tests {
( $($int:ident),+ ) => {
$(
mod $int {
use crate::{Choice, CtNeg};
#[test]
fn ct_neg() {
let n: $int = 42;
assert_eq!(n, n.ct_neg(Choice::FALSE));
assert_eq!(-n, n.ct_neg(Choice::TRUE));
}
#[test]
fn ct_neg_assign() {
let n: $int = 42;
let mut x = n;
x.ct_neg_assign(Choice::FALSE);
assert_eq!(n, x);
x.ct_neg_assign(Choice::TRUE);
assert_eq!(-n, x);
}
}
)+
};
}
/// Test `CtNeg` impl on `u*`
macro_rules! unsigned_ct_neg_tests {
( $($uint:ident),+ ) => {
$(
mod $uint {
use crate::{Choice, CtNeg};
#[test]
fn ct_neg() {
let n: $uint = 42;
assert_eq!(n, n.ct_neg(Choice::FALSE));
assert_eq!(<$uint>::MAX - n + 1, n.ct_neg(Choice::TRUE));
}
#[test]
fn ct_neg_assign() {
let n: $uint = 42;
let mut x = n;
x.ct_neg_assign(Choice::FALSE);
assert_eq!(n, x);
x.ct_neg_assign(Choice::TRUE);
assert_eq!(<$uint>::MAX - n + 1, x);
}
}
)+
};
}
signed_ct_neg_tests!(i8, i16, i32, i64, i128, isize);
unsigned_ct_neg_tests!(u8, u16, u32, u64, u128, usize);
}
+209
View File
@@ -0,0 +1,209 @@
use crate::{Choice, CtAssign, CtAssignSlice};
use core::{
cmp,
num::{
NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
},
};
#[cfg(feature = "subtle")]
use crate::CtOption;
/// Constant-time selection: choose between two values based on a given [`Choice`].
///
/// This crate provides built-in implementations for the following types:
/// - [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`isize`]
/// - [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`]
/// - [`NonZeroI8`], [`NonZeroI16`], [`NonZeroI32`], [`NonZeroI64`], [`NonZeroI128`], [`NonZeroI128`]
/// - [`NonZeroU8`], [`NonZeroU16`], [`NonZeroU32`], [`NonZeroU64`], [`NonZeroU128`],, [`NonZeroUsize`]
/// - [`cmp::Ordering`]
/// - [`Choice`]
/// - `[T; N]` where `T` impls [`CtSelectArray`], which the previously mentioned types all do,
/// as well as any type which impls [`Clone`] + [`CtAssignSlice`] + [`CtSelect`].
pub trait CtSelect: Sized {
/// Select between `self` and `other` based on `choice`, returning a copy of the value.
///
/// # Returns
/// - `self` if `choice` is [`Choice::FALSE`].
/// - `other` if `choice` is [`Choice::TRUE`].
#[must_use]
fn ct_select(&self, other: &Self, choice: Choice) -> Self;
/// Conditionally swap `self` and `other` if `choice` is [`Choice::TRUE`].
fn ct_swap(&mut self, other: &mut Self, choice: Choice) {
let tmp = self.ct_select(other, choice);
*other = Self::ct_select(other, self, choice);
*self = tmp;
}
}
/// Implementing this trait enables use of the [`CtSelect`] trait to construct `[T; N]` where `T`
/// is the `Self` type implementing the trait, via a blanket impl.
///
/// All types which impl [`Clone`] + [`CtAssignSlice`] + [`CtSelect`] will receive a blanket impl
/// of this trait and thus also be usable with the [`CtSelect`] impl for `[T; N]`.
pub trait CtSelectArray<const N: usize>: CtSelect + Sized {
/// Select between `a` and `b` in constant-time based on `choice`.
#[must_use]
fn ct_select_array(a: &[Self; N], b: &[Self; N], choice: Choice) -> [Self; N] {
core::array::from_fn(|i| Self::ct_select(&a[i], &b[i], choice))
}
}
impl<T, const N: usize> CtSelect for [T; N]
where
T: CtSelectArray<N>,
{
#[inline]
fn ct_select(&self, other: &Self, choice: Choice) -> Self {
T::ct_select_array(self, other, choice)
}
}
impl<T, const N: usize> CtSelectArray<N> for T
where
T: Clone + CtAssignSlice + CtSelect,
{
#[inline]
fn ct_select_array(a: &[Self; N], b: &[Self; N], choice: Choice) -> [Self; N] {
let mut ret = a.clone();
ret.ct_assign(b, choice);
ret
}
}
/// Marker trait which enables a blanket impl of [`CtSelect`] for types which also impl
/// [`Clone`] + [`CtAssign`].
pub trait CtSelectUsingCtAssign: Clone + CtAssign {}
impl<T: CtSelectUsingCtAssign> CtSelect for T {
#[inline]
fn ct_select(&self, other: &Self, choice: Choice) -> Self {
let mut ret = self.clone();
ret.ct_assign(other, choice);
ret
}
}
/// Macro to write impls of `CtSelectUsingCtAssign`.
macro_rules! impl_ct_select_with_ct_assign {
( $($ty:ty),+ ) => { $(impl CtSelectUsingCtAssign for $ty {})+ };
}
impl_ct_select_with_ct_assign!(
i8,
i16,
i32,
i64,
i128,
isize,
u8,
u16,
u32,
u64,
u128,
usize,
NonZeroI8,
NonZeroI16,
NonZeroI32,
NonZeroI64,
NonZeroI128,
NonZeroIsize,
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroUsize,
cmp::Ordering
);
#[cfg(feature = "subtle")]
impl CtSelect for subtle::Choice {
#[inline]
fn ct_select(&self, other: &Self, choice: Choice) -> Self {
Choice::from(*self)
.ct_select(&Choice::from(*other), choice)
.into()
}
}
#[cfg(feature = "subtle")]
impl<T> CtSelect for subtle::CtOption<T>
where
T: CtSelect + Default + subtle::ConditionallySelectable,
{
#[inline]
fn ct_select(&self, other: &Self, choice: Choice) -> Self {
CtOption::from(*self)
.ct_select(&CtOption::from(*other), choice)
.into()
}
}
#[cfg(feature = "alloc")]
mod alloc {
use super::CtSelectUsingCtAssign;
use crate::{CtAssign, CtAssignSlice};
use ::alloc::{boxed::Box, vec::Vec};
impl<T: Clone + CtAssign> CtSelectUsingCtAssign for Box<T> {}
#[cfg(feature = "alloc")]
impl<T> CtSelectUsingCtAssign for Box<[T]> where T: Clone + CtAssignSlice {}
#[cfg(feature = "alloc")]
impl<T: Clone + CtAssignSlice> CtSelectUsingCtAssign for Vec<T> {}
}
#[cfg(test)]
mod tests {
use super::{Choice, CtSelect, cmp};
macro_rules! ct_select_test_unsigned {
($ty:ty, $name:ident) => {
#[test]
fn $name() {
let a: $ty = 1;
let b: $ty = 2;
assert_eq!(a.ct_select(&b, Choice::FALSE), a);
assert_eq!(a.ct_select(&b, Choice::TRUE), b);
}
};
}
macro_rules! ct_select_test_signed {
($ty:ty, $name:ident) => {
#[test]
fn $name() {
let a: $ty = 1;
let b: $ty = -2;
assert_eq!(a.ct_select(&b, Choice::FALSE), a);
assert_eq!(a.ct_select(&b, Choice::TRUE), b);
}
};
}
ct_select_test_unsigned!(u8, u8_ct_select);
ct_select_test_unsigned!(u16, u16_ct_select);
ct_select_test_unsigned!(u32, u32_ct_select);
ct_select_test_unsigned!(u64, u64_ct_select);
ct_select_test_unsigned!(u128, u128_ct_select);
ct_select_test_unsigned!(usize, usize_ct_select);
ct_select_test_signed!(i8, i8_ct_select);
ct_select_test_signed!(i16, i16_ct_select);
ct_select_test_signed!(i32, i32_ct_select);
ct_select_test_signed!(i64, i64_ct_select);
ct_select_test_signed!(i128, i128_ct_select);
ct_select_test_signed!(isize, isize_ct_select);
#[test]
fn ordering_ct_select() {
let a = cmp::Ordering::Less;
let b = cmp::Ordering::Greater;
assert_eq!(a.ct_select(&b, Choice::FALSE), a);
assert_eq!(a.ct_select(&b, Choice::TRUE), b);
}
}
+58
View File
@@ -0,0 +1,58 @@
//! `cmov` property-based tests: randomized with shrinking.
/// Write the proptests for an integer type.
macro_rules! int_proptests {
( $($int:ident),+ ) => {
$(
mod $int {
use ctutils::{CtAssign, CtSelect, CtEq, Choice};
use proptest::prelude::*;
proptest! {
#[test]
fn ct_assign(a in any::<$int>(), b in any::<$int>(), byte in any::<u8>()) {
let choice = Choice::from_u8_lsb(byte);
let mut actual = a;
actual.ct_assign(&b, choice);
let expected = if byte & 1 == 1 {
b
} else {
a
};
prop_assert_eq!(expected, actual);
}
#[test]
fn ct_eq(a in any::<$int>(), b in any::<$int>()) {
let actual = a.ct_eq(&b);
prop_assert_eq!(a == b, actual.to_bool());
}
#[test]
fn ct_ne(a in any::<$int>(), b in any::<$int>()) {
let actual = a.ct_ne(&b);
prop_assert_eq!(a != b, actual.to_bool());
}
#[test]
fn ct_select(a in any::<$int>(), b in any::<$int>(), byte in any::<u8>()) {
let choice = Choice::from_u8_lsb(byte);
let actual = a.ct_select(&b, choice);
let expected = if byte & 1 == 1 {
b
} else {
a
};
prop_assert_eq!(expected, actual);
}
}
}
)+
};
}
int_proptests!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);