Skip to content

Commit

Permalink
Merge pull request #40 from warlock-labs/LEAST_AUTHORITY_REMEDIATION
Browse files Browse the repository at this point in the history
Least authority remediation
  • Loading branch information
trbritt authored Jan 9, 2025
2 parents 0f9f55a + 36b90ab commit 84fa3fe
Show file tree
Hide file tree
Showing 20 changed files with 830 additions and 330 deletions.
25 changes: 24 additions & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ jobs:
flags: [ "--no-default-features", "", "--all-features" ]
steps:
- uses: actions/checkout@v3
- name: Install libsodium
run: |
sudo apt-get update
sudo apt-get install -y libsodium-dev
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
Expand All @@ -44,7 +48,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Install libsodium
run: |
sudo apt-get update
sudo apt-get install -y libsodium-dev
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
Expand Down Expand Up @@ -97,6 +104,10 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- name: Install libsodium
run: |
sudo apt-get update
sudo apt-get install -y libsodium-dev
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@cargo-hack
- uses: Swatinem/rust-cache@v2
Expand All @@ -110,6 +121,10 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- name: Install libsodium
run: |
sudo apt-get update
sudo apt-get install -y libsodium-dev
- uses: dtolnay/rust-toolchain@clippy
- uses: Swatinem/rust-cache@v2
with:
Expand All @@ -123,6 +138,10 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- name: Install libsodium
run: |
sudo apt-get update
sudo apt-get install -y libsodium-dev
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
with:
Expand All @@ -136,6 +155,10 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- name: Install libsodium
run: |
sudo apt-get update
sudo apt-get install -y libsodium-dev
- uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ num-traits = "0.2.19"
sha3 = "0.11.0-pre.4"
subtle = "2.6.1"
tracing = "0.1.40"
secrets = "1.2.0"

[dev-dependencies]
confy = "0.6.1"
Expand All @@ -57,6 +58,7 @@ serde_json = "1.0.127"
sha2 = "0.11.0-pre.4"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
paste = "1.0.15"

[[bench]]
name = "mod"
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ Shallue-van de Woestijne encoding for elliptic curve points.
The multiprecision arithmetic operations are implemented in constant time, ensuring resistance to side-channel attacks.
Constant-time operations are used whenever possible, and there are currently no variable-time functions used in Sylow.

Furthermore, all key generation and manipulation utilities utilize the `secrets` crate, to ensure that all keys and
signatures are `mprotect`-ed during the entirety of the application runtime.

If you discover any security issues, please report them to [[email protected]](mailto:[email protected]).

## Documentation
Expand Down
Binary file added audits/LeastAuthority_Audit_December2024.pdf
Binary file not shown.
26 changes: 14 additions & 12 deletions examples/simple_xor_ecies.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crypto_bigint::rand_core::OsRng;
use secrets::SecretBox;
use sha3::Keccak256;
use sylow::{
sign, verify, Expander, FieldExtensionTrait, Fp, Fr, G1Projective, G2Projective, GroupTrait,
Expand Down Expand Up @@ -32,22 +33,23 @@ impl ECIESParty {
}
}

