Comments (6)
Thanks for the feedback @d-haxton. I'll file issues for some of these items.
I had written up a bunch of stuff, but I see you example is more illustrative than anything else.
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(())
}
I do really like the run_circuit!
macro and I think we should incorporate that. I know we have a bunch of work to do around types and encoders, so this is far from finalized.
I think a failure here is that our example has the same person doing everything, which is entirely unrepresentative of what someone would actually do. I'll try to write an example with the things Alice and Bob would each do, which I think would better explain the separation. A more realistic use case is:
- Bob compiles a circuit and serializes the params and an interface for his circuit (this does not exist today). Once Bob has done this, he can just write the circuit and params to disk and deserialize it later.
- Alice asks Bob for his circuit interface and params. Alice creates a public/private key, possibly relin+galois keys depending on the circuit requirements. Alice encodes and encrypts her data, gives her data, public key, relin+galois keys to Bob.
- Bob takes Alice's Ciphertexts and keys and runs the cached circuit. He then serializes the result ciphertexts and returns them to Alice. Bob has no guarantees Alice sent him the right types, as they're encrypted.
- Alice decrypts the result and then decodes it.
There are other scenarios with multi-party computation and such.
A couple of other points:
- Circuit arguments and return values may be heterogenous, so having a single encoder is too limiting.
- We want the types for a circuit to be flexible. What we have right now isn't representative of the final product. Users will be able to add new types, e.g. Matrix, Rational, etc.
- Encoders are a concept in SEAL that I think are probably going to go away. The primitives users will have are FHE types that will probably
impl
some traits and get lifted into aGraphMarker<T: BfvType>
during circuit construction. These types will feature methods for converting to and from Plaintext types. - A non-FHE type (e.g.
f64
,u64
,Vec<T>
etc) may have more than one encoding even within a single encryption scheme. - A single FHE type may have multiple encode/decode methods. For example, a Rational might take an
f64
or integral numerator and denominator. - Leveraging the type system to cause compile errors and hint to Bob what he needs to do is preferable to runtime errors. For example,
fn run_sum(a: &Ciphertext, b: &Ciphertext, public_key: &PublicKey) -> Ciphertext {
// Bob has already compiled the circuit and stored the circuit and params somewhere
let sum_runtime = RuntimeBuilder::new(&SUM_PARAMS)
.with_public_key(&public_key)
.build();
run_circuit!(sum_runtime, a, b)
}
versus
fn run_sum(a: &Ciphertext, b: &Ciphertext, public_key: &PublicKey) -> Ciphertext {
// Bob has already compiled the circuit and stored the circuit and params somewhere
let sum_runtime = RuntimeBuilder::new(&SUM_PARAMS)
.build();
run_circuit!(sum_runtime, public_key, a, b)
}
In the former API, Bob doesn't know until runtime that he remembered to pass the public_key
. In the latter case, the run_circuit macro requires a public_key as the second argument and will give a compilation error if you forget.
from sunscreen.
Oh yes, the Alice and Bob examples would probably really help. I'll probably follow up with some more thoughts later, I'm still not super clear on everything, but the realistic use case makes a lot of sense and seeing more code around that would definitely help.
One question though. Have you thought at all about how Alice and Bob are going to be sharing the circuit definitions? At a cursory glance it may make sense to use something like protobufs, but that would be going away from your nice circuit
macro. Nothing immediate on your end, but I could see it being nice for Bob to publish a protobuf file publicly so that Alices knows how to interact with him and don't all need to re-write the circuit in rust every time (which might mean less buggy code).
from sunscreen.
TODOs that came out of this and feedback from others:
- Remove encoders and just let user encrypt/decrypt FHETypes
- Annotate all FHETypes with TypeName
- Have
circuit!
macro generate interface annotating the type arguments and return values for a circuit and which keys are needed. - Compiler should return object with circuit interfaces
- Create
TryIntoPlaintexts/TryFromPlaintexts
to turn FHETypes into Vec<Plaintext>. - Create
derive(TryIntoPlaintexts/TryFromPlaintexts)
macro that FHETypes can use. - Create
CircuitBundle<T: TryIntoPlaintexts>
struct, which contains Galois, Relin keys, - Runtime should have a call method that accepts `T:
- Compiler should have method to codegen struct for circuit interface
CircuitNameArgs
andCircuitNameReturn
. - Runtime should feature
bundle<T: TryIntoPlaintexts>(x: T) -> CircuitBundle<T>
method to package arguments to send to Bob.
from sunscreen.
@ravital if @d-haxton wants to have another look at what we've done since last time, we might be able to close this.
from sunscreen.
Sorry about the lack of communication! Haven't used github in a while and don't have notifications set up.
I took another look at the calculator you have and it definitely reads a lot better. There's a TON of good improvements here and overall I'd feel pretty comfortable using this. Some comments though, feel free to ignore throw out whatever doesn't make sense.
-
[question] simply_multiply required seal as a dependency in your Cargo.toml but calculator does not?
-
[question] forced type coherence somewhere?
1 + 2.1234567890123456789
3.1234567890123457 -
[nit] you could have one channel where T type is something like:
enum Message { AlicePublicKey(PublicKey), AliceCalc(ParseResult), BobParams(Params), BobResult(Ciphertext), }
But that might complicate Alice and Bob's code a bit so /shrug
-
compile_circuits
is pretty harsh to read, there's a lot you're forcing users to repeat.-
Realistically passing in
&add_circuit.metadata.params
to everywith_params
just feels like code smell and a risk to introduce bugs. -
// In order for ciphertexts to be compatible between circuits, they must all use the same // parameters.
This is a BIG comment and we should be enforcing this more in the code/type system rather than in a comment like this.
Would love to hear your thoughts on this though. Perhaps just have
CompiledCircuit
have a generic type which is derived from the parameters, but this is probably my biggest concern right now.
-
-
[nit] destructure!
let calc = recv_calc.recv().unwrap();
->let ParseResult { left, right, op} = recv_calc.recv().unwrap();
- Then your matches are just
match op
/match left
- Then your matches are just
-
There's a lot happening here so let's try to break it down
-
let mut c = match calc.op { Operand::Add => runtime.run(&add, vec![left, right], &public_key).unwrap(), Operand::Sub => runtime.run(&sub, vec![left, right], &public_key).unwrap(), Operand::Mul => runtime.run(&mul, vec![left, right], &public_key).unwrap(), Operand::Div => runtime.run(&div, vec![left, right], &public_key).unwrap(), }; // Our circuit produces a single value, so move the value out of the vector. let c = c.drain(0..).next().unwrap(); ans = c.clone(); send_res.send(c).unwrap();
c
is redefined and complicates readingc
as a variable name is not ideal in either context.ans
continues to complicate and does not make reading intuitive. I understand it's nice to have as a product, but if this is the example you want to give to people maybe not? The looping in conjunction with reassigningans
to be able to use that as a value. Yes it's nice to see that Alice can just say "use the last result" and that she doesn't need to send it, but can't she just send the Ciphertext and that has the same result?- Is Bob caching the result going to be a common use case?
run
as a term doesn't convey that anything is being returned. considereval
orexec
as both these have fairly strong connotations already.- Unless we expect
c
(the Vec) to be refilled why do we want to use the drain API? I could understand ifc
was a Boxed reference that we Alice passed earlier for results, but feels a bit overkill otherwise.
-
[nit] having string parsing in
alice()
is a bit rough, can we refactor the rest of this out intoparse_input
or another method? -
result is defined 3 times here.
let result: Ciphertext = recv_res.recv().unwrap(); let result: Rational = match runtime.decrypt(&result, &secret) { Ok(v) => v, Err(RuntimeError::TooMuchNoise) => { println!("Decryption failed: too much noise"); continue; } Err(e) => panic!("{:#?}", e), }; let result: f64 = result.into(); println!("{}", result);
Also I'm not clear what to do if I get the too much noise error. Do we give up on that circuit? Are we done / should we panic? Is it recoverable or did that operation just fail?
from sunscreen.
[question] simply_multiply required seal as a dependency in your Cargo.toml but calculator does not?
Good catch. It used to, but no longer does since we now have our own Ciphertext
type.
[question] forced type coherence somewhere?
User input and results parse and roundtrip through an f64.
f64 which has 52 bits of mantissa (and generally looks like 1.m^(1023-exp)). The integer part of the answer is 3, which is 11 in binary. To normalize the result, we lose a bit of mantissa as the 1s place moves down into the mantissa. 2^-51 = 4.4408920985e-16. I count 16 places after 3, so the answer (at first glance at least) is within floating point roundoff.
[nit] you could have one channel where T type is something like...
I think you'd need a bunch of unwrap_variant1
, unwrap_variant2
, etc methods so the example doesn't get bogged down in if let or match statements. In a REST service, the communication would be to different endpoints with their own contracts, so I think using multiple channels kinda captures the spirit of that.
compile_circuits is pretty harsh to read, there's a lot you're forcing users to repeat.
No argument there. @ravital and I have talked about "how do I compile a collection of circuits to guarantee their schemes are compatible." Our tentative proposal is something like:
Compiler::new()
.with_circuit(add)
.with_circuit(sub)
.with_circuit(mul)
.with_circuit(div)
.noise_margin_bits(32)
.plain_modulus_constraint(PlainModulusConstraint::Raw(1_000_000))
.compile()
.unwrap();
and the compiler will run a parameter search that satisfies the noise margin for all circuits. What exists today highlights that you cannot currently do this.
[nit] destructure!
I'm currently forget to destructuring structs; I only remember to do it for tuples.
There's a lot happening here so let's try to break it down
See question below on shadowing.
The rationale behind Bob storing the ciphertext is to show that you can feed ciphertexts that come out of one circuit into another without Alice decrypting the result (so long as the params used in compilation are identical). This will be an important scenario in some applications.
The drain
is so we can move the Ciphertext out of the Vec without a clone before we sent it back to Alice. This saves copying maybe 100kb of data, but I think just cloning again is probably cleaner. We do still need to clone it once for ans.
result is defined 3 times here.
Are there any guidelines on when and when not to use shadowing in production Rust? I personally like using shadowing for successive processing of a thing. As per the Rust book:
Shadowing thus spares us from having to come up with different names, such as spaces_str and spaces_num; instead, we can reuse the simpler spaces name.
Admittedly, this is a Rustism and might be confusing to people coming from languages where this is illegal (i.e. most of them).
from sunscreen.
Related Issues (20)
- Set up macOS + Metal CI
- Set up WASM CI HOT 1
- Do we support non-uniform threadgroups on Metal?
- Support proofs with mixed bounds HOT 1
- Improve SDLP verification times
- Remove special modulus from SEAL HOT 1
- Make bulletproofs for comparing messages in ciphertexts
- wrap the 'seal/util' api in seal_fhe HOT 1
- Inaccessible page: Noise Margin HOT 4
- Sending an array of CipherTexts should allow sending [encrypt(1), encrypt(2)] rather than encrypt([1,2])
- Support trivial encryptions
- CKKS and BGV support from SEAL? HOT 2
- License clarification
- Type for Plaintext_Create4 incorrect on Debian ARM HOT 1
- Performance: Noise budget
- Sunscreen + ink! = zink!: License requirements
- Impl arithmetic ops for Fields HOT 1
- FHE Proof: Separate Client and Server HOT 2
- `sunscreen::Error` does not implement `std::error:Error` HOT 1
- Breaking out seal_fhe: repo and license HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from sunscreen.