CVE-2019-20399: Observable Discrepancy (CWE-203)
value: A timing vulnerability in the Scalar::check_overflow function in Parity libsecp256k1-rs before 0.3.1 potentially allows an attacker to leak information via a side-channel attack.
CWE description: The product behaves differently or sends different responses under different circumstances in a way that is observable to an unauthorized actor, which exposes security-relevant information about the state of the product, such as whether a particular operation was successful or not.
CVSS v3 base score: 5.9
Published: 2020-01-23T00:15Z
Repository: https://github.com/paritytech/libsecp256k1
Language: Rust
Files changed: Cargo.toml, src/lib.rs, src/scalar.rs
Commit date: 2019-10-02 18:31:32 -0700
Commit message: Prevent side channel leak in Scala::check_overflow

Diff: diff --git a/Cargo.toml b/Cargo.toml
index 478bfd4..5c5fa95 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,10 +1,10 @@
 [package]
 name = "libsecp256k1"
 description = "Pure Rust secp256k1 implementation."
 license = "Apache-2.0"
-version = "0.3.0"
+version = "0.4.0"
 authors = ["Wei Tang <hi@that.world>"]
 repository = "https://github.com/sorpaas/libsecp256k1-rs"
 keywords = [ "crypto", "ECDSA", "secp256k1", "bitcoin", "no_std" ]
 edition = "2018"
 
@@ -16,16 +16,21 @@ rand = { version = "0.6", default-features = false }
 hmac-drbg = "0.2"
 sha2 = "0.8"
 digest = "0.8"
 typenum = "1.11"
 arrayref = "0.3"
+subtle = { version = "2.2", default-features = false }
 
 [dev-dependencies]
 secp256k1-test = "0.7"
 clear_on_drop = "0.2"
 rand-test = { package = "rand", version = "0.4" }
 
+[features]
+default = ["std"]
+std = ["subtle/std", "rand/std"]
+
 [workspace]
 members = [
   "./gen/ecmult",
   "./gen/genmult",
 ]
diff --git a/src/lib.rs b/src/lib.rs
index 9dd0007..d57f38a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,11 +5,11 @@
 #![deny(unused_import_braces, unused_imports,
         unused_comparisons, unused_must_use,
         unused_variables, non_shorthand_field_patterns,
         unreachable_code, unused_parens)]
 
-#![no_std]
+#![cfg_attr(not(feature = "std"), no_std)]
 
 #[macro_use]
 mod field;
 #[macro_use]
 mod group;
diff --git a/src/scalar.rs b/src/scalar.rs
index e59eb2b..39c5971 100644
--- a/src/scalar.rs
+++ b/src/scalar.rs
@@ -1,6 +1,7 @@
 use core::ops::{Add, AddAssign, Mul, MulAssign};
+use subtle::Choice;
 
 const SECP256K1_N_0: u32 = 0xD0364141;
 const SECP256K1_N_1: u32 = 0xBFD25E8C;
 const SECP256K1_N_2: u32 = 0xAF48A03B;
 const SECP256K1_N_3: u32 = 0xBAAEDCE6;
