Skip to content

Commit 8022962

Browse files
committed
Merging Quant-TheodoreFelix's improvement to ct::Condition<u64>. Closes #7
1 parent ee08427 commit 8022962

10 files changed

Lines changed: 2412 additions & 384 deletions

File tree

.github/workflows/publish_doc_benches_to_ghpages.yaml

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,22 @@ jobs:
3737
with:
3838
name: code_quality_stats
3939
path: ./code_stats.txt
40-
run_benches:
41-
runs-on: ubuntu-latest
42-
if: github.ref == 'refs/heads/main'
43-
steps:
44-
- name: Checkout code
45-
uses: actions/checkout@v4
46-
- run: cargo bench --all
47-
- name: Save artifacts
48-
uses: actions/upload-artifact@v4
49-
with:
50-
name: bc-rust-benches
51-
path: ./target/criterion
40+
# the benches run crazy slow on the github agent. So there's really no point because it's not useful data.
41+
# run_benches:
42+
# runs-on: ubuntu-latest
43+
# if: github.ref == 'refs/heads/main'
44+
# steps:
45+
# - name: Checkout code
46+
# uses: actions/checkout@v4
47+
# - run: cargo bench --all
48+
# - name: Save artifacts
49+
# uses: actions/upload-artifact@v4
50+
# with:
51+
# name: bc-rust-benches
52+
# path: ./target/criterion
5253
collect_ghpages:
5354
if: github.ref == 'refs/heads/main'
54-
needs: [build_docs, code_stats, run_benches]
55+
needs: [build_docs, code_stats]
5556
runs-on: ubuntu-latest
5657
steps:
5758
- run: mkdir ./gh-pages
@@ -65,11 +66,11 @@ jobs:
6566
with:
6667
name: code_quality_stats
6768
path: ./gh-pages/
68-
- name: Get benches from previous job
69-
uses: actions/download-artifact@v4
70-
with:
71-
name: bc-rust-benches
72-
path: ./gh-pages/benches
69+
# - name: Get benches from previous job
70+
# uses: actions/download-artifact@v4
71+
# with:
72+
# name: bc-rust-benches
73+
# path: ./gh-pages/benches
7374
- name: Archive Compatibility Matrix For Download
7475
uses: actions/upload-pages-artifact@v3
7576
with:

CONTRIBUTING.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,23 @@ before posting anything public. See [Security Policy](SECURITY.md).
2020

2121
If a related discussion or issue doesn't exist, and the issue is not security related, you can [open a new issue](https://github.com/bcgit/bc-java/issues/new). An issue can be converted into a discussion if regarded as one.
2222

23+
## Coding philosophy
24+
25+
> Slow is smooth, smooth is fast.
26+
27+
There is a time and a place for "Move fast and break things", but the source code of a crypto library is not one of them.
28+
29+
This project takes the philosophy that taking the time to do things right pays off in the long run, both in terms of
30+
the runtime and memory footprint of the code, and it terms of the time required for a future maintainer to get up to speed with the code
31+
and avoid introducing bugs due to the code being hard to understand.
32+
33+
Some specifics:
34+
35+
* Respect that the innovative process sometimes requires exploring several dead-ends before you find the most elegant solution.
36+
* Public APIs of a library should be both ergonomic and expressive. When defining a new trait or public function, ask yourself whether a programmer who is new to cryptography is likely to use this in a way that will get them into trouble.
37+
* Variables should be well-named, well-structured, and well-commented (a comment-to-code ration of 1:1 is a goal to be strived for!). Think about memory footprint and, where possible, use unnamed scopes to allow the compiler to pop intermediate value variables off the stack as soon as they are no longer needed.
38+
* Always run your code through `cargo mutants` and get the issue count as low as your can. As a first pass, this forces you to write thorough unit tests. As a second pass, this draws your attention to bits of your code that cannot be tested from the outside. Often this means that the code can be simplified without affecting functionality (as defined by your set of unit tests) -- "simpler code" usually means faster runtime and easier future maintenance.
39+
2340
## Contribute to the code
2441

