-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generic FMD2 implementation #18
Open
larraia
wants to merge
7
commits into
main
Choose a base branch
from
enrique/inner_fmd2_implementation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
d092a64
renamed test to detect in other places
larraia 9cb816d
Trait for FMD key generation
larraia c2673bf
refactor to generic methods
larraia cdd4e23
fix clippy
larraia e052a6d
Removed unused point
larraia 97ad017
removed getters of internal struct
larraia 5be1db5
cargo fmt
larraia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
//! The FMD2 scheme specified in Figure 3 of the [FMD paper](https://eprint.iacr.org/2021/089). | ||
//! The FMD2 scheme. | ||
|
||
#[cfg(feature = "serde")] | ||
use serde::{Deserialize, Serialize}; | ||
|
@@ -7,26 +7,33 @@ use alloc::collections::BTreeSet; | |
use curve25519_dalek::{ | ||
constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint, scalar::Scalar, | ||
}; | ||
use rand_core::{CryptoRng, RngCore}; | ||
use sha2::{Digest, Sha256, Sha512}; | ||
|
||
use crate::{CcaSecure, FmdScheme, RestrictedRateSet}; | ||
use rand_core::{CryptoRng, RngCore}; | ||
|
||
#[derive(Debug, Clone)] | ||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||
pub struct SecretKey(Vec<Scalar>); | ||
use crate::{ | ||
fmd2_generic::{GenericFlagCiphertexts, GenericPublicKey, TrapdoorBasepoint}, | ||
SecretKey, DetectionKey, CcaSecure, FmdKeyGen, FmdScheme}; | ||
|
||
#[derive(Debug, Clone)] | ||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||
/// γ points. The basepoint is hardcoded to the Ristretto basepoint. | ||
pub struct PublicKey { | ||
keys: Vec<RistrettoPoint>, | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||
pub struct DetectionKey { | ||
indices: Vec<usize>, | ||
keys: Vec<Scalar>, | ||
/// A point `u`, a scalar `y`, and γ ciphertext bits `c`. | ||
pub struct FlagCiphertexts { | ||
u: RistrettoPoint, | ||
y: Scalar, | ||
c: Vec<u8>, | ||
} | ||
|
||
impl From<GenericFlagCiphertexts> for FlagCiphertexts { | ||
fn from(value: GenericFlagCiphertexts) -> Self { | ||
FlagCiphertexts { u: value.u, y: value.y, c: value.c } // Ignore basepoint. | ||
} | ||
} | ||
|
||
impl SecretKey { | ||
|
@@ -69,159 +76,97 @@ impl SecretKey { | |
} | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||
pub struct FlagCiphertexts { | ||
u: RistrettoPoint, | ||
y: Scalar, | ||
c: Vec<u8>, | ||
} | ||
|
||
impl FlagCiphertexts { | ||
fn generate_flag<R: RngCore + CryptoRng>(pk: &PublicKey, rng: &mut R) -> Self { | ||
let r = Scalar::random(rng); | ||
let z = Scalar::random(rng); | ||
let u = RISTRETTO_BASEPOINT_POINT * r; | ||
let w = RISTRETTO_BASEPOINT_POINT * z; | ||
|
||
let bit_ciphertexts: Vec<bool> = pk | ||
.keys | ||
.iter() | ||
.map(|pk_i| { | ||
let k_i = hash_to_flag_ciphertext_bit(&u, &(pk_i * r), &w); | ||
!k_i // Encrypt bit 1 with hashed mask k_i. | ||
}) | ||
.collect(); | ||
|
||
let m = hash_flag_ciphertexts(&u, &bit_ciphertexts); | ||
|
||
let r_inv = r.invert(); | ||
let y = (z - m) * r_inv; | ||
|
||
let c = FlagCiphertexts::to_bytes(&bit_ciphertexts); | ||
/// The γ > 0 parameter. | ||
/// The set of (restricted) false positive rates is 2^{-n} for 1 ≤ n ≤ γ. | ||
pub struct Fmd2Params { | ||
gamma: usize, | ||
} | ||
|
||
Self { u, y, c } | ||
} | ||
impl Fmd2Params { | ||
|
||
/// Compressed representation of the γ bit-ciphertexts of a FlagCiphertext. | ||
fn to_bytes(bit_ciphertexts: &[bool]) -> Vec<u8> { | ||
let c: Vec<u8> = bit_ciphertexts | ||
.chunks(8) | ||
.map(|bits| { | ||
let mut byte = 0u8; | ||
for (i, bit) in bits.iter().enumerate() { | ||
if *bit { | ||
byte ^= 1u8 << i | ||
} | ||
} | ||
byte | ||
}) | ||
.collect(); | ||
c | ||
/// Generate keys according to the minimum false positive rate γ. | ||
pub fn new(gamma: usize) -> Fmd2Params { | ||
Fmd2Params { gamma } | ||
} | ||
|
||
/// Decompress the inner bit-ciphertexts of this FlagCiphertext. | ||
fn to_bits(&self) -> Vec<bool> { | ||
let mut bit_ciphertexts: Vec<bool> = Vec::new(); | ||
for byte in self.c.iter() { | ||
for i in 0..8 { | ||
bit_ciphertexts.push(1u8 == (byte >> i) & 1u8); | ||
} | ||
} | ||
|
||
bit_ciphertexts | ||
/// Returns the γ parameter | ||
pub fn gamma(&self) -> usize { | ||
self.gamma | ||
} | ||
} | ||
|
||
/// This is the hash H from Fig.3 of the FMD paper, instantiated with SHA256. | ||
fn hash_to_flag_ciphertext_bit( | ||
u: &RistrettoPoint, | ||
ddh_mask: &RistrettoPoint, | ||
w: &RistrettoPoint, | ||
) -> bool { | ||
let mut hasher = Sha256::new(); | ||
|
||
hasher.update(u.compress().to_bytes()); | ||
hasher.update(ddh_mask.compress().to_bytes()); | ||
hasher.update(w.compress().to_bytes()); | ||
|
||
let k_i_byte = hasher.finalize().as_slice()[0] & 1u8; | ||
|
||
k_i_byte == 1u8 | ||
} | ||
|
||
/// This is the hash G from Fig.3 of the FMD paper, instantiated with SHA512. | ||
fn hash_flag_ciphertexts(u: &RistrettoPoint, bit_ciphertexts: &[bool]) -> Scalar { | ||
let mut m_bytes = u.compress().to_bytes().to_vec(); | ||
m_bytes.extend_from_slice(&FlagCiphertexts::to_bytes(bit_ciphertexts)); | ||
|
||
Scalar::hash_from_bytes::<Sha512>(&m_bytes) | ||
} | ||
|
||
pub struct Fmd2; | ||
|
||
impl FmdScheme for Fmd2 { | ||
impl FmdKeyGen for Fmd2Params { | ||
type PublicKey = PublicKey; | ||
|
||
type SecretKey = SecretKey; | ||
|
||
type DetectionKey = DetectionKey; | ||
|
||
type FlagCiphertexts = FlagCiphertexts; | ||
|
||
fn generate_keys<R: RngCore + CryptoRng>( | ||
rates: &RestrictedRateSet, | ||
&self, | ||
rng: &mut R, | ||
) -> (Self::PublicKey, Self::SecretKey) { | ||
let gamma = rates.gamma(); | ||
let gamma = self.gamma(); | ||
|
||
// Secret key. | ||
let sk = SecretKey::generate_keys(gamma, rng); | ||
|
||
// Public key. | ||
let pk = sk.generate_public_key(); | ||
|
||
(pk, sk) | ||
(pk,sk) | ||
} | ||
} | ||
|
||
/// The implementation from Figure 3 of the [FMD paper](https://eprint.iacr.org/2021/089). | ||
pub struct Fmd2; | ||
|
||
impl FmdScheme for Fmd2 { | ||
type PublicKey = PublicKey; | ||
|
||
type FlagCiphertexts = FlagCiphertexts; | ||
|
||
fn flag<R: RngCore + CryptoRng>(pk: &Self::PublicKey, rng: &mut R) -> Self::FlagCiphertexts { | ||
FlagCiphertexts::generate_flag(pk, rng) | ||
let gpk = GenericPublicKey{ basepoint_eg: RISTRETTO_BASEPOINT_POINT, keys: pk.keys.clone() }; | ||
|
||
GenericFlagCiphertexts::generate_flag( | ||
&gpk, | ||
&TrapdoorBasepoint::new(&gpk, &Scalar::ONE), | ||
rng | ||
).into() | ||
} | ||
|
||
fn extract(sk: &Self::SecretKey, indices: &[usize]) -> Option<Self::DetectionKey> { | ||
fn extract(sk: &SecretKey, indices: &[usize]) -> Option<DetectionKey> { | ||
|
||
sk.extract(indices) | ||
} | ||
|
||
fn detect(dsk: &Self::DetectionKey, flag_ciphers: &Self::FlagCiphertexts) -> bool { | ||
let u = flag_ciphers.u; | ||
let bit_ciphertexts = flag_ciphers.to_bits(); | ||
let m = hash_flag_ciphertexts(&u, &bit_ciphertexts); | ||
let w = RISTRETTO_BASEPOINT_POINT * m + flag_ciphers.u * flag_ciphers.y; | ||
let mut success = true; | ||
for (xi, index) in dsk.keys.iter().zip(dsk.indices.iter()) { | ||
let k_i = hash_to_flag_ciphertext_bit(&u, &(u * xi), &w); | ||
success = success && k_i != bit_ciphertexts[*index] | ||
} | ||
fn detect(dsk: &DetectionKey, flag_ciphers: &Self::FlagCiphertexts) -> bool { | ||
|
||
let gfc = GenericFlagCiphertexts::new( | ||
&RISTRETTO_BASEPOINT_POINT, | ||
&flag_ciphers.u, | ||
&flag_ciphers.y, | ||
&flag_ciphers.c); | ||
|
||
success | ||
dsk.detect(&gfc) | ||
} | ||
} | ||
|
||
/// FMD2 is proven to be IND-CCA secure in the [FMD paper](https://eprint.iacr.org/2021/089). | ||
impl CcaSecure for Fmd2 {} | ||
impl CcaSecure for Fmd2Params {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's just a marker trait to signal the scheme is IND-CCA. We can remove it if it is confusing. |
||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_flag_test() { | ||
fn test_flag_detect() { | ||
let mut csprng = rand_core::OsRng; | ||
|
||
let rates = RestrictedRateSet::new(5); | ||
let (pk, sk) = <Fmd2 as FmdScheme>::generate_keys(&rates, &mut csprng); | ||
let pp = Fmd2Params::new(5); | ||
let (pk, sk) = pp.generate_keys(&mut csprng); | ||
let flag_cipher = <Fmd2 as FmdScheme>::flag(&pk, &mut csprng); | ||
let dk = <Fmd2 as FmdScheme>::extract(&sk, &(0..rates.gamma()).collect::<Vec<_>>()); | ||
let dk = <Fmd2 as FmdScheme>::extract(&sk, &(0..pp.gamma()).collect::<Vec<_>>()); | ||
assert!(<Fmd2 as FmdScheme>::detect(&dk.unwrap(), &flag_cipher)); | ||
} | ||
|
||
|
@@ -230,20 +175,20 @@ mod tests { | |
fn test_extract_checks() { | ||
let mut csprng = rand_core::OsRng; | ||
|
||
let rates = RestrictedRateSet::new(5); | ||
let (_pk, sk) = <Fmd2 as FmdScheme>::generate_keys(&rates, &mut csprng); | ||
let pp = Fmd2Params::new(5); | ||
let (_pk, sk) = pp.generate_keys(&mut csprng); | ||
|
||
assert!(<Fmd2 as FmdScheme>::extract(&sk, &[0, 0, 1]).is_none()); | ||
assert!(<Fmd2 as FmdScheme>::extract(&sk, &[0, 1, 2, 3, 4, 5, 6]).is_none()); | ||
assert!(<Fmd2 as FmdScheme>::extract(&sk, &[6]).is_none()); | ||
} | ||
|
||
#[test] | ||
fn test_flag_test_with_partial_detection_key() { | ||
fn test_flag_detect_with_partial_detection_key() { | ||
let mut csprng = rand_core::OsRng; | ||
|
||
let rates = RestrictedRateSet::new(5); | ||
let (pk, sk) = <Fmd2 as FmdScheme>::generate_keys(&rates, &mut csprng); | ||
let pp = Fmd2Params::new(5); | ||
let (pk, sk) = pp.generate_keys(&mut csprng); | ||
for _i in 0..10 { | ||
let flag_cipher = <Fmd2 as FmdScheme>::flag(&pk, &mut csprng); | ||
let dk = <Fmd2 as FmdScheme>::extract(&sk, &[0, 2, 4]); | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to suggest we use the same import format as Namada. There are three import blocks: First is from core, std, alloc, etc. The second is third party. The last is crate, super, etc. There should a single return between blocks, and no returns within a block. Each block should be alphabetized