From f84779bf9d700cf366fedadf562174060a65b12c Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Mon, 8 May 2023 19:59:38 +1000 Subject: [PATCH 01/18] Moved primitives into separate subcrate --- Cargo.toml | 26 +++---------- core/Cargo.toml | 37 +++++++++++++++++++ {benches => core/benches}/oreaes128.rs | 0 {examples => core/examples}/encrypt.rs | 0 {src => core/src}/ciphertext.rs | 2 +- {src => core/src}/convert.rs | 0 {src => core/src}/encrypt.rs | 0 {src => core/src}/lib.rs | 2 +- {src => core/src}/scheme.rs | 0 {src => core/src}/scheme/bit2.rs | 8 ++-- {src => core/src}/scheme/bit2/block_types.rs | 2 +- primitives/Cargo.toml | 12 ++++++ {src/primitives => primitives/src}/hash.rs | 2 +- src/primitives.rs => primitives/src/lib.rs | 0 primitives/src/main.rs | 3 ++ {src/primitives => primitives/src}/prf.rs | 2 +- {src/primitives => primitives/src}/prp.rs | 4 +- .../primitives => primitives/src}/prp/prng.rs | 0 18 files changed, 69 insertions(+), 31 deletions(-) create mode 100644 core/Cargo.toml rename {benches => core/benches}/oreaes128.rs (100%) rename {examples => core/examples}/encrypt.rs (100%) rename {src => core/src}/ciphertext.rs (99%) rename {src => core/src}/convert.rs (100%) rename {src => core/src}/encrypt.rs (100%) rename {src => core/src}/lib.rs (99%) rename {src => core/src}/scheme.rs (100%) rename {src => core/src}/scheme/bit2.rs (99%) rename {src => core/src}/scheme/bit2/block_types.rs (98%) create mode 100644 primitives/Cargo.toml rename {src/primitives => primitives/src}/hash.rs (98%) rename src/primitives.rs => primitives/src/lib.rs (100%) create mode 100644 primitives/src/main.rs rename {src/primitives => primitives/src}/prf.rs (97%) rename {src/primitives => primitives/src}/prp.rs (96%) rename {src/primitives => primitives/src}/prp/prng.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index dca9d3a..e10966f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,11 @@ -[package] -name = "ore-rs" -version = "0.7.0" -authors = ["Dan Draper "] -edition = "2018" -homepage = "https://cipherstash.com" -description = "Order-revealing encryption library used by the CipherStash searchable encryption platform" -license-file = "LICENCE" +[workspace] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dev-dependencies] -criterion = "0.3.5" -quickcheck = "1.0.3" +members = [ + "core", + "primitives", +] -[dependencies] +[workspace.dependencies] aes = { version = "0.8.2", features = ["zeroize"]} block-modes = "0.8.1" byteorder = "1.4.3" @@ -25,10 +18,3 @@ subtle-ng = "2.5.0" zeroize = { version = "1.5.7", features = [ "zeroize_derive", "alloc" ] } lazy_static = "1.4.0" thiserror = "1.0.38" - -[[bench]] -name = "oreaes128" -harness = false - -[[example]] -name = "encrypt" diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..6dea96b --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "ore-rs" +version = "0.7.0" +authors = ["Dan Draper "] +edition = "2018" +homepage = "https://cipherstash.com" +description = "Order-revealing encryption library used by the CipherStash searchable encryption platform" +license-file = "LICENCE" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dev-dependencies] +criterion = "0.3.5" +quickcheck = "1.0.3" + +[dependencies] +primitives = { path = "../primitives" } +aes = { version = "0.8.2", features = ["zeroize"]} +block-modes = "0.8.1" +byteorder = "1.4.3" +hex-literal = "0.3.2" +rand = "0.8.5" +rand_chacha = "0.3.1" +num = "0.4.0" +hex = "0.4.3" +subtle-ng = "2.5.0" +zeroize = { version = "1.5.7", features = [ "zeroize_derive", "alloc" ] } +lazy_static = "1.4.0" +thiserror = "1.0.38" + + + +[[bench]] +name = "oreaes128" +harness = false + +[[example]] +name = "encrypt" diff --git a/benches/oreaes128.rs b/core/benches/oreaes128.rs similarity index 100% rename from benches/oreaes128.rs rename to core/benches/oreaes128.rs diff --git a/examples/encrypt.rs b/core/examples/encrypt.rs similarity index 100% rename from examples/encrypt.rs rename to core/examples/encrypt.rs diff --git a/src/ciphertext.rs b/core/src/ciphertext.rs similarity index 99% rename from src/ciphertext.rs rename to core/src/ciphertext.rs index 43c93f3..782b5a5 100644 --- a/src/ciphertext.rs +++ b/core/src/ciphertext.rs @@ -1,6 +1,6 @@ use thiserror::Error; -use crate::primitives::NONCE_SIZE; +use primitives::NONCE_SIZE; pub use crate::OreCipher; /// The trait of any encryption output (either Left, Right or combined). diff --git a/src/convert.rs b/core/src/convert.rs similarity index 100% rename from src/convert.rs rename to core/src/convert.rs diff --git a/src/encrypt.rs b/core/src/encrypt.rs similarity index 100% rename from src/encrypt.rs rename to core/src/encrypt.rs diff --git a/src/lib.rs b/core/src/lib.rs similarity index 99% rename from src/lib.rs rename to core/src/lib.rs index 1dd3814..ed2b16a 100644 --- a/src/lib.rs +++ b/core/src/lib.rs @@ -137,7 +137,7 @@ mod ciphertext; mod convert; mod encrypt; -mod primitives; +//mod primitives; pub mod scheme; pub use crate::ciphertext::*; pub use crate::encrypt::OreEncrypt; diff --git a/src/scheme.rs b/core/src/scheme.rs similarity index 100% rename from src/scheme.rs rename to core/src/scheme.rs diff --git a/src/scheme/bit2.rs b/core/src/scheme/bit2.rs similarity index 99% rename from src/scheme/bit2.rs rename to core/src/scheme/bit2.rs index ef5c87f..e08defa 100644 --- a/src/scheme/bit2.rs +++ b/core/src/scheme/bit2.rs @@ -2,12 +2,12 @@ * Block ORE Implemenation using a 2-bit indicator function */ +use primitives::{ + hash::Aes128Z2Hash, prf::Aes128Prf, prp::KnuthShufflePRP, AesBlock, Hash, HashKey, Prf, + Prp, NONCE_SIZE, +}; use crate::{ ciphertext::*, - primitives::{ - hash::Aes128Z2Hash, prf::Aes128Prf, prp::KnuthShufflePRP, AesBlock, Hash, HashKey, Prf, - Prp, NONCE_SIZE, - }, OreCipher, OreError, PlainText, }; diff --git a/src/scheme/bit2/block_types.rs b/core/src/scheme/bit2/block_types.rs similarity index 98% rename from src/scheme/bit2/block_types.rs rename to core/src/scheme/bit2/block_types.rs index 8811003..ae06b99 100644 --- a/src/scheme/bit2/block_types.rs +++ b/core/src/scheme/bit2/block_types.rs @@ -1,7 +1,7 @@ use zeroize::Zeroize; use crate::ciphertext::{CipherTextBlock, ParseError}; -use crate::primitives::AesBlock; +use primitives::AesBlock; pub type LeftBlock16 = AesBlock; diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml new file mode 100644 index 0000000..1c7ee4b --- /dev/null +++ b/primitives/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "primitives" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +thiserror = { workspace = true } +aes = { workspace = true } +zeroize = { workspace = true } +hex-literal = { workspace = true } \ No newline at end of file diff --git a/src/primitives/hash.rs b/primitives/src/hash.rs similarity index 98% rename from src/primitives/hash.rs rename to primitives/src/hash.rs index afe1582..09e79ed 100644 --- a/src/primitives/hash.rs +++ b/primitives/src/hash.rs @@ -1,4 +1,4 @@ -use crate::primitives::{AesBlock, Hash, HashKey}; +use crate::{AesBlock, Hash, HashKey}; use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}; use aes::Aes128; use zeroize::ZeroizeOnDrop; diff --git a/src/primitives.rs b/primitives/src/lib.rs similarity index 100% rename from src/primitives.rs rename to primitives/src/lib.rs diff --git a/primitives/src/main.rs b/primitives/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/primitives/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/primitives/prf.rs b/primitives/src/prf.rs similarity index 97% rename from src/primitives/prf.rs rename to primitives/src/prf.rs index 2edf565..b30a0cf 100644 --- a/src/primitives/prf.rs +++ b/primitives/src/prf.rs @@ -1,4 +1,4 @@ -use crate::primitives::{AesBlock, Prf, PrfKey}; +use crate::{AesBlock, Prf, PrfKey}; use aes::cipher::{BlockEncrypt, KeyInit}; use aes::Aes128; use zeroize::ZeroizeOnDrop; diff --git a/src/primitives/prp.rs b/primitives/src/prp.rs similarity index 96% rename from src/primitives/prp.rs rename to primitives/src/prp.rs index ff41320..15efc00 100644 --- a/src/primitives/prp.rs +++ b/primitives/src/prp.rs @@ -1,6 +1,6 @@ pub mod prng; -use crate::primitives::prp::prng::Aes128Prng; -use crate::primitives::{Prp, PrpError, PrpResult}; +use crate::prp::prng::Aes128Prng; +use crate::{Prp, PrpError, PrpResult}; use std::convert::TryFrom; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/src/primitives/prp/prng.rs b/primitives/src/prp/prng.rs similarity index 100% rename from src/primitives/prp/prng.rs rename to primitives/src/prp/prng.rs From 505aa665ea055035c687e324abf510d26bcdbffb Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Tue, 9 May 2023 09:25:49 +0800 Subject: [PATCH 02/18] Some refactoring of the primitives --- primitives/src/lib.rs | 3 ++- primitives/src/prf.rs | 29 +++++++++++++++++++---------- primitives/src/prp.rs | 14 +++++++------- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index d304618..c35ac21 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -4,6 +4,7 @@ pub mod prp; use aes::cipher::{consts::U16, generic_array::GenericArray}; use aes::Block; +use prf::PrfBlock; use thiserror::Error; pub type AesBlock = Block; pub type PrfKey = GenericArray; @@ -12,7 +13,7 @@ pub const NONCE_SIZE: usize = 16; pub trait Prf { fn new(key: &PrfKey) -> Self; - fn encrypt_all(&self, data: &mut [AesBlock]); + fn encrypt_all(&self, data: &mut [PrfBlock]); } pub trait Hash { diff --git a/primitives/src/prf.rs b/primitives/src/prf.rs index b30a0cf..6467f6d 100644 --- a/primitives/src/prf.rs +++ b/primitives/src/prf.rs @@ -2,6 +2,14 @@ use crate::{AesBlock, Prf, PrfKey}; use aes::cipher::{BlockEncrypt, KeyInit}; use aes::Aes128; use zeroize::ZeroizeOnDrop; +use std::slice; + +pub type PrfBlock = [u8; 16]; + +fn convert_slice<'a>(input: &'a mut [PrfBlock]) -> &'a mut [AesBlock] { + let ptr = input.as_mut_ptr() as *mut AesBlock; + unsafe { slice::from_raw_parts_mut(ptr, input.len()) } +} #[derive(Debug, ZeroizeOnDrop)] pub struct Aes128Prf { @@ -14,20 +22,20 @@ pub struct Aes128Prf { */ impl Prf for Aes128Prf { fn new(key: &PrfKey) -> Self { - //let key_array = GenericArray::from_slice(key); let cipher = Aes128::new(key); Self { cipher } } - fn encrypt_all(&self, data: &mut [AesBlock]) { - self.cipher.encrypt_blocks(data); + fn encrypt_all(&self, data: &mut [PrfBlock]) { + let blocks = convert_slice(data); + self.cipher.encrypt_blocks(blocks); } } #[cfg(test)] mod tests { use super::*; - use aes::cipher::generic_array::{arr, GenericArray}; + use aes::cipher::generic_array::GenericArray; use hex_literal::hex; fn init_prf() -> Aes128Prf { @@ -38,21 +46,22 @@ mod tests { #[test] fn prf_test_single_block() { - let mut input = [arr![u8; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 170]]; + let mut input = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 170]]; + let prf = init_prf(); prf.encrypt_all(&mut input); assert_eq!( input, - [arr![u8; 183, 103, 151, 211, 249, 253, 170, 135, 117, 243, 131, 50, 27, 15, 170, 59]] + [[183, 103, 151, 211, 249, 253, 170, 135, 117, 243, 131, 50, 27, 15, 170, 59]] ); } #[test] fn prf_test_2_blocks() { let mut input = [ - arr![u8; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 170], - arr![u8; 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 170, 255, 221, 97, 170], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 170], + [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 170, 255, 221, 97, 170], ]; let prf = init_prf(); @@ -60,8 +69,8 @@ mod tests { assert_eq!( input, [ - arr![u8; 183, 103, 151, 211, 249, 253, 170, 135, 117, 243, 131, 50, 27, 15, 170, 59], - arr![u8; 100, 192, 41, 108, 208, 245, 146, 251, 188, 245, 156, 28, 33, 210, 70, 50] + [183, 103, 151, 211, 249, 253, 170, 135, 117, 243, 131, 50, 27, 15, 170, 59], + [100, 192, 41, 108, 208, 245, 146, 251, 188, 245, 156, 28, 33, 210, 70, 50] ] ); } diff --git a/primitives/src/prp.rs b/primitives/src/prp.rs index 15efc00..032dcb7 100644 --- a/primitives/src/prp.rs +++ b/primitives/src/prp.rs @@ -20,25 +20,25 @@ impl Drop for KnuthShufflePRP { // Impl the ZeroizeOnDrop marker trait since we're zeroizing above impl ZeroizeOnDrop for KnuthShufflePRP {} -impl Prp for KnuthShufflePRP { +impl Prp for KnuthShufflePRP { /* - * Initialize an 8-bit (256 element) PRP using a KnuthShuffle - * and a 64-bit random seed + * Initialize an 8-bit (N element) PRP using a KnuthShuffle */ fn new(key: &[u8]) -> PrpResult { + assert!(N <= 256); let mut rng = Aes128Prng::init(key); // TODO: Use Result type here, too let mut perm = Self { - permutation: [0u8; 256], - inverse: [0u8; 256], + permutation: [0u8; N], + inverse: [0u8; N], }; // Initialize values - for i in 0..=255 { + for i in 0..N { perm.permutation[i] = i as u8; } - (0..=255usize).into_iter().rev().for_each(|i| { + (0..N).into_iter().rev().for_each(|i| { let j = rng.gen_range(i as u8); perm.permutation.swap(i, j as usize); }); From d01f13ae912ea2ddd3d7b04e8fb039e08eb54c8a Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Tue, 9 May 2023 09:26:38 +0800 Subject: [PATCH 03/18] First steps on 5-bit impl --- Cargo.toml | 1 + ore-rs-5bit/Cargo.toml | 19 ++++++ ore-rs-5bit/src/lib.rs | 117 +++++++++++++++++++++++++++++++++++++ ore-rs-5bit/src/main.rs | 39 +++++++++++++ ore-rs-5bit/src/packing.rs | 55 +++++++++++++++++ 5 files changed, 231 insertions(+) create mode 100644 ore-rs-5bit/Cargo.toml create mode 100644 ore-rs-5bit/src/lib.rs create mode 100644 ore-rs-5bit/src/main.rs create mode 100644 ore-rs-5bit/src/packing.rs diff --git a/Cargo.toml b/Cargo.toml index e10966f..b170143 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "core", "primitives", + "ore-rs-5bit", ] [workspace.dependencies] diff --git a/ore-rs-5bit/Cargo.toml b/ore-rs-5bit/Cargo.toml new file mode 100644 index 0000000..173d88f --- /dev/null +++ b/ore-rs-5bit/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ore-rs-5bit" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +primitives = { path = "../primitives" } +zeroize = { workspace = true } +rand = { workspace = true } +rand_chacha = { workspace = true } +aes = { workspace = true } + +# TODO: Only use this in examples +hex-literal = "0.3.2" +cmac = "0.7.2" +bit-vec = "0.6.3" +hex.workspace = true diff --git a/ore-rs-5bit/src/lib.rs b/ore-rs-5bit/src/lib.rs new file mode 100644 index 0000000..9e1e2ac --- /dev/null +++ b/ore-rs-5bit/src/lib.rs @@ -0,0 +1,117 @@ +use std::cell::RefCell; +use primitives::{prf::{Aes128Prf, PrfBlock}, Prf, prp::KnuthShufflePRP, Prp, AesBlock}; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use zeroize::ZeroizeOnDrop; +use aes::{cipher::generic_array::GenericArray, Aes128}; +use cmac::{Cmac, Mac, digest::FixedOutput}; + +use crate::packing::packed_prefixes; + +pub mod packing; + +#[derive(Debug, ZeroizeOnDrop)] +pub struct Ore5Bit { + // TODO: Temp + k2: [u8; 16], + + prf1: Aes128Prf, + prf2: Aes128Prf, + #[zeroize(skip)] + rng: RefCell, +} + +// TODO: use a trait type +#[derive(Debug)] +pub struct OreError; + +pub type Ore5BitChaCha20 = Ore5Bit; + +#[derive(Debug)] +pub struct Out(Vec, u8); + +// TODO: Include block prefixes +pub fn cmac(key: &[u8; 16], input: &[u8]) -> PrfBlock { + let mut out: PrfBlock = Default::default(); + let mut buf = GenericArray::from_mut_slice(&mut out); + // TODO: This might be really inefficient! + let mut mac = Cmac::::new_from_slice(key).unwrap(); + mac.update(input); + mac.finalize_into(&mut buf); + //result.into_bytes().to_vec() + out +} + +// TODO: Make this use the ORE traits once we've cleaned these up +impl Ore5Bit { + // TODO: This should be an implementation of OreInit + pub fn init(k1: &[u8; 16], k2: &[u8; 16]) -> Result { + // TODO: k1 and k2 should be Key types and we should have a set of traits to abstract the + // behaviour ro parsing/loading etc + + let rng: R = SeedableRng::from_entropy(); + + return Ok(Self { + k2: k2.clone(), + prf1: Prf::new(GenericArray::from_slice(k1)), + prf2: Prf::new(GenericArray::from_slice(k2)), + rng: RefCell::new(rng), + }); + } + + /// Takes a slice of 5-bit values (represented by a slice of `u8` but the + /// most significant 3-bits of each value are ignored). + /// TODO: Create a wrapper type + /// TODO: This might be faster if we do it blocks of statically allocated chunks + pub fn encrypt_left(&self, input: &[u8]) -> Vec { + // TODO: Can we pack the input bytes?? + debug_assert!(input.len() <= 16); + // TODO: We could possibly use the stack and just do a single extend on to a vec after each round + // Format: [, , ] + let mut out: Vec = Vec::new(); // TODO: What capacity? + + // Here we'll model a PRF using a single block of AES + // This will be OK for up to 16-bytes of input (or 25 5-bit values) + // For larger inputs we can chain the values by XORing the last output + // with the next input (a little like CMAC). + let mut output_blocks = packed_prefixes(input); + self.prf2.encrypt_all(&mut output_blocks); + out.push(output_blocks.len().try_into().unwrap()); // TODO: DOn't unwrap + + // This deviates from the paper slightly. + // Instead of calling PRF1 with the plaintext prefix, we call it + // with the output of the PRF2 of the prefix. + // This avoids a copy and should have the same effect. + for (output_block, input_block) in output_blocks.iter_mut().zip(input.iter()) { + let prp: KnuthShufflePRP = Prp::new(output_block).unwrap(); // TODO: + let p_n = prp.permute(*input_block).unwrap(); + out.push(p_n); + output_block[15] = p_n; + } + + self.prf1.encrypt_all(&mut output_blocks); + for output_block in output_blocks { + out.extend_from_slice(&output_block); + } + + out + } + + // For the right encryption we could either use the approach that we do in the current version, + // or instead of doing a comparison of every number, we set all bits >= the input to 1 and then do + // a bitwise permutation. + // Current approach: + // - Permute + // - Traverse permutation + // - N comparisons (which may not be constant time!) + // - N bit sets (left shift, or) + // Proposed approach: + // - Set all bits to one (>= the plaintext) + // - Bitwise permute: N bit get, N bit set (we avoid the comparisons!) +} + + +#[cfg(test)] +mod tests { + use super::*; +} diff --git a/ore-rs-5bit/src/main.rs b/ore-rs-5bit/src/main.rs new file mode 100644 index 0000000..3df4bcd --- /dev/null +++ b/ore-rs-5bit/src/main.rs @@ -0,0 +1,39 @@ +use hex_literal::hex; +use ore_rs_5bit::{Ore5BitChaCha20, cmac, packing::packed_prefixes}; + +fn permute_u32(input: u32, perm: &[usize; 32]) -> u32 { + let mut output: u32 = 0; + + for (i, &p) in perm.iter().enumerate() { + // Extract the bit from the input value at the index specified by the permutation array + let bit = (input >> p) & 1; + + // Set the bit in the output value at the corresponding index + output |= bit << i; + } + + output +} + +fn main() { + let input: u32 = 0b11010101_10101010_11001100_00110011; + let permutation: [usize; 32] = [ + 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, + ]; + + let output = permute_u32(input, &permutation); + println!("Input: {:032b}", input); + println!("Output: {:032b}", output); +} + +/* +fn main() { + let k1 = hex!("00010203 04050607 08090a0b 0c0d0e0f"); + let k2 = hex!("d0d007a5 3f9a6848 83bc1f21 0f6595a3"); + + let input = vec![7, 23, 30, 2, 13, 6]; + let ore = Ore5BitChaCha20::init(&k1, &k2).unwrap(); + let o = ore.encrypt_left(&input); + println!("{}", hex::encode(o)); +}*/ \ No newline at end of file diff --git a/ore-rs-5bit/src/packing.rs b/ore-rs-5bit/src/packing.rs new file mode 100644 index 0000000..57d9198 --- /dev/null +++ b/ore-rs-5bit/src/packing.rs @@ -0,0 +1,55 @@ +use bit_vec::BitVec; +use primitives::prf::PrfBlock; + +// TODO: Probably more efficient to code by hand +// Or use bitvec or bitvec-simd +// TODO: Also include the index (like in the original implementation) +pub fn packed_prefixes(slice: &[u8]) -> Vec { + let mut bit_vec = BitVec::new(); + let mut prefixes: Vec = Vec::with_capacity((slice.len() * 8 / 5) + 1); + for &value in slice { + let mut fblock: PrfBlock = Default::default(); + for i in 0..5 { + bit_vec.push(value & (1 << i) != 0); + } + let bytes = bit_vec.to_bytes(); + fblock[0..bytes.len()].copy_from_slice(&bytes); + prefixes.push(fblock); + } + prefixes +} + + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pack_unpack() { + let input = vec![7, 23, 30, 2, 19, 1]; + let packed = packed_prefixes(&input); + println!("Prefixes: {:?}", packed); + + assert!(false); + //assert_eq!(input, unpack_u8_slice_bitvec(&packed, input.len())); + } + + fn unpack_u8_slice_bitvec(packed: &[u8], count: usize) -> Vec { + let bit_vec = BitVec::from_bytes(packed); + let mut unpacked = Vec::with_capacity(count); + + for i in 0..count { + let mut value = 0u8; + for j in 0..5 { + if bit_vec[i * 5 + j] { + value |= 1 << j; + } + } + unpacked.push(value); + } + + unpacked + } +} + From 55b7f3dd1bbf7c1cd18aea4a69e2e2c803622873 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Sat, 13 May 2023 14:10:47 +1000 Subject: [PATCH 04/18] Added bitwise to PRP --- primitives/src/prp/bitwise.rs | 89 +++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 primitives/src/prp/bitwise.rs diff --git a/primitives/src/prp/bitwise.rs b/primitives/src/prp/bitwise.rs new file mode 100644 index 0000000..62fb501 --- /dev/null +++ b/primitives/src/prp/bitwise.rs @@ -0,0 +1,89 @@ +use crate::{Prp, NewPrp}; + +pub trait BitwisePrp: Sized { + fn shuffle(self, prp: &NewPrp) -> Self; + fn inverse_shuffle(self, prp: &NewPrp) -> Self; +} + +impl BitwisePrp<32> for u32 { + fn shuffle(self, prp: &NewPrp) -> Self { + let mut output: Self = 0; + + for (i, &p) in prp.forward() { + let bit = (self >> p) & 1; + output |= bit << i; + } + + output + } + + fn inverse_shuffle(self, prp: &NewPrp) -> Self { + let mut output: Self = 0; + + for (i, &p) in prp.inverse() { + let bit = (self >> p) & 1; + output |= bit << i; + } + + output + } +} + +impl BitwisePrp<8> for u8 { + fn shuffle(self, prp: &NewPrp) -> Self { + let mut output: Self = 0; + + for (i, &p) in prp.forward() { + let bit = (self >> p) & 1; + output |= bit << i; + } + + output + } + + fn inverse_shuffle(self, prp: &NewPrp) -> Self { + let mut output: Self = 0; + + for (i, &p) in prp.inverse() { + let bit = (self >> p) & 1; + output |= bit << i; + } + + output + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::PrpGenerator; + + struct StaticPrpGenerator { + perm: [u8; 8] + } + + impl PrpGenerator for StaticPrpGenerator { + fn generate(self) -> crate::NewPrp { + let mut inverse: [u8; 8] = [0; 8]; + + for (index, val) in self.perm.iter().enumerate() { + inverse[*val as usize] = index as u8; + } + crate::NewPrp { + forward: self.perm, + inverse + } + } + } + + #[test] + fn test_forward() { + let gen = StaticPrpGenerator { + perm: [1, 3, 2, 7, 6, 4, 0, 5] + }; + let prp = PrpGenerator::generate(gen); + let input = 0b00110110u8; + assert_eq!(input, input.shuffle(&prp).inverse_shuffle(&prp)); + assert_eq!(0b10100101, input.shuffle(&prp)); + } +} \ No newline at end of file From 8efc9173efa09966f4329b86b087e8d6b27f5d39 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Sat, 13 May 2023 14:11:43 +1000 Subject: [PATCH 05/18] Work on improved interfaces to primitives --- primitives/Cargo.toml | 3 +- primitives/src/hash.rs | 90 ++++++++++++++++++++++++++++++++++++++++-- primitives/src/lib.rs | 72 ++++++++++++++++++++++++++++++++- primitives/src/prp.rs | 8 ++++ 4 files changed, 168 insertions(+), 5 deletions(-) diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 1c7ee4b..dd29688 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -9,4 +9,5 @@ edition = "2021" thiserror = { workspace = true } aes = { workspace = true } zeroize = { workspace = true } -hex-literal = { workspace = true } \ No newline at end of file +hex-literal = { workspace = true } +num-traits = "0.2.15" diff --git a/primitives/src/hash.rs b/primitives/src/hash.rs index 09e79ed..fcc5eeb 100644 --- a/primitives/src/hash.rs +++ b/primitives/src/hash.rs @@ -1,13 +1,36 @@ +use std::slice; use crate::{AesBlock, Hash, HashKey}; use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}; use aes::Aes128; use zeroize::ZeroizeOnDrop; +pub type HashBlock = [u8; 16]; + +fn convert_slice<'a>(input: &'a [HashBlock]) -> &'a mut [AesBlock] { + let ptr = input.as_ptr() as *mut AesBlock; + unsafe { slice::from_raw_parts_mut(ptr, input.len()) } +} + #[derive(ZeroizeOnDrop)] pub struct Aes128Z2Hash { cipher: Aes128, } +impl Aes128Z2Hash { + pub fn hash_all_onto_u32(&self, data: &[HashBlock]) -> u32 { + assert!(data.len() <= 32); + let mut out: u32 = 0; + let mut blocks = convert_slice(data); + self.cipher.encrypt_blocks(&mut blocks); + + for (i, block) in blocks.iter().enumerate() { + out |= ((block[0] & 1u8) as u32) << i; + } + + out + } +} + impl Hash for Aes128Z2Hash { fn new(key: &HashKey) -> Self { let key_array = GenericArray::from_slice(key); @@ -32,10 +55,11 @@ impl Hash for Aes128Z2Hash { } // TODO: this mutates - see how much a copy effects performance (clone_from_slice) - fn hash_all(&self, data: &mut [AesBlock]) -> Vec { - self.cipher.encrypt_blocks(data); + fn hash_all(&self, data: &mut [HashBlock]) -> Vec { + let mut blocks = convert_slice(data); + self.cipher.encrypt_blocks(&mut blocks); - let mut vec = Vec::with_capacity(data.len()); + let mut vec = Vec::with_capacity(blocks.len()); for &mut block in data { // Output is Z2 (1-bit) vec.push(block[0] & 1u8); @@ -89,4 +113,64 @@ mod tests { hash.hash(&input); } + + #[test] + fn hash_all_onto_u32_one_elem() { + let hash = init_hash(); + + let mut input: [[u8; 16]; 1] = [ + hex!("00110211 04050607 08090a0b 0c0d0e0f"), + ]; + let res = hash.hash_all_onto_u32(&mut input); + assert_eq!( + res, + 0b1, + ); + } + + #[test] + fn hash_all_onto_u32_three_elems() { + let hash = init_hash(); + + let mut input: [[u8; 16]; 3] = [ + hex!("00110211 04050607 08090a0b 0c0d0e0f"), + hex!("00000000 04050607 08090a0b 0c0d0e0f"), + hex!("00110211 04050607 08090a0b 0c0d0e0f"), + ]; + let res = hash.hash_all_onto_u32(&mut input); + assert_eq!( + res, + 0b101, + ); + } + + #[test] + fn hash_all_onto_u32_16_elems() { + let hash = init_hash(); + + let mut input: [[u8; 16]; 16] = [ + hex!("00110211 04050607 08090a0b 0c0d0e0f"), + hex!("00000000 04050607 08090a0b 0c0d0e0f"), + hex!("00110211 04050607 08090a0b 0c0d0e0f"), + hex!("00000000 04050607 08090a0b 0c0d0e0f"), + hex!("00110211 04050607 08090a0b 0c0d0e0f"), + hex!("00000000 04050607 08090a0b 0c0d0e0f"), + hex!("00110211 04050607 08090a0b 0c0d0e0f"), + hex!("00000000 04050607 08090a0b 0c0d0e0f"), + hex!("00110211 04050607 08090a0b 0c0d0e0f"), + hex!("00000000 04050607 08090a0b 0c0d0e0f"), + hex!("00110211 04050607 08090a0b 0c0d0e0f"), + hex!("00000000 04050607 08090a0b 0c0d0e0f"), + hex!("00110211 04050607 08090a0b 0c0d0e0f"), + hex!("00000000 04050607 08090a0b 0c0d0e0f"), + hex!("00110211 04050607 08090a0b 0c0d0e0f"), + hex!("00110211 04050607 08090a0b 0c0d0e0f"), + ]; + let res = hash.hash_all_onto_u32(&mut input); + println!("{res:b}"); + assert_eq!( + res, + 0b1101_0101_0101_0101, + ); + } } diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index c35ac21..08491e9 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -2,10 +2,17 @@ pub mod hash; pub mod prf; pub mod prp; +use std::iter::Enumerate; +use std::slice::Iter; + use aes::cipher::{consts::U16, generic_array::GenericArray}; use aes::Block; +use hash::HashBlock; use prf::PrfBlock; use thiserror::Error; +use zeroize::Zeroize; + +use crate::prp::prng::Aes128Prng; pub type AesBlock = Block; pub type PrfKey = GenericArray; pub type HashKey = GenericArray; @@ -19,7 +26,7 @@ pub trait Prf { pub trait Hash { fn new(key: &HashKey) -> Self; fn hash(&self, data: &[u8]) -> u8; - fn hash_all(&self, input: &mut [AesBlock]) -> Vec; + fn hash_all(&self, input: &mut [HashBlock]) -> Vec; } #[derive(Debug, Error)] @@ -27,8 +34,71 @@ pub trait Hash { pub struct PrpError; pub type PrpResult = Result; +// TODO: There should be a single "permutation type" +// and a generator trait to use different approaches of generating it pub trait Prp: Sized { fn new(key: &[u8]) -> PrpResult; fn permute(&self, data: T) -> PrpResult; fn invert(&self, data: T) -> PrpResult; + fn enumerate(&self) -> Enumerate>; +} + +pub struct NewPrp { + forward: [T; N], + inverse: [T; N] +} + +impl NewPrp { + pub fn forward(&self) -> Enumerate> { + self.forward.iter().enumerate() + } + + pub fn inverse(&self) -> Enumerate> { + self.inverse.iter().enumerate() + } + + // TODO: Can we make this able to be called only once? + pub fn permute(&self, input: impl Into) -> T { + self.forward[input.into()] + } +} + +pub trait PrpGenerator { + fn generate(self) -> NewPrp; } + +pub struct KnuthShuffleGenerator<'p> { + prng_seed: &'p [u8] +} + +impl<'p> KnuthShuffleGenerator<'p> { + pub fn new(prng_seed: &'p [u8]) -> Self { + Self { prng_seed } + } +} + +// TODO: We could avoid code repetition with macros +impl <'p> PrpGenerator for KnuthShuffleGenerator<'p> { + fn generate(self) -> NewPrp { + let mut rng = Aes128Prng::init(self.prng_seed); // TODO: Use Result type here, too + + let mut forward = [0u8; 32]; + let mut inverse = [0u8; 32]; + + // Initialize values + for i in 0..32 { + forward[i] = i as u8; + } + + (0..32).into_iter().rev().for_each(|i| { + let j = rng.gen_range(i as u8); + forward.swap(i, j as usize); + }); + + for (index, val) in forward.iter().enumerate() { + inverse[*val as usize] = index as u8; + } + + NewPrp { forward, inverse } + } +} \ No newline at end of file diff --git a/primitives/src/prp.rs b/primitives/src/prp.rs index 032dcb7..e889865 100644 --- a/primitives/src/prp.rs +++ b/primitives/src/prp.rs @@ -1,7 +1,10 @@ pub mod prng; +pub mod bitwise; use crate::prp::prng::Aes128Prng; use crate::{Prp, PrpError, PrpResult}; use std::convert::TryFrom; +use std::iter::Enumerate; +use std::slice::Iter; use zeroize::{Zeroize, ZeroizeOnDrop}; #[derive(Zeroize)] @@ -20,6 +23,7 @@ impl Drop for KnuthShufflePRP { // Impl the ZeroizeOnDrop marker trait since we're zeroizing above impl ZeroizeOnDrop for KnuthShufflePRP {} +// TODO: This would make more sense if we defined PRP as a generator impl Prp for KnuthShufflePRP { /* * Initialize an 8-bit (N element) PRP using a KnuthShuffle @@ -76,6 +80,10 @@ impl Prp for KnuthShufflePRP { None => Err(PrpError), } } + + fn enumerate(&self) -> Enumerate> { + self.permutation.iter().enumerate() + } } #[cfg(test)] From 9816ee9e9786b8bb5d090473eeae8dcda4711870 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Sat, 13 May 2023 14:12:00 +1000 Subject: [PATCH 06/18] Work on right encryption for 5-bit scheme --- ore-rs-5bit/src/lib.rs | 66 ++++++++++++++++++++++++++++++++++++++++- ore-rs-5bit/src/main.rs | 21 +++---------- 2 files changed, 69 insertions(+), 18 deletions(-) diff --git a/ore-rs-5bit/src/lib.rs b/ore-rs-5bit/src/lib.rs index 9e1e2ac..003facc 100644 --- a/ore-rs-5bit/src/lib.rs +++ b/ore-rs-5bit/src/lib.rs @@ -1,5 +1,5 @@ use std::cell::RefCell; -use primitives::{prf::{Aes128Prf, PrfBlock}, Prf, prp::KnuthShufflePRP, Prp, AesBlock}; +use primitives::{prf::{Aes128Prf, PrfBlock}, Prf, prp::{KnuthShufflePRP, bitwise::BitwisePrp}, Prp, AesBlock, KnuthShuffleGenerator, PrpGenerator, NewPrp, hash::Aes128Z2Hash, Hash, HashKey}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; use zeroize::ZeroizeOnDrop; @@ -97,6 +97,70 @@ impl Ore5Bit { out } + pub fn encrypt(&self, input: &[u8]) -> (Vec, Vec) { + let mut nonce: [u8; 16] = Default::default(); + self.rng.borrow_mut().try_fill(&mut nonce).unwrap(); + + // TODO: Can we pack the input bytes?? + debug_assert!(input.len() <= 16); + // TODO: We could possibly use the stack and just do a single extend on to a vec after each round + // Format: [, , ] + let mut left: Vec = Vec::new(); // TODO: What capacity? + let mut right: Vec = Vec::new(); // TODO: What capacity? + + // Here we'll model a PRF using a single block of AES + // This will be OK for up to 16-bytes of input (or 25 5-bit values) + // For larger inputs we can chain the values by XORing the last output + // with the next input (a little like CMAC). + let mut left_blks = packed_prefixes(input); + let mut right_blocks: Vec = Vec::new(); // TODO: Len + self.prf2.encrypt_all(&mut left_blks); + left.push(left_blks.len().try_into().unwrap()); // TODO: DOn't unwrap + right.push(left_blks.len().try_into().unwrap()); // TODO: DOn't unwrap + + // This deviates from the paper slightly. + // Instead of calling PRF1 with the plaintext prefix, we call it + // with the output of the PRF2 of the prefix. + // This avoids a copy and should have the same effect. + // We also use a mask to set the comparison bits in one constant time + // operation and then perform a bitwise shuffle using the PRP + // instead of performing comparisons on each value (which would not be constant time). + for (out_left_blk, in_blk) in left_blks.iter_mut().zip(input.iter()) { + let out_right_blk: u32 = 0xFFFFFFFF >> *in_blk; + let prp: NewPrp = KnuthShuffleGenerator::new(out_left_blk).generate(); + let p_n = prp.permute(*in_blk); + out_right_blk.inverse_shuffle(&prp); + left.push(p_n); + out_left_blk[15] = p_n; + right_blocks.push(out_right_blk.inverse_shuffle(&prp)); + } + + self.prf1.encrypt_all(&mut left_blks); + + for output_block in left_blks { + left.extend_from_slice(&output_block); + let mut ro_keys: [[u8; 16]; 32] = Default::default(); + + for (j, ro_key) in ro_keys.iter_mut().enumerate() { + ro_key.copy_from_slice(&output_block); + ro_key[15] = j as u8; + } + self.prf1.encrypt_all(&mut ro_keys); + // TODO: Hash all of these keys with the nonce + // set the bits and and Xor with the right_block + // Push bytes onto right output vec + let hasher: Aes128Z2Hash = Hash::new(&nonce.into()); + let blind_block = hasher.hash_all_onto_u32(&ro_keys); + } + + + + + // TODO: Final XORs + + (left, right) + } + // For the right encryption we could either use the approach that we do in the current version, // or instead of doing a comparison of every number, we set all bits >= the input to 1 and then do // a bitwise permutation. diff --git a/ore-rs-5bit/src/main.rs b/ore-rs-5bit/src/main.rs index 3df4bcd..6d99a1f 100644 --- a/ore-rs-5bit/src/main.rs +++ b/ore-rs-5bit/src/main.rs @@ -15,25 +15,12 @@ fn permute_u32(input: u32, perm: &[usize; 32]) -> u32 { output } -fn main() { - let input: u32 = 0b11010101_10101010_11001100_00110011; - let permutation: [usize; 32] = [ - 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, - 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, - ]; - - let output = permute_u32(input, &permutation); - println!("Input: {:032b}", input); - println!("Output: {:032b}", output); -} - -/* fn main() { let k1 = hex!("00010203 04050607 08090a0b 0c0d0e0f"); let k2 = hex!("d0d007a5 3f9a6848 83bc1f21 0f6595a3"); - let input = vec![7, 23, 30, 2, 13, 6]; + let input = vec![7, 23, 30, 2]; let ore = Ore5BitChaCha20::init(&k1, &k2).unwrap(); - let o = ore.encrypt_left(&input); - println!("{}", hex::encode(o)); -}*/ \ No newline at end of file + let (left, right) = ore.encrypt(&input); + println!("{}, {}, {}", left.len(), right.len(), hex::encode(right)); +} \ No newline at end of file From 46215862603b23d4cf9417e60febc2a306a0f74d Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Sat, 13 May 2023 20:36:58 +1000 Subject: [PATCH 07/18] Created internal data structure for ORE Ciphertexts --- ore-rs-5bit/src/format.rs | 140 +++++++++++++++++++++++++++++++ ore-rs-5bit/src/format/header.rs | 91 ++++++++++++++++++++ ore-rs-5bit/src/lib.rs | 17 ++-- 3 files changed, 240 insertions(+), 8 deletions(-) create mode 100644 ore-rs-5bit/src/format.rs create mode 100644 ore-rs-5bit/src/format/header.rs diff --git a/ore-rs-5bit/src/format.rs b/ore-rs-5bit/src/format.rs new file mode 100644 index 0000000..a350281 --- /dev/null +++ b/ore-rs-5bit/src/format.rs @@ -0,0 +1,140 @@ +use self::header::Header; + +mod header; + +const LEFT_BLOCKSIZE: usize = 16; +const RIGHT_BLOCKSIZE: usize = 32; +const NONCE_SIZE: usize = 16; + +#[derive(Clone, Copy, PartialEq, Debug)] +enum CtType { + Left = 0, + Right = 1, + Combined = 2, +} + +impl From for CtType { + fn from(value: u8) -> Self { + match value { + 0 => Self::Left, + 1 => Self::Right, + 2 => Self::Combined, + _ => panic!("Unknown Ciphertext Type") + } + } +} + +struct RawCiphertext { + data: Vec +} + +impl RawCiphertext { + fn new(header: Header, body_len: usize) -> Self { + let mut data = Vec::with_capacity(Header::HEADER_LEN + body_len); + data.extend(header.to_vec()); + Self { data } + } + + fn header(&self) -> Header { + Header::from_slice(&self.data) + } + + fn set_header(&mut self, hdr: &Header) { + self.data[0..Header::HEADER_LEN].copy_from_slice(&hdr.to_vec()); + } + + /// Returns a slice to the body of the ciphertext. + /// That is, everything after the header. + pub fn body(&self) -> &[u8] { + &self.data[Header::HEADER_LEN..] + } + + fn extend(&mut self, iter: I) + where + I: IntoIterator + { + self.data.extend(iter) + } + + fn extend_from_slice(&mut self, slice: &[u8]) { + self.data.extend_from_slice(slice); + } +} + +pub struct LeftCiphertext { + data: RawCiphertext +} + +pub struct RightCiphertext { + data: RawCiphertext +} + +pub struct CombinedCiphertext { + data: RawCiphertext +} + +// TODO: Should we include a scheme and/or version number? +/// Wrapper to a structured byte array representing a Left ciphertext. +/// +/// ## Format +/// +/// | Field | Number of Bytes | +/// |-------|-----------------| +/// | type | 1 | +/// | num_blocks | 2 (up to 65535 blocks) | +/// | block* | 17 | +/// +/// * There are `num_blocks` blocks of 17 bytes. +/// +impl LeftCiphertext { + const BLOCK_SIZE: usize = 17; + + pub fn new(num_blocks: usize) -> Self { + let hdr = Header::new(CtType::Left, num_blocks); + + Self { data: RawCiphertext::new(hdr, num_blocks * Self::BLOCK_SIZE) } + } + + pub fn add_block(&mut self, block: &[u8; 16], permuted: u8) { + self.data.extend_from_slice(block); + self.data.extend([permuted]); + } +} + +impl RightCiphertext { + // TODO: This could be generic + const BLOCKSIZE: usize = 32; + const NONCE_SIZE: usize = 16; + + pub fn new(num_blocks: usize, nonce: &[u8; 16]) -> Self { + let hdr = Header::new(CtType::Left, num_blocks); + let mut data = RawCiphertext::new(hdr, Self::NONCE_SIZE + (num_blocks * Self::BLOCKSIZE)); + data.extend_from_slice(nonce); + Self { data } + } + + pub fn add_block(&mut self, block: u32) { + self.data.extend(block.to_be_bytes().into_iter()); + } +} + +impl CombinedCiphertext { + /// Creates a new CombinedCiphertext by merging (and consuming) the given left and right Ciphertexts. + /// The headers must be comparable (See [Header]) and have the same block length. + /// The resulting Ciphertext has a single header representing both ciphertexts. + pub fn new(mut left: LeftCiphertext, right: RightCiphertext) -> Self { + let mut l_hdr = left.data.header(); + let r_hdr = right.data.header(); + + if !l_hdr.comparable(&r_hdr) || l_hdr.num_blocks != r_hdr.num_blocks { + panic!("Cannot combine incompatible ciphertexts"); + } + + // Steal and reuse the left + l_hdr.ct_type = CtType::Combined; + left.data.set_header(&l_hdr); + left.data.extend_from_slice(right.data.body()); + + Self { data: left.data } + } +} \ No newline at end of file diff --git a/ore-rs-5bit/src/format/header.rs b/ore-rs-5bit/src/format/header.rs new file mode 100644 index 0000000..7a1ca3e --- /dev/null +++ b/ore-rs-5bit/src/format/header.rs @@ -0,0 +1,91 @@ +use super::CtType; + +#[derive(PartialEq, Debug)] +pub(super) struct Header { + pub(super) version: u16, + pub(super) scheme: u8, + pub(super) ct_type: CtType, + pub(super) num_blocks: u16 +} + +impl Header { + pub(super) const HEADER_LEN: usize = 6; + + pub(super) fn new(ct_type: CtType, num_blocks: usize) -> Self { + assert!(num_blocks < (u16::MAX as usize)); + + Self { + // Hardcode version and scheme for now + version: 0, + scheme: 0, + ct_type, + num_blocks: num_blocks as u16 + } + } + + /// Indicates if this ciphertext header is comparable to another. + /// This means the version and scheme must be the same. + /// Specific schemes may impose additional restrictions (such as matching lengths). + pub(super) fn comparable(&self, other: &Header) -> bool { + use CtType::*; + if self.version != other.version { return false } + if self.scheme != self.scheme { return false } + + match (self.ct_type, other.ct_type) { + (Combined, Combined) => true, + (Left, Right) => true, + _ => false + } + } + + pub(super) fn to_vec(&self) -> Vec { + let mut hdr: Vec = Vec::with_capacity(Self::HEADER_LEN); + hdr.extend(self.version.to_be_bytes()); + hdr.push(self.scheme); + hdr.push(*&self.ct_type as u8); + hdr.extend(self.num_blocks.to_be_bytes()); + hdr + } + + pub(super) fn from_slice(hdr: &[u8]) -> Self { // TODO: Handle error + assert!(hdr.len() >= Self::HEADER_LEN, "Header cannot be read from slice of less than {} bytes", Self::HEADER_LEN); + let mut iter = hdr.into_iter(); + let version: u16 = u16::from_be_bytes(iter.next_chunk::<2>().unwrap().map(|c| *c)); + let scheme: u8 = *iter.next().unwrap(); + let ct_type: CtType = (*iter.next().unwrap()).into(); + let num_blocks: u16 = u16::from_be_bytes(iter.next_chunk::<2>().unwrap().map(|c| *c)); + + Self { version, scheme, ct_type, num_blocks } + } +} + +#[cfg(test)] +mod tests { + use crate::format::CtType; + use super::Header; + + #[test] + fn test_new() { + let header = Header::new(CtType::Right, 12); + assert_eq!(header.version, 0); + assert_eq!(header.scheme, 0); + assert_eq!(header.ct_type, CtType::Right); + assert_eq!(header.num_blocks, 12); + } + + #[test] + fn test_roundtrip() { + let header = Header::new(CtType::Left, 8); + let bytes = header.to_vec(); + assert_eq!(header, Header::from_slice(&bytes)); + } + + #[test] + fn test_roundtrip_with_ignored_trailing_bytes() { + let header = Header::new(CtType::Left, 8); + let mut bytes: Vec = Vec::new(); + bytes.extend(header.to_vec()); + bytes.extend(vec![1, 2, 3, 4]); + assert_eq!(header, Header::from_slice(&bytes)); + } +} diff --git a/ore-rs-5bit/src/lib.rs b/ore-rs-5bit/src/lib.rs index 003facc..3b9e569 100644 --- a/ore-rs-5bit/src/lib.rs +++ b/ore-rs-5bit/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(iter_next_chunk)] use std::cell::RefCell; use primitives::{prf::{Aes128Prf, PrfBlock}, Prf, prp::{KnuthShufflePRP, bitwise::BitwisePrp}, Prp, AesBlock, KnuthShuffleGenerator, PrpGenerator, NewPrp, hash::Aes128Z2Hash, Hash, HashKey}; use rand::{Rng, SeedableRng}; @@ -5,10 +6,9 @@ use rand_chacha::ChaCha20Rng; use zeroize::ZeroizeOnDrop; use aes::{cipher::generic_array::GenericArray, Aes128}; use cmac::{Cmac, Mac, digest::FixedOutput}; - use crate::packing::packed_prefixes; - pub mod packing; +pub mod format; #[derive(Debug, ZeroizeOnDrop)] pub struct Ore5Bit { @@ -115,8 +115,8 @@ impl Ore5Bit { let mut left_blks = packed_prefixes(input); let mut right_blocks: Vec = Vec::new(); // TODO: Len self.prf2.encrypt_all(&mut left_blks); - left.push(left_blks.len().try_into().unwrap()); // TODO: DOn't unwrap - right.push(left_blks.len().try_into().unwrap()); // TODO: DOn't unwrap + left.push(input.len().try_into().unwrap()); // TODO: DOn't unwrap + right.push(input.len().try_into().unwrap()); // TODO: DOn't unwrap // This deviates from the paper slightly. // Instead of calling PRF1 with the plaintext prefix, we call it @@ -137,12 +137,12 @@ impl Ore5Bit { self.prf1.encrypt_all(&mut left_blks); - for output_block in left_blks { - left.extend_from_slice(&output_block); + for (left_blk, right_blk) in left_blks.iter().zip(right_blocks.iter()) { + left.extend_from_slice(left_blk); let mut ro_keys: [[u8; 16]; 32] = Default::default(); for (j, ro_key) in ro_keys.iter_mut().enumerate() { - ro_key.copy_from_slice(&output_block); + ro_key.copy_from_slice(left_blk); ro_key[15] = j as u8; } self.prf1.encrypt_all(&mut ro_keys); @@ -150,7 +150,8 @@ impl Ore5Bit { // set the bits and and Xor with the right_block // Push bytes onto right output vec let hasher: Aes128Z2Hash = Hash::new(&nonce.into()); - let blind_block = hasher.hash_all_onto_u32(&ro_keys); + let final_right = right_blk ^ hasher.hash_all_onto_u32(&ro_keys); + right.extend_from_slice(&final_right.to_be_bytes()); } From ff98b8c55b48a368b6a865623b5ecf72541c3b4f Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Sun, 14 May 2023 20:58:45 +1000 Subject: [PATCH 08/18] Separated out ciphertext data structures and formats into separate crate --- Cargo.toml | 1 + formats/Cargo.toml | 8 ++ formats/src/ciphertext/combined.rs | 74 ++++++++++++++++++ formats/src/ciphertext/left.rs | 72 ++++++++++++++++++ formats/src/ciphertext/mod.rs | 23 ++++++ formats/src/ciphertext/right.rs | 55 ++++++++++++++ formats/src/data_with_header.rs | 70 +++++++++++++++++ formats/src/header.rs | 91 ++++++++++++++++++++++ formats/src/lib.rs | 34 +++++++++ formats/src/main.rs | 3 + ore-rs-5bit/src/format.rs | 117 ++++++++++++++++++++++++----- ore-rs-5bit/src/format/header.rs | 10 +-- ore-rs-5bit/src/lib.rs | 41 +++++----- ore-rs-5bit/src/main.rs | 7 +- 14 files changed, 563 insertions(+), 43 deletions(-) create mode 100644 formats/Cargo.toml create mode 100644 formats/src/ciphertext/combined.rs create mode 100644 formats/src/ciphertext/left.rs create mode 100644 formats/src/ciphertext/mod.rs create mode 100644 formats/src/ciphertext/right.rs create mode 100644 formats/src/data_with_header.rs create mode 100644 formats/src/header.rs create mode 100644 formats/src/lib.rs create mode 100644 formats/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index b170143..145e507 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "core", + "formats", "primitives", "ore-rs-5bit", ] diff --git a/formats/Cargo.toml b/formats/Cargo.toml new file mode 100644 index 0000000..432194f --- /dev/null +++ b/formats/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "formats" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/formats/src/ciphertext/combined.rs b/formats/src/ciphertext/combined.rs new file mode 100644 index 0000000..7b9dc16 --- /dev/null +++ b/formats/src/ciphertext/combined.rs @@ -0,0 +1,74 @@ +use std::{slice::Iter, marker::PhantomData}; +use crate::{data_with_header::DataWithHeader, header::Header, ParseError, CtType}; +use super::{CipherTextBlock, CipherText, left::LeftCiphertext, right::RightCiphertext}; + +pub struct CombinedBlock(L, R); + +impl CipherTextBlock for CombinedBlock { + fn byte_size() -> usize { + L::byte_size() + R::byte_size() + } + + fn extend_into(&self, out: &mut Vec) { + todo!() + } +} + +pub struct CombinedCiphertext { + data: DataWithHeader, + _phantom: (PhantomData, PhantomData), +} + + +impl CombinedCiphertext { + /// Creates a new CombinedCiphertext by merging (and consuming) the given left and right Ciphertexts. + /// The headers must be comparable (See [Header]) and have the same block length. + /// The resulting Ciphertext has a single header representing both ciphertexts. + pub fn new(mut left: LeftCiphertext, right: RightCiphertext) -> Self { + let mut l_hdr = left.header(); + let r_hdr = right.header(); + + if !l_hdr.comparable(&r_hdr) || l_hdr.num_blocks != r_hdr.num_blocks { + panic!("Cannot combine incompatible ciphertexts"); + } + + // Steal and reuse the left + l_hdr.ct_type = CtType::Combined; + left.data.set_header(&l_hdr); + left.data.extend_from_slice(right.data.body()); + + Self { data: left.data, _phantom: (PhantomData, PhantomData) } + } +} + +impl CipherText for CombinedCiphertext { + type Block = CombinedBlock; + + fn header(&self) -> Header { + self.data.header() + } + + fn blocks(&self) -> Iter { + todo!() + } +} + + +impl TryFrom<&[u8]> for CombinedCiphertext { + type Error = ParseError; + + fn try_from(data: &[u8]) -> Result { + let hdr = Header::from_slice(data); + if matches!(hdr.ct_type, CtType::Combined) { + Ok(Self { data: data.into(), _phantom: (PhantomData, PhantomData) }) + } else { + Err(ParseError { }) + } + } +} + +impl AsRef<[u8]> for CombinedCiphertext { + fn as_ref(&self) -> &[u8] { + self.data.as_ref() + } +} diff --git a/formats/src/ciphertext/left.rs b/formats/src/ciphertext/left.rs new file mode 100644 index 0000000..cb2b625 --- /dev/null +++ b/formats/src/ciphertext/left.rs @@ -0,0 +1,72 @@ +use std::{marker::PhantomData, slice::Iter}; +use crate::{data_with_header::DataWithHeader, header::Header, ParseError, CtType}; +use super::{CipherTextBlock, CipherText}; + + + + +pub struct LeftCiphertext { + pub(crate) data: DataWithHeader, + _phantom: PhantomData, +} + +// TODO: Should we include a scheme and/or version number? +/// Wrapper to a structured byte array representing a Left ciphertext. +/// +/// ## Format +/// +/// | Field | Number of Bytes | +/// |-------|-----------------| +/// | type | 1 | +/// | num_blocks | 2 (up to 65535 blocks) | +/// | block* | 17 | +/// +/// * There are `num_blocks` blocks of 17 bytes. +/// +impl LeftCiphertext { + pub fn new(num_blocks: usize) -> Self { + let hdr = Header::new(CtType::Left, num_blocks); + + Self { + data: DataWithHeader::new(hdr, num_blocks * ::Block::byte_size()), + _phantom: PhantomData + } + } + + pub fn add_block(&mut self, block: &[u8; 16], permuted: u8) { + self.data.extend_from_slice(block); + self.data.extend([permuted]); + } + +} + +impl CipherText for LeftCiphertext { + type Block = B; + + fn header(&self) -> Header { + self.data.header() + } + + fn blocks(&self) -> Iter { + todo!() + } +} + +impl TryFrom<&[u8]> for LeftCiphertext { + type Error = ParseError; + + fn try_from(data: &[u8]) -> Result { + let hdr = Header::from_slice(data); + if matches!(hdr.ct_type, CtType::Left) { + Ok(Self { data: data.into(), _phantom: PhantomData }) + } else { + Err(ParseError { }) + } + } +} + +impl AsRef<[u8]> for LeftCiphertext { + fn as_ref(&self) -> &[u8] { + self.data.as_ref() + } +} diff --git a/formats/src/ciphertext/mod.rs b/formats/src/ciphertext/mod.rs new file mode 100644 index 0000000..74e11dc --- /dev/null +++ b/formats/src/ciphertext/mod.rs @@ -0,0 +1,23 @@ +use std::slice::Iter; +use crate::header::Header; +mod left; +mod right; +mod combined; + +// TODO: make the new and add_block functions a separate trait +pub trait CipherText { + type Block: CipherTextBlock; + + fn comparable(&self, to: &impl CipherText) -> bool { + self.header().comparable(&to.header()) + } + // TODO: Probs shouldn't expose the header + fn header(&self) -> Header; + + fn blocks(&self) -> Iter; +} + +pub trait CipherTextBlock { + fn byte_size() -> usize; + fn extend_into(&self, out: &mut Vec); +} diff --git a/formats/src/ciphertext/right.rs b/formats/src/ciphertext/right.rs new file mode 100644 index 0000000..3587d27 --- /dev/null +++ b/formats/src/ciphertext/right.rs @@ -0,0 +1,55 @@ +use std::{marker::PhantomData, slice::Iter}; +use crate::{data_with_header::DataWithHeader, ParseError, header::Header, CtType}; +use super::{CipherTextBlock, CipherText}; + +pub struct RightCiphertext { + pub(crate) data: DataWithHeader, + _phantom: PhantomData, +} + +impl RightCiphertext { + const NONCE_SIZE: usize = 16; + + pub fn new(num_blocks: usize, nonce: &[u8; 16]) -> Self { + let hdr = Header::new(CtType::Left, num_blocks); + let mut data = DataWithHeader::new(hdr, Self::NONCE_SIZE + (num_blocks * ::Block::byte_size())); + data.extend_from_slice(nonce); + Self { data, _phantom: PhantomData } + } + + pub fn add_block(&mut self, block: u32) { + self.data.extend(block.to_be_bytes().into_iter()); + } +} + + +impl CipherText for RightCiphertext { + type Block = B; + + fn header(&self) -> Header { + self.data.header() + } + + fn blocks(&self) -> Iter { + todo!() + } +} + +impl TryFrom<&[u8]> for RightCiphertext { + type Error = ParseError; + + fn try_from(data: &[u8]) -> Result { + let hdr = Header::from_slice(data); + if matches!(hdr.ct_type, CtType::Right) { + Ok(Self { data: data.into(), _phantom: PhantomData }) + } else { + Err(ParseError { }) + } + } +} + +impl AsRef<[u8]> for RightCiphertext { + fn as_ref(&self) -> &[u8] { + self.data.as_ref() + } +} \ No newline at end of file diff --git a/formats/src/data_with_header.rs b/formats/src/data_with_header.rs new file mode 100644 index 0000000..d05d109 --- /dev/null +++ b/formats/src/data_with_header.rs @@ -0,0 +1,70 @@ +use crate::header::Header; + +pub(crate) struct DataWithHeader { + data: Vec +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum CtType { + Left = 0, + Right = 1, + Combined = 2, +} + +impl From for CtType { + fn from(value: u8) -> Self { + match value { + 0 => Self::Left, + 1 => Self::Right, + 2 => Self::Combined, + _ => panic!("Unknown Ciphertext Type") + } + } +} + +impl DataWithHeader { + pub(crate) fn new(header: Header, body_len: usize) -> Self { + let mut data = Vec::with_capacity(Header::HEADER_LEN + body_len); + data.extend(header.to_vec()); + Self { data } + } + + pub(crate) fn header(&self) -> Header { + Header::from_slice(&self.data) + } + + pub(crate) fn set_header(&mut self, hdr: &Header) { + self.data[0..Header::HEADER_LEN].copy_from_slice(&hdr.to_vec()); + } + + /// Returns a slice to the body of the ciphertext. + /// That is, everything after the header. + pub(crate) fn body(&self) -> &[u8] { + &self.data[Header::HEADER_LEN..] + } + + pub(crate) fn extend(&mut self, iter: I) + where + I: IntoIterator + { + self.data.extend(iter) + } + + pub(crate) fn extend_from_slice(&mut self, slice: &[u8]) { + self.data.extend_from_slice(slice); + } +} + +impl AsRef<[u8]> for DataWithHeader { + fn as_ref(&self) -> &[u8] { + self.data.as_ref() + } +} + +impl From<&[u8]> for DataWithHeader { + fn from(data: &[u8]) -> Self { + assert!(data.len() >= Header::HEADER_LEN); + // TODO: It would be nice if we could avoid this copy! + Self { data: data.to_vec() } + } +} \ No newline at end of file diff --git a/formats/src/header.rs b/formats/src/header.rs new file mode 100644 index 0000000..a4dbd18 --- /dev/null +++ b/formats/src/header.rs @@ -0,0 +1,91 @@ +use super::CtType; + +#[derive(PartialEq, Debug)] +pub struct Header { + pub version: u16, + pub scheme: u8, + pub ct_type: CtType, + pub num_blocks: u16 +} + +impl Header { + pub(super) const HEADER_LEN: usize = 6; + + pub(super) fn new(ct_type: CtType, num_blocks: usize) -> Self { + assert!(num_blocks < (u16::MAX as usize)); + + Self { + // Hardcode version and scheme for now + version: 0, + scheme: 0, + ct_type, + num_blocks: num_blocks as u16 + } + } + + /// Indicates if this ciphertext header is comparable to another. + /// This means the version and scheme must be the same. + /// Specific schemes may impose additional restrictions (such as matching lengths). + pub(super) fn comparable(&self, other: &Header) -> bool { + use CtType::*; + if self.version != other.version { return false } + if self.scheme != self.scheme { return false } + + match (self.ct_type, other.ct_type) { + (Combined, Combined) => true, + (Left, Right) => true, + _ => false + } + } + + pub(super) fn to_vec(&self) -> Vec { + let mut hdr: Vec = Vec::with_capacity(Self::HEADER_LEN); + hdr.extend(self.version.to_be_bytes()); + hdr.push(self.scheme); + hdr.push(*&self.ct_type as u8); + hdr.extend(self.num_blocks.to_be_bytes()); + hdr + } + + pub(super) fn from_slice(hdr: &[u8]) -> Self { // TODO: Handle error + assert!(hdr.len() >= Self::HEADER_LEN, "Header cannot be read from slice of less than {} bytes", Self::HEADER_LEN); + let mut iter = hdr.into_iter(); + let version: u16 = u16::from_be_bytes(iter.next_chunk::<2>().unwrap().map(|c| *c)); + let scheme: u8 = *iter.next().unwrap(); + let ct_type: CtType = (*iter.next().unwrap()).into(); + let num_blocks: u16 = u16::from_be_bytes(iter.next_chunk::<2>().unwrap().map(|c| *c)); + + Self { version, scheme, ct_type, num_blocks } + } +} + +#[cfg(test)] +mod tests { + use crate::CtType; + use super::Header; + + #[test] + fn test_new() { + let header = Header::new(CtType::Right, 12); + assert_eq!(header.version, 0); + assert_eq!(header.scheme, 0); + assert_eq!(header.ct_type, CtType::Right); + assert_eq!(header.num_blocks, 12); + } + + #[test] + fn test_roundtrip() { + let header = Header::new(CtType::Left, 8); + let bytes = header.to_vec(); + assert_eq!(header, Header::from_slice(&bytes)); + } + + #[test] + fn test_roundtrip_with_ignored_trailing_bytes() { + let header = Header::new(CtType::Left, 8); + let mut bytes: Vec = Vec::new(); + bytes.extend(header.to_vec()); + bytes.extend(vec![1, 2, 3, 4]); + assert_eq!(header, Header::from_slice(&bytes)); + } +} diff --git a/formats/src/lib.rs b/formats/src/lib.rs new file mode 100644 index 0000000..aea890f --- /dev/null +++ b/formats/src/lib.rs @@ -0,0 +1,34 @@ +#![feature(iter_next_chunk)] +mod data_with_header; +mod header; +mod ciphertext; + +#[derive(Debug)] +pub struct ParseError {} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum CtType { + Left = 0, + Right = 1, + Combined = 2, +} + +impl From for CtType { + fn from(value: u8) -> Self { + match value { + 0 => Self::Left, + 1 => Self::Right, + 2 => Self::Combined, + _ => panic!("Unknown Ciphertext Type") + } + } +} + + + + + + + +pub struct LeftBlock([u8; 16], u8); + diff --git a/formats/src/main.rs b/formats/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/formats/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/ore-rs-5bit/src/format.rs b/ore-rs-5bit/src/format.rs index a350281..668be48 100644 --- a/ore-rs-5bit/src/format.rs +++ b/ore-rs-5bit/src/format.rs @@ -1,13 +1,24 @@ -use self::header::Header; +use std::slice::Iter; +use self::header::Header; mod header; -const LEFT_BLOCKSIZE: usize = 16; -const RIGHT_BLOCKSIZE: usize = 32; -const NONCE_SIZE: usize = 16; +// TODO: make the new and add_block functions a separate trait + +pub trait CipherText { + fn comparable(&self, to: &impl CipherText) -> bool { + self.header().comparable(&to.header()) + } + fn header(&self) -> Header; + + fn blocks(&self) -> Iter; +} + +#[derive(Debug)] +pub struct ParseError {} #[derive(Clone, Copy, PartialEq, Debug)] -enum CtType { +pub enum CtType { Left = 0, Right = 1, Combined = 2, @@ -24,11 +35,11 @@ impl From for CtType { } } -struct RawCiphertext { +struct DataWithHeader { data: Vec } -impl RawCiphertext { +impl DataWithHeader { fn new(header: Header, body_len: usize) -> Self { let mut data = Vec::with_capacity(Header::HEADER_LEN + body_len); data.extend(header.to_vec()); @@ -61,18 +72,88 @@ impl RawCiphertext { } } -pub struct LeftCiphertext { - data: RawCiphertext +pub struct LeftCiphertext { + data: DataWithHeader } pub struct RightCiphertext { - data: RawCiphertext + data: DataWithHeader +} + +pub struct CombinedCiphertext { + data: DataWithHeader +} + +impl CipherText for LeftCiphertext { + fn header(&self) -> Header { + self.data.header() + } +} + +impl CipherText for CombinedCiphertext { + fn header(&self) -> Header { + self.data.header() + } +} + +impl TryFrom<&[u8]> for LeftCiphertext { + type Error = ParseError; + + fn try_from(data: &[u8]) -> Result { + let hdr = Header::from_slice(data); + if matches!(hdr.ct_type, CtType::Left) { + // TODO: It would be nice if we could avoid this copy! + let raw = DataWithHeader { data: data.to_vec() }; + Ok(LeftCiphertext { data: raw }) + } else { + Err(ParseError { }) + } + } +} + +impl TryFrom<&[u8]> for CombinedCiphertext { + type Error = ParseError; + + fn try_from(data: &[u8]) -> Result { + let hdr = Header::from_slice(data); + if matches!(hdr.ct_type, CtType::Combined) { + // TODO: It would be nice if we could avoid this copy! + let raw = DataWithHeader { data: data.to_vec() }; + Ok(Self { data: raw }) + } else { + Err(ParseError { }) + } + } } -pub struct CombinedCiphertext { - data: RawCiphertext +impl AsRef<[u8]> for DataWithHeader { + fn as_ref(&self) -> &[u8] { + self.data.as_ref() + } +} + +impl AsRef<[u8]> for LeftCiphertext { + fn as_ref(&self) -> &[u8] { + self.data.as_ref() + } } +impl AsRef<[u8]> for RightCiphertext { + fn as_ref(&self) -> &[u8] { + self.data.as_ref() + } +} + +impl AsRef<[u8]> for CombinedCiphertext { + fn as_ref(&self) -> &[u8] { + self.data.as_ref() + } +} + +pub struct LeftBlock([u8; 16], u8); + +pub struct CombinedBlock(LeftBlock, u32); + // TODO: Should we include a scheme and/or version number? /// Wrapper to a structured byte array representing a Left ciphertext. /// @@ -86,19 +167,21 @@ pub struct CombinedCiphertext { /// /// * There are `num_blocks` blocks of 17 bytes. /// -impl LeftCiphertext { +impl LeftCiphertext { + // TODO: Self::from() should take an AsRef<[u8]> (not a slice) const BLOCK_SIZE: usize = 17; pub fn new(num_blocks: usize) -> Self { let hdr = Header::new(CtType::Left, num_blocks); - Self { data: RawCiphertext::new(hdr, num_blocks * Self::BLOCK_SIZE) } + Self { data: DataWithHeader::new(hdr, num_blocks * Self::BLOCK_SIZE) } } pub fn add_block(&mut self, block: &[u8; 16], permuted: u8) { self.data.extend_from_slice(block); self.data.extend([permuted]); } + } impl RightCiphertext { @@ -108,7 +191,7 @@ impl RightCiphertext { pub fn new(num_blocks: usize, nonce: &[u8; 16]) -> Self { let hdr = Header::new(CtType::Left, num_blocks); - let mut data = RawCiphertext::new(hdr, Self::NONCE_SIZE + (num_blocks * Self::BLOCKSIZE)); + let mut data = DataWithHeader::new(hdr, Self::NONCE_SIZE + (num_blocks * Self::BLOCKSIZE)); data.extend_from_slice(nonce); Self { data } } @@ -118,11 +201,11 @@ impl RightCiphertext { } } -impl CombinedCiphertext { +impl CombinedCiphertext { /// Creates a new CombinedCiphertext by merging (and consuming) the given left and right Ciphertexts. /// The headers must be comparable (See [Header]) and have the same block length. /// The resulting Ciphertext has a single header representing both ciphertexts. - pub fn new(mut left: LeftCiphertext, right: RightCiphertext) -> Self { + pub fn new(mut left: LeftCiphertext, right: RightCiphertext) -> Self { let mut l_hdr = left.data.header(); let r_hdr = right.data.header(); diff --git a/ore-rs-5bit/src/format/header.rs b/ore-rs-5bit/src/format/header.rs index 7a1ca3e..514e473 100644 --- a/ore-rs-5bit/src/format/header.rs +++ b/ore-rs-5bit/src/format/header.rs @@ -1,11 +1,11 @@ use super::CtType; #[derive(PartialEq, Debug)] -pub(super) struct Header { - pub(super) version: u16, - pub(super) scheme: u8, - pub(super) ct_type: CtType, - pub(super) num_blocks: u16 +pub struct Header { + pub version: u16, + pub scheme: u8, + pub ct_type: CtType, + pub num_blocks: u16 } impl Header { diff --git a/ore-rs-5bit/src/lib.rs b/ore-rs-5bit/src/lib.rs index 3b9e569..d0c4002 100644 --- a/ore-rs-5bit/src/lib.rs +++ b/ore-rs-5bit/src/lib.rs @@ -1,12 +1,12 @@ -#![feature(iter_next_chunk)] use std::cell::RefCell; +use format::CombinedCiphertext; use primitives::{prf::{Aes128Prf, PrfBlock}, Prf, prp::{KnuthShufflePRP, bitwise::BitwisePrp}, Prp, AesBlock, KnuthShuffleGenerator, PrpGenerator, NewPrp, hash::Aes128Z2Hash, Hash, HashKey}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; use zeroize::ZeroizeOnDrop; use aes::{cipher::generic_array::GenericArray, Aes128}; use cmac::{Cmac, Mac, digest::FixedOutput}; -use crate::packing::packed_prefixes; +use crate::{packing::packed_prefixes, format::{LeftCiphertext, CipherText}}; pub mod packing; pub mod format; @@ -63,35 +63,36 @@ impl Ore5Bit { /// most significant 3-bits of each value are ignored). /// TODO: Create a wrapper type /// TODO: This might be faster if we do it blocks of statically allocated chunks - pub fn encrypt_left(&self, input: &[u8]) -> Vec { - // TODO: Can we pack the input bytes?? + pub fn encrypt_left(&self, input: &[u8]) -> LeftCiphertext { + // We're limited to 16 input blocks for now because we're using AES as the PRF (1 block) debug_assert!(input.len() <= 16); // TODO: We could possibly use the stack and just do a single extend on to a vec after each round // Format: [, , ] - let mut out: Vec = Vec::new(); // TODO: What capacity? + let mut out = LeftCiphertext::new(input.len()); // Here we'll model a PRF using a single block of AES // This will be OK for up to 16-bytes of input (or 25 5-bit values) // For larger inputs we can chain the values by XORing the last output // with the next input (a little like CMAC). - let mut output_blocks = packed_prefixes(input); - self.prf2.encrypt_all(&mut output_blocks); - out.push(output_blocks.len().try_into().unwrap()); // TODO: DOn't unwrap + let mut prefixes = packed_prefixes(input); + self.prf2.encrypt_all(&mut prefixes); // This deviates from the paper slightly. // Instead of calling PRF1 with the plaintext prefix, we call it // with the output of the PRF2 of the prefix. // This avoids a copy and should have the same effect. - for (output_block, input_block) in output_blocks.iter_mut().zip(input.iter()) { + // TODO: Change this to use functional callbacks rather than for + let mut p_ns = vec![]; + for (output_block, input_block) in prefixes.iter_mut().zip(input.iter()) { let prp: KnuthShufflePRP = Prp::new(output_block).unwrap(); // TODO: let p_n = prp.permute(*input_block).unwrap(); - out.push(p_n); + p_ns.push(p_n); output_block[15] = p_n; } - self.prf1.encrypt_all(&mut output_blocks); - for output_block in output_blocks { - out.extend_from_slice(&output_block); + self.prf1.encrypt_all(&mut prefixes); + for (prf_block, permuted) in prefixes.iter().zip(p_ns.iter()) { + out.add_block(prf_block, *permuted); } out @@ -154,14 +155,18 @@ impl Ore5Bit { right.extend_from_slice(&final_right.to_be_bytes()); } - - - - // TODO: Final XORs - (left, right) } + // TODO: The CipherText types might be better to implement a CipherText trait + // so that we can handle the different variations here? + pub fn compare_slices(a: impl AsRef<[u8]>, b: impl AsRef<[u8]>) -> u8 { + let left: LeftCiphertext = a.as_ref().try_into().unwrap(); + let combined: CombinedCiphertext = b.as_ref().try_into().unwrap(); + assert!(left.comparable(&combined)); // TODO: Error + 0 + } + // For the right encryption we could either use the approach that we do in the current version, // or instead of doing a comparison of every number, we set all bits >= the input to 1 and then do // a bitwise permutation. diff --git a/ore-rs-5bit/src/main.rs b/ore-rs-5bit/src/main.rs index 6d99a1f..6bc8ad0 100644 --- a/ore-rs-5bit/src/main.rs +++ b/ore-rs-5bit/src/main.rs @@ -1,5 +1,5 @@ use hex_literal::hex; -use ore_rs_5bit::{Ore5BitChaCha20, cmac, packing::packed_prefixes}; +use ore_rs_5bit::{Ore5BitChaCha20, packing::packed_prefixes}; fn permute_u32(input: u32, perm: &[usize; 32]) -> u32 { let mut output: u32 = 0; @@ -21,6 +21,7 @@ fn main() { let input = vec![7, 23, 30, 2]; let ore = Ore5BitChaCha20::init(&k1, &k2).unwrap(); - let (left, right) = ore.encrypt(&input); - println!("{}, {}, {}", left.len(), right.len(), hex::encode(right)); + let left = ore.encrypt_left(&input); + println!("Left encrypt: {}", hex::encode(&left)); + //println!("{}, {}, {}", left.len(), right.len(), hex::encode(right)); } \ No newline at end of file From e5eace4ee5f24ad27d2cafcfa44c98291e6aa75a Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Sun, 14 May 2023 21:47:11 +1000 Subject: [PATCH 09/18] Start using the new data structures in the new ORE scheme --- formats/src/ciphertext/combined.rs | 4 +- formats/src/ciphertext/left.rs | 24 +--- formats/src/ciphertext/mod.rs | 12 +- formats/src/ciphertext/right.rs | 2 +- formats/src/data_with_header.rs | 6 +- formats/src/header.rs | 4 +- formats/src/lib.rs | 36 ++--- ore-rs-5bit/Cargo.toml | 3 +- ore-rs-5bit/src/format.rs | 223 ----------------------------- ore-rs-5bit/src/format/header.rs | 91 ------------ ore-rs-5bit/src/lib.rs | 82 ++++++----- ore-rs-5bit/src/main.rs | 20 +-- 12 files changed, 77 insertions(+), 430 deletions(-) delete mode 100644 ore-rs-5bit/src/format.rs delete mode 100644 ore-rs-5bit/src/format/header.rs diff --git a/formats/src/ciphertext/combined.rs b/formats/src/ciphertext/combined.rs index 7b9dc16..ac1bddc 100644 --- a/formats/src/ciphertext/combined.rs +++ b/formats/src/ciphertext/combined.rs @@ -1,5 +1,5 @@ use std::{slice::Iter, marker::PhantomData}; -use crate::{data_with_header::DataWithHeader, header::Header, ParseError, CtType}; +use crate::{data_with_header::{CtType, DataWithHeader}, header::Header, ParseError}; use super::{CipherTextBlock, CipherText, left::LeftCiphertext, right::RightCiphertext}; pub struct CombinedBlock(L, R); @@ -9,7 +9,7 @@ impl CipherTextBlock for CombinedBlock) { + fn extend_into(&self, out: &mut DataWithHeader) { todo!() } } diff --git a/formats/src/ciphertext/left.rs b/formats/src/ciphertext/left.rs index cb2b625..e780d17 100644 --- a/formats/src/ciphertext/left.rs +++ b/formats/src/ciphertext/left.rs @@ -1,28 +1,12 @@ use std::{marker::PhantomData, slice::Iter}; -use crate::{data_with_header::DataWithHeader, header::Header, ParseError, CtType}; +use crate::{data_with_header::{CtType, DataWithHeader}, header::Header, ParseError}; use super::{CipherTextBlock, CipherText}; - - - pub struct LeftCiphertext { pub(crate) data: DataWithHeader, _phantom: PhantomData, } -// TODO: Should we include a scheme and/or version number? -/// Wrapper to a structured byte array representing a Left ciphertext. -/// -/// ## Format -/// -/// | Field | Number of Bytes | -/// |-------|-----------------| -/// | type | 1 | -/// | num_blocks | 2 (up to 65535 blocks) | -/// | block* | 17 | -/// -/// * There are `num_blocks` blocks of 17 bytes. -/// impl LeftCiphertext { pub fn new(num_blocks: usize) -> Self { let hdr = Header::new(CtType::Left, num_blocks); @@ -33,11 +17,9 @@ impl LeftCiphertext { } } - pub fn add_block(&mut self, block: &[u8; 16], permuted: u8) { - self.data.extend_from_slice(block); - self.data.extend([permuted]); + pub fn add_block(&mut self, block: &B) { + block.extend_into(&mut self.data); } - } impl CipherText for LeftCiphertext { diff --git a/formats/src/ciphertext/mod.rs b/formats/src/ciphertext/mod.rs index 74e11dc..eaced44 100644 --- a/formats/src/ciphertext/mod.rs +++ b/formats/src/ciphertext/mod.rs @@ -1,8 +1,8 @@ use std::slice::Iter; -use crate::header::Header; -mod left; -mod right; -mod combined; +use crate::{header::Header, data_with_header::DataWithHeader}; +pub(crate) mod left; +pub(crate) mod right; +pub(crate) mod combined; // TODO: make the new and add_block functions a separate trait pub trait CipherText { @@ -17,7 +17,7 @@ pub trait CipherText { fn blocks(&self) -> Iter; } -pub trait CipherTextBlock { +pub trait CipherTextBlock { // TODO: Zeroize fn byte_size() -> usize; - fn extend_into(&self, out: &mut Vec); + fn extend_into(&self, out: &mut DataWithHeader); } diff --git a/formats/src/ciphertext/right.rs b/formats/src/ciphertext/right.rs index 3587d27..71a2c05 100644 --- a/formats/src/ciphertext/right.rs +++ b/formats/src/ciphertext/right.rs @@ -1,5 +1,5 @@ use std::{marker::PhantomData, slice::Iter}; -use crate::{data_with_header::DataWithHeader, ParseError, header::Header, CtType}; +use crate::{data_with_header::{DataWithHeader, CtType}, ParseError, header::Header}; use super::{CipherTextBlock, CipherText}; pub struct RightCiphertext { diff --git a/formats/src/data_with_header.rs b/formats/src/data_with_header.rs index d05d109..b204168 100644 --- a/formats/src/data_with_header.rs +++ b/formats/src/data_with_header.rs @@ -1,6 +1,6 @@ use crate::header::Header; -pub(crate) struct DataWithHeader { +pub struct DataWithHeader { data: Vec } @@ -43,14 +43,14 @@ impl DataWithHeader { &self.data[Header::HEADER_LEN..] } - pub(crate) fn extend(&mut self, iter: I) + pub fn extend(&mut self, iter: I) where I: IntoIterator { self.data.extend(iter) } - pub(crate) fn extend_from_slice(&mut self, slice: &[u8]) { + pub fn extend_from_slice(&mut self, slice: &[u8]) { self.data.extend_from_slice(slice); } } diff --git a/formats/src/header.rs b/formats/src/header.rs index a4dbd18..6198651 100644 --- a/formats/src/header.rs +++ b/formats/src/header.rs @@ -1,4 +1,4 @@ -use super::CtType; +use crate::data_with_header::CtType; #[derive(PartialEq, Debug)] pub struct Header { @@ -61,7 +61,7 @@ impl Header { #[cfg(test)] mod tests { - use crate::CtType; + use crate::data_with_header::CtType; use super::Header; #[test] diff --git a/formats/src/lib.rs b/formats/src/lib.rs index aea890f..8c60a4c 100644 --- a/formats/src/lib.rs +++ b/formats/src/lib.rs @@ -3,32 +3,14 @@ mod data_with_header; mod header; mod ciphertext; +pub use ciphertext::{ + CipherText, + CipherTextBlock, + left::LeftCiphertext, + right::RightCiphertext, + combined::CombinedCiphertext +}; +pub use data_with_header::DataWithHeader; + #[derive(Debug)] pub struct ParseError {} - -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum CtType { - Left = 0, - Right = 1, - Combined = 2, -} - -impl From for CtType { - fn from(value: u8) -> Self { - match value { - 0 => Self::Left, - 1 => Self::Right, - 2 => Self::Combined, - _ => panic!("Unknown Ciphertext Type") - } - } -} - - - - - - - -pub struct LeftBlock([u8; 16], u8); - diff --git a/ore-rs-5bit/Cargo.toml b/ore-rs-5bit/Cargo.toml index 173d88f..2bdcd08 100644 --- a/ore-rs-5bit/Cargo.toml +++ b/ore-rs-5bit/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] primitives = { path = "../primitives" } +formats = { path = "../formats" } zeroize = { workspace = true } rand = { workspace = true } rand_chacha = { workspace = true } @@ -16,4 +17,4 @@ aes = { workspace = true } hex-literal = "0.3.2" cmac = "0.7.2" bit-vec = "0.6.3" -hex.workspace = true +hex = { workspace = true } diff --git a/ore-rs-5bit/src/format.rs b/ore-rs-5bit/src/format.rs deleted file mode 100644 index 668be48..0000000 --- a/ore-rs-5bit/src/format.rs +++ /dev/null @@ -1,223 +0,0 @@ -use std::slice::Iter; - -use self::header::Header; -mod header; - -// TODO: make the new and add_block functions a separate trait - -pub trait CipherText { - fn comparable(&self, to: &impl CipherText) -> bool { - self.header().comparable(&to.header()) - } - fn header(&self) -> Header; - - fn blocks(&self) -> Iter; -} - -#[derive(Debug)] -pub struct ParseError {} - -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum CtType { - Left = 0, - Right = 1, - Combined = 2, -} - -impl From for CtType { - fn from(value: u8) -> Self { - match value { - 0 => Self::Left, - 1 => Self::Right, - 2 => Self::Combined, - _ => panic!("Unknown Ciphertext Type") - } - } -} - -struct DataWithHeader { - data: Vec -} - -impl DataWithHeader { - fn new(header: Header, body_len: usize) -> Self { - let mut data = Vec::with_capacity(Header::HEADER_LEN + body_len); - data.extend(header.to_vec()); - Self { data } - } - - fn header(&self) -> Header { - Header::from_slice(&self.data) - } - - fn set_header(&mut self, hdr: &Header) { - self.data[0..Header::HEADER_LEN].copy_from_slice(&hdr.to_vec()); - } - - /// Returns a slice to the body of the ciphertext. - /// That is, everything after the header. - pub fn body(&self) -> &[u8] { - &self.data[Header::HEADER_LEN..] - } - - fn extend(&mut self, iter: I) - where - I: IntoIterator - { - self.data.extend(iter) - } - - fn extend_from_slice(&mut self, slice: &[u8]) { - self.data.extend_from_slice(slice); - } -} - -pub struct LeftCiphertext { - data: DataWithHeader -} - -pub struct RightCiphertext { - data: DataWithHeader -} - -pub struct CombinedCiphertext { - data: DataWithHeader -} - -impl CipherText for LeftCiphertext { - fn header(&self) -> Header { - self.data.header() - } -} - -impl CipherText for CombinedCiphertext { - fn header(&self) -> Header { - self.data.header() - } -} - -impl TryFrom<&[u8]> for LeftCiphertext { - type Error = ParseError; - - fn try_from(data: &[u8]) -> Result { - let hdr = Header::from_slice(data); - if matches!(hdr.ct_type, CtType::Left) { - // TODO: It would be nice if we could avoid this copy! - let raw = DataWithHeader { data: data.to_vec() }; - Ok(LeftCiphertext { data: raw }) - } else { - Err(ParseError { }) - } - } -} - -impl TryFrom<&[u8]> for CombinedCiphertext { - type Error = ParseError; - - fn try_from(data: &[u8]) -> Result { - let hdr = Header::from_slice(data); - if matches!(hdr.ct_type, CtType::Combined) { - // TODO: It would be nice if we could avoid this copy! - let raw = DataWithHeader { data: data.to_vec() }; - Ok(Self { data: raw }) - } else { - Err(ParseError { }) - } - } -} - -impl AsRef<[u8]> for DataWithHeader { - fn as_ref(&self) -> &[u8] { - self.data.as_ref() - } -} - -impl AsRef<[u8]> for LeftCiphertext { - fn as_ref(&self) -> &[u8] { - self.data.as_ref() - } -} - -impl AsRef<[u8]> for RightCiphertext { - fn as_ref(&self) -> &[u8] { - self.data.as_ref() - } -} - -impl AsRef<[u8]> for CombinedCiphertext { - fn as_ref(&self) -> &[u8] { - self.data.as_ref() - } -} - -pub struct LeftBlock([u8; 16], u8); - -pub struct CombinedBlock(LeftBlock, u32); - -// TODO: Should we include a scheme and/or version number? -/// Wrapper to a structured byte array representing a Left ciphertext. -/// -/// ## Format -/// -/// | Field | Number of Bytes | -/// |-------|-----------------| -/// | type | 1 | -/// | num_blocks | 2 (up to 65535 blocks) | -/// | block* | 17 | -/// -/// * There are `num_blocks` blocks of 17 bytes. -/// -impl LeftCiphertext { - // TODO: Self::from() should take an AsRef<[u8]> (not a slice) - const BLOCK_SIZE: usize = 17; - - pub fn new(num_blocks: usize) -> Self { - let hdr = Header::new(CtType::Left, num_blocks); - - Self { data: DataWithHeader::new(hdr, num_blocks * Self::BLOCK_SIZE) } - } - - pub fn add_block(&mut self, block: &[u8; 16], permuted: u8) { - self.data.extend_from_slice(block); - self.data.extend([permuted]); - } - -} - -impl RightCiphertext { - // TODO: This could be generic - const BLOCKSIZE: usize = 32; - const NONCE_SIZE: usize = 16; - - pub fn new(num_blocks: usize, nonce: &[u8; 16]) -> Self { - let hdr = Header::new(CtType::Left, num_blocks); - let mut data = DataWithHeader::new(hdr, Self::NONCE_SIZE + (num_blocks * Self::BLOCKSIZE)); - data.extend_from_slice(nonce); - Self { data } - } - - pub fn add_block(&mut self, block: u32) { - self.data.extend(block.to_be_bytes().into_iter()); - } -} - -impl CombinedCiphertext { - /// Creates a new CombinedCiphertext by merging (and consuming) the given left and right Ciphertexts. - /// The headers must be comparable (See [Header]) and have the same block length. - /// The resulting Ciphertext has a single header representing both ciphertexts. - pub fn new(mut left: LeftCiphertext, right: RightCiphertext) -> Self { - let mut l_hdr = left.data.header(); - let r_hdr = right.data.header(); - - if !l_hdr.comparable(&r_hdr) || l_hdr.num_blocks != r_hdr.num_blocks { - panic!("Cannot combine incompatible ciphertexts"); - } - - // Steal and reuse the left - l_hdr.ct_type = CtType::Combined; - left.data.set_header(&l_hdr); - left.data.extend_from_slice(right.data.body()); - - Self { data: left.data } - } -} \ No newline at end of file diff --git a/ore-rs-5bit/src/format/header.rs b/ore-rs-5bit/src/format/header.rs deleted file mode 100644 index 514e473..0000000 --- a/ore-rs-5bit/src/format/header.rs +++ /dev/null @@ -1,91 +0,0 @@ -use super::CtType; - -#[derive(PartialEq, Debug)] -pub struct Header { - pub version: u16, - pub scheme: u8, - pub ct_type: CtType, - pub num_blocks: u16 -} - -impl Header { - pub(super) const HEADER_LEN: usize = 6; - - pub(super) fn new(ct_type: CtType, num_blocks: usize) -> Self { - assert!(num_blocks < (u16::MAX as usize)); - - Self { - // Hardcode version and scheme for now - version: 0, - scheme: 0, - ct_type, - num_blocks: num_blocks as u16 - } - } - - /// Indicates if this ciphertext header is comparable to another. - /// This means the version and scheme must be the same. - /// Specific schemes may impose additional restrictions (such as matching lengths). - pub(super) fn comparable(&self, other: &Header) -> bool { - use CtType::*; - if self.version != other.version { return false } - if self.scheme != self.scheme { return false } - - match (self.ct_type, other.ct_type) { - (Combined, Combined) => true, - (Left, Right) => true, - _ => false - } - } - - pub(super) fn to_vec(&self) -> Vec { - let mut hdr: Vec = Vec::with_capacity(Self::HEADER_LEN); - hdr.extend(self.version.to_be_bytes()); - hdr.push(self.scheme); - hdr.push(*&self.ct_type as u8); - hdr.extend(self.num_blocks.to_be_bytes()); - hdr - } - - pub(super) fn from_slice(hdr: &[u8]) -> Self { // TODO: Handle error - assert!(hdr.len() >= Self::HEADER_LEN, "Header cannot be read from slice of less than {} bytes", Self::HEADER_LEN); - let mut iter = hdr.into_iter(); - let version: u16 = u16::from_be_bytes(iter.next_chunk::<2>().unwrap().map(|c| *c)); - let scheme: u8 = *iter.next().unwrap(); - let ct_type: CtType = (*iter.next().unwrap()).into(); - let num_blocks: u16 = u16::from_be_bytes(iter.next_chunk::<2>().unwrap().map(|c| *c)); - - Self { version, scheme, ct_type, num_blocks } - } -} - -#[cfg(test)] -mod tests { - use crate::format::CtType; - use super::Header; - - #[test] - fn test_new() { - let header = Header::new(CtType::Right, 12); - assert_eq!(header.version, 0); - assert_eq!(header.scheme, 0); - assert_eq!(header.ct_type, CtType::Right); - assert_eq!(header.num_blocks, 12); - } - - #[test] - fn test_roundtrip() { - let header = Header::new(CtType::Left, 8); - let bytes = header.to_vec(); - assert_eq!(header, Header::from_slice(&bytes)); - } - - #[test] - fn test_roundtrip_with_ignored_trailing_bytes() { - let header = Header::new(CtType::Left, 8); - let mut bytes: Vec = Vec::new(); - bytes.extend(header.to_vec()); - bytes.extend(vec![1, 2, 3, 4]); - assert_eq!(header, Header::from_slice(&bytes)); - } -} diff --git a/ore-rs-5bit/src/lib.rs b/ore-rs-5bit/src/lib.rs index d0c4002..1cd8d4f 100644 --- a/ore-rs-5bit/src/lib.rs +++ b/ore-rs-5bit/src/lib.rs @@ -1,14 +1,12 @@ use std::cell::RefCell; -use format::CombinedCiphertext; +use formats::{LeftCiphertext, CipherTextBlock, DataWithHeader}; use primitives::{prf::{Aes128Prf, PrfBlock}, Prf, prp::{KnuthShufflePRP, bitwise::BitwisePrp}, Prp, AesBlock, KnuthShuffleGenerator, PrpGenerator, NewPrp, hash::Aes128Z2Hash, Hash, HashKey}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; use zeroize::ZeroizeOnDrop; use aes::{cipher::generic_array::GenericArray, Aes128}; -use cmac::{Cmac, Mac, digest::FixedOutput}; -use crate::{packing::packed_prefixes, format::{LeftCiphertext, CipherText}}; +use crate::packing::packed_prefixes; pub mod packing; -pub mod format; #[derive(Debug, ZeroizeOnDrop)] pub struct Ore5Bit { @@ -26,22 +24,26 @@ pub struct Ore5Bit { pub struct OreError; pub type Ore5BitChaCha20 = Ore5Bit; +pub type Ore5BitLeft = LeftCiphertext; #[derive(Debug)] pub struct Out(Vec, u8); -// TODO: Include block prefixes -pub fn cmac(key: &[u8; 16], input: &[u8]) -> PrfBlock { - let mut out: PrfBlock = Default::default(); - let mut buf = GenericArray::from_mut_slice(&mut out); - // TODO: This might be really inefficient! - let mut mac = Cmac::::new_from_slice(key).unwrap(); - mac.update(input); - mac.finalize_into(&mut buf); - //result.into_bytes().to_vec() - out +pub struct LeftBlock([u8; 16], u8); +pub struct RightBlock(u32); + +impl CipherTextBlock for LeftBlock { + fn byte_size() -> usize { + 17 + } + + fn extend_into(&self, out: &mut DataWithHeader) { + out.extend_from_slice(&self.0); + out.extend([self.1]); + } } + // TODO: Make this use the ORE traits once we've cleaned these up impl Ore5Bit { // TODO: This should be an implementation of OreInit @@ -63,12 +65,12 @@ impl Ore5Bit { /// most significant 3-bits of each value are ignored). /// TODO: Create a wrapper type /// TODO: This might be faster if we do it blocks of statically allocated chunks - pub fn encrypt_left(&self, input: &[u8]) -> LeftCiphertext { + pub fn encrypt_left(&self, input: &[u8]) -> Ore5BitLeft { // We're limited to 16 input blocks for now because we're using AES as the PRF (1 block) debug_assert!(input.len() <= 16); // TODO: We could possibly use the stack and just do a single extend on to a vec after each round // Format: [, , ] - let mut out = LeftCiphertext::new(input.len()); + let mut out = Ore5BitLeft::new(input.len()); // Here we'll model a PRF using a single block of AES // This will be OK for up to 16-bytes of input (or 25 5-bit values) @@ -83,22 +85,23 @@ impl Ore5Bit { // This avoids a copy and should have the same effect. // TODO: Change this to use functional callbacks rather than for let mut p_ns = vec![]; - for (output_block, input_block) in prefixes.iter_mut().zip(input.iter()) { - let prp: KnuthShufflePRP = Prp::new(output_block).unwrap(); // TODO: - let p_n = prp.permute(*input_block).unwrap(); + for (enc_prefix, in_blk) in prefixes.iter_mut().zip(input.iter()) { + //let prp: KnuthShufflePRP = Prp::new(out_blk).unwrap(); // TODO: + let prp: NewPrp = KnuthShuffleGenerator::new(enc_prefix).generate(); + let p_n = prp.permute(*in_blk); p_ns.push(p_n); - output_block[15] = p_n; + enc_prefix[15] = p_n; } self.prf1.encrypt_all(&mut prefixes); for (prf_block, permuted) in prefixes.iter().zip(p_ns.iter()) { - out.add_block(prf_block, *permuted); + out.add_block(&LeftBlock(*prf_block, *permuted)); } out } - pub fn encrypt(&self, input: &[u8]) -> (Vec, Vec) { + pub fn encrypt(&self, input: &[u8]) -> (Ore5BitLeft, Vec) { // TODO: Combined let mut nonce: [u8; 16] = Default::default(); self.rng.borrow_mut().try_fill(&mut nonce).unwrap(); @@ -106,17 +109,18 @@ impl Ore5Bit { debug_assert!(input.len() <= 16); // TODO: We could possibly use the stack and just do a single extend on to a vec after each round // Format: [, , ] - let mut left: Vec = Vec::new(); // TODO: What capacity? + let mut left = Ore5BitLeft::new(input.len()); + let mut right: Vec = Vec::new(); // TODO: What capacity? // Here we'll model a PRF using a single block of AES // This will be OK for up to 16-bytes of input (or 25 5-bit values) // For larger inputs we can chain the values by XORing the last output // with the next input (a little like CMAC). - let mut left_blks = packed_prefixes(input); + let mut prefixes = packed_prefixes(input); let mut right_blocks: Vec = Vec::new(); // TODO: Len - self.prf2.encrypt_all(&mut left_blks); - left.push(input.len().try_into().unwrap()); // TODO: DOn't unwrap + self.prf2.encrypt_all(&mut prefixes); + right.push(input.len().try_into().unwrap()); // TODO: DOn't unwrap // This deviates from the paper slightly. @@ -126,24 +130,28 @@ impl Ore5Bit { // We also use a mask to set the comparison bits in one constant time // operation and then perform a bitwise shuffle using the PRP // instead of performing comparisons on each value (which would not be constant time). - for (out_left_blk, in_blk) in left_blks.iter_mut().zip(input.iter()) { + let mut p_ns: Vec = Vec::with_capacity(input.len()); + + for (enc_prefix, in_blk) in prefixes.iter_mut().zip(input.iter()) { let out_right_blk: u32 = 0xFFFFFFFF >> *in_blk; - let prp: NewPrp = KnuthShuffleGenerator::new(out_left_blk).generate(); + let prp: NewPrp = KnuthShuffleGenerator::new(enc_prefix).generate(); let p_n = prp.permute(*in_blk); + p_ns.push(p_n); + enc_prefix[15] = p_n; + out_right_blk.inverse_shuffle(&prp); - left.push(p_n); - out_left_blk[15] = p_n; right_blocks.push(out_right_blk.inverse_shuffle(&prp)); } - self.prf1.encrypt_all(&mut left_blks); + self.prf1.encrypt_all(&mut prefixes); - for (left_blk, right_blk) in left_blks.iter().zip(right_blocks.iter()) { - left.extend_from_slice(left_blk); + // TODO: This feels a bit janky + for ((enc_prefix, right_blk), p_n) in prefixes.iter().zip(right_blocks.iter()).zip(p_ns.iter()) { + left.add_block(&LeftBlock(*enc_prefix, *p_n)); let mut ro_keys: [[u8; 16]; 32] = Default::default(); for (j, ro_key) in ro_keys.iter_mut().enumerate() { - ro_key.copy_from_slice(left_blk); + ro_key.copy_from_slice(enc_prefix); ro_key[15] = j as u8; } self.prf1.encrypt_all(&mut ro_keys); @@ -161,9 +169,9 @@ impl Ore5Bit { // TODO: The CipherText types might be better to implement a CipherText trait // so that we can handle the different variations here? pub fn compare_slices(a: impl AsRef<[u8]>, b: impl AsRef<[u8]>) -> u8 { - let left: LeftCiphertext = a.as_ref().try_into().unwrap(); - let combined: CombinedCiphertext = b.as_ref().try_into().unwrap(); - assert!(left.comparable(&combined)); // TODO: Error + let left: Ore5BitLeft = a.as_ref().try_into().unwrap(); + //let combined: CombinedCiphertext = b.as_ref().try_into().unwrap(); + //assert!(left.comparable(&combined)); // TODO: Error 0 } diff --git a/ore-rs-5bit/src/main.rs b/ore-rs-5bit/src/main.rs index 6bc8ad0..9dc0eda 100644 --- a/ore-rs-5bit/src/main.rs +++ b/ore-rs-5bit/src/main.rs @@ -1,27 +1,15 @@ use hex_literal::hex; -use ore_rs_5bit::{Ore5BitChaCha20, packing::packed_prefixes}; - -fn permute_u32(input: u32, perm: &[usize; 32]) -> u32 { - let mut output: u32 = 0; - - for (i, &p) in perm.iter().enumerate() { - // Extract the bit from the input value at the index specified by the permutation array - let bit = (input >> p) & 1; - - // Set the bit in the output value at the corresponding index - output |= bit << i; - } - - output -} +use ore_rs_5bit::Ore5BitChaCha20; fn main() { let k1 = hex!("00010203 04050607 08090a0b 0c0d0e0f"); let k2 = hex!("d0d007a5 3f9a6848 83bc1f21 0f6595a3"); - let input = vec![7, 23, 30, 2]; + let input = vec![7]; let ore = Ore5BitChaCha20::init(&k1, &k2).unwrap(); let left = ore.encrypt_left(&input); + let (l1, r) = ore.encrypt(&input); println!("Left encrypt: {}", hex::encode(&left)); + println!("Left encrypt: {}", hex::encode(&l1)); //println!("{}, {}, {}", left.len(), right.len(), hex::encode(right)); } \ No newline at end of file From 513c095be68fc1ce74f6d3f71184765f4c4c3edb Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Sun, 14 May 2023 22:01:25 +1000 Subject: [PATCH 10/18] Use Right and Combined data structures for new scheme --- formats/src/ciphertext/right.rs | 2 +- ore-rs-5bit/src/lib.rs | 32 ++++++++++++++++---------------- ore-rs-5bit/src/main.rs | 7 +++++-- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/formats/src/ciphertext/right.rs b/formats/src/ciphertext/right.rs index 71a2c05..2008640 100644 --- a/formats/src/ciphertext/right.rs +++ b/formats/src/ciphertext/right.rs @@ -11,7 +11,7 @@ impl RightCiphertext { const NONCE_SIZE: usize = 16; pub fn new(num_blocks: usize, nonce: &[u8; 16]) -> Self { - let hdr = Header::new(CtType::Left, num_blocks); + let hdr = Header::new(CtType::Right, num_blocks); let mut data = DataWithHeader::new(hdr, Self::NONCE_SIZE + (num_blocks * ::Block::byte_size())); data.extend_from_slice(nonce); Self { data, _phantom: PhantomData } diff --git a/ore-rs-5bit/src/lib.rs b/ore-rs-5bit/src/lib.rs index 1cd8d4f..54c33a8 100644 --- a/ore-rs-5bit/src/lib.rs +++ b/ore-rs-5bit/src/lib.rs @@ -1,5 +1,5 @@ use std::cell::RefCell; -use formats::{LeftCiphertext, CipherTextBlock, DataWithHeader}; +use formats::{LeftCiphertext, CipherTextBlock, DataWithHeader, RightCiphertext, CombinedCiphertext}; use primitives::{prf::{Aes128Prf, PrfBlock}, Prf, prp::{KnuthShufflePRP, bitwise::BitwisePrp}, Prp, AesBlock, KnuthShuffleGenerator, PrpGenerator, NewPrp, hash::Aes128Z2Hash, Hash, HashKey}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; @@ -25,9 +25,8 @@ pub struct OreError; pub type Ore5BitChaCha20 = Ore5Bit; pub type Ore5BitLeft = LeftCiphertext; - -#[derive(Debug)] -pub struct Out(Vec, u8); +pub type Ore5BitRight = RightCiphertext; +pub type Ore5BitCombined = CombinedCiphertext; pub struct LeftBlock([u8; 16], u8); pub struct RightBlock(u32); @@ -43,6 +42,15 @@ impl CipherTextBlock for LeftBlock { } } +impl CipherTextBlock for RightBlock { + fn byte_size() -> usize { + 32 + } + + fn extend_into(&self, out: &mut DataWithHeader) { + out.extend(self.0.to_be_bytes()); + } +} // TODO: Make this use the ORE traits once we've cleaned these up impl Ore5Bit { @@ -68,8 +76,6 @@ impl Ore5Bit { pub fn encrypt_left(&self, input: &[u8]) -> Ore5BitLeft { // We're limited to 16 input blocks for now because we're using AES as the PRF (1 block) debug_assert!(input.len() <= 16); - // TODO: We could possibly use the stack and just do a single extend on to a vec after each round - // Format: [, , ] let mut out = Ore5BitLeft::new(input.len()); // Here we'll model a PRF using a single block of AES @@ -86,7 +92,6 @@ impl Ore5Bit { // TODO: Change this to use functional callbacks rather than for let mut p_ns = vec![]; for (enc_prefix, in_blk) in prefixes.iter_mut().zip(input.iter()) { - //let prp: KnuthShufflePRP = Prp::new(out_blk).unwrap(); // TODO: let prp: NewPrp = KnuthShuffleGenerator::new(enc_prefix).generate(); let p_n = prp.permute(*in_blk); p_ns.push(p_n); @@ -101,17 +106,14 @@ impl Ore5Bit { out } - pub fn encrypt(&self, input: &[u8]) -> (Ore5BitLeft, Vec) { // TODO: Combined + pub fn encrypt(&self, input: &[u8]) -> Ore5BitCombined { let mut nonce: [u8; 16] = Default::default(); self.rng.borrow_mut().try_fill(&mut nonce).unwrap(); // TODO: Can we pack the input bytes?? debug_assert!(input.len() <= 16); - // TODO: We could possibly use the stack and just do a single extend on to a vec after each round - // Format: [, , ] let mut left = Ore5BitLeft::new(input.len()); - - let mut right: Vec = Vec::new(); // TODO: What capacity? + let mut right = Ore5BitRight::new(input.len(), &nonce); // Here we'll model a PRF using a single block of AES // This will be OK for up to 16-bytes of input (or 25 5-bit values) @@ -121,8 +123,6 @@ impl Ore5Bit { let mut right_blocks: Vec = Vec::new(); // TODO: Len self.prf2.encrypt_all(&mut prefixes); - right.push(input.len().try_into().unwrap()); // TODO: DOn't unwrap - // This deviates from the paper slightly. // Instead of calling PRF1 with the plaintext prefix, we call it // with the output of the PRF2 of the prefix. @@ -160,10 +160,10 @@ impl Ore5Bit { // Push bytes onto right output vec let hasher: Aes128Z2Hash = Hash::new(&nonce.into()); let final_right = right_blk ^ hasher.hash_all_onto_u32(&ro_keys); - right.extend_from_slice(&final_right.to_be_bytes()); + right.add_block(final_right); } - (left, right) + Ore5BitCombined::new(left, right) } // TODO: The CipherText types might be better to implement a CipherText trait diff --git a/ore-rs-5bit/src/main.rs b/ore-rs-5bit/src/main.rs index 9dc0eda..2a98e43 100644 --- a/ore-rs-5bit/src/main.rs +++ b/ore-rs-5bit/src/main.rs @@ -1,6 +1,9 @@ use hex_literal::hex; use ore_rs_5bit::Ore5BitChaCha20; +// TODO: Revise the Main ORE traits +// TODO: Add PartialOrd implementations for CipherText variants + fn main() { let k1 = hex!("00010203 04050607 08090a0b 0c0d0e0f"); let k2 = hex!("d0d007a5 3f9a6848 83bc1f21 0f6595a3"); @@ -8,8 +11,8 @@ fn main() { let input = vec![7]; let ore = Ore5BitChaCha20::init(&k1, &k2).unwrap(); let left = ore.encrypt_left(&input); - let (l1, r) = ore.encrypt(&input); + let combined = ore.encrypt(&input); println!("Left encrypt: {}", hex::encode(&left)); - println!("Left encrypt: {}", hex::encode(&l1)); + println!("Combines : {}", hex::encode(&combined)); //println!("{}, {}, {}", left.len(), right.len(), hex::encode(right)); } \ No newline at end of file From 5523d910e0025a2c17ef5c36f143273c87fa9ddf Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Tue, 16 May 2023 22:27:11 +1000 Subject: [PATCH 11/18] Work on ciphertext and comparison traits --- formats/Cargo.toml | 1 + formats/src/ciphertext/combined.rs | 134 ++++++++++++++++++++++------- formats/src/ciphertext/left.rs | 61 ++++++++++--- formats/src/ciphertext/mod.rs | 30 +++++-- formats/src/ciphertext/right.rs | 27 +++--- formats/src/lib.rs | 5 ++ formats/src/main.rs | 3 - ore-rs-5bit/Cargo.toml | 1 + ore-rs-5bit/src/lib.rs | 105 +++++++++++++++++----- ore-rs-5bit/src/main.rs | 12 ++- 10 files changed, 290 insertions(+), 89 deletions(-) delete mode 100644 formats/src/main.rs diff --git a/formats/Cargo.toml b/formats/Cargo.toml index 432194f..e770597 100644 --- a/formats/Cargo.toml +++ b/formats/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +subtle-ng = { workspace = true } diff --git a/formats/src/ciphertext/combined.rs b/formats/src/ciphertext/combined.rs index ac1bddc..299217b 100644 --- a/formats/src/ciphertext/combined.rs +++ b/formats/src/ciphertext/combined.rs @@ -1,10 +1,29 @@ -use std::{slice::Iter, marker::PhantomData}; -use crate::{data_with_header::{CtType, DataWithHeader}, header::Header, ParseError}; -use super::{CipherTextBlock, CipherText, left::LeftCiphertext, right::RightCiphertext}; +use std::marker::PhantomData; +use crate::{data_with_header::{CtType, DataWithHeader}, header::Header, ParseError, LeftBlockEq, LeftCipherTextBlock, OreBlockOrd}; +use super::{CipherTextBlock, CipherText, left::LeftCiphertext, right::RightCiphertext, RightCipherTextBlock}; + +#[derive(Debug)] +pub struct CombinedBlock<'a, L: CipherTextBlock<'a>, R: CipherTextBlock<'a>> { + pub left: L, + pub right: R, + _phantom: PhantomData<&'a L> +} -pub struct CombinedBlock(L, R); +/// A combined ciphertext block also implements Right Block +impl<'a, L, R> RightCipherTextBlock<'a> for CombinedBlock<'a, L, R> +where L: LeftCipherTextBlock<'a>, + R: RightCipherTextBlock<'a> +{} + +impl <'a, L: CipherTextBlock<'a>, R: CipherTextBlock<'a>> From<&'a [u8]> for CombinedBlock<'a, L, R> { + fn from(value: &'a [u8]) -> Self { + let left = L::from(&value[..L::byte_size()]); + let right = R::from(&value[L::byte_size()..]); + Self { left, right, _phantom: PhantomData } + } +} -impl CipherTextBlock for CombinedBlock { +impl<'a, L: CipherTextBlock<'a>, R: CipherTextBlock<'a>> CipherTextBlock<'a> for CombinedBlock<'a, L, R> { fn byte_size() -> usize { L::byte_size() + R::byte_size() } @@ -14,50 +33,72 @@ impl CipherTextBlock for CombinedBlock { +pub struct CombinedCiphertext<'a, L: LeftCipherTextBlock<'a>, R: RightCipherTextBlock<'a>> { data: DataWithHeader, - _phantom: (PhantomData, PhantomData), + _phantom: (PhantomData<&'a L>, PhantomData<&'a R>), } -impl CombinedCiphertext { - /// Creates a new CombinedCiphertext by merging (and consuming) the given left and right Ciphertexts. - /// The headers must be comparable (See [Header]) and have the same block length. - /// The resulting Ciphertext has a single header representing both ciphertexts. - pub fn new(mut left: LeftCiphertext, right: RightCiphertext) -> Self { - let mut l_hdr = left.header(); - let r_hdr = right.header(); - - if !l_hdr.comparable(&r_hdr) || l_hdr.num_blocks != r_hdr.num_blocks { - panic!("Cannot combine incompatible ciphertexts"); - } +impl<'a, L, R> CombinedCiphertext<'a, L, R> +where + L: LeftCipherTextBlock<'a>, + R: RightCipherTextBlock<'a> +{ + pub fn new(num_blocks: usize, nonce: &[u8; 16]) -> Self { + let hdr = Header::new(CtType::Combined, num_blocks); + let mut data = DataWithHeader::new( + hdr, + RightCiphertext::<'a, R>::NONCE_SIZE + (num_blocks * ::Block::byte_size()) + ); + data.extend_from_slice(nonce); + Self { data, _phantom: (PhantomData, PhantomData) } + } - // Steal and reuse the left - l_hdr.ct_type = CtType::Combined; - left.data.set_header(&l_hdr); - left.data.extend_from_slice(right.data.body()); + // TODO: We should probably pass the args as references (same for left and right impls) + pub fn add_block(&mut self, left: L, right: R) { + left.extend_into(&mut self.data); + right.extend_into(&mut self.data); + } - Self { data: left.data, _phantom: (PhantomData, PhantomData) } + pub fn nonce(&self) -> &[u8] { + &self.data.body()[..RightCiphertext::<'a, R>::NONCE_SIZE] } } -impl CipherText for CombinedCiphertext { - type Block = CombinedBlock; +impl<'a, L, R> CipherText<'a> for CombinedCiphertext<'a, L, R> +where + L: LeftCipherTextBlock<'a>, + R: RightCipherTextBlock<'a> +{ + type Block = CombinedBlock<'a, L, R>; fn header(&self) -> Header { self.data.header() } - fn blocks(&self) -> Iter { - todo!() + // TODO: This can go into the trait if we add a body method + // Right is different though because we have the nonce! + fn blocks(&'a self) -> Box + 'a> { + Box::new( + self.data.body()[RightCiphertext::<'a, R>::NONCE_SIZE..] + .chunks(Self::Block::byte_size()) + .map(|bytes| { + println!("BYTES LEN: {}", bytes.len()); + Self::Block::from(bytes) + }) + ) } } -impl TryFrom<&[u8]> for CombinedCiphertext { +impl<'a, L, R> TryFrom<&'a [u8]> for CombinedCiphertext<'a, L, R> +where + L: LeftCipherTextBlock<'a>, + R: RightCipherTextBlock<'a> +{ type Error = ParseError; - fn try_from(data: &[u8]) -> Result { + fn try_from(data: &'a [u8]) -> Result { let hdr = Header::from_slice(data); if matches!(hdr.ct_type, CtType::Combined) { Ok(Self { data: data.into(), _phantom: (PhantomData, PhantomData) }) @@ -67,8 +108,41 @@ impl TryFrom<&[u8]> for CombinedCipherte } } -impl AsRef<[u8]> for CombinedCiphertext { +impl<'a, L, R> AsRef<[u8]> for CombinedCiphertext<'a, L, R> +where + L: LeftCipherTextBlock<'a>, + R: RightCipherTextBlock<'a> +{ fn as_ref(&self) -> &[u8] { self.data.as_ref() } } + +/// Blanket implementation to compare a left block to the left of any combined block +impl<'a, L, R> LeftBlockEq<'a, CombinedBlock<'a, L, R>> for L +where + L: LeftCipherTextBlock<'a>, + R: RightCipherTextBlock<'a> +{ + type Other = CombinedBlock<'a, L, R>; + + fn constant_eq(&self, other: &Self::Other) -> subtle_ng::Choice { + self.constant_eq(&other.left) + } +} + +/// Blanket implementation for a left block to Ore compare to the right block +/// of a combined block. +impl<'a, L, R> OreBlockOrd<'a, CombinedBlock<'a, L, R>> for L +where + L: LeftCipherTextBlock<'a>, + R: RightCipherTextBlock<'a>, + L: OreBlockOrd<'a, R> +{ + type Other = CombinedBlock<'a, L, R>; + + fn ore_compare(&self, other: &Self::Other) -> u8 { + self.ore_compare(&other.right) + } +} + diff --git a/formats/src/ciphertext/left.rs b/formats/src/ciphertext/left.rs index e780d17..8fb1958 100644 --- a/formats/src/ciphertext/left.rs +++ b/formats/src/ciphertext/left.rs @@ -1,13 +1,15 @@ -use std::{marker::PhantomData, slice::Iter}; -use crate::{data_with_header::{CtType, DataWithHeader}, header::Header, ParseError}; -use super::{CipherTextBlock, CipherText}; +use std::{marker::PhantomData, ops::BitOr}; +use subtle_ng::{Choice, CtOption}; -pub struct LeftCiphertext { +use crate::{data_with_header::{CtType, DataWithHeader}, header::Header, ParseError, LeftCipherTextBlock, OreBlockOrd}; +use super::{CipherTextBlock, CipherText, LeftBlockEq}; + +pub struct LeftCiphertext<'a, B: LeftCipherTextBlock<'a>> { pub(crate) data: DataWithHeader, - _phantom: PhantomData, + _phantom: PhantomData<&'a B>, } -impl LeftCiphertext { +impl<'a, B: LeftCipherTextBlock<'a>> LeftCiphertext<'a, B> { pub fn new(num_blocks: usize) -> Self { let hdr = Header::new(CtType::Left, num_blocks); @@ -17,27 +19,60 @@ impl LeftCiphertext { } } - pub fn add_block(&mut self, block: &B) { + pub fn add_block(&mut self, block: B) { block.extend_into(&mut self.data); } + + // TODO: this should return an Option of the first block that differs + /// Compare all the blocks of self with all the blocks in the given iterator, up to the `n` + /// where `n` is the length of the shorter iterator. + /// In the case that the two iterators are not equal length and all `n` blocks are equal, + /// TODO??? What do we return? Perhaps we can do the ORE comparison in this step, too? + /// The ordering mechanism is important here, too (i.e. Lexicographic or Numerical) + /// If its numerical then the shorter value is always less than the other. + pub fn compare_blocks(&'a self, other: Box + 'a>) -> u8 // TODO: Return the PartialOrd enum + where + B: LeftBlockEq<'a, O> + OreBlockOrd<'a, O> + { + // TODO: We should use CtOption and acc.conditionally_select + let eq = self.blocks().zip(other).fold(None, { |acc: Option<(B, O)>, (a, b): (B, O)| + // FIXME: This definitely isn't constant time + if a.constant_eq(&b).into() { + acc.or(None) + } else { + Some((a, b)) + } + }); + + // FIXME: This isn't constant time either + if let Some((a, b)) = eq { + a.ore_compare(&b) + } else { + // No blocks differ so say that the values are equal + 1 + } + + // TODO: Handle different lengths + } } -impl CipherText for LeftCiphertext { +impl<'a, B: LeftCipherTextBlock<'a>> CipherText<'a> for LeftCiphertext<'a, B> { type Block = B; fn header(&self) -> Header { self.data.header() } - fn blocks(&self) -> Iter { - todo!() + fn blocks(&'a self) -> Box + 'a> { + // TODO: Should we assert that length is a multiple of the block size? + Box::new(self.data.body().chunks(Self::Block::byte_size()).map(|bytes| B::from(bytes))) } } -impl TryFrom<&[u8]> for LeftCiphertext { +impl<'a, B: LeftCipherTextBlock<'a>> TryFrom<&'a [u8]> for LeftCiphertext<'a, B> { type Error = ParseError; - fn try_from(data: &[u8]) -> Result { + fn try_from(data: &'a [u8]) -> Result { let hdr = Header::from_slice(data); if matches!(hdr.ct_type, CtType::Left) { Ok(Self { data: data.into(), _phantom: PhantomData }) @@ -47,7 +82,7 @@ impl TryFrom<&[u8]> for LeftCiphertext { } } -impl AsRef<[u8]> for LeftCiphertext { +impl<'a, B: LeftCipherTextBlock<'a>> AsRef<[u8]> for LeftCiphertext<'a, B> { fn as_ref(&self) -> &[u8] { self.data.as_ref() } diff --git a/formats/src/ciphertext/mod.rs b/formats/src/ciphertext/mod.rs index eaced44..25ea2e9 100644 --- a/formats/src/ciphertext/mod.rs +++ b/formats/src/ciphertext/mod.rs @@ -1,23 +1,41 @@ -use std::slice::Iter; +use subtle_ng::{ConstantTimeEq, Choice}; use crate::{header::Header, data_with_header::DataWithHeader}; pub(crate) mod left; pub(crate) mod right; pub(crate) mod combined; // TODO: make the new and add_block functions a separate trait -pub trait CipherText { - type Block: CipherTextBlock; +pub trait CipherText<'a> { + type Block: CipherTextBlock<'a>; - fn comparable(&self, to: &impl CipherText) -> bool { + // TODO: Remove this + fn comparable(&self, to: &impl CipherText<'a>) -> bool { self.header().comparable(&to.header()) } // TODO: Probs shouldn't expose the header fn header(&self) -> Header; - fn blocks(&self) -> Iter; + fn blocks(&'a self) -> Box + 'a>; } -pub trait CipherTextBlock { // TODO: Zeroize +pub trait CipherTextBlock<'a>: From<&'a [u8]> { // TODO: Zeroize fn byte_size() -> usize; fn extend_into(&self, out: &mut DataWithHeader); } + +pub trait LeftCipherTextBlock<'a>: LeftBlockEq<'a> + CipherTextBlock<'a> {} + +pub trait RightCipherTextBlock<'a>: CipherTextBlock<'a> {} + +pub trait LeftBlockEq<'a, Other = Self> { + type Other: ?Sized + CipherTextBlock<'a>; + + // TODO: Maybe this is choice? Or a wrapper of choice at least + fn constant_eq(&self, other: &Other) -> Choice; +} + +pub trait OreBlockOrd<'a, Other> { + type Other: ?Sized + RightCipherTextBlock<'a>; + + fn ore_compare(&self, right: &Other) -> u8; // TODO: Return a PartialOrd enum value +} diff --git a/formats/src/ciphertext/right.rs b/formats/src/ciphertext/right.rs index 2008640..9a3f3ec 100644 --- a/formats/src/ciphertext/right.rs +++ b/formats/src/ciphertext/right.rs @@ -1,14 +1,14 @@ -use std::{marker::PhantomData, slice::Iter}; +use std::marker::PhantomData; use crate::{data_with_header::{DataWithHeader, CtType}, ParseError, header::Header}; -use super::{CipherTextBlock, CipherText}; +use super::{CipherTextBlock, CipherText, RightCipherTextBlock}; -pub struct RightCiphertext { +pub struct RightCiphertext<'a, B: RightCipherTextBlock<'a>> { pub(crate) data: DataWithHeader, - _phantom: PhantomData, + _phantom: PhantomData<&'a B>, } -impl RightCiphertext { - const NONCE_SIZE: usize = 16; +impl<'a, B: RightCipherTextBlock<'a>> RightCiphertext<'a, B> { + pub const NONCE_SIZE: usize = 16; pub fn new(num_blocks: usize, nonce: &[u8; 16]) -> Self { let hdr = Header::new(CtType::Right, num_blocks); @@ -17,28 +17,27 @@ impl RightCiphertext { Self { data, _phantom: PhantomData } } - pub fn add_block(&mut self, block: u32) { - self.data.extend(block.to_be_bytes().into_iter()); + pub fn add_block(&mut self, block: B) { + block.extend_into(&mut self.data); } } - -impl CipherText for RightCiphertext { +impl<'a, B: RightCipherTextBlock<'a>> CipherText<'a> for RightCiphertext<'a, B> { type Block = B; fn header(&self) -> Header { self.data.header() } - fn blocks(&self) -> Iter { + fn blocks(&self) -> Box> { todo!() } } -impl TryFrom<&[u8]> for RightCiphertext { +impl<'a, B: RightCipherTextBlock<'a>> TryFrom<&'a [u8]> for RightCiphertext<'a, B> { type Error = ParseError; - fn try_from(data: &[u8]) -> Result { + fn try_from(data: &'a [u8]) -> Result { let hdr = Header::from_slice(data); if matches!(hdr.ct_type, CtType::Right) { Ok(Self { data: data.into(), _phantom: PhantomData }) @@ -48,7 +47,7 @@ impl TryFrom<&[u8]> for RightCiphertext { } } -impl AsRef<[u8]> for RightCiphertext { +impl<'a, B: RightCipherTextBlock<'a>> AsRef<[u8]> for RightCiphertext<'a, B> { fn as_ref(&self) -> &[u8] { self.data.as_ref() } diff --git a/formats/src/lib.rs b/formats/src/lib.rs index 8c60a4c..ba1e89d 100644 --- a/formats/src/lib.rs +++ b/formats/src/lib.rs @@ -6,6 +6,11 @@ mod ciphertext; pub use ciphertext::{ CipherText, CipherTextBlock, + LeftCipherTextBlock, + RightCipherTextBlock, + LeftBlockEq, + OreBlockOrd, + // TODO: Make the naming of these consistent left::LeftCiphertext, right::RightCiphertext, combined::CombinedCiphertext diff --git a/formats/src/main.rs b/formats/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/formats/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/ore-rs-5bit/Cargo.toml b/ore-rs-5bit/Cargo.toml index 2bdcd08..883bb05 100644 --- a/ore-rs-5bit/Cargo.toml +++ b/ore-rs-5bit/Cargo.toml @@ -12,6 +12,7 @@ zeroize = { workspace = true } rand = { workspace = true } rand_chacha = { workspace = true } aes = { workspace = true } +subtle-ng = { workspace = true } # TODO: Only use this in examples hex-literal = "0.3.2" diff --git a/ore-rs-5bit/src/lib.rs b/ore-rs-5bit/src/lib.rs index 54c33a8..9d8d045 100644 --- a/ore-rs-5bit/src/lib.rs +++ b/ore-rs-5bit/src/lib.rs @@ -1,10 +1,11 @@ -use std::cell::RefCell; -use formats::{LeftCiphertext, CipherTextBlock, DataWithHeader, RightCiphertext, CombinedCiphertext}; -use primitives::{prf::{Aes128Prf, PrfBlock}, Prf, prp::{KnuthShufflePRP, bitwise::BitwisePrp}, Prp, AesBlock, KnuthShuffleGenerator, PrpGenerator, NewPrp, hash::Aes128Z2Hash, Hash, HashKey}; +use std::{cell::RefCell, ops::BitAnd}; +use formats::{LeftCiphertext, CipherTextBlock, DataWithHeader, RightCiphertext, CombinedCiphertext, CipherText, LeftCipherTextBlock, RightCipherTextBlock, OreBlockOrd, LeftBlockEq}; +use primitives::{prf::Aes128Prf, Prf, prp::bitwise::BitwisePrp, Prp, KnuthShuffleGenerator, PrpGenerator, NewPrp, hash::Aes128Z2Hash, Hash}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; +use subtle_ng::ConstantTimeEq; use zeroize::ZeroizeOnDrop; -use aes::{cipher::generic_array::GenericArray, Aes128}; +use aes::cipher::generic_array::GenericArray; use crate::packing::packed_prefixes; pub mod packing; @@ -24,14 +25,16 @@ pub struct Ore5Bit { pub struct OreError; pub type Ore5BitChaCha20 = Ore5Bit; -pub type Ore5BitLeft = LeftCiphertext; -pub type Ore5BitRight = RightCiphertext; -pub type Ore5BitCombined = CombinedCiphertext; +pub type Ore5BitLeft<'a> = LeftCiphertext<'a, LeftBlock>; +pub type Ore5BitRight<'a> = RightCiphertext<'a, RightBlock>; +pub type Ore5BitCombined<'a> = CombinedCiphertext<'a, LeftBlock, RightBlock>; +#[derive(Debug)] pub struct LeftBlock([u8; 16], u8); +#[derive(Debug)] pub struct RightBlock(u32); -impl CipherTextBlock for LeftBlock { +impl<'a> CipherTextBlock<'a> for LeftBlock { fn byte_size() -> usize { 17 } @@ -42,9 +45,38 @@ impl CipherTextBlock for LeftBlock { } } -impl CipherTextBlock for RightBlock { +impl ConstantTimeEq for LeftBlock { + fn ct_eq(&self, other: &Self) -> subtle_ng::Choice { + self.0.ct_eq(&other.0).bitand(self.1.ct_eq(&other.1)) + } +} + +impl<'a> LeftBlockEq<'a, LeftBlock> for LeftBlock { + type Other = LeftBlock; + + fn constant_eq(&self, other: &Self) -> subtle_ng::Choice { + self.ct_eq(other) + } +} + +// TODO: Derive macro? +impl<'a> LeftCipherTextBlock<'a> for LeftBlock {} + +impl<'a> OreBlockOrd<'a, RightBlock> for LeftBlock { + type Other = RightBlock; + + fn ore_compare(&self, right: &RightBlock) -> u8 { + 0 + } +} + +impl<'a> RightCipherTextBlock<'a> for RightBlock {} + + +// TODO: Can we derive macro any of this, too?? +impl<'a> CipherTextBlock<'a> for RightBlock { fn byte_size() -> usize { - 32 + 4 } fn extend_into(&self, out: &mut DataWithHeader) { @@ -52,6 +84,30 @@ impl CipherTextBlock for RightBlock { } } +/*impl OreBlockOrd for LeftBlock { + fn ore_compare(&self, right: &RightBlock) -> bool { + false + } +}*/ + +impl From<&[u8]> for LeftBlock { + fn from(value: &[u8]) -> Self { + assert!(value.len() == Self::byte_size()); + let mut buf: [u8; 16] = Default::default(); + buf.copy_from_slice(&value[0..16]); + LeftBlock(buf, value[16]) + } +} + +impl From<&[u8]> for RightBlock { + fn from(value: &[u8]) -> Self { + assert!(value.len() == Self::byte_size()); + let mut buf: [u8; 4] = Default::default(); + buf.copy_from_slice(&value[0..4]); + RightBlock(u32::from_be_bytes(buf)) + } +} + // TODO: Make this use the ORE traits once we've cleaned these up impl Ore5Bit { // TODO: This should be an implementation of OreInit @@ -100,7 +156,7 @@ impl Ore5Bit { self.prf1.encrypt_all(&mut prefixes); for (prf_block, permuted) in prefixes.iter().zip(p_ns.iter()) { - out.add_block(&LeftBlock(*prf_block, *permuted)); + out.add_block(LeftBlock(*prf_block, *permuted)); } out @@ -112,7 +168,7 @@ impl Ore5Bit { // TODO: Can we pack the input bytes?? debug_assert!(input.len() <= 16); - let mut left = Ore5BitLeft::new(input.len()); + let mut out = Ore5BitCombined::new(input.len(), &nonce); let mut right = Ore5BitRight::new(input.len(), &nonce); // Here we'll model a PRF using a single block of AES @@ -147,7 +203,7 @@ impl Ore5Bit { // TODO: This feels a bit janky for ((enc_prefix, right_blk), p_n) in prefixes.iter().zip(right_blocks.iter()).zip(p_ns.iter()) { - left.add_block(&LeftBlock(*enc_prefix, *p_n)); + //left.add_block(&LeftBlock(*enc_prefix, *p_n)); let mut ro_keys: [[u8; 16]; 32] = Default::default(); for (j, ro_key) in ro_keys.iter_mut().enumerate() { @@ -160,19 +216,28 @@ impl Ore5Bit { // Push bytes onto right output vec let hasher: Aes128Z2Hash = Hash::new(&nonce.into()); let final_right = right_blk ^ hasher.hash_all_onto_u32(&ro_keys); - right.add_block(final_right); + //right.add_block(RightBlock(final_right)); + + out.add_block(LeftBlock(*enc_prefix, *p_n), RightBlock(final_right)); } - Ore5BitCombined::new(left, right) + out } - // TODO: The CipherText types might be better to implement a CipherText trait - // so that we can handle the different variations here? - pub fn compare_slices(a: impl AsRef<[u8]>, b: impl AsRef<[u8]>) -> u8 { + // TODO: Do this as a PartialOrd impl (and handle versions) + // TODO: Handle different length slices. Compare the first n-bytes and if they're equal then the + // longer value will be more "more-than" + pub fn compare_slices(a: impl AsRef<[u8]>, b: impl AsRef<[u8]>) -> bool { let left: Ore5BitLeft = a.as_ref().try_into().unwrap(); - //let combined: CombinedCiphertext = b.as_ref().try_into().unwrap(); + let combined: Ore5BitCombined = b.as_ref().try_into().unwrap(); //assert!(left.comparable(&combined)); // TODO: Error - 0 + // TODO: Should this iteration also be constant time? + // We could make our own iterator type which works in constant time and zips another + + let x = left.compare_blocks(combined.blocks()); + dbg!(x); + + true } // For the right encryption we could either use the approach that we do in the current version, diff --git a/ore-rs-5bit/src/main.rs b/ore-rs-5bit/src/main.rs index 2a98e43..08b9538 100644 --- a/ore-rs-5bit/src/main.rs +++ b/ore-rs-5bit/src/main.rs @@ -8,11 +8,17 @@ fn main() { let k1 = hex!("00010203 04050607 08090a0b 0c0d0e0f"); let k2 = hex!("d0d007a5 3f9a6848 83bc1f21 0f6595a3"); - let input = vec![7]; + let a = vec![7, 1]; + let b = vec![7, 2]; let ore = Ore5BitChaCha20::init(&k1, &k2).unwrap(); - let left = ore.encrypt_left(&input); - let combined = ore.encrypt(&input); + let left = ore.encrypt_left(&a); + let combined = ore.encrypt(&b); println!("Left encrypt: {}", hex::encode(&left)); println!("Combines : {}", hex::encode(&combined)); + + // TODO: + // Can we use a derive macro for types to implement LeftCipherTextBlock and RightCipherTextBlock + // Also, those names are very long + dbg!(Ore5BitChaCha20::compare_slices(&left, &combined)); //println!("{}, {}, {}", left.len(), right.len(), hex::encode(right)); } \ No newline at end of file From 17306734146fbf4040465747ba3963772edf4fe0 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Tue, 16 May 2023 23:13:11 +1000 Subject: [PATCH 12/18] Comparison seems to work but more testing required --- formats/src/ciphertext/combined.rs | 16 ++++++------- formats/src/ciphertext/left.rs | 17 +++++++++----- formats/src/ciphertext/mod.rs | 14 ++++++----- formats/src/ciphertext/right.rs | 4 ++++ formats/src/data_with_header.rs | 4 ++++ ore-rs-5bit/src/lib.rs | 37 +++++++++++------------------- ore-rs-5bit/src/main.rs | 12 ++++++---- 7 files changed, 57 insertions(+), 47 deletions(-) diff --git a/formats/src/ciphertext/combined.rs b/formats/src/ciphertext/combined.rs index 299217b..2301097 100644 --- a/formats/src/ciphertext/combined.rs +++ b/formats/src/ciphertext/combined.rs @@ -1,4 +1,4 @@ -use std::marker::PhantomData; +use std::{marker::PhantomData, cmp::Ordering}; use crate::{data_with_header::{CtType, DataWithHeader}, header::Header, ParseError, LeftBlockEq, LeftCipherTextBlock, OreBlockOrd}; use super::{CipherTextBlock, CipherText, left::LeftCiphertext, right::RightCiphertext, RightCipherTextBlock}; @@ -72,6 +72,10 @@ where { type Block = CombinedBlock<'a, L, R>; + fn len(&self) -> usize { + self.data.len() + } + fn header(&self) -> Header { self.data.header() } @@ -124,9 +128,7 @@ where L: LeftCipherTextBlock<'a>, R: RightCipherTextBlock<'a> { - type Other = CombinedBlock<'a, L, R>; - - fn constant_eq(&self, other: &Self::Other) -> subtle_ng::Choice { + fn constant_eq(&self, other: &CombinedBlock<'a, L, R>) -> subtle_ng::Choice { self.constant_eq(&other.left) } } @@ -139,10 +141,8 @@ where R: RightCipherTextBlock<'a>, L: OreBlockOrd<'a, R> { - type Other = CombinedBlock<'a, L, R>; - - fn ore_compare(&self, other: &Self::Other) -> u8 { - self.ore_compare(&other.right) + fn ore_compare(&self, nonce: &[u8], other: &CombinedBlock<'a, L, R>) -> Ordering { + self.ore_compare(nonce, &other.right) } } diff --git a/formats/src/ciphertext/left.rs b/formats/src/ciphertext/left.rs index 8fb1958..ded65f0 100644 --- a/formats/src/ciphertext/left.rs +++ b/formats/src/ciphertext/left.rs @@ -1,7 +1,7 @@ -use std::{marker::PhantomData, ops::BitOr}; +use std::{marker::PhantomData, ops::BitOr, cmp::Ordering}; use subtle_ng::{Choice, CtOption}; -use crate::{data_with_header::{CtType, DataWithHeader}, header::Header, ParseError, LeftCipherTextBlock, OreBlockOrd}; +use crate::{data_with_header::{CtType, DataWithHeader}, header::Header, ParseError, LeftCipherTextBlock, OreBlockOrd, RightCipherTextBlock}; use super::{CipherTextBlock, CipherText, LeftBlockEq}; pub struct LeftCiphertext<'a, B: LeftCipherTextBlock<'a>> { @@ -30,9 +30,10 @@ impl<'a, B: LeftCipherTextBlock<'a>> LeftCiphertext<'a, B> { /// TODO??? What do we return? Perhaps we can do the ORE comparison in this step, too? /// The ordering mechanism is important here, too (i.e. Lexicographic or Numerical) /// If its numerical then the shorter value is always less than the other. - pub fn compare_blocks(&'a self, other: Box + 'a>) -> u8 // TODO: Return the PartialOrd enum + pub fn compare_blocks(&'a self, nonce: &[u8], other: Box + 'a>) -> Ordering where - B: LeftBlockEq<'a, O> + OreBlockOrd<'a, O> + B: LeftBlockEq<'a, O> + OreBlockOrd<'a, O>, + O: RightCipherTextBlock<'a> { // TODO: We should use CtOption and acc.conditionally_select let eq = self.blocks().zip(other).fold(None, { |acc: Option<(B, O)>, (a, b): (B, O)| @@ -46,10 +47,10 @@ impl<'a, B: LeftCipherTextBlock<'a>> LeftCiphertext<'a, B> { // FIXME: This isn't constant time either if let Some((a, b)) = eq { - a.ore_compare(&b) + a.ore_compare(nonce, &b) } else { // No blocks differ so say that the values are equal - 1 + Ordering::Equal } // TODO: Handle different lengths @@ -59,6 +60,10 @@ impl<'a, B: LeftCipherTextBlock<'a>> LeftCiphertext<'a, B> { impl<'a, B: LeftCipherTextBlock<'a>> CipherText<'a> for LeftCiphertext<'a, B> { type Block = B; + fn len(&self) -> usize { + self.data.len() + } + fn header(&self) -> Header { self.data.header() } diff --git a/formats/src/ciphertext/mod.rs b/formats/src/ciphertext/mod.rs index 25ea2e9..117d3b0 100644 --- a/formats/src/ciphertext/mod.rs +++ b/formats/src/ciphertext/mod.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use subtle_ng::{ConstantTimeEq, Choice}; use crate::{header::Header, data_with_header::DataWithHeader}; pub(crate) mod left; @@ -16,6 +18,8 @@ pub trait CipherText<'a> { fn header(&self) -> Header; fn blocks(&'a self) -> Box + 'a>; + + fn len(&self) -> usize; } pub trait CipherTextBlock<'a>: From<&'a [u8]> { // TODO: Zeroize @@ -27,15 +31,13 @@ pub trait LeftCipherTextBlock<'a>: LeftBlockEq<'a> + CipherTextBlock<'a> {} pub trait RightCipherTextBlock<'a>: CipherTextBlock<'a> {} -pub trait LeftBlockEq<'a, Other = Self> { - type Other: ?Sized + CipherTextBlock<'a>; +pub trait LeftBlockEq<'a, Other: ?Sized + CipherTextBlock<'a> = Self> { + //type Other: ?Sized + CipherTextBlock<'a>; // TODO: Maybe this is choice? Or a wrapper of choice at least fn constant_eq(&self, other: &Other) -> Choice; } -pub trait OreBlockOrd<'a, Other> { - type Other: ?Sized + RightCipherTextBlock<'a>; - - fn ore_compare(&self, right: &Other) -> u8; // TODO: Return a PartialOrd enum value +pub trait OreBlockOrd<'a, Other: ?Sized + RightCipherTextBlock<'a>> { + fn ore_compare(&self, nonce: &[u8], right: &Other) -> Ordering; } diff --git a/formats/src/ciphertext/right.rs b/formats/src/ciphertext/right.rs index 9a3f3ec..f137807 100644 --- a/formats/src/ciphertext/right.rs +++ b/formats/src/ciphertext/right.rs @@ -25,6 +25,10 @@ impl<'a, B: RightCipherTextBlock<'a>> RightCiphertext<'a, B> { impl<'a, B: RightCipherTextBlock<'a>> CipherText<'a> for RightCiphertext<'a, B> { type Block = B; + fn len(&self) -> usize { + self.data.len() + } + fn header(&self) -> Header { self.data.header() } diff --git a/formats/src/data_with_header.rs b/formats/src/data_with_header.rs index b204168..f171c8f 100644 --- a/formats/src/data_with_header.rs +++ b/formats/src/data_with_header.rs @@ -29,6 +29,10 @@ impl DataWithHeader { Self { data } } + pub(crate) fn len(&self) -> usize { + self.data.len() + } + pub(crate) fn header(&self) -> Header { Header::from_slice(&self.data) } diff --git a/ore-rs-5bit/src/lib.rs b/ore-rs-5bit/src/lib.rs index 9d8d045..d007412 100644 --- a/ore-rs-5bit/src/lib.rs +++ b/ore-rs-5bit/src/lib.rs @@ -1,9 +1,9 @@ -use std::{cell::RefCell, ops::BitAnd}; +use std::{cell::RefCell, ops::BitAnd, cmp::Ordering}; use formats::{LeftCiphertext, CipherTextBlock, DataWithHeader, RightCiphertext, CombinedCiphertext, CipherText, LeftCipherTextBlock, RightCipherTextBlock, OreBlockOrd, LeftBlockEq}; use primitives::{prf::Aes128Prf, Prf, prp::bitwise::BitwisePrp, Prp, KnuthShuffleGenerator, PrpGenerator, NewPrp, hash::Aes128Z2Hash, Hash}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; -use subtle_ng::ConstantTimeEq; +use subtle_ng::{ConstantTimeEq, Choice}; use zeroize::ZeroizeOnDrop; use aes::cipher::generic_array::GenericArray; use crate::packing::packed_prefixes; @@ -52,8 +52,6 @@ impl ConstantTimeEq for LeftBlock { } impl<'a> LeftBlockEq<'a, LeftBlock> for LeftBlock { - type Other = LeftBlock; - fn constant_eq(&self, other: &Self) -> subtle_ng::Choice { self.ct_eq(other) } @@ -63,10 +61,14 @@ impl<'a> LeftBlockEq<'a, LeftBlock> for LeftBlock { impl<'a> LeftCipherTextBlock<'a> for LeftBlock {} impl<'a> OreBlockOrd<'a, RightBlock> for LeftBlock { - type Other = RightBlock; - - fn ore_compare(&self, right: &RightBlock) -> u8 { - 0 + fn ore_compare(&self, nonce: &[u8], RightBlock(right): &RightBlock) -> Ordering { + let hasher: Aes128Z2Hash = Hash::new(nonce.into()); + // TODO: Use conditional_select + if ((right >> self.1) as u8 & 1u8) & hasher.hash(&self.0) == 1 { + Ordering::Less + } else { + Ordering::Greater + } } } @@ -84,12 +86,6 @@ impl<'a> CipherTextBlock<'a> for RightBlock { } } -/*impl OreBlockOrd for LeftBlock { - fn ore_compare(&self, right: &RightBlock) -> bool { - false - } -}*/ - impl From<&[u8]> for LeftBlock { fn from(value: &[u8]) -> Self { assert!(value.len() == Self::byte_size()); @@ -169,7 +165,6 @@ impl Ore5Bit { // TODO: Can we pack the input bytes?? debug_assert!(input.len() <= 16); let mut out = Ore5BitCombined::new(input.len(), &nonce); - let mut right = Ore5BitRight::new(input.len(), &nonce); // Here we'll model a PRF using a single block of AES // This will be OK for up to 16-bytes of input (or 25 5-bit values) @@ -203,7 +198,6 @@ impl Ore5Bit { // TODO: This feels a bit janky for ((enc_prefix, right_blk), p_n) in prefixes.iter().zip(right_blocks.iter()).zip(p_ns.iter()) { - //left.add_block(&LeftBlock(*enc_prefix, *p_n)); let mut ro_keys: [[u8; 16]; 32] = Default::default(); for (j, ro_key) in ro_keys.iter_mut().enumerate() { @@ -227,17 +221,14 @@ impl Ore5Bit { // TODO: Do this as a PartialOrd impl (and handle versions) // TODO: Handle different length slices. Compare the first n-bytes and if they're equal then the // longer value will be more "more-than" - pub fn compare_slices(a: impl AsRef<[u8]>, b: impl AsRef<[u8]>) -> bool { + pub fn compare_slices(a: impl AsRef<[u8]>, b: impl AsRef<[u8]>) -> Ordering { let left: Ore5BitLeft = a.as_ref().try_into().unwrap(); let combined: Ore5BitCombined = b.as_ref().try_into().unwrap(); //assert!(left.comparable(&combined)); // TODO: Error - // TODO: Should this iteration also be constant time? - // We could make our own iterator type which works in constant time and zips another - - let x = left.compare_blocks(combined.blocks()); - dbg!(x); - true + // With most of the work now in the LeftCipherText and the block types, this + // could be a default implementation in the main trait + left.compare_blocks(combined.nonce(), combined.blocks()) } // For the right encryption we could either use the approach that we do in the current version, diff --git a/ore-rs-5bit/src/main.rs b/ore-rs-5bit/src/main.rs index 08b9538..c4e9a7c 100644 --- a/ore-rs-5bit/src/main.rs +++ b/ore-rs-5bit/src/main.rs @@ -1,5 +1,6 @@ use hex_literal::hex; use ore_rs_5bit::Ore5BitChaCha20; +use formats::CipherText; // TODO: Revise the Main ORE traits // TODO: Add PartialOrd implementations for CipherText variants @@ -8,17 +9,20 @@ fn main() { let k1 = hex!("00010203 04050607 08090a0b 0c0d0e0f"); let k2 = hex!("d0d007a5 3f9a6848 83bc1f21 0f6595a3"); - let a = vec![7, 1]; - let b = vec![7, 2]; + let a = vec![7, 10, 2]; + let b = vec![7, 6, 1]; let ore = Ore5BitChaCha20::init(&k1, &k2).unwrap(); let left = ore.encrypt_left(&a); let combined = ore.encrypt(&b); - println!("Left encrypt: {}", hex::encode(&left)); - println!("Combines : {}", hex::encode(&combined)); + println!("Left encrypt: [{} bytes] {}", left.len(), hex::encode(&left)); + println!("Combined : [{} bytes] {}", left.len(), hex::encode(&combined)); // TODO: // Can we use a derive macro for types to implement LeftCipherTextBlock and RightCipherTextBlock // Also, those names are very long + // TODO: Tests and benchmarks + // TODO: create a plaintext trait for the ORE trait methods + // For the 5-bit scheme, create a Packed variant dbg!(Ore5BitChaCha20::compare_slices(&left, &combined)); //println!("{}, {}, {}", left.len(), right.len(), hex::encode(right)); } \ No newline at end of file From b8e38058dd124fb30c916cad2a15b4e32b606015 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 17 May 2023 23:49:22 +1000 Subject: [PATCH 13/18] Comparisons work for ORE 5-bit --- Cargo.toml | 1 + formats/src/ciphertext/combined.rs | 5 +- ore-rs-5bit/Cargo.toml | 2 + ore-rs-5bit/src/left_block.rs | 117 ++++++++++++ ore-rs-5bit/src/lib.rs | 284 +++++++++++++++++++---------- ore-rs-5bit/src/main.rs | 2 + ore-rs-5bit/src/packing.rs | 12 ++ ore-rs-5bit/src/right_block.rs | 101 ++++++++++ primitives/src/lib.rs | 4 + primitives/src/prp/bitwise.rs | 16 +- 10 files changed, 440 insertions(+), 104 deletions(-) create mode 100644 ore-rs-5bit/src/left_block.rs create mode 100644 ore-rs-5bit/src/right_block.rs diff --git a/Cargo.toml b/Cargo.toml index 145e507..36bbb6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ subtle-ng = "2.5.0" zeroize = { version = "1.5.7", features = [ "zeroize_derive", "alloc" ] } lazy_static = "1.4.0" thiserror = "1.0.38" +quickcheck = "1.0.3" diff --git a/formats/src/ciphertext/combined.rs b/formats/src/ciphertext/combined.rs index 2301097..84f8225 100644 --- a/formats/src/ciphertext/combined.rs +++ b/formats/src/ciphertext/combined.rs @@ -86,10 +86,7 @@ where Box::new( self.data.body()[RightCiphertext::<'a, R>::NONCE_SIZE..] .chunks(Self::Block::byte_size()) - .map(|bytes| { - println!("BYTES LEN: {}", bytes.len()); - Self::Block::from(bytes) - }) + .map(|bytes| Self::Block::from(bytes)) ) } } diff --git a/ore-rs-5bit/Cargo.toml b/ore-rs-5bit/Cargo.toml index 883bb05..110687f 100644 --- a/ore-rs-5bit/Cargo.toml +++ b/ore-rs-5bit/Cargo.toml @@ -13,6 +13,8 @@ rand = { workspace = true } rand_chacha = { workspace = true } aes = { workspace = true } subtle-ng = { workspace = true } +thiserror = { workspace = true } +quickcheck = { workspace = true } # TODO: Only use this in examples hex-literal = "0.3.2" diff --git a/ore-rs-5bit/src/left_block.rs b/ore-rs-5bit/src/left_block.rs new file mode 100644 index 0000000..2ccda0c --- /dev/null +++ b/ore-rs-5bit/src/left_block.rs @@ -0,0 +1,117 @@ +use std::{cmp::Ordering, ops::BitAnd}; +use formats::{CipherTextBlock, DataWithHeader, LeftBlockEq, LeftCipherTextBlock, OreBlockOrd}; +use primitives::{hash::Aes128Z2Hash, Hash}; +use subtle_ng::ConstantTimeEq; +use crate::right_block::RightBlock; + +// TODO: We could make the array a reference +// That way we can encrypt it externally but just pass the prefix to an init function +#[derive(Debug)] +pub struct LeftBlock(pub(super) [u8; 16], pub(super) u8); + + +impl<'a> CipherTextBlock<'a> for LeftBlock { + fn byte_size() -> usize { + 17 + } + + fn extend_into(&self, out: &mut DataWithHeader) { + out.extend_from_slice(&self.0); + out.extend([self.1]); + } +} + + +impl<'a> LeftBlockEq<'a, LeftBlock> for LeftBlock { + fn constant_eq(&self, other: &Self) -> subtle_ng::Choice { + self.0.ct_eq(&other.0).bitand(self.1.ct_eq(&other.1)) + } +} + +impl<'a> OreBlockOrd<'a, RightBlock> for LeftBlock { + // FIXME: Nonce *must* be 16-bytes + fn ore_compare(&self, nonce: &[u8], right: &RightBlock) -> Ordering { + // TODO: This would be cleaner if we defined a method on RightBlock + let hasher: Aes128Z2Hash = Hash::new(nonce.into()); + // TODO: Use conditional_select + //if ((right << self.1) as u8 & 1u8) ^ hasher.hash(&self.0) == 1 { + let mask = hasher.hash(&self.0); + if (right.get_bit(self.1) ^ mask) == 1 { + Ordering::Greater + } else { + Ordering::Less + } + } +} + +// TODO: Derive macro? +impl<'a> LeftCipherTextBlock<'a> for LeftBlock {} + + +impl From<&[u8]> for LeftBlock { + fn from(value: &[u8]) -> Self { + assert!(value.len() == Self::byte_size()); + let mut buf: [u8; 16] = Default::default(); + buf.copy_from_slice(&value[0..16]); + LeftBlock(buf, value[16]) + } +} + +#[cfg(test)] +mod tests { + use primitives::{NewPrp, KnuthShuffleGenerator, PrpGenerator}; + use rand::{thread_rng, Fill}; + use super::*; + + #[test] + fn test_ore_compare_block() { + let mut rng = thread_rng(); + let mut prefix: [u8; 16] = Default::default(); + let mut nonce: [u8; 16] = Default::default(); + //prefix.try_fill(&mut rng).unwrap(); + nonce.try_fill(&mut rng).unwrap(); + let prp: NewPrp = KnuthShuffleGenerator::new(&prefix).generate(); + let mut right = RightBlock::init(10).shuffle(&prp); + + let mut ro_keys: [[u8; 16]; 32] = Default::default(); + + for (j, ro_key) in ro_keys.iter_mut().enumerate() { + ro_key.copy_from_slice(&prefix); + ro_key[15] = j as u8; + } + let hasher: Aes128Z2Hash = Hash::new(&nonce.into()); + //self.prf1.encrypt_all(&mut ro_keys); + let mask = hasher.hash_all_onto_u32(&ro_keys); + + println!("MASK: {mask:b}"); + println!("PERMUTE plaintext 10 becomes {}", prp.inverse_permute(10u8)); + println!("PERMUTE plaintext 5 becomes {}", prp.inverse_permute(5u8)); + println!("PERMUTE plaintext 24 becomes {}", prp.inverse_permute(24u8)); + + right ^= mask; + + assert_eq!( + LeftBlock( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, prp.inverse_permute(10u8)], + prp.inverse_permute(10u8) + ).ore_compare(&nonce, &right), + Ordering::Greater + ); + + assert_eq!( + LeftBlock( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, prp.inverse_permute(5u8)], + prp.inverse_permute(5u8) + ).ore_compare(&nonce, &right), + Ordering::Less + ); + + assert_eq!( + LeftBlock( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, prp.inverse_permute(24u8)], + prp.inverse_permute(24u8) + ).ore_compare(&nonce, &right), + Ordering::Greater + ); + } +} \ No newline at end of file diff --git a/ore-rs-5bit/src/lib.rs b/ore-rs-5bit/src/lib.rs index d007412..471d92a 100644 --- a/ore-rs-5bit/src/lib.rs +++ b/ore-rs-5bit/src/lib.rs @@ -1,13 +1,17 @@ -use std::{cell::RefCell, ops::BitAnd, cmp::Ordering}; +use std::{cell::RefCell, ops::{BitAnd, BitXor, BitXorAssign}, cmp::Ordering}; use formats::{LeftCiphertext, CipherTextBlock, DataWithHeader, RightCiphertext, CombinedCiphertext, CipherText, LeftCipherTextBlock, RightCipherTextBlock, OreBlockOrd, LeftBlockEq}; +use left_block::LeftBlock; use primitives::{prf::Aes128Prf, Prf, prp::bitwise::BitwisePrp, Prp, KnuthShuffleGenerator, PrpGenerator, NewPrp, hash::Aes128Z2Hash, Hash}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; +use right_block::RightBlock; use subtle_ng::{ConstantTimeEq, Choice}; use zeroize::ZeroizeOnDrop; use aes::cipher::generic_array::GenericArray; -use crate::packing::packed_prefixes; +use crate::packing::prefixes; pub mod packing; +mod right_block; +mod left_block; #[derive(Debug, ZeroizeOnDrop)] pub struct Ore5Bit { @@ -21,6 +25,7 @@ pub struct Ore5Bit { } // TODO: use a trait type +/// This type is deliberately opaque as to avoid potential side-channel leakage. #[derive(Debug)] pub struct OreError; @@ -29,80 +34,7 @@ pub type Ore5BitLeft<'a> = LeftCiphertext<'a, LeftBlock>; pub type Ore5BitRight<'a> = RightCiphertext<'a, RightBlock>; pub type Ore5BitCombined<'a> = CombinedCiphertext<'a, LeftBlock, RightBlock>; -#[derive(Debug)] -pub struct LeftBlock([u8; 16], u8); -#[derive(Debug)] -pub struct RightBlock(u32); - -impl<'a> CipherTextBlock<'a> for LeftBlock { - fn byte_size() -> usize { - 17 - } - - fn extend_into(&self, out: &mut DataWithHeader) { - out.extend_from_slice(&self.0); - out.extend([self.1]); - } -} - -impl ConstantTimeEq for LeftBlock { - fn ct_eq(&self, other: &Self) -> subtle_ng::Choice { - self.0.ct_eq(&other.0).bitand(self.1.ct_eq(&other.1)) - } -} - -impl<'a> LeftBlockEq<'a, LeftBlock> for LeftBlock { - fn constant_eq(&self, other: &Self) -> subtle_ng::Choice { - self.ct_eq(other) - } -} - -// TODO: Derive macro? -impl<'a> LeftCipherTextBlock<'a> for LeftBlock {} - -impl<'a> OreBlockOrd<'a, RightBlock> for LeftBlock { - fn ore_compare(&self, nonce: &[u8], RightBlock(right): &RightBlock) -> Ordering { - let hasher: Aes128Z2Hash = Hash::new(nonce.into()); - // TODO: Use conditional_select - if ((right >> self.1) as u8 & 1u8) & hasher.hash(&self.0) == 1 { - Ordering::Less - } else { - Ordering::Greater - } - } -} - -impl<'a> RightCipherTextBlock<'a> for RightBlock {} - - -// TODO: Can we derive macro any of this, too?? -impl<'a> CipherTextBlock<'a> for RightBlock { - fn byte_size() -> usize { - 4 - } - - fn extend_into(&self, out: &mut DataWithHeader) { - out.extend(self.0.to_be_bytes()); - } -} - -impl From<&[u8]> for LeftBlock { - fn from(value: &[u8]) -> Self { - assert!(value.len() == Self::byte_size()); - let mut buf: [u8; 16] = Default::default(); - buf.copy_from_slice(&value[0..16]); - LeftBlock(buf, value[16]) - } -} -impl From<&[u8]> for RightBlock { - fn from(value: &[u8]) -> Self { - assert!(value.len() == Self::byte_size()); - let mut buf: [u8; 4] = Default::default(); - buf.copy_from_slice(&value[0..4]); - RightBlock(u32::from_be_bytes(buf)) - } -} // TODO: Make this use the ORE traits once we've cleaned these up impl Ore5Bit { @@ -134,7 +66,8 @@ impl Ore5Bit { // This will be OK for up to 16-bytes of input (or 25 5-bit values) // For larger inputs we can chain the values by XORing the last output // with the next input (a little like CMAC). - let mut prefixes = packed_prefixes(input); + let mut prefixes = prefixes(input); + self.prf2.encrypt_all(&mut prefixes); // This deviates from the paper slightly. @@ -143,14 +76,20 @@ impl Ore5Bit { // This avoids a copy and should have the same effect. // TODO: Change this to use functional callbacks rather than for let mut p_ns = vec![]; - for (enc_prefix, in_blk) in prefixes.iter_mut().zip(input.iter()) { - let prp: NewPrp = KnuthShuffleGenerator::new(enc_prefix).generate(); + + for (n, in_blk) in input.iter().enumerate() { + let prp: NewPrp = KnuthShuffleGenerator::new(&prefixes[n]).generate(); let p_n = prp.permute(*in_blk); p_ns.push(p_n); - enc_prefix[15] = p_n; + + prefixes[n].iter_mut().for_each(|x| *x = 0); + prefixes[n][0..n].clone_from_slice(&input[0..n]); + prefixes[n][n] = p_n; + prefixes[n][15] = n as u8; } self.prf1.encrypt_all(&mut prefixes); + for (prf_block, permuted) in prefixes.iter().zip(p_ns.iter()) { out.add_block(LeftBlock(*prf_block, *permuted)); } @@ -170,8 +109,8 @@ impl Ore5Bit { // This will be OK for up to 16-bytes of input (or 25 5-bit values) // For larger inputs we can chain the values by XORing the last output // with the next input (a little like CMAC). - let mut prefixes = packed_prefixes(input); - let mut right_blocks: Vec = Vec::new(); // TODO: Len + let mut prefixes = prefixes(input); + let mut right_blocks: Vec = Vec::with_capacity(input.len()); self.prf2.encrypt_all(&mut prefixes); // This deviates from the paper slightly. @@ -183,37 +122,80 @@ impl Ore5Bit { // instead of performing comparisons on each value (which would not be constant time). let mut p_ns: Vec = Vec::with_capacity(input.len()); - for (enc_prefix, in_blk) in prefixes.iter_mut().zip(input.iter()) { - let out_right_blk: u32 = 0xFFFFFFFF >> *in_blk; - let prp: NewPrp = KnuthShuffleGenerator::new(enc_prefix).generate(); + for (n, in_blk) in input.iter().enumerate() { + let prp: NewPrp = KnuthShuffleGenerator::new(&prefixes[n]).generate(); let p_n = prp.permute(*in_blk); p_ns.push(p_n); - enc_prefix[15] = p_n; - out_right_blk.inverse_shuffle(&prp); - right_blocks.push(out_right_blk.inverse_shuffle(&prp)); + prefixes[n].iter_mut().for_each(|x| *x = 0); + prefixes[n][0..n].clone_from_slice(&input[0..n]); + prefixes[n][n] = p_n; + prefixes[n][15] = n as u8; + + + // encrypt_left and encrypt functions are identical, except that we have this encrypt right block stuff in the middle + let mut right_blk = RightBlock::init(*in_blk).shuffle(&prp); + + // TODO: Use the same approach here as we do in the original implementation + let mut ro_keys: [[u8; 16]; 32] = Default::default(); + + for (j, ro_key) in ro_keys.iter_mut().enumerate() { + ro_key[0..n].copy_from_slice(&input[0..n]); + ro_key[n] = j as u8; + } + + self.prf1.encrypt_all(&mut ro_keys); + + // TODO: Hash all of these keys with the nonce + // set the bits and and Xor with the right_block + // Push bytes onto right output vec + let hasher: Aes128Z2Hash = Hash::new(&nonce.into()); + // TODO: Hash all onto could be generic (right block) + // A RightBlock is like an "indicator set" + let mask = hasher.hash_all_onto_u32(&ro_keys); + + right_blk ^= mask; + + right_blocks.push(right_blk); + } self.prf1.encrypt_all(&mut prefixes); + for ((left, p_n), right) in prefixes.into_iter().zip(p_ns.into_iter()).zip(right_blocks.into_iter()) { + out.add_block(LeftBlock(left, p_n), right); + } + + + // TODO: This feels a bit janky - for ((enc_prefix, right_blk), p_n) in prefixes.iter().zip(right_blocks.iter()).zip(p_ns.iter()) { + /*for ((enc_prefix, mut right_blk), p_n) in prefixes.iter().zip(right_blocks.into_iter()).zip(p_ns.iter()) { let mut ro_keys: [[u8; 16]; 32] = Default::default(); for (j, ro_key) in ro_keys.iter_mut().enumerate() { ro_key.copy_from_slice(enc_prefix); ro_key[15] = j as u8; } + + for (j, prefix) in ro_keys.iter().enumerate() { + println!("RIGHT: {} -> {:?}", j, prefix); + } + self.prf1.encrypt_all(&mut ro_keys); + // TODO: Hash all of these keys with the nonce // set the bits and and Xor with the right_block // Push bytes onto right output vec let hasher: Aes128Z2Hash = Hash::new(&nonce.into()); - let final_right = right_blk ^ hasher.hash_all_onto_u32(&ro_keys); - //right.add_block(RightBlock(final_right)); + // TODO: Hash all onto could be generic (right block) + // A RightBlock is like an "indicator set" + let mask = hasher.hash_all_onto_u32(&ro_keys); + println!("MASK: {mask:b}"); - out.add_block(LeftBlock(*enc_prefix, *p_n), RightBlock(final_right)); - } + right_blk ^= mask; + + out.add_block(LeftBlock(*enc_prefix, *p_n), right_blk); + }*/ out } @@ -244,8 +226,126 @@ impl Ore5Bit { // - Bitwise permute: N bit get, N bit set (we avoid the comparisons!) } +#[cfg(test)] +#[macro_use] +extern crate quickcheck; #[cfg(test)] mod tests { + use quickcheck::{Arbitrary, QuickCheck}; + use super::*; + + type ORE = Ore5BitChaCha20; + + fn init_ore() -> Result { + let mut k1: [u8; 16] = Default::default(); + let mut k2: [u8; 16] = Default::default(); + + let mut rng = ChaCha20Rng::from_entropy(); + + rng.fill(&mut k1); + rng.fill(&mut k2); + + // TODO: This will work when have the trait setup correctly + //OreCipher::init(&k1, &k2).unwrap() + Ore5BitChaCha20::init(&k1, &k2) + } + + #[test] + fn test_single_block_eq() -> Result<(), OreError> { + let a = vec![10]; + let ore = init_ore()?; + let left = ore.encrypt_left(&a); + let combined = ore.encrypt(&a); + + assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Equal); + Ok(()) + } + + #[test] + fn test_single_block_lt() -> Result<(), OreError> { + let a = vec![10]; + let b = vec![29]; + let ore = init_ore()?; + let left = ore.encrypt_left(&a); + let combined = ore.encrypt(&b); + + assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Less); + + Ok(()) + } + + #[test] + fn test_single_block_gt() -> Result<(), OreError> { + let a = vec![11]; + let b = vec![0]; + let ore = init_ore()?; + let left = ore.encrypt_left(&a); + let combined = ore.encrypt(&b); + + assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Greater); + + Ok(()) + } + + #[derive(Debug, Copy, Clone, PartialEq)] + struct U5(u8); + + impl Arbitrary for U5 { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + loop { + let v: u8 = Arbitrary::arbitrary(g); + + if v <= 31 { + return Self(v) + } + + } + } + } + + impl PartialOrd for U5 { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } + } + + #[test] + fn test_quick() { + fn single_elem(a: U5, b: U5) -> bool { + let ore = init_ore().unwrap(); + let ax = [a.0]; + let bx = [b.0]; + let left = ore.encrypt_left(&ax); + let combined = ore.encrypt(&bx); + + match ORE::compare_slices(&left, &combined) { + Ordering::Less => a < b, + Ordering::Equal => a == b, + Ordering::Greater => a > b + } + } + + QuickCheck::new().max_tests(1).quickcheck(single_elem as fn(U5, U5) -> bool) + } + + #[test] + fn test_quick2() { + fn multiple_elems(a: Vec, b: Vec) -> bool { + let ax: Vec = a.into_iter().map(|U5(x)| x).collect(); + let bx: Vec = b.into_iter().map(|U5(x)| x).collect(); + let ore = init_ore().unwrap(); + let left = ore.encrypt_left(&ax); + let combined = ore.encrypt(&bx); + + match ORE::compare_slices(&left, &combined) { + Ordering::Less => ax < bx, + Ordering::Equal => ax == bx, + Ordering::Greater => ax > bx + } + } + + QuickCheck::new().max_tests(1).quickcheck(multiple_elems as fn(Vec, Vec) -> bool) + } } diff --git a/ore-rs-5bit/src/main.rs b/ore-rs-5bit/src/main.rs index c4e9a7c..ed6e88a 100644 --- a/ore-rs-5bit/src/main.rs +++ b/ore-rs-5bit/src/main.rs @@ -20,6 +20,8 @@ fn main() { // TODO: // Can we use a derive macro for types to implement LeftCipherTextBlock and RightCipherTextBlock // Also, those names are very long + // * I think we only need the forward PRP now + // * Could the test failures be due to the changes I made to prefix generation? // TODO: Tests and benchmarks // TODO: create a plaintext trait for the ORE trait methods // For the 5-bit scheme, create a Packed variant diff --git a/ore-rs-5bit/src/packing.rs b/ore-rs-5bit/src/packing.rs index 57d9198..e5e3c25 100644 --- a/ore-rs-5bit/src/packing.rs +++ b/ore-rs-5bit/src/packing.rs @@ -1,6 +1,18 @@ use bit_vec::BitVec; use primitives::prf::PrfBlock; + +pub fn prefixes(slice: &[u8]) -> Vec { + let mut prefixes: Vec = Vec::with_capacity(slice.len()); + for i in 0..slice.len() { + let mut fblock: PrfBlock = Default::default(); + fblock[0..i].copy_from_slice(&slice[0..i]); + prefixes.push(fblock); + } + + prefixes +} + // TODO: Probably more efficient to code by hand // Or use bitvec or bitvec-simd // TODO: Also include the index (like in the original implementation) diff --git a/ore-rs-5bit/src/right_block.rs b/ore-rs-5bit/src/right_block.rs new file mode 100644 index 0000000..5ab6239 --- /dev/null +++ b/ore-rs-5bit/src/right_block.rs @@ -0,0 +1,101 @@ +use std::ops::BitXorAssign; + +use formats::{CipherTextBlock, DataWithHeader, RightCipherTextBlock}; +use primitives::{NewPrp, prp::bitwise::BitwisePrp}; + + +#[derive(Debug)] +pub struct RightBlock(pub(super) u32); + +impl<'a> RightCipherTextBlock<'a> for RightBlock {} + +impl RightBlock { + pub(crate) fn init(plaintext: u8) -> Self { + assert!(plaintext < 32, "Block cannot encode more than 32-bits"); + Self(0xFFFFFFFF << plaintext) + } + + pub(crate) fn shuffle(&self, prp: &NewPrp) -> Self { + Self(self.0.bitwise_inverse_shuffle(prp)) + } + + pub(crate) fn get_bit(&self, bit: u8) -> u8 { + ((self.0 >> bit) & 1).try_into().unwrap() + } + + // TODO: Instead of get_bit, we could define get_indicator which also takes a "blind" + // parameter (i.e. the hash function output) +} + +impl BitXorAssign for RightBlock { + fn bitxor_assign(&mut self, rhs: u32) { + self.0 ^= rhs; + } +} + +// TODO: Can we derive macro any of this, too?? +impl<'a> CipherTextBlock<'a> for RightBlock { + fn byte_size() -> usize { + 4 + } + + fn extend_into(&self, out: &mut DataWithHeader) { + out.extend(self.0.to_be_bytes()); + } +} + +impl From<&[u8]> for RightBlock { + fn from(value: &[u8]) -> Self { + assert!(value.len() == Self::byte_size()); + let mut buf: [u8; 4] = Default::default(); + buf.copy_from_slice(&value[0..4]); + RightBlock(u32::from_be_bytes(buf)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_init_0() { + let block = RightBlock::init(0); + assert!(matches!(block, RightBlock(0b11111111111111111111111111111111))); + } + + #[test] + fn test_init_28() { + let block = RightBlock::init(28); + assert!(matches!(block, RightBlock(0b11110000000000000000000000000000))); + } + + #[test] + fn test_init_31() { + let block = RightBlock::init(31); + assert!(matches!(block, RightBlock(0b10000000000000000000000000000000))); + } + + #[test] + #[should_panic(expected="Block cannot encode more than 32-bits")] + fn test_init_32() { + RightBlock::init(32); + } + + #[test] + fn get_bit() { + let block = RightBlock::init(28); + assert_eq!(block.get_bit(0), 0); + assert_eq!(block.get_bit(1), 0); + assert_eq!(block.get_bit(2), 0); + assert_eq!(block.get_bit(27), 0); + assert_eq!(block.get_bit(28), 1); + assert_eq!(block.get_bit(31), 1); + } + + #[test] + #[should_panic] + fn get_bit_out_of_range() { + let block = RightBlock::init(28); + block.get_bit(32); + } +} \ No newline at end of file diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 08491e9..6a09052 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -61,6 +61,10 @@ impl NewPrp { pub fn permute(&self, input: impl Into) -> T { self.forward[input.into()] } + + pub fn inverse_permute(&self, input: impl Into) -> T { + self.inverse[input.into()] + } } pub trait PrpGenerator { diff --git a/primitives/src/prp/bitwise.rs b/primitives/src/prp/bitwise.rs index 62fb501..f364e15 100644 --- a/primitives/src/prp/bitwise.rs +++ b/primitives/src/prp/bitwise.rs @@ -1,12 +1,12 @@ use crate::{Prp, NewPrp}; pub trait BitwisePrp: Sized { - fn shuffle(self, prp: &NewPrp) -> Self; - fn inverse_shuffle(self, prp: &NewPrp) -> Self; + fn bitwise_shuffle(self, prp: &NewPrp) -> Self; + fn bitwise_inverse_shuffle(self, prp: &NewPrp) -> Self; } impl BitwisePrp<32> for u32 { - fn shuffle(self, prp: &NewPrp) -> Self { + fn bitwise_shuffle(self, prp: &NewPrp) -> Self { let mut output: Self = 0; for (i, &p) in prp.forward() { @@ -17,7 +17,7 @@ impl BitwisePrp<32> for u32 { output } - fn inverse_shuffle(self, prp: &NewPrp) -> Self { + fn bitwise_inverse_shuffle(self, prp: &NewPrp) -> Self { let mut output: Self = 0; for (i, &p) in prp.inverse() { @@ -30,7 +30,7 @@ impl BitwisePrp<32> for u32 { } impl BitwisePrp<8> for u8 { - fn shuffle(self, prp: &NewPrp) -> Self { + fn bitwise_shuffle(self, prp: &NewPrp) -> Self { let mut output: Self = 0; for (i, &p) in prp.forward() { @@ -41,7 +41,7 @@ impl BitwisePrp<8> for u8 { output } - fn inverse_shuffle(self, prp: &NewPrp) -> Self { + fn bitwise_inverse_shuffle(self, prp: &NewPrp) -> Self { let mut output: Self = 0; for (i, &p) in prp.inverse() { @@ -83,7 +83,7 @@ mod tests { }; let prp = PrpGenerator::generate(gen); let input = 0b00110110u8; - assert_eq!(input, input.shuffle(&prp).inverse_shuffle(&prp)); - assert_eq!(0b10100101, input.shuffle(&prp)); + assert_eq!(input, input.bitwise_shuffle(&prp).bitwise_inverse_shuffle(&prp)); + assert_eq!(0b10100101, input.bitwise_shuffle(&prp)); } } \ No newline at end of file From eea6d513c713467f8b7b3a36edf73f4c5bc64829 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Thu, 18 May 2023 22:40:08 +1000 Subject: [PATCH 14/18] Variable length comparison working --- formats/src/ciphertext/left.rs | 56 ++++++++++------ ore-rs-5bit/Cargo.toml | 2 - ore-rs-5bit/src/lib.rs | 116 +++++++++++++++++++++++---------- ore-rs-5bit/src/packing.rs | 54 --------------- 4 files changed, 118 insertions(+), 110 deletions(-) diff --git a/formats/src/ciphertext/left.rs b/formats/src/ciphertext/left.rs index ded65f0..39639f5 100644 --- a/formats/src/ciphertext/left.rs +++ b/formats/src/ciphertext/left.rs @@ -23,11 +23,8 @@ impl<'a, B: LeftCipherTextBlock<'a>> LeftCiphertext<'a, B> { block.extend_into(&mut self.data); } - // TODO: this should return an Option of the first block that differs /// Compare all the blocks of self with all the blocks in the given iterator, up to the `n` /// where `n` is the length of the shorter iterator. - /// In the case that the two iterators are not equal length and all `n` blocks are equal, - /// TODO??? What do we return? Perhaps we can do the ORE comparison in this step, too? /// The ordering mechanism is important here, too (i.e. Lexicographic or Numerical) /// If its numerical then the shorter value is always less than the other. pub fn compare_blocks(&'a self, nonce: &[u8], other: Box + 'a>) -> Ordering @@ -35,25 +32,44 @@ impl<'a, B: LeftCipherTextBlock<'a>> LeftCiphertext<'a, B> { B: LeftBlockEq<'a, O> + OreBlockOrd<'a, O>, O: RightCipherTextBlock<'a> { - // TODO: We should use CtOption and acc.conditionally_select - let eq = self.blocks().zip(other).fold(None, { |acc: Option<(B, O)>, (a, b): (B, O)| - // FIXME: This definitely isn't constant time - if a.constant_eq(&b).into() { - acc.or(None) - } else { - Some((a, b)) - } - }); - // FIXME: This isn't constant time either - if let Some((a, b)) = eq { - a.ore_compare(nonce, &b) - } else { - // No blocks differ so say that the values are equal - Ordering::Equal + let mut ai = self.blocks(); + let mut bi = other; // TODO: Don't pass an iterator to this func, pass an impl CipherText + + // TODO: Perhaps the LeftBlock could define the whole comparison (rather than splitting Eq and Ord like this) + let mut result: Option = None; + // We only need to keep an accum for Less or Greater! + loop { + match (ai.next(), bi.next()) { + (None, None) => return result.unwrap_or(Ordering::Equal), + // TODO: These 2 cases will depend on result + (Some(_), None) => { + if let Some(Ordering::Equal) = result { + return Ordering::Greater; + } else { + return result.unwrap_or(Ordering::Greater); + } + }, + (None, Some(_)) => { + if let Some(Ordering::Equal) = result { + return Ordering::Less; + } else { + return result.unwrap_or(Ordering::Less); + } + }, + (Some(x), Some(y)) => { + if x.constant_eq(&y).into() { + result = result.or(Some(Ordering::Equal)); + } else { + if let Some(Ordering::Equal) = result { + result = Some(x.ore_compare(nonce, &y)); + } else { + result = result.or(Some(x.ore_compare(nonce, &y))); + } + } + } + } } - - // TODO: Handle different lengths } } diff --git a/ore-rs-5bit/Cargo.toml b/ore-rs-5bit/Cargo.toml index 110687f..8f03088 100644 --- a/ore-rs-5bit/Cargo.toml +++ b/ore-rs-5bit/Cargo.toml @@ -18,6 +18,4 @@ quickcheck = { workspace = true } # TODO: Only use this in examples hex-literal = "0.3.2" -cmac = "0.7.2" -bit-vec = "0.6.3" hex = { workspace = true } diff --git a/ore-rs-5bit/src/lib.rs b/ore-rs-5bit/src/lib.rs index 471d92a..bccafbf 100644 --- a/ore-rs-5bit/src/lib.rs +++ b/ore-rs-5bit/src/lib.rs @@ -166,37 +166,6 @@ impl Ore5Bit { out.add_block(LeftBlock(left, p_n), right); } - - - // TODO: This feels a bit janky - /*for ((enc_prefix, mut right_blk), p_n) in prefixes.iter().zip(right_blocks.into_iter()).zip(p_ns.iter()) { - let mut ro_keys: [[u8; 16]; 32] = Default::default(); - - for (j, ro_key) in ro_keys.iter_mut().enumerate() { - ro_key.copy_from_slice(enc_prefix); - ro_key[15] = j as u8; - } - - for (j, prefix) in ro_keys.iter().enumerate() { - println!("RIGHT: {} -> {:?}", j, prefix); - } - - self.prf1.encrypt_all(&mut ro_keys); - - // TODO: Hash all of these keys with the nonce - // set the bits and and Xor with the right_block - // Push bytes onto right output vec - let hasher: Aes128Z2Hash = Hash::new(&nonce.into()); - // TODO: Hash all onto could be generic (right block) - // A RightBlock is like an "indicator set" - let mask = hasher.hash_all_onto_u32(&ro_keys); - println!("MASK: {mask:b}"); - - right_blk ^= mask; - - out.add_block(LeftBlock(*enc_prefix, *p_n), right_blk); - }*/ - out } @@ -233,11 +202,12 @@ extern crate quickcheck; #[cfg(test)] mod tests { use quickcheck::{Arbitrary, QuickCheck}; - use super::*; type ORE = Ore5BitChaCha20; + // TODO: Can we make these a macro so that we can reuse for every scheme? + fn init_ore() -> Result { let mut k1: [u8; 16] = Default::default(); let mut k2: [u8; 16] = Default::default(); @@ -289,6 +259,84 @@ mod tests { Ok(()) } + #[test] + fn test_empty_lt() -> Result<(), OreError> { + let a = vec![]; + let b = vec![0]; + let ore = init_ore()?; + let left = ore.encrypt_left(&a); + let combined = ore.encrypt(&b); + + assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Less); + + Ok(()) + } + + #[test] + fn test_empty_gt() -> Result<(), OreError> { + let a = vec![0]; + let b = vec![]; + let ore = init_ore()?; + let left = ore.encrypt_left(&a); + let combined = ore.encrypt(&b); + + assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Greater); + + Ok(()) + } + + #[test] + fn test_uneven_common_prefix_gt() -> Result<(), OreError> { + let a = vec![10, 15]; + let b = vec![10]; + let ore = init_ore()?; + let left = ore.encrypt_left(&a); + let combined = ore.encrypt(&b); + + assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Greater); + + Ok(()) + } + + #[test] + fn test_uneven_gt() -> Result<(), OreError> { + let a = vec![10]; + let b = vec![7, 20]; + let ore = init_ore()?; + let left = ore.encrypt_left(&a); + let combined = ore.encrypt(&b); + + assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Greater); + + Ok(()) + } + + #[test] + fn test_uneven_common_prefix_lt() -> Result<(), OreError> { + let a = vec![10]; + let b = vec![10, 15]; + let ore = init_ore()?; + let left = ore.encrypt_left(&a); + let combined = ore.encrypt(&b); + + assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Less); + + Ok(()) + } + + #[test] + fn test_uneven_lt() -> Result<(), OreError> { + let a = vec![10, 20]; + let b = vec![27]; + let ore = init_ore()?; + let left = ore.encrypt_left(&a); + let combined = ore.encrypt(&b); + + assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Less); + + Ok(()) + } + #[derive(Debug, Copy, Clone, PartialEq)] struct U5(u8); @@ -330,7 +378,7 @@ mod tests { QuickCheck::new().max_tests(1).quickcheck(single_elem as fn(U5, U5) -> bool) } - #[test] + /*#[test] fn test_quick2() { fn multiple_elems(a: Vec, b: Vec) -> bool { let ax: Vec = a.into_iter().map(|U5(x)| x).collect(); @@ -347,5 +395,5 @@ mod tests { } QuickCheck::new().max_tests(1).quickcheck(multiple_elems as fn(Vec, Vec) -> bool) - } + }*/ } diff --git a/ore-rs-5bit/src/packing.rs b/ore-rs-5bit/src/packing.rs index e5e3c25..bd32fb7 100644 --- a/ore-rs-5bit/src/packing.rs +++ b/ore-rs-5bit/src/packing.rs @@ -1,4 +1,3 @@ -use bit_vec::BitVec; use primitives::prf::PrfBlock; @@ -12,56 +11,3 @@ pub fn prefixes(slice: &[u8]) -> Vec { prefixes } - -// TODO: Probably more efficient to code by hand -// Or use bitvec or bitvec-simd -// TODO: Also include the index (like in the original implementation) -pub fn packed_prefixes(slice: &[u8]) -> Vec { - let mut bit_vec = BitVec::new(); - let mut prefixes: Vec = Vec::with_capacity((slice.len() * 8 / 5) + 1); - for &value in slice { - let mut fblock: PrfBlock = Default::default(); - for i in 0..5 { - bit_vec.push(value & (1 << i) != 0); - } - let bytes = bit_vec.to_bytes(); - fblock[0..bytes.len()].copy_from_slice(&bytes); - prefixes.push(fblock); - } - prefixes -} - - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_pack_unpack() { - let input = vec![7, 23, 30, 2, 19, 1]; - let packed = packed_prefixes(&input); - println!("Prefixes: {:?}", packed); - - assert!(false); - //assert_eq!(input, unpack_u8_slice_bitvec(&packed, input.len())); - } - - fn unpack_u8_slice_bitvec(packed: &[u8], count: usize) -> Vec { - let bit_vec = BitVec::from_bytes(packed); - let mut unpacked = Vec::with_capacity(count); - - for i in 0..count { - let mut value = 0u8; - for j in 0..5 { - if bit_vec[i * 5 + j] { - value |= 1 << j; - } - } - unpacked.push(value); - } - - unpacked - } -} - From e6e4a60d4800da05a242cef23b42131184101282 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Thu, 18 May 2023 22:48:53 +1000 Subject: [PATCH 15/18] Simplified comparison logic --- formats/src/ciphertext/left.rs | 31 +++++-------------------------- ore-rs-5bit/src/lib.rs | 2 +- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/formats/src/ciphertext/left.rs b/formats/src/ciphertext/left.rs index 39639f5..96d4fa5 100644 --- a/formats/src/ciphertext/left.rs +++ b/formats/src/ciphertext/left.rs @@ -32,40 +32,19 @@ impl<'a, B: LeftCipherTextBlock<'a>> LeftCiphertext<'a, B> { B: LeftBlockEq<'a, O> + OreBlockOrd<'a, O>, O: RightCipherTextBlock<'a> { - let mut ai = self.blocks(); let mut bi = other; // TODO: Don't pass an iterator to this func, pass an impl CipherText // TODO: Perhaps the LeftBlock could define the whole comparison (rather than splitting Eq and Ord like this) let mut result: Option = None; - // We only need to keep an accum for Less or Greater! loop { match (ai.next(), bi.next()) { - (None, None) => return result.unwrap_or(Ordering::Equal), - // TODO: These 2 cases will depend on result - (Some(_), None) => { - if let Some(Ordering::Equal) = result { - return Ordering::Greater; - } else { - return result.unwrap_or(Ordering::Greater); - } - }, - (None, Some(_)) => { - if let Some(Ordering::Equal) = result { - return Ordering::Less; - } else { - return result.unwrap_or(Ordering::Less); - } - }, + (None, None) => return result.unwrap_or(Ordering::Equal), + (Some(_), None) => return result.unwrap_or(Ordering::Greater), + (None, Some(_)) => return result.unwrap_or(Ordering::Less), (Some(x), Some(y)) => { - if x.constant_eq(&y).into() { - result = result.or(Some(Ordering::Equal)); - } else { - if let Some(Ordering::Equal) = result { - result = Some(x.ore_compare(nonce, &y)); - } else { - result = result.or(Some(x.ore_compare(nonce, &y))); - } + if !Into::::into(x.constant_eq(&y)) { + result = result.or(Some(x.ore_compare(nonce, &y))); } } } diff --git a/ore-rs-5bit/src/lib.rs b/ore-rs-5bit/src/lib.rs index bccafbf..04fc948 100644 --- a/ore-rs-5bit/src/lib.rs +++ b/ore-rs-5bit/src/lib.rs @@ -375,7 +375,7 @@ mod tests { } } - QuickCheck::new().max_tests(1).quickcheck(single_elem as fn(U5, U5) -> bool) + QuickCheck::new().max_tests(1000).quickcheck(single_elem as fn(U5, U5) -> bool) } /*#[test] From 80733d09299493cd8f4dd2f457d0215da062286d Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Thu, 18 May 2023 23:28:21 +1000 Subject: [PATCH 16/18] Work on constant time characteristic of compare --- formats/src/ciphertext/combined.rs | 2 +- formats/src/ciphertext/left.rs | 22 ++++++++++++---------- formats/src/ciphertext/mod.rs | 2 +- ore-rs-5bit/src/left_block.rs | 12 ++++++------ ore-rs-5bit/src/lib.rs | 27 ++++++++++++++------------- 5 files changed, 34 insertions(+), 31 deletions(-) diff --git a/formats/src/ciphertext/combined.rs b/formats/src/ciphertext/combined.rs index 84f8225..85e4fa0 100644 --- a/formats/src/ciphertext/combined.rs +++ b/formats/src/ciphertext/combined.rs @@ -138,7 +138,7 @@ where R: RightCipherTextBlock<'a>, L: OreBlockOrd<'a, R> { - fn ore_compare(&self, nonce: &[u8], other: &CombinedBlock<'a, L, R>) -> Ordering { + fn ore_compare(&self, nonce: &[u8], other: &CombinedBlock<'a, L, R>) -> i8 { self.ore_compare(nonce, &other.right) } } diff --git a/formats/src/ciphertext/left.rs b/formats/src/ciphertext/left.rs index 96d4fa5..ea256a5 100644 --- a/formats/src/ciphertext/left.rs +++ b/formats/src/ciphertext/left.rs @@ -1,5 +1,5 @@ -use std::{marker::PhantomData, ops::BitOr, cmp::Ordering}; -use subtle_ng::{Choice, CtOption}; +use std::{marker::PhantomData, ops::{BitOr, BitAnd}, cmp::Ordering}; +use subtle_ng::{Choice, CtOption, ConditionallySelectable}; use crate::{data_with_header::{CtType, DataWithHeader}, header::Header, ParseError, LeftCipherTextBlock, OreBlockOrd, RightCipherTextBlock}; use super::{CipherTextBlock, CipherText, LeftBlockEq}; @@ -27,7 +27,7 @@ impl<'a, B: LeftCipherTextBlock<'a>> LeftCiphertext<'a, B> { /// where `n` is the length of the shorter iterator. /// The ordering mechanism is important here, too (i.e. Lexicographic or Numerical) /// If its numerical then the shorter value is always less than the other. - pub fn compare_blocks(&'a self, nonce: &[u8], other: Box + 'a>) -> Ordering + pub fn compare_blocks(&'a self, nonce: &[u8], other: Box + 'a>) -> i8 where B: LeftBlockEq<'a, O> + OreBlockOrd<'a, O>, O: RightCipherTextBlock<'a> @@ -36,16 +36,18 @@ impl<'a, B: LeftCipherTextBlock<'a>> LeftCiphertext<'a, B> { let mut bi = other; // TODO: Don't pass an iterator to this func, pass an impl CipherText // TODO: Perhaps the LeftBlock could define the whole comparison (rather than splitting Eq and Ord like this) - let mut result: Option = None; + let mut result: CtOption = CtOption::new(0, Choice::from(0)); + loop { match (ai.next(), bi.next()) { - (None, None) => return result.unwrap_or(Ordering::Equal), - (Some(_), None) => return result.unwrap_or(Ordering::Greater), - (None, Some(_)) => return result.unwrap_or(Ordering::Less), + (None, None) => return result.unwrap_or(0), + (Some(_), None) => return result.unwrap_or(1), + (None, Some(_)) => return result.unwrap_or(-1), (Some(x), Some(y)) => { - if !Into::::into(x.constant_eq(&y)) { - result = result.or(Some(x.ore_compare(nonce, &y))); - } + // Always do the ore compare (even though we don't need to) to keep timing constant. + // Then assign the comparison to result if result is None and the left values are not equal. + let comparison = CtOption::new(x.ore_compare(nonce, &y), Choice::from(1)); + result.conditional_assign(&comparison, result.is_none().bitand(!x.constant_eq(&y))); } } } diff --git a/formats/src/ciphertext/mod.rs b/formats/src/ciphertext/mod.rs index 117d3b0..cdaa340 100644 --- a/formats/src/ciphertext/mod.rs +++ b/formats/src/ciphertext/mod.rs @@ -39,5 +39,5 @@ pub trait LeftBlockEq<'a, Other: ?Sized + CipherTextBlock<'a> = Self> { } pub trait OreBlockOrd<'a, Other: ?Sized + RightCipherTextBlock<'a>> { - fn ore_compare(&self, nonce: &[u8], right: &Other) -> Ordering; + fn ore_compare(&self, nonce: &[u8], right: &Other) -> i8; } diff --git a/ore-rs-5bit/src/left_block.rs b/ore-rs-5bit/src/left_block.rs index 2ccda0c..841694b 100644 --- a/ore-rs-5bit/src/left_block.rs +++ b/ore-rs-5bit/src/left_block.rs @@ -30,16 +30,16 @@ impl<'a> LeftBlockEq<'a, LeftBlock> for LeftBlock { impl<'a> OreBlockOrd<'a, RightBlock> for LeftBlock { // FIXME: Nonce *must* be 16-bytes - fn ore_compare(&self, nonce: &[u8], right: &RightBlock) -> Ordering { + fn ore_compare(&self, nonce: &[u8], right: &RightBlock) -> i8 { // TODO: This would be cleaner if we defined a method on RightBlock let hasher: Aes128Z2Hash = Hash::new(nonce.into()); // TODO: Use conditional_select //if ((right << self.1) as u8 & 1u8) ^ hasher.hash(&self.0) == 1 { let mask = hasher.hash(&self.0); if (right.get_bit(self.1) ^ mask) == 1 { - Ordering::Greater + 1 } else { - Ordering::Less + -1 } } } @@ -95,7 +95,7 @@ mod tests { [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, prp.inverse_permute(10u8)], prp.inverse_permute(10u8) ).ore_compare(&nonce, &right), - Ordering::Greater + 1 ); assert_eq!( @@ -103,7 +103,7 @@ mod tests { [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, prp.inverse_permute(5u8)], prp.inverse_permute(5u8) ).ore_compare(&nonce, &right), - Ordering::Less + -1 ); assert_eq!( @@ -111,7 +111,7 @@ mod tests { [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, prp.inverse_permute(24u8)], prp.inverse_permute(24u8) ).ore_compare(&nonce, &right), - Ordering::Greater + 1 ); } } \ No newline at end of file diff --git a/ore-rs-5bit/src/lib.rs b/ore-rs-5bit/src/lib.rs index 04fc948..ad27697 100644 --- a/ore-rs-5bit/src/lib.rs +++ b/ore-rs-5bit/src/lib.rs @@ -172,7 +172,7 @@ impl Ore5Bit { // TODO: Do this as a PartialOrd impl (and handle versions) // TODO: Handle different length slices. Compare the first n-bytes and if they're equal then the // longer value will be more "more-than" - pub fn compare_slices(a: impl AsRef<[u8]>, b: impl AsRef<[u8]>) -> Ordering { + pub fn compare_slices(a: impl AsRef<[u8]>, b: impl AsRef<[u8]>) -> i8 { let left: Ore5BitLeft = a.as_ref().try_into().unwrap(); let combined: Ore5BitCombined = b.as_ref().try_into().unwrap(); //assert!(left.comparable(&combined)); // TODO: Error @@ -229,7 +229,7 @@ mod tests { let left = ore.encrypt_left(&a); let combined = ore.encrypt(&a); - assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Equal); + assert_eq!(ORE::compare_slices(&left, &combined), 0); Ok(()) } @@ -241,7 +241,7 @@ mod tests { let left = ore.encrypt_left(&a); let combined = ore.encrypt(&b); - assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Less); + assert_eq!(ORE::compare_slices(&left, &combined), -1); Ok(()) } @@ -254,7 +254,7 @@ mod tests { let left = ore.encrypt_left(&a); let combined = ore.encrypt(&b); - assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Greater); + assert_eq!(ORE::compare_slices(&left, &combined), 1); Ok(()) } @@ -267,7 +267,7 @@ mod tests { let left = ore.encrypt_left(&a); let combined = ore.encrypt(&b); - assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Less); + assert_eq!(ORE::compare_slices(&left, &combined), -1); Ok(()) } @@ -280,7 +280,7 @@ mod tests { let left = ore.encrypt_left(&a); let combined = ore.encrypt(&b); - assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Greater); + assert_eq!(ORE::compare_slices(&left, &combined), 1); Ok(()) } @@ -293,7 +293,7 @@ mod tests { let left = ore.encrypt_left(&a); let combined = ore.encrypt(&b); - assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Greater); + assert_eq!(ORE::compare_slices(&left, &combined), 1); Ok(()) } @@ -306,7 +306,7 @@ mod tests { let left = ore.encrypt_left(&a); let combined = ore.encrypt(&b); - assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Greater); + assert_eq!(ORE::compare_slices(&left, &combined), 1); Ok(()) } @@ -319,7 +319,7 @@ mod tests { let left = ore.encrypt_left(&a); let combined = ore.encrypt(&b); - assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Less); + assert_eq!(ORE::compare_slices(&left, &combined), -1); Ok(()) } @@ -332,7 +332,7 @@ mod tests { let left = ore.encrypt_left(&a); let combined = ore.encrypt(&b); - assert_eq!(ORE::compare_slices(&left, &combined), Ordering::Less); + assert_eq!(ORE::compare_slices(&left, &combined), -1); Ok(()) } @@ -369,9 +369,10 @@ mod tests { let combined = ore.encrypt(&bx); match ORE::compare_slices(&left, &combined) { - Ordering::Less => a < b, - Ordering::Equal => a == b, - Ordering::Greater => a > b + -1 => a < b, + 0 => a == b, + 1 => a > b, + _ => panic!() } } From 2b2c215cf151130b476e797d0d3cd881d8502f24 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Mon, 12 Jun 2023 11:36:59 +1000 Subject: [PATCH 17/18] Added notes to self --- ore-rs-5bit/src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ore-rs-5bit/src/main.rs b/ore-rs-5bit/src/main.rs index ed6e88a..2ec8ab3 100644 --- a/ore-rs-5bit/src/main.rs +++ b/ore-rs-5bit/src/main.rs @@ -24,7 +24,10 @@ fn main() { // * Could the test failures be due to the changes I made to prefix generation? // TODO: Tests and benchmarks // TODO: create a plaintext trait for the ORE trait methods - // For the 5-bit scheme, create a Packed variant + // For the 5-bit scheme, create a U5/Packed variant + // TODO: Consider using Heapless https://docs.rs/heapless/0.7.16/heapless/ + // Input would need to be done in batches (let SIMD width help us decide). + // TODO: Try a SIMD PRP dbg!(Ore5BitChaCha20::compare_slices(&left, &combined)); //println!("{}, {}, {}", left.len(), right.len(), hex::encode(right)); } \ No newline at end of file From 3a899161f42b58909c10e490a632ebbf7035fcd4 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Sun, 17 Sep 2023 22:16:19 +1000 Subject: [PATCH 18/18] WIP --- primitives/src/hash.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/primitives/src/hash.rs b/primitives/src/hash.rs index fcc5eeb..735a007 100644 --- a/primitives/src/hash.rs +++ b/primitives/src/hash.rs @@ -1,6 +1,7 @@ use std::slice; use crate::{AesBlock, Hash, HashKey}; -use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}; +use aes::cipher::KeyInit; +use aes::cipher::{generic_array::GenericArray, BlockEncrypt}; use aes::Aes128; use zeroize::ZeroizeOnDrop; @@ -33,8 +34,7 @@ impl Aes128Z2Hash { impl Hash for Aes128Z2Hash { fn new(key: &HashKey) -> Self { - let key_array = GenericArray::from_slice(key); - let cipher = Aes128::new(key_array); + let cipher = Aes128::new_from_slice(&key).unwrap(); Self { cipher } }