pub fn get_public_key(&self) -> G2Projective {
self.key_pair.public_key
pub fn get_public_key(&self) -> SecretBox<G2Projective> {
self.key_pair.public_key.clone()
}

#[allow(clippy::type_complexity)]
#[instrument(skip(self, recipient_public_key, message), fields(message_len = message.len()))]
pub fn encrypt(
&self,
recipient_public_key: &G2Projective,
recipient_public_key: &SecretBox<G2Projective>,
message: &[u8],
) -> Result<(G1Projective, Vec<u8>, G1Projective), ECIESError> {
) -> Result<(SecretBox<G1Projective>, Vec<u8>, SecretBox<G1Projective>), ECIESError> {
debug!("Generating ephemeral key pair");
let ephemeral_private_key = Fp::new(Fr::rand(&mut OsRng).value());
let ephemeral_public_key = G1Projective::generator() * ephemeral_private_key;
let ephemeral_private_key = SecretBox::new(|s| *s = Fp::new(Fr::rand(&mut OsRng).value()));
let ephemeral_public_key =
SecretBox::new(|s| *s = G1Projective::generator() * *ephemeral_private_key.borrow());

debug!("Computing shared secret");
let shared_secret = *recipient_public_key * ephemeral_private_key;
let shared_secret = *recipient_public_key.borrow() * *ephemeral_private_key.borrow();
let encryption_key = self.derive_key(&shared_secret)?;

debug!("Encrypting message");
Expand All @@ -67,10 +69,10 @@ impl ECIESParty {
#[instrument(skip(self, ephemeral_public_key, ciphertext, signature, sender_public_key), fields(ciphertext_len = ciphertext.len()))]
pub fn decrypt(
&self,
ephemeral_public_key: &G1Projective,
ephemeral_public_key: &SecretBox<G1Projective>,
ciphertext: &[u8],
signature: &G1Projective,
sender_public_key: &G2Projective,
signature: &SecretBox<G1Projective>,
sender_public_key: &SecretBox<G2Projective>,
) -> Result<Vec<u8>, ECIESError> {
debug!("Verifying signature");
if !verify(sender_public_key, ciphertext, signature)
Expand All @@ -81,7 +83,7 @@ impl ECIESParty {
}

debug!("Computing shared secret");
let shared_secret = *ephemeral_public_key * self.key_pair.secret_key;
let shared_secret = *ephemeral_public_key.borrow() * *self.key_pair.secret_key.borrow();
let decryption_key = self.derive_key(&shared_secret)?;

debug!("Decrypting message");
Expand Down
4 changes: 3 additions & 1 deletion src/fields/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,9 @@ impl<const D: usize, const N: usize, F: FieldExtensionTrait<D, N>> Default
{
/// Returns the default value for the field extension (all zero elements).
fn default() -> Self {
Self::new(&[F::default(); N])
let mut retval = [F::zero(); N];
retval[0] = F::default();
Self::new(&retval)
}
}

Expand Down
22 changes: 0 additions & 22 deletions src/fields/fp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -643,24 +643,6 @@ impl Fp {
}
}

/// Computes the Non-Adjacent Form (NAF) representation of the field element
///
/// There is a need to at times move to a representation of the field element with
/// a lower Hamming weight, for instance in the case of multiplication of a group element by
/// such a scalar. This implements the prodinger algorithm, and returns a string of the
/// positive bits and a string of negative bits for the NAF representation
/// see <http://math.colgate.edu/~integers/a8/a8.pdf>
pub(crate) fn compute_naf(self) -> (U256, U256) {
let x = self.value();
let xh = x >> 1;
let x3 = x + xh;
let c = xh ^ x3;
let np = x3 & c;
let nm = xh & c;

(np, nm)
}

/// Converts a big-endian byte representation to a field element
///
/// This generates an element in the base field from the byte array. It could be as simple as
Expand Down Expand Up @@ -739,10 +721,6 @@ impl Fp {

/// Implements the r-torsion field elements
impl Fr {
/// Computes the Non-Adjacent Form (NAF) representation of the field element
pub(crate) fn compute_naf(self) -> (U256, U256) {
Fp::from(self).compute_naf()
}
pub fn from_be_bytes(arr: &[u8; 32]) -> CtOption<Self> {
#[inline(always)]
const fn sbb(a: u64, b: u64, borrow: u64) -> (u64, u64) {
Expand Down
88 changes: 28 additions & 60 deletions src/fields/fp12.rs
Original file line number Diff line number Diff line change
Expand Up @@ -552,26 +552,9 @@ impl Fp12 {
#[cfg(test)]
mod tests {
use super::*;
use crypto_bigint::{rand_core::OsRng, U256};
use crypto_bigint::rand_core::OsRng;
use subtle::ConstantTimeEq;

fn create_field(value: [u64; 4]) -> Fp {
Fp::new(U256::from_words(value))
}
fn create_field_extension(v: [[u64; 4]; 12]) -> Fp12 {
Fp12::new(&[
Fp6::new(&[
Fp2::new(&[create_field(v[0]), create_field(v[1])]),
Fp2::new(&[create_field(v[2]), create_field(v[3])]),
Fp2::new(&[create_field(v[4]), create_field(v[5])]),
]),
Fp6::new(&[
Fp2::new(&[create_field(v[6]), create_field(v[7])]),
Fp2::new(&[create_field(v[8]), create_field(v[9])]),
Fp2::new(&[create_field(v[10]), create_field(v[11])]),
]),
])
}
mod addition_tests {
use super::*;
#[test]
Expand Down Expand Up @@ -623,53 +606,14 @@ mod tests {

#[test]
fn test_multiplication_cases() {
let a = create_field_extension([
[1, 0, 0, 0],
[0, 2, 0, 0],
[0, 0, 3, 0],
[0, 0, 0, 4],
[5, 0, 0, 0],
[0, 6, 0, 0],
[1, 0, 0, 0],
[0, 2, 0, 0],
[0, 0, 3, 0],
[0, 0, 0, 4],
[5, 0, 0, 0],
[0, 6, 0, 0],
]);
let b = create_field_extension([
[0, 6, 0, 0],
[5, 0, 0, 0],
[0, 0, 0, 4],
[0, 0, 3, 0],
[0, 2, 0, 0],
[1, 0, 0, 0],
[0, 6, 0, 0],
[5, 0, 0, 0],
[0, 0, 0, 4],
[0, 0, 3, 0],
[0, 2, 0, 0],
[1, 0, 0, 0],
]);
let a = Fp12::rand(&mut OsRng);
let b = Fp12::rand(&mut OsRng);
assert_eq!(a.square(), a * a, "Squaring and mul failed");
assert_eq!(b.square(), b * b, "Squaring and mul failed");
}
#[test]
fn test_frobenius() {
let a = create_field_extension([
[1, 0, 0, 0],
[0, 2, 0, 0],
[0, 0, 3, 0],
[0, 0, 0, 4],
[5, 0, 0, 0],
[0, 6, 0, 0],
[1, 0, 0, 0],
[0, 2, 0, 0],
[0, 0, 3, 0],
[0, 0, 0, 4],
[5, 0, 0, 0],
[0, 6, 0, 0],
]);
let a = Fp12::rand(&mut OsRng);
assert_eq!(
a,
a.frobenius(1)
Expand Down Expand Up @@ -707,6 +651,28 @@ mod tests {
"Frobenius failed at cycle order 2"
);
}
#[test]
fn test_sparse() {
let a = Fp12::rand(&mut OsRng);
let two = Fp12::one() + Fp12::one();

let [ell0, ell_vv, ell_vw] = two.0[0].0;
// this is an element of the form, in the 2x 𝔽ₚ⁶ representation:
// f = [[g0, g1, g2], [h0, h1, h2]] = [ [2, 0, 0], [0, 0, 0]] = g + hw,
// which would then be, in the 6x 𝔽ₚ² representation
// f = g_0 + h_0w + g_1w^2 + h_1w^3 + g_2w^4 + h_2w^5
// = [2, 0, 0, 0, 0, 0]
// The elements at indices (0, 2, 4) and therefore (2, 0, 0) respectively
assert_eq!(ell0, Fp2::one() + Fp2::one(), "Index 0 extraction failed");
assert_eq!(ell_vv, Fp2::zero(), "Index 2 extraction failed");
assert_eq!(ell_vw, Fp2::zero(), "Index 4 extraction failed");

assert_eq!(
a.sparse_mul(ell0, ell_vv, ell_vw),
a * two,
"Sparse mul failed"
);
}
}
mod division_tests {
use super::*;
Expand All @@ -728,6 +694,8 @@ mod tests {

assert_eq!(a / one, a, "Division by one failed");
assert_eq!((a / b) * b, a, "Division-Mult composition failed");

assert_eq!(a.inv(), Fp12::one() / a, "Inverse failed");
}
#[test]
// #[should_panic(expected = "assertion failed: self.is_some.is_true_vartime()")]
Expand Down
38 changes: 1 addition & 37 deletions src/fields/fp2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use crate::fields::extensions::FieldExtension;
use crate::fields::fp::{FieldExtensionTrait, Fp, BN254_FP_MODULUS, FP_QUADRATIC_NON_RESIDUE};
use crypto_bigint::{rand_core::CryptoRngCore, subtle::ConditionallySelectable, U256};
use num_traits::{Inv, One, Pow, Zero};
use num_traits::{Inv, One, Zero};
use std::ops::{Div, DivAssign, Mul, MulAssign};
use subtle::{Choice, ConstantTimeEq, CtOption};

Expand Down Expand Up @@ -170,28 +170,6 @@ impl Fp2 {
Self([a * b, c * self.0[1]])
}

/// Determines if the element is a quadratic residue (square) in the field.
///
/// # Returns
///
/// A `Choice` representing whether the element is a square (1) or not (0)
pub fn is_square(&self) -> Choice {
let legendre = |x: &Fp| -> i32 {
let res = x.pow(P_MINUS_1_OVER_2.value());

if res.is_one() {
1
} else if res.is_zero() {
0
} else {
-1
}
};
let sum = self.0[0].square() + FP_QUADRATIC_NON_RESIDUE * (-self.0[0]).square();
tracing::trace!(?sum, "Fp2::is_square");
Choice::from((legendre(&sum) != -1) as u8)
}

/// Converts a byte array to an 𝔽ₚ² element.
///
/// # Arguments
Expand Down Expand Up @@ -745,20 +723,6 @@ mod tests {
let _ = a / zero;
}
}
mod square_tests {
use super::*;

#[test]
fn test_square() {
use crypto_bigint::rand_core::OsRng;

for _ in 0..100 {
let a = <Fp2 as FieldExtensionTrait<2, 2>>::rand(&mut OsRng);
let b = a.square();
assert!(bool::from(b.is_square()), "Is square failed");
}
}
}
#[test]
fn test_conditional_select() {
let a = create_field_extension([4, 3, 2, 1], [1, 1, 1, 1]);
Expand Down
Loading

0 comments on commit 84fa3fe

Please sign in to comment.