mirror of
https://github.com/Sunscreen-tech/Sunscreen.git
synced 2026-04-19 03:00:06 -04:00
Decode shared Signed values into field elements
This commit is contained in:
@@ -19,7 +19,7 @@ use crate::{
|
||||
};
|
||||
|
||||
use sunscreen_runtime::{
|
||||
InnerPlaintext, NumCiphertexts, Plaintext, ShareWithZKP, TryFromPlaintext, TryIntoPlaintext,
|
||||
InnerPlaintext, NumCiphertexts, Plaintext, TryFromPlaintext, TryIntoPlaintext,
|
||||
};
|
||||
|
||||
use std::ops::*;
|
||||
@@ -36,12 +36,12 @@ impl NumCiphertexts for Signed {
|
||||
const NUM_CIPHERTEXTS: usize = 1;
|
||||
}
|
||||
|
||||
impl ShareWithZKP for Signed {
|
||||
#[cfg(feature = "linkedproofs")]
|
||||
impl sunscreen_runtime::ShareWithZKP for Signed {
|
||||
/// While a freshly encoded plaintext will only use up to 64 coefficients, plaintexts resulting
|
||||
/// from a multiplicative circuit can result in valid Signed encodings that use more than 64
|
||||
/// coefficients. A bound of 128 should work for virtually all cases.
|
||||
const DEGREE_BOUND: usize = 64;
|
||||
// const DEGREE_BOUND: usize = 128;
|
||||
const DEGREE_BOUND: usize = 128;
|
||||
}
|
||||
|
||||
impl FheProgramInputTrait for Signed {}
|
||||
|
||||
@@ -5,7 +5,7 @@ use paste::paste;
|
||||
use seal_fhe::Plaintext as SealPlaintext;
|
||||
|
||||
use sunscreen_runtime::{
|
||||
InnerPlaintext, NumCiphertexts, Plaintext, ShareWithZKP, TryFromPlaintext, TryIntoPlaintext,
|
||||
InnerPlaintext, NumCiphertexts, Plaintext, TryFromPlaintext, TryIntoPlaintext,
|
||||
};
|
||||
|
||||
use crate as sunscreen;
|
||||
@@ -341,12 +341,17 @@ type_synonyms! {
|
||||
64, 128, 256, 512
|
||||
}
|
||||
|
||||
impl ShareWithZKP for Unsigned64 {
|
||||
const DEGREE_BOUND: usize = 128;
|
||||
}
|
||||
#[cfg(feature = "linkedproofs")]
|
||||
mod sharing {
|
||||
use sunscreen_runtime::ShareWithZKP;
|
||||
|
||||
impl ShareWithZKP for Unsigned128 {
|
||||
const DEGREE_BOUND: usize = 255;
|
||||
impl ShareWithZKP for super::Unsigned64 {
|
||||
const DEGREE_BOUND: usize = 128;
|
||||
}
|
||||
|
||||
impl ShareWithZKP for super::Unsigned128 {
|
||||
const DEGREE_BOUND: usize = 255;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
201
sunscreen/src/types/zkp/bfv_plaintext.rs
Normal file
201
sunscreen/src/types/zkp/bfv_plaintext.rs
Normal file
@@ -0,0 +1,201 @@
|
||||
use sunscreen_compiler_macros::TypeName;
|
||||
use sunscreen_runtime::ShareWithZKP;
|
||||
use sunscreen_zkp_backend::{BigInt, FieldSpec};
|
||||
|
||||
use crate::{
|
||||
invoke_gadget,
|
||||
types::{bfv::Signed, zkp::ProgramNode},
|
||||
zkp::{with_zkp_ctx, ZkpContextOps},
|
||||
};
|
||||
|
||||
use super::{gadgets::SignedModulus, Field, NumFieldElements, ToNativeFields};
|
||||
|
||||
use crate as sunscreen;
|
||||
|
||||
/// A BFV plaintext polynomial that has been shared to a ZKP program.
|
||||
///
|
||||
/// Note that `C` represents the size of the 2s complement decomposition of each coefficient in the
|
||||
/// polynomial. This is unfortunately plaintext modulus dependent, and can be calculated by
|
||||
/// `plaintext_modulus.ilog2() + 1`.
|
||||
///
|
||||
/// Similarly `N` is the degree of the _shared_ polynomial, which may be less than the full lattice
|
||||
/// dimension. See [`Share::DEGREE_BOUND`](sunscreen_runtime::Share).
|
||||
//
|
||||
// TODO if we just commit to 64-bit coefficient bounds, we can get rid of dynamic C.
|
||||
#[derive(Debug, Clone, TypeName)]
|
||||
struct BfvPlaintext<F: FieldSpec, const C: usize, const N: usize> {
|
||||
data: Box<[[Field<F>; C]; N]>,
|
||||
}
|
||||
|
||||
impl<F: FieldSpec, T, const N: usize, const R: usize> From<[[T; N]; R]> for BfvPlaintext<F, N, R>
|
||||
where
|
||||
T: Into<Field<F>> + std::fmt::Debug,
|
||||
{
|
||||
fn from(x: [[T; N]; R]) -> Self {
|
||||
Self {
|
||||
data: Box::new(x.map(|x| x.map(|x| x.into()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FieldSpec, const C: usize, const N: usize> NumFieldElements for BfvPlaintext<F, C, N> {
|
||||
const NUM_NATIVE_FIELD_ELEMENTS: usize = C * N;
|
||||
}
|
||||
|
||||
impl<F: FieldSpec, const C: usize, const N: usize> ToNativeFields for BfvPlaintext<F, C, N> {
|
||||
fn to_native_fields(&self) -> Vec<BigInt> {
|
||||
self.data.into_iter().flatten().map(|x| x.val).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, TypeName)]
|
||||
/// A [BFV signed integer](crate::types::bfv::Signed) that has been shared to a ZKP program.
|
||||
pub struct BfvSigned<F: FieldSpec, const C: usize>(
|
||||
BfvPlaintext<F, C, { <Signed as ShareWithZKP>::DEGREE_BOUND }>,
|
||||
);
|
||||
|
||||
impl<F: FieldSpec, const C: usize> NumFieldElements for BfvSigned<F, C> {
|
||||
const NUM_NATIVE_FIELD_ELEMENTS: usize = <BfvPlaintext<
|
||||
F,
|
||||
C,
|
||||
{ <Signed as ShareWithZKP>::DEGREE_BOUND },
|
||||
> as NumFieldElements>::NUM_NATIVE_FIELD_ELEMENTS;
|
||||
}
|
||||
|
||||
impl<F: FieldSpec, const C: usize> ToNativeFields for BfvSigned<F, C> {
|
||||
fn to_native_fields(&self) -> Vec<BigInt> {
|
||||
self.0.to_native_fields()
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode the underlying plaintext polynomial into the field.
|
||||
pub trait AsFieldElement<F: FieldSpec> {
|
||||
/**
|
||||
* Return a structure scaled by `x`.
|
||||
*/
|
||||
fn into_field_elem(self) -> ProgramNode<Field<F>>;
|
||||
}
|
||||
|
||||
impl<F: FieldSpec, const C: usize> AsFieldElement<F> for ProgramNode<BfvSigned<F, C>> {
|
||||
fn into_field_elem(self) -> ProgramNode<Field<F>> {
|
||||
let (plain_modulus, two, mut coeffs) = with_zkp_ctx(|ctx| {
|
||||
let plain_modulus = ctx.add_constant(&BigInt::from_u32(4096));
|
||||
let two = ctx.add_constant(&BigInt::from_u32(2));
|
||||
// Get coeffs via 2s complement construction
|
||||
let coeffs = self
|
||||
.ids
|
||||
.chunks(C)
|
||||
.map(|xs| {
|
||||
let mut c = ctx.add_constant(&BigInt::ZERO);
|
||||
for (i, x) in xs.iter().enumerate() {
|
||||
let pow = ctx.add_constant(&(BigInt::ONE << i));
|
||||
let mul = ctx.add_multiplication(pow, *x);
|
||||
if i == C - 1 {
|
||||
c = ctx.add_subtraction(c, mul);
|
||||
} else {
|
||||
c = ctx.add_addition(c, mul);
|
||||
}
|
||||
}
|
||||
c
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
(plain_modulus, two, coeffs)
|
||||
});
|
||||
|
||||
// Translate coefficients into field modulus
|
||||
let cutoff_divider = SignedModulus::new(F::FIELD_MODULUS, 1);
|
||||
let divider = SignedModulus::new(F::FIELD_MODULUS, 63);
|
||||
// TODO make sure this is correct, might need +1 somewhere to match the signed encoding
|
||||
let neg_cutoff = invoke_gadget(cutoff_divider, &[plain_modulus, two])[0];
|
||||
for c in coeffs.iter_mut() {
|
||||
let is_negative = invoke_gadget(divider, &[*c, neg_cutoff])[0];
|
||||
*c = with_zkp_ctx(|ctx| {
|
||||
let shift = ctx.add_multiplication(plain_modulus, is_negative);
|
||||
ctx.add_subtraction(*c, shift)
|
||||
});
|
||||
}
|
||||
|
||||
ProgramNode::new(&[with_zkp_ctx(|ctx| {
|
||||
// Get signed value by decoding according to Signed implementation.
|
||||
let mut x = ctx.add_constant(&BigInt::ZERO);
|
||||
for (i, c) in coeffs.iter().enumerate() {
|
||||
let pow = ctx.add_constant(&(BigInt::ONE << i));
|
||||
let mul = ctx.add_multiplication(pow, *c);
|
||||
x = ctx.add_addition(x, mul);
|
||||
}
|
||||
x
|
||||
})])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use sunscreen_zkp_backend::bulletproofs::BulletproofsBackend;
|
||||
use sunscreen_zkp_backend::FieldSpec;
|
||||
|
||||
use crate::types::zkp::{BulletproofsField, Field};
|
||||
use crate::zkp_program;
|
||||
use crate::{self as sunscreen, ZkpProgramFnExt};
|
||||
|
||||
#[zkp_program]
|
||||
fn is_eq<F: FieldSpec>(#[private] x: BfvSigned<F, 13>, #[public] y: Field<F>) {
|
||||
x.into_field_elem().constrain_eq(y);
|
||||
}
|
||||
|
||||
// TODO accept plaintext as zkp program arg, fold into BfvSigned, and test odd pt moduli.
|
||||
|
||||
#[test]
|
||||
fn can_decode_signed_properly() {
|
||||
let is_eq_zkp = is_eq.compile::<BulletproofsBackend>().unwrap();
|
||||
let runtime = is_eq.runtime::<BulletproofsBackend>().unwrap();
|
||||
|
||||
let plain_modulus = 4096_u64;
|
||||
const LOG_P: usize = 13; // i.e. `plain_modulus.ilog2() + 1`
|
||||
|
||||
for val in [3i64, -3] {
|
||||
// Simulate the polynomial signed encoding
|
||||
let mut signed_encoding = [0; 128];
|
||||
let abs_val = val.unsigned_abs();
|
||||
for (i, c) in signed_encoding.iter_mut().take(64).enumerate() {
|
||||
let bit = (abs_val & 0x1 << i) >> i;
|
||||
*c = if val.is_negative() {
|
||||
bit * (plain_modulus - bit)
|
||||
} else {
|
||||
bit
|
||||
};
|
||||
}
|
||||
|
||||
// Now further break down each coeff into 2s complement
|
||||
// Note that these numbers will virtually always be positive in the Zq context, for all but
|
||||
// pathological plaintext moduli.
|
||||
let coeffs = signed_encoding.map(|c| {
|
||||
let mut bits = [0; LOG_P];
|
||||
for (i, b) in bits.iter_mut().enumerate() {
|
||||
let bit = (c & (0x1 << i)) >> i;
|
||||
*b = bit;
|
||||
}
|
||||
bits
|
||||
});
|
||||
|
||||
let encoded = BfvSigned(BfvPlaintext {
|
||||
data: Box::new(coeffs.map(|c| c.map(BulletproofsField::from))),
|
||||
});
|
||||
|
||||
let proof = runtime
|
||||
.proof_builder(&is_eq_zkp)
|
||||
.private_input(encoded)
|
||||
.public_input(BulletproofsField::from(val))
|
||||
.prove()
|
||||
.unwrap();
|
||||
|
||||
runtime
|
||||
.verification_builder(&is_eq_zkp)
|
||||
.proof(&proof)
|
||||
.public_input(BulletproofsField::from(val))
|
||||
.verify()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use crate::zkp::{invoke_gadget, with_zkp_ctx, ZkpContextOps};
|
||||
|
||||
use super::ToUInt;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SignedModulus {
|
||||
field_modulus: BigInt,
|
||||
max_remainder_bits: usize,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#[cfg(feature = "linkedproofs")]
|
||||
mod bfv_plaintext;
|
||||
mod field;
|
||||
mod gadgets;
|
||||
mod program_node;
|
||||
mod rns_polynomial;
|
||||
|
||||
#[cfg(feature = "linkedproofs")]
|
||||
pub use bfv_plaintext::*;
|
||||
pub use field::*;
|
||||
// N.B. `NodeIndex` is actually common to both FHE and ZKP, but it's really only leaked as an
|
||||
// implementation detail on the ZKP side (via gadgets). So I think it makes sense to export under
|
||||
// sunscreen::types::zkp.
|
||||
pub use petgraph::stable_graph::NodeIndex;
|
||||
pub use program_node::*;
|
||||
pub use rns_polynomial::*;
|
||||
|
||||
@@ -6,7 +6,7 @@ mod linked_tests {
|
||||
use logproof::rings::ZqSeal128_1024;
|
||||
use logproof::test::seal_bfv_encryption_linear_relation;
|
||||
use sunscreen::types::bfv::{Signed, Unsigned, Unsigned64};
|
||||
use sunscreen::types::zkp::{BulletproofsField, Mod};
|
||||
use sunscreen::types::zkp::{AsFieldElement, BfvSigned, BulletproofsField, Mod};
|
||||
use sunscreen::{
|
||||
types::zkp::{ConstrainCmp, Field, FieldSpec, ProgramNode},
|
||||
zkp_program, zkp_var, Compiler,
|
||||
@@ -26,61 +26,21 @@ mod linked_tests {
|
||||
};
|
||||
}
|
||||
|
||||
/// Convert a twos complement represented signed integer into a field element.
|
||||
fn from_twos_complement_field_element<F: FieldSpec>(
|
||||
x: &[ProgramNode<Field<F>>],
|
||||
) -> ProgramNode<Field<F>> {
|
||||
let mut x_recon = zkp_var!(0);
|
||||
let n = x.len();
|
||||
|
||||
for (i, x_i) in x.iter().enumerate().take(n - 1) {
|
||||
x_recon = x_recon + (zkp_var!(sunscreen_zkp_backend::BigInt::ONE << i) * (*x_i));
|
||||
}
|
||||
|
||||
x_recon = x_recon - zkp_var!(sunscreen_zkp_backend::BigInt::ONE << (n - 1)) * x[n - 1];
|
||||
|
||||
x_recon
|
||||
}
|
||||
|
||||
// Convert a coeff into native big int
|
||||
// TODO package this up for shared types
|
||||
fn from_signed_encoding<F: FieldSpec>(x: &[ProgramNode<Field<F>>]) -> ProgramNode<Field<F>> {
|
||||
let mut x_recon = zkp_var!(0);
|
||||
let n = x.len();
|
||||
let plain_modulus = zkp_var!(4096);
|
||||
|
||||
for (i, x_i) in x.iter().enumerate() {
|
||||
// TODO this is not correct. 4095 (equiv to -1 in plaintext context) just reduces to
|
||||
// native field element 4095. We need the _reverse_ of the signed_reduce function.
|
||||
// Hence, this is currently broken for negative numbers.
|
||||
let c = Field::signed_reduce(*x_i, plain_modulus, 15);
|
||||
x_recon = x_recon + (zkp_var!(sunscreen_zkp_backend::BigInt::ONE << i) * c);
|
||||
}
|
||||
|
||||
x_recon
|
||||
}
|
||||
|
||||
#[zkp_program]
|
||||
fn valid_transaction<F: FieldSpec>(
|
||||
#[private] x: [Field<F>; 832],
|
||||
// #[private] x: [Field<F>; 1664],
|
||||
#[private] tx: BfvSigned<F, 13>,
|
||||
#[public] balance: Field<F>,
|
||||
) {
|
||||
let lower_bound = zkp_var!(0);
|
||||
|
||||
// Reconstruct x from the bag of bits
|
||||
let plain_modulus_log_2 = 4096u64.ilog2() as usize + 1;
|
||||
let coeffs = x
|
||||
.chunks(plain_modulus_log_2)
|
||||
.map(|c| from_twos_complement_field_element(c))
|
||||
.collect::<Vec<_>>();
|
||||
let x_recon = from_signed_encoding(&coeffs);
|
||||
// Reconstruct tx
|
||||
let tx_recon = tx.into_field_elem();
|
||||
|
||||
// Constraint that x is less than or equal to balance
|
||||
balance.constrain_ge_bounded(x_recon, 64);
|
||||
balance.constrain_ge_bounded(tx_recon, 64);
|
||||
|
||||
// Constraint that x is greater than or equal to zero
|
||||
lower_bound.constrain_le_bounded(x_recon, 64);
|
||||
lower_bound.constrain_le_bounded(tx_recon, 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -145,24 +105,15 @@ mod linked_tests {
|
||||
}
|
||||
|
||||
#[zkp_program]
|
||||
fn is_eq<F: FieldSpec>(#[private] x: [Field<F>; 832], #[public] y: Field<F>) {
|
||||
// Reconstruct x from the bag of bits
|
||||
let plain_modulus_log_2 = 4096u64.ilog2() as usize + 1;
|
||||
let coeffs = x
|
||||
.chunks(plain_modulus_log_2)
|
||||
.map(|c| from_twos_complement_field_element(c))
|
||||
.collect::<Vec<_>>();
|
||||
let x_recon = from_signed_encoding(&coeffs);
|
||||
|
||||
// Constraint that x is less than or equal to balance
|
||||
x_recon.constrain_eq(y);
|
||||
fn is_eq<F: FieldSpec>(#[private] x: BfvSigned<F, 13>, #[public] y: Field<F>) {
|
||||
x.into_field_elem().constrain_eq(y);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_eq() {
|
||||
let rt = FheZkpRuntime::new(&SMALL_PARAMS, &BulletproofsBackend::new()).unwrap();
|
||||
let (public_key, _secret_key) = rt.generate_keys().unwrap();
|
||||
let is_eq_zkp = valid_transaction.compile::<BulletproofsBackend>().unwrap();
|
||||
let is_eq_zkp = is_eq.compile::<BulletproofsBackend>().unwrap();
|
||||
|
||||
for val in [3, -3] {
|
||||
let mut proof_builder = LogProofBuilder::new(&rt);
|
||||
|
||||
Reference in New Issue
Block a user