@@ -67,25 +68,25 @@ impl Scalar {
         }
     }
 
     #[must_use]
     fn check_overflow(&self) -> bool {
-        let mut yes: bool = false;
-        let mut no: bool = false;
-        no = no || (self.0[7] < SECP256K1_N_7); /* No need for a > check. */
-        no = no || (self.0[6] < SECP256K1_N_6); /* No need for a > check. */
-        no = no || (self.0[5] < SECP256K1_N_5); /* No need for a > check. */
-        no = no || (self.0[4] < SECP256K1_N_4);
-        yes = yes || ((self.0[4] > SECP256K1_N_4) && !no);
-        no = no || ((self.0[3] < SECP256K1_N_3) && !yes);
-        yes = yes || ((self.0[3] > SECP256K1_N_3) && !no);
-        no = no || ((self.0[2] < SECP256K1_N_2) && !yes);
-        yes = yes || ((self.0[2] > SECP256K1_N_2) && !no);
-        no = no || ((self.0[1] < SECP256K1_N_1) && !yes);
-        yes = yes || ((self.0[1] > SECP256K1_N_1) && !no);
-        yes = yes || ((self.0[0] >= SECP256K1_N_0) && !no);
-        return yes;
+        let mut yes: Choice = 0.into();
+        let mut no: Choice = 0.into();
+        no |= Choice::from((self.0[7] < SECP256K1_N_7) as u8); /* No need for a > check. */
+        no |= Choice::from((self.0[6] < SECP256K1_N_6) as u8); /* No need for a > check. */
+        no |= Choice::from((self.0[5] < SECP256K1_N_5) as u8); /* No need for a > check. */
+        no |= Choice::from((self.0[4] < SECP256K1_N_4) as u8);
+        yes |= Choice::from((self.0[4] > SECP256K1_N_4) as u8) & !no;
+        no |= Choice::from((self.0[3] < SECP256K1_N_3) as u8) & !yes;
+        yes |= Choice::from((self.0[3] > SECP256K1_N_3) as u8) & !no;
+        no |= Choice::from((self.0[2] < SECP256K1_N_2) as u8) & !yes;
+        yes |= Choice::from((self.0[2] > SECP256K1_N_2) as u8) & !no;
+        no |= Choice::from((self.0[1] < SECP256K1_N_1) as u8) & !yes;
+        yes |= Choice::from((self.0[1] > SECP256K1_N_1) as u8) & !no;
+        yes |= Choice::from((self.0[0] >= SECP256K1_N_0) as u8) & !no;
+        return yes.into();
     }
 
     #[must_use]
     fn reduce(&mut self, overflow: bool) -> bool {
         let o: u64 = if overflow { 1 } else { 0 };

Vulnerable code: version = "0.3.0"
#![no_std]
        let mut yes: bool = false;
        let mut no: bool = false;
        no = no || (self.0[7] < SECP256K1_N_7); /* No need for a > check. */
        no = no || (self.0[6] < SECP256K1_N_6); /* No need for a > check. */
        no = no || (self.0[5] < SECP256K1_N_5); /* No need for a > check. */
        no = no || (self.0[4] < SECP256K1_N_4);
        yes = yes || ((self.0[4] > SECP256K1_N_4) && !no);
        no = no || ((self.0[3] < SECP256K1_N_3) && !yes);
        yes = yes || ((self.0[3] > SECP256K1_N_3) && !no);
        no = no || ((self.0[2] < SECP256K1_N_2) && !yes);
        yes = yes || ((self.0[2] > SECP256K1_N_2) && !no);
        no = no || ((self.0[1] < SECP256K1_N_1) && !yes);
        yes = yes || ((self.0[1] > SECP256K1_N_1) && !no);
        yes = yes || ((self.0[0] >= SECP256K1_N_0) && !no);
        return yes;
Fixed code: version = "0.4.0"
subtle = { version = "2.2", default-features = false }
[features]
default = ["std"]
std = ["subtle/std", "rand/std"]

#![cfg_attr(not(feature = "std"), no_std)]
use subtle::Choice;
        let mut yes: Choice = 0.into();
        let mut no: Choice = 0.into();
        no |= Choice::from((self.0[7] < SECP256K1_N_7) as u8); /* No need for a > check. */
        no |= Choice::from((self.0[6] < SECP256K1_N_6) as u8); /* No need for a > check. */
        no |= Choice::from((self.0[5] < SECP256K1_N_5) as u8); /* No need for a > check. */
        no |= Choice::from((self.0[4] < SECP256K1_N_4) as u8);
        yes |= Choice::from((self.0[4] > SECP256K1_N_4) as u8) & !no;
        no |= Choice::from((self.0[3] < SECP256K1_N_3) as u8) & !yes;
        yes |= Choice::from((self.0[3] > SECP256K1_N_3) as u8) & !no;
        no |= Choice::from((self.0[2] < SECP256K1_N_2) as u8) & !yes;
        yes |= Choice::from((self.0[2] > SECP256K1_N_2) as u8) & !no;
        no |= Choice::from((self.0[1] < SECP256K1_N_1) as u8) & !yes;
        yes |= Choice::from((self.0[1] > SECP256K1_N_1) as u8) & !no;
        yes |= Choice::from((self.0[0] >= SECP256K1_N_0) as u8) & !no;
        return yes.into();