Skip to content

Hackathon; or, various compiler improvements#272

Merged
samtay merged 42 commits into
mainfrom
samtay/hackathon
Jul 5, 2023
Merged

Hackathon; or, various compiler improvements#272
samtay merged 42 commits into
mainfrom
samtay/hackathon

Conversation

@samtay

@samtay samtay commented Jun 30, 2023

Copy link
Copy Markdown
Contributor

Improvements

Disallow mut

Previously this was allowed, nothing would go wrong, but it could be misleading for the end user, as FHE programs cannot mutate their inputs.

error: ZKP program arguments cannot be `mut`
  --> examples/sudoku_zkp/src/main.rs:56:5
   |
56 |     mut board: [[NativeField<F>; 9]; 9],
   |     ^^^

Disallow generics

Prior, you'd get some funky error messages, with nonsensical recommendations:

error[E0412]: cannot find type `T` in this scope
  --> examples/simple_multiply/src/main.rs:24:33
   |
24 | fn simple_multiply<T>(a: Cipher<T>, b: Cipher<Signed>) -> Cipher<Signed> {
   |                                 ^ not found in this scope

error[E0412]: cannot find type `T` in this scope
  --> examples/simple_multiply/src/main.rs:24:33
   |
24 | fn simple_multiply<T>(a: Cipher<T>, b: Cipher<Signed>) -> Cipher<Signed> {
   |                                 ^ not found in this scope
   |
help: you might be missing a type parameter
   |
24 | fn simple_multiply<T>(a: Cipher<T><T>, b: Cipher<Signed>) -> Cipher<Signed> {
   |                                +++

error[E0412]: cannot find type `T` in this scope
  --> examples/simple_multiply/src/main.rs:24:33
   |
23 | #[fhe_program(scheme = "bfv")]
   | ------------------------------ similarly named type alias `T0` defined here
24 | fn simple_multiply<T>(a: Cipher<T>, b: Cipher<Signed>) -> Cipher<Signed> {
   |                                 ^ help: a type alias with a similar name exists: `T0`

Now you get the issue up front:

error: FHE programs do not support generics.
  --> examples/simple_multiply/src/main.rs:24:20
   |
24 | fn simple_multiply<T>(a: Cipher<Signed>, b: Cipher<Signed>) -> Cipher<Signed> {
   |                    ^

Fix unrelated macro error from popping up

error: macros that expand to items must be delimited with braces or followed by a semicolon
  --> examples/simple_multiply/src/main.rs:21:20
   |
21 | fn simple_multiply(self, a: Cipher<Signed>, b: Cipher<Signed>) -> Cipher<Signed> {
   |                    ^^^^
   |
help: change the delimiters to curly braces
   |
21 | fn simple_multiply({}, a: Cipher<Signed>, b: Cipher<Signed>) -> Cipher<Signed> {
   |                    ~
help: add a semicolon
   |
21 | fn simple_multiply(self;, a: Cipher<Signed>, b: Cipher<Signed>) -> Cipher<Signed> {
   |                        +

error: FHE programs must not contain `self`
  --> examples/simple_multiply/src/main.rs:21:20
   |
21 | fn simple_multiply(self, a: Cipher<Signed>, b: Cipher<Signed>) -> Cipher<Signed> {
   |                    ^^^^

Resolved so that the user just sees

error: FHE programs must not contain `self`
  --> examples/simple_multiply/src/main.rs:21:20
   |
21 | fn simple_multiply(self, a: Cipher<Signed>, b: Cipher<Signed>) -> Cipher<Signed> {
   |                    ^^^^

Fix compiler error on argument attributes

This one had the same issue as the previous error message, an unrelated note about macro invocations. Also, I made it so that the compiler error message targets the attribute instead of the argument identifier:

error: FHE program arguments do not support attributes.
  --> examples/simple_multiply/src/main.rs:24:20
   |
24 | fn simple_multiply(#[poo] a: Cipher<Signed>, b: Cipher<Signed>) -> Cipher<Signed> {
   |                    ^

Better invalid return error message

Before:

error[E0308]: mismatched types
  --> examples/simple_multiply/src/main.rs:23:5
   |
23 |     0
   |     ^ expected `FheProgramNode<i64>`, found integer
   |
   = note: expected struct `FheProgramNode<i64>`
                found type `{integer}`

error[E0277]: the trait bound `i64: NumCiphertexts` is not satisfied
  --> examples/simple_multiply/src/main.rs:20:1
   |
20 | #[fhe_program(scheme = "bfv")]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `NumCiphertexts` is not implemented for `i64`
   |
   = help: the following other types implement trait `NumCiphertexts`:
             Batched<LANES>
             FheProgramNode<T>
             Fractional<INT_BITS>
             Indeterminate<L, T>
             Rational
             Signed
             Unsigned<LIMBS>
             [T; N]
             sunscreen::types::Cipher<T>
note: required by a bound in `FheProgramNode`
  --> /home/sam/code/sunscreen/sunscreen/src/types/intern/fhe_program_node.rs:46:30
   |
46 | pub struct FheProgramNode<T: NumCiphertexts, S = ()> {
   |                              ^^^^^^^^^^^^^^ required by this bound in `FheProgramNode`
   = note: this error originates in the attribute macro `fhe_program` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0599]: the method `output` exists for struct `FheProgramNode<i64>`, but its trait bounds were not satisfied
  --> examples/simple_multiply/src/main.rs:21:61
   |
21 | fn simple_multiply(a: Cipher<Signed>, b: Cipher<Signed>) -> i64 {
   |                                                             ^^^ method cannot be called on `FheProgramNode<i64>` due to unsatisfied trait bounds
   |
  ::: /home/sam/code/sunscreen/sunscreen/src/types/intern/fhe_program_node.rs:46:1
   |
46 | pub struct FheProgramNode<T: NumCiphertexts, S = ()> {
   | ---------------------------------------------------- doesn't satisfy `_: Output`
   |
   = note: the following trait bounds were not satisfied:
           `i64: NumCiphertexts`
           which is required by `FheProgramNode<i64>: sunscreen::types::intern::Output`

error[E0599]: no function or associated item named `type_name` found for type `i64` in the current scope
  --> examples/simple_multiply/src/main.rs:20:1
   |
20 | #[fhe_program(scheme = "bfv")]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `i64`
   |
   = note: this error originates in the attribute macro `fhe_program` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0599]: no associated item named `NUM_CIPHERTEXTS` found for type `i64` in the current scope
  --> examples/simple_multiply/src/main.rs:20:1
   |
20 | #[fhe_program(scheme = "bfv")]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated item not found in `i64`
   |
   = note: this error originates in the attribute macro `fhe_program` (in Nightly builds, run with -Z macro-backtrace for more info)

Now they see the more succinct:

error[E0277]: the trait bound `i64: NumCiphertexts` is not satisfied
  --> examples/simple_multiply/src/main.rs:21:61
   |
21 | fn simple_multiply(a: Cipher<Signed>, b: Cipher<Signed>) -> i64 {
   |                                                             ^^^ the trait `NumCiphertexts` is not implemented for `i64`
   |
   = help: the following other types implement trait `NumCiphertexts`:
             Batched<LANES>
             FheProgramNode<T>
             Fractional<INT_BITS>
             Indeterminate<L, T>
             Rational
             Signed
             Unsigned<LIMBS>
             [T; N]
             sunscreen::types::Cipher<T>
   = note: required for `FheProgramNode<i64>` to implement `sunscreen::types::intern::Output`
   = help: see issue #48214

For more information about this error, try `rustc --explain E0277`.
error: could not compile `simple_multiply` (bin "simple_multiply") due to previous error

Marginally better invalid input error message

Similarly, you used to get something like this when passing an i64 argument:

error[E0277]: the trait bound `i64: NumCiphertexts` is not satisfied
  --> examples/simple_multiply/src/main.rs:21:48
   |
21 | fn simple_multiply(a: [Cipher<Signed>; 10], b: i64) -> Cipher<Signed> {
   |                                                ^^^ the trait `NumCiphertexts` is not implemented for `i64`
   |
   = help: the following other types implement trait `NumCiphertexts`:
             Batched<LANES>
             FheProgramNode<T>
             Fractional<INT_BITS>
             Indeterminate<L, T>
             Rational
             Signed
             Unsigned<LIMBS>
             [T; N]
             sunscreen::types::Cipher<T>
note: required by a bound in `FheProgramNode`
  --> /home/sam/code/sunscreen/sunscreen/src/types/intern/fhe_program_node.rs:47:30
   |
47 | pub struct FheProgramNode<T: NumCiphertexts, S = ()> {
   |                              ^^^^^^^^^^^^^^ required by this bound in `FheProgramNode`

error[E0271]: type mismatch resolving `<Signed as GraphCipherConstMul>::Right == FheProgramNode<i64>`
  --> examples/simple_multiply/src/main.rs:22:10
   |
22 |     a[0] * b
   |          ^ expected `i64`, found `FheProgramNode<i64>`
   |
   = note: expected type `i64`
            found struct `FheProgramNode<i64>`
   = note: required for `FheProgramNode<sunscreen::types::Cipher<Signed>>` to implement `Mul<FheProgramNode<i64>>`

error[E0277]: the trait bound `FheProgramNode<i64>: FheLiteral` is not satisfied
  --> examples/simple_multiply/src/main.rs:22:10
   |
22 |     a[0] * b
   |          ^ the trait `FheLiteral` is not implemented for `FheProgramNode<i64>`
   |
   = help: the following other types implement trait `FheLiteral`:
             crypto_bigint::uint::UInt<LIMBS>
             f64
             i64
             u64
   = note: required for `FheProgramNode<sunscreen::types::Cipher<Signed>>` to implement `Mul<FheProgramNode<i64>>`

error[E0277]: the trait bound `i64: NumCiphertexts` is not satisfied
  --> examples/simple_multiply/src/main.rs:22:10
   |
22 |     a[0] * b
   |          ^ the trait `NumCiphertexts` is not implemented for `i64`
   |
   = help: the following other types implement trait `NumCiphertexts`:
             Batched<LANES>
             FheProgramNode<T>
             Fractional<INT_BITS>
             Indeterminate<L, T>
             Rational
             Signed
             Unsigned<LIMBS>
             [T; N]
             sunscreen::types::Cipher<T>
note: required by a bound in `FheProgramNode`
  --> /home/sam/code/sunscreen/sunscreen/src/types/intern/fhe_program_node.rs:47:30
   |
47 | pub struct FheProgramNode<T: NumCiphertexts, S = ()> {
   |                              ^^^^^^^^^^^^^^ required by this bound in `FheProgramNode`

error[E0277]: the trait bound `Signed: From<FheProgramNode<i64>>` is not satisfied
  --> examples/simple_multiply/src/main.rs:22:10
   |
22 |     a[0] * b
   |          ^ the trait `From<FheProgramNode<i64>>` is not implemented for `Signed`
   |
   = help: the trait `From<i64>` is implemented for `Signed`
   = note: required for `FheProgramNode<i64>` to implement `Into<Signed>`
   = note: required for `Signed` to implement `TryFrom<FheProgramNode<i64>>`
   = note: required for `FheProgramNode<sunscreen::types::Cipher<Signed>>` to implement `Mul<FheProgramNode<i64>>`

error[E0277]: the trait bound `i64: TypeName` is not satisfied
  --> examples/simple_multiply/src/main.rs:21:48
   |
21 | fn simple_multiply(a: [Cipher<Signed>; 10], b: i64) -> Cipher<Signed> {
   |                                                ^^^ the trait `TypeName` is not implemented for `i64`
   |
   = help: the following other types implement trait `TypeName`:
             Batched<LANES>
             FheProgramNode<T>
             Fractional<INT_BITS>
             NativeField<F>
             Rational
             RnsRingPolynomial<F, N, R>
             Signed
             Unsigned<LIMBS>
           and 2 others
   = note: required for `FheProgramNode<i64>` to implement `Input`

error[E0277]: the trait bound `i64: NumCiphertexts` is not satisfied
  --> examples/simple_multiply/src/main.rs:20:1
   |
20 | #[fhe_program(scheme = "bfv")]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `NumCiphertexts` is not implemented for `i64`
   |
   = help: the following other types implement trait `NumCiphertexts`:
             Batched<LANES>
             FheProgramNode<T>
             Fractional<INT_BITS>
             Indeterminate<L, T>
             Rational
             Signed
             Unsigned<LIMBS>
             [T; N]
             sunscreen::types::Cipher<T>
note: required by a bound in `FheProgramNode`
  --> /home/sam/code/sunscreen/sunscreen/src/types/intern/fhe_program_node.rs:47:30
   |
47 | pub struct FheProgramNode<T: NumCiphertexts, S = ()> {
   |                              ^^^^^^^^^^^^^^ required by this bound in `FheProgramNode`
   = note: this error originates in the attribute macro `fhe_program` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0599]: no function or associated item named `type_name` found for type `i64` in the current scope
  --> examples/simple_multiply/src/main.rs:20:1
   |
20 | #[fhe_program(scheme = "bfv")]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `i64`
   |
   = note: this error originates in the attribute macro `fhe_program` (in Nightly builds, run with -Z macro-backtrace for more info)

Some errors have detailed explanations: E0271, E0277, E0599.
For more information about an error, try `rustc --explain E0271`.
error: could not compile `simple_multiply` (bin "simple_multiply") due to 8 previous errors

Now, you get the much more succinct issue, just the two missing impls necessary for Input:

error[E0277]: the trait bound `i64: NumCiphertexts` is not satisfied
  --> examples/simple_multiply/src/main.rs:21:48
   |
21 | fn simple_multiply(a: [Cipher<Signed>; 10], b: i64) -> Cipher<Signed> {
   |                                                ^^^ the trait `NumCiphertexts` is not implemented for `i64`
   |
   = help: the following other types implement trait `NumCiphertexts`:
             Batched<LANES>
             FheProgramNode<T>
             Fractional<INT_BITS>
             Indeterminate<L, T>
             Rational
             Signed
             Unsigned<LIMBS>
             [T; N]
             sunscreen::types::Cipher<T>
   = note: required for `FheProgramNode<i64>` to implement `Input`
   = help: see issue #48214

error[E0277]: the trait bound `i64: TypeName` is not satisfied
  --> examples/simple_multiply/src/main.rs:21:48
   |
21 | fn simple_multiply(a: [Cipher<Signed>; 10], b: i64) -> Cipher<Signed> {
   |                                                ^^^ the trait `TypeName` is not implemented for `i64`
   |
   = help: the following other types implement trait `TypeName`:
             Batched<LANES>
             FheProgramNode<T>
             Fractional<INT_BITS>
             NativeField<F>
             Rational
             RnsRingPolynomial<F, N, R>
             Signed
             Unsigned<LIMBS>
           and 2 others
   = note: required for `FheProgramNode<i64>` to implement `Input`
   = help: see issue #48214

For more information about this error, try `rustc --explain E0277`.
error: could not compile `simple_multiply` (bin "simple_multiply") due to 2 previous errors

Get rid of .into_program_node() noise

I've made a zkp_var! macro. I think this glorious commit sums it up. Basically

// anywhere you saw
NativeField::<F>::from(1).into_program_node()
// replace it with
zkp_var!(1)

Really, zkp_var can work as a function. But as a macro it does support things like zkp_var![0; 10] so that's nice.

Allow "initialized" ciphertext variables

As we've discussed extensively in #fhe-compiler, there's kind of been this awkward dance when folding over ciphertexts. In math/haskell speak: ciphertexts formed a Semigroup but not quite a Monoid; these changes give us the required Monoid::mempty.

Now you can:

fn simple_sum(a: Cipher<Signed>, b: Cipher<Signed>) -> Cipher<Signed> {
    let mut sum = fhe_var!(0);
    sum = sum + a;
    sum = sum + b;
    sum
}

Again, fhe_var can work as a function but this way we support fhe_var![0; 10].

Implementation note: I've done this as unobtrusively as possible, so that the only nodes for which we lose type-level invariants are on the newly added Indeterminate nodes. We could add the enum Stage {Literal, Cipher} to all Cipher nodes, and then all the types would be shared and we wouldn't have to juggle different node types. But that requires moving all those invariants to the value level (invariants like ciphertext addition vs. plaintext addition happening on the correct nodes).

Another note: you can't use a binary operation on two of these fhe_var! outputs. You can only mix them with ciphertexts. In order to support both (sanely) we need to first add plaintext/plaintext arithmetic operations. (Insanely, you could just make the appropriate impl and just panic if both vars are still plaintexts - but this is something I'd rather the user see at Rust compile time than FHE program compile time.)

Auto .into()

Due to limitations of impls on foreign traits, this is done via a custom Coerce trait for coercing types behind FheProgramNode. Essentially, all of your return values get an automatic .coerce() call, like .into(). We support the trivial identity coercion, of course, and the coercion from indeterminate nodes created with fhe_var! to Cipher nodes. Additionally we provide impls for arrays of coercible values. This means you can return arbitrarily array-nested indeterminate nodes and coercion will still work. (However, you can't mix types within an array: [ciphertext_input, sum] is still illegal, since those values are of different types.)

Refactored the fhe_program proc macro

Apologies for adding this to the diff. It wasn't bad to begin with, but by the time I finished adding the nested function stuff to support the auto .coerce(), it was getting unwieldy. It's more pleasant to read now.

Leftovers

  • Implement plain/plain FHE operations. These are missing, and without them, we can't safely perform operations on two indeterminate nodes, which is pretty limiting.
  • Trivial encryptions. If we had these, we could do away with Indeterminate and Stage etc. altogether, and fhe_var! could just create genuine Cipher nodes. This is probably the best thing we could do, both in terms of user experience and the easiest code to maintain.

samtay added 30 commits June 26, 2023 12:03
No functionality changes
NOTE: Not fully implemented. Will not work on Rational types until we
factor out literal->plaintext into a proper trait.

This allows, e.g.

```rust
fn simple_sum(a: Cipher<Signed>, b: Cipher<Signed>) -> Cipher<Signed> {
    let mut sum = fhe_var(0);
    sum = sum + a;
    sum = sum + b;
    fhe_out(sum)
}
````
These I think are typically ignored by default when consuming proc macros but might as well be explicit
Comment thread sunscreen/src/types/intern/fhe_program_node.rs Outdated
Comment thread sunscreen/src/types/intern/fhe_program_node.rs Outdated
Comment thread sunscreen/tests/fhe_program_tests.rs Outdated
Comment thread sunscreen/tests/fhe_program_tests.rs
@samtay samtay merged commit 5faf981 into main Jul 5, 2023
@samtay samtay deleted the samtay/hackathon branch July 5, 2023 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant