Skip to content

Conversation

@tarcieri
Copy link
Member

(for lack of a better name)

This is woefully incomplete but I'm pushing it up anyway since several people have asked about const fn support for subtle.

This is effectively a rewrite of subtle using the cmov crate for both constant-time selection/predication as well as equality comparisons. The cmov crate uses architecture-specific predication instructions on x86(_64) and ARM, with a portable "best effort" fallback.

It uses core::hint::black_box on-access as an optimization barrier, however this is a belt-and-suspenders defense paired with the use of intrinsics where available. This is a bit different than subtle which uses a similar black box optimization barrier at initialization time. There are a couple problems with this approach:

  1. The optimizer can assume the value is unchanged after repeat accesses to the same Choice, which means it could potentially insert a branch to e.g. shortcut-on-zero
  2. black_box is (rather annoyingly) only const fn in Rust 1.86. This is targeting an initial MSRV of 1.85, as well as supporting const fn constructors for Choice which are a big missing piece in subtle right now

I'm not intending to replace our usages of subtle with this yet (I'd much rather ship everything), but would like to have a testbed for using cmov for constant-time operations which can perhaps inform a potential subtle v3.0 (if I can make that happen).

To be useful, this still needs an equivalent of CtOption (ideally with much more const fn support), which I was hoping to implement before pushing this up.

One thing we could consider is trying to get this complete enough to use in crypto-bigint to replace ConstChoice/ConstCtOption, though it would likely need all of the methods on Choice to be const fn, which would probably involve shipping Choice without black_box (i.e. what crypto-bigint is already doing), and then adding a subtle integration for converting ctutil::Choice -> subtle::Choice and a prospective ctutil::CtOption -> subtle::CtOption.

cc @andrewwhitehead @fjarri @ycscaly

/// This is used as a "belt-and-suspenders" defense in addition to rhs mechanisms like
/// constant-time predication intrinsics, and is never expected to be the only line of defense.
#[derive(Copy, Clone, Debug)]
pub struct Choice(u8);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: u8 internal representation versus Word in crypto-bigint. This is motivated by the cmov API.

Comment on lines +14 to +18
/// The falsy value.
pub const FALSE: Self = Self(0);

/// The truthy value.
pub const TRUE: Self = Self(1);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These follow subtle conventions, versus crypto-bigint which uses 0 vs Word::MAX. I'm not completely sure about the motivation for the latter?

Note that cmov doesn't care, as the predication instructions are zero-vs-nonzero.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the choice was based on how they were used (generally you'd want a mask, not 1).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that becomes less relevant given the cmov API

/// Create a new [`Choice`] from the given `u8` value, which should be either `0` or `1`.
#[inline]
pub const fn new(value: u8) -> Self {
debug_assert!(value == 0 || value == 1, "Choice::new accepts only 0 or 1");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Branching here means we aren't constant-time in debug mode, which is something that should probably be called out in the comments/documentation

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value >> 1 == 0 ? :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would still leave an effective if value >> 1 == 0

@@ -0,0 +1,92 @@
# [RustCrypto]: CMOV (Conditional Move)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a new README.md 😅

@fjarri
Copy link

fjarri commented Oct 28, 2025

So why not just bump the MSRV to 1.86?

@tarcieri
Copy link
Member Author

tarcieri commented Oct 28, 2025

@fjarri we're trying to ship everything as 1.85 so it can be packaged on Debian stable, then bumping MSRV after that (now that there's an MSRV-aware resolver)


That said, there are several places post-1.85 features would be nice in crypto-bigint, particularly inference for const-generic trait parameters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants