Coder Social home page Coder Social logo

sunscreen-tech / sunscreen Goto Github PK

View Code? Open in Web Editor NEW
223.0 6.0 26.0 111.57 MB

A compiler for fully homomorphic encryption and zero knowledge proofs

Home Page: https://docs.sunscreen.tech

License: GNU Affero General Public License v3.0

C 3.39% Rust 89.12% HTML 1.54% Shell 0.01% Metal 2.17% Python 0.10% Cuda 2.09% Common Lisp 0.01% WGSL 1.34% TeX 0.24%
cryptography homomorphic-encryption zero-knowledge

sunscreen's Introduction

Rust

Intro

Sunscreen is an ecosystem for building privacy-preserving applications using fully homomorphic encryption (and later on zero-knowledge proofs as well). Fully homomorphic encryption (FHE) is a special kind of encryption scheme that allows anyone to perform computations directly on encrypted data. Since it's quite hard to write FHE programs, we've created a "compiler" to make this process easier for developers.

If you'd like to try out our FHE compiler before downloading it, we offer a playground.

Extensive documentation can be found here.

WARNING! This library is meant for experiments only. It has not been externally audited and is not intended for use in production.

Set up

These directions cover the requirements for developing the sunscreen platform itself, which may be more than needed to merely consume it as a dependency. If you wish to develop an application using Sunscreen, see the installation.

Install Rust

Install Rustup and follow the directions for your OS. We recommend stable Rust 1.58 or later.

MacOS

brew install cmake git

Linux

Install prereqs

Using yum: sudo yum install gcc gcc-c++ cmake3 openssl-devel clang git

Using apt: sudo apt install build-essential clang cmake3 libssl-dev git

After installing prereqs, make a link to cmake3 named cmake sudo ln /usr/bin/cmake3 <somwhere/under/$PATH/>cmake

Windows

We recommend developing sunscreen on macOS or Linux, as Windows is really slow.

Cmake

Install cmake 3.

Clang

Install llvm+clang. In the installer, select the option to add LLVM to your %PATH%. If you forget to do check this option, add C:\Program Files\LLVM\bin to your %PATH%.

MSVC C++

Install the MSVC C++ toolchain

When prompted for what to install, ensure you additionally check the Windows 10 SDK. You'll need to rerun the tool and modify your installation if you forget to do this.

Enable long file paths

Some of our compilation tests produce really long file paths. These tests will fail unless you enable long file paths. TL;DR; run regedit.exe, set HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem to 1 and reboot.

git

Install git. Defaults are fine.

Initialize submodules

git submodule update --init --recursive

Dev workflows

Working with Sunscreen is as you'd expect with any other Rust repository:

Build

cargo build

Test

cargo test --release

Docs

cargo doc --open

Format

cargo fmt

Debug

We have a launch.json checked in that defines a bunch of debug configurations. In VSCode, you should see a bunch of dropdowns in the debug menu to debug the tests and examples.

License

This project is licensed under the terms of the GNU AGPLv3 license. If you require a different license for your application, please reach out to us.

Contribute

Feel free to open up issues!

If you'd like to submit a pull request, you'll need to agree to a Contributor License Agreement. For more info, contact us at [email protected].

sunscreen's People

Contributors

bryanyli avatar dev43 avatar josesk999 avatar matthew-liu801 avatar oneeman avatar ravital avatar rickwebiii avatar ryanorendorff avatar samtay avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

sunscreen's Issues

Support trivial encryptions

We can make valid ciphertexts where the sampled $u, e_1, e_2$ are all set to zero. This would allow for fhe_var! created variables to be legitimate Cipher nodes while still being deterministic, and allow us to remove the Indeterminate, Stage, etc. types from crate::types::intern::fhe_program_node (added in #272).

It looks like this would require factoring seal::util::rlwe::encrypt_zero_asymmetric to support accepting these values, rather than always generating them via parms.random_generator()->create();.

Additionally, we should ensure that SEAL_THROW_ON_TRANSPARENT_CIPHERTEXT is set to false, since there are a number of checks to ensure that these values are not zero. E.g. here.

However, it would be good to expose that is_transparent() method and call it in the Cipher::output implementation as a safeguard for the user not to return transparent ciphertexts from an FHE program.

FHE Proof: Separate Client and Server

I was wondering if it were possible to implement the FHE proof in a client-server architecture instead of keeping everything in the same file. I was wondering since all examples use a single-file structure to split the FHE between a Client and Server file how would that work?

I'm asking since I noticed that for FHE both the Client and Server objects (or Bob and Alice depending on the example), the Client has to access the Server object (since we need the Server to create the Client) and vice-versa.

Is that something that cannot be done in the current state of the compiler or am I just approaching this wrongly?

SealError on simple matrix multiplication

Hey all,

Love the library and have been playing around a bit with it trying to get simple FHE matrix multiplication up and running. As a disclaimer, I am by no means an FHE expert.

In my setup, I have two square (DIM x DIM) matrices A and B which I am attempting to multiply (A x B). Matrices in this instance are represented as 2-d arrays of (encrypted) Sunscreen types (Signed, Fractional etc...). When both A and B are encrypted my toy setup works just fine. When B is not encrypted the Sunscreen runtime throws :

Error: RuntimeError(FheProgramRunError(SealError))

I've been trying to find the source of this error and it seems to occur when assigning to the individual indices of the multiplication result (res[row][col]). Let me know if you are able to replicate it with the code below:

use std::time::Instant;
use std::vec::Vec;
use sunscreen::{
    fhe_program,
    types::{bfv::Signed, Cipher},
    Compiler, FheProgramInput, Runtime,
};

const DIM: usize = 2;

/**
 * Naive matrix multiplication
 */
#[fhe_program(scheme = "bfv")]
fn mat_mul_naive(
    a: [[Cipher<Signed>; DIM]; DIM],
    b: [[Signed; DIM]; DIM],
) -> [[Cipher<Signed>; DIM]; DIM] {
    let mut res = a.clone();

    for row in 0..DIM {
        for col in 0..DIM {
            for v in 0..DIM {
                if col == 0 {
                    res[row][col] = a[row][v] * b[v][col];
                } else {
                    res[row][col] = res[row][col] + a[row][v] * b[v][col];
                }
            }
        }
    }
    res
}

/**
 * Creates a matrix and returns it represented as a
 * [`Signed`] type.
 */
fn make_matrix_signed<const M: usize>() -> Result<([[Signed; M]; M]), sunscreen::Error> {
    let mut f_array = [[Signed::default(); M]; M];

    for i in 0..M {
        f_array[i] = (0..M as i64)
            .map(|x| Signed::try_from(x).unwrap())
            .collect::<Vec<Signed>>()
            .try_into()
            .unwrap_or_else(|v: Vec<Signed>| {
                panic!("Expected a Vec of length {} but it was {}", M, v.len())
            });
    }

    Ok(f_array)
}

fn main() -> Result<(), sunscreen::Error> {
    let a_frac = make_matrix_signed::<DIM>()?;

    let b_frac = make_matrix_signed::<DIM>()?;

    let start = Instant::now();

    let app = Compiler::new().fhe_program(mat_mul_naive).compile()?;
    let end = start.elapsed();

    println!("Compiled in {}s", end.as_secs_f64());

    let runtime = Runtime::new(app.params())?;

    let (public_key, private_key) = runtime.generate_keys()?;
    let a_enc = runtime.encrypt(a_frac, &public_key)?;

    let args: Vec<FheProgramInput> = vec![a_enc.clone().into(), b_frac.clone().into()];

    let start = Instant::now();
    let results = runtime.run(app.get_program(mat_mul_naive).unwrap(), args, &public_key)?;
    let end = start.elapsed();

    let fhe_mul: [[Signed; DIM]; DIM] = runtime.decrypt(&results[0], &private_key)?;

    println!("FHE mat mul = {:?} Time: {}s", fhe_mul, end.as_secs_f64());

    Ok(())
}

License clarification

Thanks for all your effort on sunscreen. I came across this via the ZK podcast (Anna Rose, et. al.)

There appears to be some ambiguity/confusion, perhaps unless you are a lawyer, around the effect of having a downstream project include a AGPL library, such as the sunscreen AGPL licensed crate.

However, some certain there is no ambiguity:

Using AGPL software requires that anything it links to must also be licensed under the AGPL.

I'm more familiar with the Rust convention of having dual Apache 2/MIT licenses.

If possible could you clarify what the AGPL is intended to prevent, which the dual Apache 2/MIT licenses would allow?

One possibility is to add to each example in the repo the license you say would be permitted if that example were used as described?

In case none of the examples would have to be AGPL, could you give some detail on what use would require a project be AGPL when it uses the sunscreen crate?

Performance: Noise budget

The following may not be appropriate given the approach you are taken to parameter generation.

If it is possible, it would be informative to know something like the "noise budget" for each of the approaches you compare in the performance table here.

If notions of a noise budget are not appropriate, it would be useful to know some detail for that conclusion.

Sunscreen example comments

Sunscreen comments

Most of the comments were written as going through the example library (not after). Some may or may not make sense so feel free to ignore those.

  • Having multiple error types bubble up to the user is confusing.
    • I'm unable to write a program with 1 Result return type and use try! or ? due to the fact that there are so many different Result and different Error types.
  • unwrap is over-used.
    • The try! macro or the ? operator when returning a Result should be used where possible and makes sense. Example: https://gist.github.com/d-haxton/7daf68b461a56ac4f842c5b3f8e0b368
    • If unwrap is still required or needed then would recommend using expect. unwrap is generally used where you know an error is never going to happen, which I don't believe the case is here.
  • Compiler could just return a Runtime. Due to the fact that the RuntimeBuilder accepts only 1 argument which is a Params from Compiler it could be easily returned in the tuple from compile() as well.
  • Consider Rc or Box over passing everything by reference.
    • For example, we may want our PublicKey and SecretKey to exist on the heap rather than the stack.
  • Rename validate_and_run_program or run_program_unchecked to have a more consistent naming convention.
    • i.e., run_program_checked and run_program_unchecked
  • Consider using a custom Serde serializer/deserializer instead of rolling your own in encoder.
  • Does it make sense to have the Runtime to be able to encrypt with respect to different encoding types?
    • If not, then in RuntimeBuilder take in the encoder as a part of the params.
      With Serde then you should be able to encode the value based off of the type passed into encrypt and no longer have to expose the encoder or any specific encoder methods.
  • Why does RuntimeBuilder require you to new it first (where it just clones the params) and then asks you to build? Seems like you shouldn't ever been a new instance of RuntimeBuilder?
  • Runtime as an enum strikes me as an odd design decision. Haven't look too much time into it so feel free to ignore this one if it doesn't make sense.
    • It seems unnecessary to have Runtime generate the keys for us but still require you to pass them back in to every encrypt call.
    • Traits seem like the perfect solution to having multiple implementations for the different type of Runtimes for the different schemes.
    • Runtime implies that there will be some persistent state, however that's not the case and they're effectively all just static methods patterning matching on the enum.
    • It's really unfortunate that Runtime doesn't maintain a copy of any of the keys it generates as all the methods become increasingly more complex.
  • Consider leveraging the must_use attribute where ignoring a Result could lead to a security risk.
  • validate_and_run_program is a prime case of refactoring into a macro.
    • $circuit:ident, $runtime:ident, $( $exp:expr ),* is an example macro_rule where it will let you take in an arbitrary amount of expressions.
  • In an ideal world, this is how I would like to be able to write code (I don't write crypto)
fn main() -> Result<()> {
    let runtime = Compiler::with_circuit(simple_multiply)
        .plain_modulus_constraint(PlainModulusConstraint::Raw(600))
        .noise_margin_bits(5)
        .encoder(BFVScalarEncoder::new())
        .relin(true)
        .compile()?;

    let a: u32 = 15;
    let b: u32 = 5;

    let results = run_program!(simple_multiply, runtime, a, b)?;

    assert_eq!(1, results.len());

    let c: u32 = runtime.decrypt_and_decode(results[0]);

    assert_eq!(c, 75);

    Ok(())
}

A few comments on the above:

  • The design philosophy should be: hard to start, easy to use. It's ok to make things a little more difficult for configuring and setting up with the trade-off being that once I've configured everything it's very easy for me to use.
  • Taking in the encoder and whether we want to relineralize immediately so that you can't swap different encoders. (Once again, I don't write crypto, but I would imagine that opinionated crypto is good crypto).
  • Completely hide the encoding from the user outside of initialization. Unless there's a good reason for the user to be aware and use the encoded data don't make them aware of it. Also if the happy path is above but there exists a case where the user should be aware of the encoder then I would say optimize for the happy path but still support the other cases as needed (don't over support them).
  • I don't need circuit, params, public/private key, encoder, or circuit. I was primarily just using them for the library to pass them around. (With the exception of encoder which we just talked about). Don't force the user to use them unless they need to be.
  • The macro run_program! takes in the circuit name as an identifier (which you should match to the corresponding built circuit), the runtime, and then at least one expression.
  • The runtime.decrypt_and_decode could be cleaner, but I don't have any immediate ideas there.

Type for Plaintext_Create4 incorrect on Debian ARM

When attempting to build sunscreen within a debian ARM docker image, the following error occurs:

#0 602.2 error[E0308]: mismatched types
#0 602.2     --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/seal_fhe-0.8.1/src/plaintext_ciphertext.rs:207:40
#0 602.2      |
#0 602.2 207  |             bindgen::Plaintext_Create4(hex_string.as_ptr() as *mut i8, null_mut(), &mut handle)
#0 602.2      |             -------------------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `*mut u8`, found `*mut i8`
#0 602.2      |             |
#0 602.2      |             arguments to this function are incorrect
#0 602.2      |
#0 602.2      = note: expected raw pointer `*mut u8`
#0 602.2                 found raw pointer `*mut i8`
#0 602.2 note: function defined here
#0 602.2     --> /tmp/cargo-installSvX1mt/local/build/seal_fhe-da7ac6757a92e7e3/out/bindings.rs:1421:12
#0 602.2      |
#0 602.2 1421 |     pub fn Plaintext_Create4(
#0 602.2      |            ^^^^^^^^^^^^^^^^^
#0 602.2
#0 602.2 For more information about this error, try `rustc --explain E0308`.

From the definition of Plaintext_Create4, u8 does appear to be the correct type.

Suggested fix: convert i8 to u8. That's it! :)

Signed values panic when decoding results in overflow (debug mode).

I ran into this when decoding ciphertexts that exceeded noise budget. We now return an error when decryption errors for this reason.

In release mode, you just get garbage.

We should probably move the internal storage type for Signed/Unsigned to BigInt to eliminate overflow and impl TryFrom to convert to i64/u64.

Sending an array of CipherTexts should allow sending [encrypt(1), encrypt(2)] rather than encrypt([1,2])

pub enum FheProgramInput {
/**
* The argument is a ciphertext.
*/
Ciphertext(Ciphertext),

/**
 * The argument is a plaintext.
 */
Plaintext(Box<dyn FheProgramInputTrait>),

}

The issue is this enum here. We need to change Ciphertext(Ciphertext) to something like Ciphertext(Box). We can then impl this trait for Ciphertext and [Ciphertext; N] and I think we'll be in business. Open an issue for this.

Impl arithmetic ops for Fields

Tracking an issue I faced during the ethglobal nyc hackathon. During the hackathon, I wanted to do things like implement counters using field elements. Doing so looked something like the following:

let counters_list = [BulletproofsField::from(0); 100];
for (i, f) in counters_list.into_iter().enumerate() {
    if i == alices_index {
        counters_list[i] = f + BulletproofsField::from(BigInt::ONE);
    }
}

Currently, this is not possible because Add is not implemented for Field. I recommend Add be implemented, as well as all the other arithmetic methods. This would be a convenient change for devs, because the current state is that they would need to fork the repo to implement themselves.

Inaccessible page: Noise Margin

The deployed version of docs.sunscreen.tech on page https://docs.sunscreen.tech/fhe_programs/writing_an_fhe_program/limitations.html links to
https://docs.sunscreen.tech/fhe_programs/writing_an_fhe_program/advanced/noise_margin.html under "Bounded Computation".

Publically, this translates to the following view on webpage:

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>WS4B9GEZTBEF4BPF</RequestId>
<HostId>f9+abKGPtQPCSw/hLVsjYM2GbmBGtX8ZO1hDeNwU6v4ELd4OiIGwyTXKK83jPpLNzfqPgKVdsY0=</HostId>
</Error>

Breaking out seal_fhe: repo and license

Thanks for the effort put into the SEAL bindings.

Would you be willing to make the SEAL bindings crate available under the typical Rust dual license of MIT or Apache 2?
If so would you accept a PR making the seal_fhe folder a sub-repo?

relinearize_inplace doesn't reduce noise

I have written the following code using seal_fhe = "0.8.1":

use seal_fhe::{BFVEncoder, BFVEvaluator, CoefficientModulus, Context, Decryptor, Encryptor,
               Evaluator, KeyGenerator, PlainModulus, SecurityLevel};

#[test]
fn test_relin() -> Result<(),seal_fhe::Error> {
    // Create FHE handles
    let poly_modulus_degree = 4096;
    let bit_size = 16;
    let modx = CoefficientModulus::bfv_default(poly_modulus_degree, SecurityLevel::TC128)?;
    let modp = PlainModulus::batching(poly_modulus_degree, bit_size + 1)?;
    let v = seal_fhe::BfvEncryptionParametersBuilder::new()
        .set_poly_modulus_degree(poly_modulus_degree)
        .set_coefficient_modulus(modx)
        .set_plain_modulus(modp)
        .build()?;
    let context = Context::new(&v, false, SecurityLevel::TC128)?;
    let encoder = BFVEncoder::new(&context)?;
    let evaluator = BFVEvaluator::new(&context)?;
    let key_generator = KeyGenerator::new(&context)?;
    let public_key = key_generator.create_public_key();
    let secret_key = key_generator.secret_key();
    let relin_keys = key_generator.create_relinearization_keys()?;
    let encryptor = Encryptor::with_public_and_secret_key(&context, &public_key, &secret_key)?;
    let decryptor = Decryptor::new(&context, &secret_key)?;


    // Start computation
    let start_val = 100;
    let encoded = encoder.encode_unsigned(&[start_val])?;
    let mut ciphered = encryptor.encrypt(&encoded)?;
    let val_two = encoder.encode_unsigned(&[2])?;
    println!("Initial noise budget {}", decryptor.invariant_noise_budget(&ciphered)?);
    for i in 0..5 {
        evaluator.multiply_plain_inplace(&mut ciphered, &val_two)?;
        evaluator.relinearize_inplace(&mut ciphered, &relin_keys)?;
        println!("Noise budget after {} iterations {}",i+1, decryptor.invariant_noise_budget(&ciphered)?);
    }
    Ok(())
}

When i run it I have the following output:

Initial noise budget 48
Noise budget after 1 iterations 27
Noise budget after 2 iterations 5
Noise budget after 3 iterations 0
Noise budget after 4 iterations 0
Noise budget after 5 iterations 0

But since I use relinearize_inplace I didn't expect the noise budget to ever reach 0. I'm new to this library and I don't know if I am doing something wrong.

Set up WASM CI

WASM build is currently broken. Set up a CI so it doesn't break in the future.

Do we support non-uniform threadgroups on Metal?

We should either support non-uniform threadgroups or fail fast if non-uniform aren't supported. These have been around for a while so I don't think we lose much by not supporting them. To support them, you simply need to check that the current thread id in each kernel doesn't exceed the length of the vector. There's a technique called ubershaders that lets us use feature detection and work with both methods, but it's annoying. See here.

Sunscreen + ink! = zink!: License requirements

This does not negate the existing issue on this topic, #311, but it does focus it.

I believe your Rust macro approach to FHE parameter generation is the correct approach in terms of being readily extensible.

One extension, of likely several, is to utilize/integrate sunscreen with ink! - "... a programming language for smart contracts".

If this were to be done. What is the license that zink! would have to be issued under?

  • Whatever it likes, my preference is dual MIT and Apache 2.0
  • AGPL

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.