However, when I try to decrypt the result with the following code, it seems like the product was not saved in the variable “product” and instead, it only saved the glwe.clone

let plaintext = engine.decrypt_glwe_ciphertext(&key, &product)?;
let output: Vec<u32> = engine.retrieve_plaintext_vector(&plaintext)?;
println!("{}",output[1]>>20);

I would like to know how to solve this problem.
Thank you for your support.

Hello!
Indeed, I can reproduce the behavior on my laptop. The bootstrap tests pass so we know the external product implementation itself should be correct, but I don’t know yet why the external product as a standalone function doesn’t work as expected. We’ll dig into this issue and come back to you as soon as possible.

Hello,
I tested the function again (after updating the cargo to concrete core 1.02) on the new example and now, what I obtain is a random value that is different every time.

Also the decrypt function in Concrete-core does not perform the rounding (this is handled in Concrete itself). To decrypt & round a message that was shifted 60 bits to the left and encrypted with 64 bits you can do:

let plaintext_vector = default_engine.decrypt_glwe_ciphertext(&key, &product).unwrap();
let mut output: Vec<u64> = default_engine.retrieve_plaintext_vector(&plaintext_vector).unwrap();
for p in output.iter_mut() {
*p = *p >> 59;
let carry = *p % 2;
*p = ((*p >> 1) + carry) % (1 << (64 - 60));
}

I tested with a very low noise, and added the rounding to my code which is the following

// DISCLAIMER: the parameters used here are only for test purpose, and are not secure.
let glwe_dimension = GlweDimension(2);
let polynomial_size = PolynomialSize(256);
let level = DecompositionLevelCount(1);
let base_log = DecompositionBaseLog(4);
// Here a hard-set encoding is applied (shift by 20 bits)
let input_ggsw = 3_u32;
let input_glwe = vec![3_u32 << 20; polynomial_size.0];
let noise = Variance(2_f64.powf(-250.));
// Unix seeder must be given a secret input.
// Here we just give it 0, which is totally unsafe.
const UNSAFE_SECRET: u128 = 0;
let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET)))?;
let mut fft_engine = FftEngine::new(())?;
let key: GlweSecretKey32 =
default_engine.generate_new_glwe_secret_key(glwe_dimension, polynomial_size)?;
let plaintext_ggsw = default_engine.create_plaintext_from(&input_ggsw)?;
let plaintext_glwe = default_engine.create_plaintext_vector_from(&input_glwe)?;
let ggsw = default_engine.encrypt_scalar_ggsw_ciphertext(
&key,
&plaintext_ggsw,
noise,
level,
base_log,
)?;
let complex_ggsw: FftFourierGgswCiphertext32 = fft_engine.convert_ggsw_ciphertext(&ggsw)?;
let glwe = default_engine.encrypt_glwe_ciphertext(&key, &plaintext_glwe, noise)?;
// We allocate an output ciphertext simply by cloning the input.
// The content of this output ciphertext will by wiped by the external product.
let mut product = glwe.clone();
fft_engine.discard_compute_external_product_glwe_ciphertext_ggsw_ciphertext(
&glwe,
&complex_ggsw,
&mut product,
)?;
let plaintext = default_engine.decrypt_glwe_ciphertext(&key, &product)?;
let mut output: Vec<u32> = default_engine.retrieve_plaintext_vector(&plaintext)?;
for p in output.iter_mut() {
*p = *p >> 19;
let carry = *p % 2;
*p = ((*p >> 1) + carry) % (1 << (32 - 20));
}
println!("{}", output[3]);
Ok(())
}```
Yet, what I retrieve is a multiple of 256 instead of 9.
Thank you again,
Victor

What if you try to replace let mut product = glwe.clone();
by

let zero_input_glwe = vec![0_u64; polynomial_size.0];
let zero_plaintext_glwe = default_engine.create_plaintext_vector_from(&zero_input_glwe).unwrap();
let mut product = default_engine.trivially_encrypt_glwe_ciphertext(glwe_dimension.to_glwe_size(), &zero_plaintext_glwe).unwrap();

Strange, here’s the example code I have that works with Concrete-core 1.0.2:

use concrete_core::prelude::*;
/*
cargo run --release
*/
fn main() {
let glwe_dimension = GlweDimension(1);
let polynomial_size = PolynomialSize(256);
let level = DecompositionLevelCount(3);
let base_log = DecompositionBaseLog(8);
// Here a hard-set encoding is applied (shift by 20 bits)
let input_ggsw = 3_u64;
let input_glwe = vec![2_u64 << 60; polynomial_size.0];
let zero_input_glwe = vec![1_u64 << 60; polynomial_size.0];
let noise = Variance(2_f64.powf(-10000.));
// Unix seeder must be given a secret input.
// Here we just give it 0, which is totally unsafe.
const UNSAFE_SECRET: u128 = 0;
let mut default_engine = DefaultEngine::new(Box::new(UnixSeeder::new(UNSAFE_SECRET))).unwrap();
let mut fft_engine = FftEngine::new(()).unwrap();
let mut key: GlweSecretKey64 =
default_engine.generate_new_glwe_secret_key(glwe_dimension, polynomial_size).unwrap();
let plaintext_ggsw = default_engine.create_plaintext_from(&input_ggsw).unwrap();
let plaintext_glwe = default_engine.create_plaintext_vector_from(&input_glwe).unwrap();
let zero_plaintext_glwe = default_engine.create_plaintext_vector_from(&zero_input_glwe).unwrap();
let ggsw = default_engine.encrypt_scalar_ggsw_ciphertext(
&key,
&plaintext_ggsw,
noise,
level,
base_log,
).unwrap();
let complex_ggsw: FftFourierGgswCiphertext64 =
fft_engine.convert_ggsw_ciphertext(&ggsw).unwrap();
let glwe = default_engine.encrypt_glwe_ciphertext(&key,
&plaintext_glwe, noise).unwrap();
// We allocate an output ciphertext simply by cloning the input.
// The content of this output ciphertext will by wiped by the
// external product.
let mut product = default_engine.trivially_encrypt_glwe_ciphertext(glwe_dimension.to_glwe_size(), &zero_plaintext_glwe).unwrap();
fft_engine.discard_compute_external_product_glwe_ciphertext_ggsw_ciphertext(
&glwe,
&complex_ggsw,
&mut product,
).unwrap();
let plaintext = default_engine.decrypt_glwe_ciphertext(&key, &product).unwrap();
let mut output: Vec<u64> = default_engine.retrieve_plaintext_vector(&plaintext).unwrap();
for p in output.iter_mut() {
*p = *p >> 59;
let carry = *p % 2;
*p = ((*p >> 1) + carry) % (1 << (64 - 60));
}
println!("{:?}",output);
}

I tried your example and it is working.
The problem in my example was coming from the parameter DecompositionLevelCount (At 1 it outputs random results, at 2 it outputs close results and at 3 it seems to always work).

Thank you a lot for your help and patience.
Victor