2542
For substantial, non-trivial contributions, you may be asked to sign a contributor assignment agreement. Optionally, you can also have your name and contact information listed in [Contributors](https://www.bouncycastle.org/contributors.html).
@@ -56,5 +73,5 @@ Don't forget to self-review. Please follow these simple guidelines:
5673

5774
#### Your pull request is merged
5875

59-
For acceptance, pull requests need to meet specific quality criteria, including tests for anything substantial. Someone on the Bouncy Castle core team will review the pull request when there is time, and let you know if something is missing or suggest improvements. If it is a useful and generic feature it will be integrated in Bouncy Castle to be available in a later release.
76+
Someone on the Bouncy Castle core team will review the pull request when there is time, and let you know if something is missing or suggest improvements. If it is a useful and generic feature it will be integrated in Bouncy Castle to be available in a later release.
6077

crypto/core/src/errors.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,4 @@ impl From<KeyMaterialError> for SignatureError {
145145
impl From<RNGError> for SignatureError {
146146
fn from(e: RNGError) -> SignatureError { Self::RNGError(e) }
147147
}
148+

crypto/core/src/key_material.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
4040
use crate::errors::KeyMaterialError;
4141
use crate::traits::{RNG, SecurityStrength, Secret};
42-
use bouncycastle_utils::{ct, max, min};
42+
use bouncycastle_utils::{ct, min};
4343

4444
use core::cmp::{Ordering, PartialOrd};
4545
use core::fmt;
@@ -569,8 +569,8 @@ impl<const KEY_LEN: usize> KeyMaterialTrait for KeyMaterial<KEY_LEN> {
569569
}
570570
self.buf[self.key_len..new_key_len].copy_from_slice(other.ref_to_bytes());
571571
self.key_len += other.key_len();
572-
self.key_type = max(&self.key_type, &other.key_type()).clone();
573-
self.security_strength = max(&self.security_strength, &other.security_strength()).clone();
572+
self.key_type = min(&self.key_type, &other.key_type()).clone();
573+
self.security_strength = min(&self.security_strength, &other.security_strength()).clone();
574574
Ok(self.key_len())
575575
}
576576

crypto/core/tests/key_material_tests.rs

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -620,8 +620,9 @@ mod test_key_material {
620620
assert_eq!(zeroized_key.key_len(), 8);
621621
zeroized_key.concatenate(&key2).unwrap();
622622
assert_eq!(zeroized_key.key_len(), 24);
623-
// should take max(Zeroized, BytesLowEntropy)
624-
assert_eq!(zeroized_key.key_type(), KeyType::BytesLowEntropy);
623+
// The result takes the lesser (min) of the two key types: min(Zeroized, BytesLowEntropy).
624+
// Folding in zeroized (uninitialized) bytes taints the whole buffer as Zeroized.
625+
assert_eq!(zeroized_key.key_type(), KeyType::Zeroized);
625626
assert_eq!(zeroized_key.security_strength(), SecurityStrength::None);
626627

627628
// This should be symmetric, so test it in the other direction too.
@@ -634,8 +635,8 @@ mod test_key_material {
634635
let mut key2 = KeyMaterial256::from_bytes(&[1u8; 16]).unwrap();
635636
key2.concatenate(&zeroized_key).unwrap();
636637
assert_eq!(key2.key_len(), 24);
637-
// should take max(Zeroized, BytesLowEntropy)
638-
assert_eq!(key2.key_type(), KeyType::BytesLowEntropy);
638+
// The result takes the lesser (min) of the two key types: min(BytesLowEntropy, Zeroized).
639+
assert_eq!(key2.key_type(), KeyType::Zeroized);
639640
assert_eq!(key2.security_strength(), SecurityStrength::None);
640641

641642
// now try it with keys of different key types
@@ -644,33 +645,34 @@ mod test_key_material {
644645
let full_entropy_key =
645646
KeyMaterial256::from_bytes_as_type(&[2u8; 16], KeyType::BytesFullEntropy).unwrap();
646647
low_entropy_key.concatenate(&full_entropy_key).unwrap();
647-
// should take max(BytesLowEntropy, BytesFullEntropy)
648-
assert_eq!(low_entropy_key.key_type(), KeyType::BytesFullEntropy);
649-
// should take max(None, _128Bit)
650-
assert_eq!(low_entropy_key.security_strength(), SecurityStrength::_128bit);
648+
// Conservative model: concatenating a full-entropy key with a low-entropy key yields a
649+
// low-entropy key. min(BytesLowEntropy, BytesFullEntropy) == BytesLowEntropy.
650+
assert_eq!(low_entropy_key.key_type(), KeyType::BytesLowEntropy);
651+
// min(None, _128bit) == None (and BytesLowEntropy keys must have strength None anyway).
652+
assert_eq!(low_entropy_key.security_strength(), SecurityStrength::None);
651653

652654
// and in the other direction too
653655
let low_entropy_key =
654656
KeyMaterial256::from_bytes_as_type(&[1u8; 16], KeyType::BytesLowEntropy).unwrap();
655657
let mut full_entropy_key =
656658
KeyMaterial256::from_bytes_as_type(&[2u8; 16], KeyType::BytesFullEntropy).unwrap();
657659
full_entropy_key.concatenate(&low_entropy_key).unwrap();
658-
// should take max(BytesLowEntropy, BytesFullEntropy)
659-
assert_eq!(full_entropy_key.key_type(), KeyType::BytesFullEntropy);
660-
// should take max(None, _128Bit)
661-
assert_eq!(full_entropy_key.security_strength(), SecurityStrength::_128bit);
660+
// min(BytesFullEntropy, BytesLowEntropy) == BytesLowEntropy.
661+
assert_eq!(full_entropy_key.key_type(), KeyType::BytesLowEntropy);
662+
// min(_128bit, None) == None.
663+
assert_eq!(full_entropy_key.security_strength(), SecurityStrength::None);
662664

663665
// now with full entropy keys at different security levels
664-
let mut low_entropy_key =
666+
let mut full_entropy_key_112 =
665667
KeyMaterial512::from_bytes_as_type(&[1u8; 16], KeyType::BytesFullEntropy).unwrap();
666668
// Now we're gonna explictly tag it at the 112bit security level -- does not require allow_hazardous_operations().
667-
low_entropy_key.set_security_strength(SecurityStrength::_112bit).unwrap();
669+
full_entropy_key_112.set_security_strength(SecurityStrength::_112bit).unwrap();
668670
let full_entropy_key =
669671
KeyMaterial256::from_bytes_as_type(&[2u8; 32], KeyType::BytesFullEntropy).unwrap();
670-
low_entropy_key.concatenate(&full_entropy_key).unwrap();
671-
assert_eq!(low_entropy_key.key_type(), KeyType::BytesFullEntropy);
672-
// should take max(_112Bit, _256Bit)
673-
assert_eq!(low_entropy_key.security_strength(), SecurityStrength::_256bit);
672+
full_entropy_key_112.concatenate(&full_entropy_key).unwrap();
673+
assert_eq!(full_entropy_key_112.key_type(), KeyType::BytesFullEntropy);
674+
// The combined key keeps the lower of the two security strengths: min(_112bit, _256bit).
675+
assert_eq!(full_entropy_key_112.security_strength(), SecurityStrength::_112bit);
674676
}
675677

676678
#[test]

crypto/mldsa_lowmemory/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
//!
4646
//! We also get a surprising amount of memory-savings by good coding hygiene:
4747
//! Using un-named scopes to tell the compiler when an intermediate variable is no longer needed and
48-
//! con be popped off the stack. This sometimes requires re-ordering the steps of the algorithms given in
48+
//! can be popped off the stack. This sometimes requires re-ordering the steps of the algorithms given in
4949
//! FIPS 204 so that variables can be created, used, and released in a self-contained block.
5050
//! Sometimes this is not possible and we have to make a choice between keeping the variable around
5151
//! or releasing it and re-deriving it later.

crypto/utils/src/ct.rs

Lines changed: 63 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ impl Condition<i64> {
3838
// MikeO: TODO: there are a bunch of impls in here that seem to be generic and not related to i64,
3939
// MikeO: TODO: could those be moved to a generic impl<T> for Condition<T> ?
4040

41-
pub const TRUE: Self = Self(1);
41+
/// TRUE is the bit vector of all 1's
42+
pub const TRUE: Self = Self(-1);
43+
/// FALSE is the bit vector of all 0's
4244
pub const FALSE: Self = Self(0);
4345

4446
pub const fn from_bool<const VALUE: bool>() -> Self {
@@ -111,13 +113,31 @@ impl Condition<i64> {
111113
*dst = self.select(src, *dst);
112114
}
113115

114-
// MikeO: TODO: I have no idea what this does, .negate(-1) seems to give -3 ?? Is that a bug?
115116
/// Conditionally negate the value.
117+
///
118+
/// negate(-1) gives -3
119+
///
120+
/// `value` is `-1` (i.e., all bits are `1`, `...1111`)
121+
///
122+
/// Condition `self.0` is 1 (`...0001`) (assuming `TRUE`)
123+
///
124+
/// XOR operation was executed as `value ^ self.0`
125+
///
126+
/// Then `...1111 XOR ...0001 = ...1110` (i.e., `-2`)
127+
///
128+
/// Subtraction operation is `wrapping_sub(self.0)`
129+
///
130+
/// Then `-2 - 1 = -3`
131+
///
132+
/// As a result, `1`, which is the negation of `-1`, should be returned, but `-3` is output.
133+
///
134+
/// Therefore, if the [Self::TRUE] constant value of the i64 [Condition] implementation is changed to `-1`,
135+
/// the test also runs normally.
116136
pub const fn negate(self, value: i64) -> i64 {
117137
(value ^ self.0).wrapping_sub(self.0)
118138
}
119139

120-
const fn or_halves(value: i64) -> i64 {
140+
pub const fn or_halves(value: i64) -> i64 {
121141
(value | (value >> 32)) & 0xFFFFFFFF
122142
}
123143

@@ -136,93 +156,36 @@ impl Condition<i64> {
136156
}
137157
}
138158

139-
// TODO: ... this doesn't ... work. We should get this working and then then do u8.
159+
// TODO: We should do Condition<u8>.
140160
// TODO: then and change Hex and Base64 to use this.
141161
// TODO: (there's probably no noticeable performance difference u8 and u64 bit ops on a 64-bit machine,
142162
// TODO: but there would be on a 8, 16, or 32-bit machine.)
143-
// impl Condition<u64> {
144-
// pub const TRUE: Self = Self(1);
145-
// pub const FALSE: Self = Self(0);
146-
//
147-
// pub const fn new<const VALUE: bool>() -> Self {
148-
// Self((VALUE as u64).wrapping_neg())
149-
// }
150-
//
151-
// pub const fn from_bool(value: bool) -> Self {
152-
// Self((value as u64).wrapping_neg())
153-
// }
154-
//
155-
// pub const fn is_bit_set(value: u64, bit: u64) -> Self {
156-
// Self(((value >> bit) & 1).wrapping_neg())
157-
// }
158-
//
159-
// // MikeO: TODO ?? What does "negative" mean for an unsigned value?
160-
// pub const fn is_negative(value: u64) -> Self {
161-
// Self(((value as i64) >> 63) as u64)
162-
// }
163-
//
164-
// pub const fn is_not_zero(value: u64) -> Self {
165-
// Self::is_negative(Self::or_halves(value).wrapping_neg())
166-
// }
167-
//
168-
// pub const fn is_zero(value: u64) -> Self {
169-
// Self::is_negative(Self::or_halves(value).wrapping_sub(1))
170-
// }
171-
//
172-
// // MikeO: TODO: I borrowed this formula from Botan, but rust complains about u64 subtraction overflow if x < y, so this works in C but won't work in rust.
173-
// // MikeO: TODO: I played with u64.wrapping_sub(y) but that doesn't work either.
174-
// pub const fn is_lt(x: u64, y: u64) -> Self {
175-
// Self::is_zero(x ^ ((x ^ y) | (x.wrapping_sub(y)) ^ x))
176-
// }
177-
//
178-
// // Note: haven't found a clever way to make this const, since it either needs a (non-const) not (!) or a boolean OR is_zero.
179-
// // pub fn is_lte(x: i64, y: i64) -> Self { !Self::is_gt(x, y) }
180-
//
181-
// // pub const fn is_gt(x: i64, y: i64) -> Self { Self::is_lt(y, x) }
182-
//
183-
// // Note: haven't found a clever way to make this const, since it either needs a (non-const) not (!) or a boolean OR is_zero.
184-
// // pub fn is_gte(x: i64, y: i64) -> Self { !Self::is_lt(x, y) }
185-
//
186-
// pub fn is_in_list(value: u64, list: &[u64]) -> Self {
187-
// // Research question: is this actually constant-time?
188-
// // A clever compiler might turn this into a short-circuiting loop.
189-
// // A quick google search shows that rust doesn't have the ability to annotate specific code blocks
190-
// // as no-optimize; the only option is to insert direct assembly.
191-
//
192-
// let mut c = Self::FALSE;
193-
// for i in 0..list.len() {
194-
// let diff = value ^ list[i];
195-
// c |= Condition::<u64>::is_zero(diff);
196-
// }
197-
//
198-
// c
199-
// }
200-
//
201-
// pub fn mov(self, src: u64, dst: &mut u64) {
202-
// *dst = self.select(src, *dst);
203-
// }
204-
//
205-
// // MikeO: TODO: This needs a docstring because I have no idea what this does.
206-
// pub const fn negate(self, value: u64) -> u64 {
207-
// (value ^ self.0).wrapping_sub(self.0)
208-
// }
209-
//
210-
// const fn or_halves(value: u64) -> u64 {
211-
// (value & 0xFFFFFFFF) | (value >> 32)
212-
// }
213-
//
214-
// pub const fn select(self, true_value: u64, false_value: u64) -> u64 {
215-
// (true_value & self.0) | (false_value & !self.0)
216-
// }
217-
//
218-
// pub const fn swap(self, lhs: u64, rhs: u64) -> (u64, u64) {
219-
// (self.select(rhs, lhs), self.select(lhs, rhs))
220-
// }
221-
//
222-
// pub const fn to_bool_var(self) -> bool {
223-
// self.0 != 0
224-
// }
225-
// }
163+
impl Condition<u64> {
164+
/// TRUE is the bit vector of all 1's
165+
pub const TRUE: Self = Self(u64::MAX);
166+
/// FALSE is the bit vector of all 0's
167+
pub const FALSE: Self = Self(0);
168+
169+
// this is the core logic for constant-time mask generation for unsigned integers
170+
// Unlike signed integers where we can rely on Two's Complement via negation `-(v as i64)`,
171+
// for u64 we must use wrapping subtraction to achieve the all-ones bit pattern (u64::MAX) for true
172+
pub const fn from_bool<const VALUE: bool>() -> Self {
173+
// If VALUE is true (1) -> 0 - 1 = u64::MAX (All 1s)
174+
// If VALUE is false (0) -> 0 - 0 = 0 (All 0s)
175+
Self(0u64.wrapping_sub(VALUE as u64))
176+
}
177+
178+
// the select function manually for u64
179+
// although a fully generic impl<T> would be the ultimate long-term goal
180+
pub fn select(self, a: u64, b: u64) -> u64 {
181+
let mask = self.0;
182+
(a & mask) | (b & !mask)
183+
}
184+
185+
pub fn is_true(&self) -> bool {
186+
self.0 != 0
187+
}
188+
}
226189

227190
impl<T> BitAnd for Condition<T>
228191
where
@@ -327,22 +290,22 @@ pub fn conditional_copy_bytes<const LEN: usize>(
327290
a: &[u8; LEN],
328291
b: &[u8; LEN],
329292
out: &mut [u8; LEN],
330-
take_a: bool) {
331-
332-
// we want the behaviour of
293+
take_a: bool,
294+
) {
295+
// we want the behaviour of
333296
// if take_a { 0xFF } else { 0x00 }
334297
// but without using any branches that could leak timing signals
335-
let mask: u8 = (take_a as u8) |
336-
(take_a as u8) <<1 |
337-
(take_a as u8) <<2 |
338-
(take_a as u8) <<3 |
339-
(take_a as u8) <<4 |
340-
(take_a as u8) <<5 |
341-
(take_a as u8) <<6 |
342-
(take_a as u8) <<7;
343-
298+
let mask: u8 = (take_a as u8)
299+
| (take_a as u8) << 1
300+
| (take_a as u8) << 2
301+
| (take_a as u8) << 3
302+
| (take_a as u8) << 4
303+
| (take_a as u8) << 5
304+
| (take_a as u8) << 6
305+
| (take_a as u8) << 7;
306+
344307
debug_assert_eq!(mask, if take_a { 0xFF } else { 0x00 });
345-
308+
346309
for i in 0..LEN {
347310
out[i] = std::hint::black_box(a[i] & mask) | std::hint::black_box(b[i] & !mask);
348311
}

0 commit comments

Comments
 (0)