boo_hoo/lib.rs
1//! A library for Non-Interactive Zero-Knowledge Proofs of Knowledge (NIZKPoKs) for
2//! boolean circuits.
3//!
4//! **This library is experimental Cryptographic Software: use at your own peril.**
5//!
6//! The idea is that given a program `P` and some secret input `I` you can provide
7//! a proof that you know some input `I` such that the output `O` is equal to `P(I)`.
8//! This proof can then be independently checked by anyone knowing the program `P`
9//! and the claimed output `O`, and they'll be convinced that you know such an `I`.
10//!
11//! This is done via the [ZKBoo scheme](https://eprint.iacr.org/2016/163).
12//!
13//! # Example
14//!
15//! As an example, let's say that you want to create a proof that you know
16//! two secret bits `x0` and `x1` such that `x0 & x1 == 1`. First, you'll need to
17//! create a program which represents this circuit:
18//!
19//! ```
20//! use boo_hoo::*;
21//! # use rand_core::OsRng;
22//!
23//! let raw_program = Program::new([
24//! Operation::PushArg(0),
25//! Operation::PushArg(1),
26//! Operation::And,
27//! Operation::PopOutput
28//! ]);
29//! # let program = raw_program.validate().expect("failed to validate program!");
30//! #
31//! # let ctx = b"example context";
32//! # let input = [0b10];
33//! # let output = [0];
34//! # let proof = prove(&mut OsRng, ctx, &program, &input, &output).expect("input or output were insufficient");
35//! # let result = verify(ctx, &program, &output, &proof);
36//! # assert_eq!(result, Ok(true));
37//! ```
38//! Circuits are represented with a stack based bytecode. Operations manipulate elements
39//! on the stack. We can move an indexed bit of the input onto the stack with `PushArg`.
40//! We use this in our program to move the two input bits on the stack. Then,
41//! we and them together with `And`. We can also use `Not` or `Xor` as other operations.
42//! Finally, we move the top element of the stack into the output buffer, with `PushOutput`.
43//! It's possible that our program is malformed, in that it pops from an empty stack,
44//! or accesses undefined elements on the stack. Because of this, we first need
45//! to validate our program:
46//!
47//! ```
48//! # use boo_hoo::*;
49//! # use rand_core::OsRng;
50//!
51//! # let raw_program = Program::new([
52//! # Operation::PushArg(0),
53//! # Operation::PushArg(1),
54//! # Operation::And,
55//! # Operation::PopOutput
56//! # ]);
57//! let program = raw_program.validate().expect("failed to validate program!");
58//! #
59//! # let ctx = b"example context";
60//! # let input = [0b10];
61//! # let output = [0];
62//! # let proof = prove(&mut OsRng, ctx, &program, &input, &output).expect("input or output were insufficient");
63//! # let result = verify(ctx, &program, &output, &proof);
64//! # assert_eq!(result, Ok(true));
65//! ```
66//!
67//! The validate method produces a `ValidatedProgram`, which has been validated against
68//! obviously incorrect manipulations, and which knows exactly how many input and output
69//! bits the program uses. In our case, the program has two input bits, and two output bits.
70//!
71//! Now, we can generate a proof for this program, using our secret inputs:
72//!
73//! ```
74//! use boo_hoo::*;
75//! use rand_core::OsRng;
76//!
77//! # let raw_program = Program::new([
78//! # Operation::PushArg(0),
79//! # Operation::PushArg(1),
80//! # Operation::And,
81//! # Operation::PopOutput
82//! # ]);
83//! # let program = raw_program.validate().expect("failed to validate program!");
84//! let ctx = b"example context";
85//! let input = [0b10];
86//! let output = [0];
87//! let proof = prove(&mut OsRng, ctx, &program, &input, &output).expect("input or output were insufficient");
88//! # let result = verify(ctx, &program, &output, &proof);
89//! # assert_eq!(result, Ok(true));
90//! ```
91//!
92//! The input and the output are provided as `&[u8]`. The bits are read from the first
93//! byte in the slice to the least, and from the least significant to the most significant
94//! bit inside of each byte. If an insufficient number of input or output bits are provided,
95//! then the proof construction will fail.
96//!
97//! We also pass in a "context". This context makes it so that the proof can only be verified
98//! with that context string. This allows binding a proof to a particular application,
99//! or even to an arbitrary message. The proof will fail to verify if a different context is used.
100//!
101//! Now, we can verify the proof:
102//!
103//! ```
104//! # use boo_hoo::*;
105//! # use rand_core::OsRng;
106//!
107//! # let raw_program = Program::new([
108//! # Operation::PushArg(0),
109//! # Operation::PushArg(1),
110//! # Operation::And,
111//! # Operation::PopOutput
112//! # ]);
113//! # let program = raw_program.validate().expect("failed to validate program!");
114//! # let ctx = b"example context";
115//! # let input = [0b10];
116//! # let output = [0];
117//! # let proof = prove(&mut OsRng, ctx, &program, &input, &output).expect("input or output were insufficient");
118//! let result = verify(ctx, &program, &output, &proof);
119//! assert_eq!(result, Ok(true));
120//! ```
121//!
122//! And that's all there is to it, really.
123//!
124//! # Details
125//!
126//! This is a relatively straightforward implementation of the scheme from the paper.
127//! In fact, this implementation is very "by-the-books" and intended to be easy
128//! to understand, rather than being particularly performant. Operations are done
129//! bit-by-bit, which is much more inefficient than operation on `u32`s or `u64`s directly.
130//! In most boolean circuits for real programs, like `SHA256` or other benchmarks,
131//! you'll be doing boolean operations on these large bundles, and performance could
132//! be greatly improved by processing multiple bits at once.
133mod bits;
134mod commitment;
135mod constants;
136mod program;
137mod proof;
138mod rng;
139
140pub use program::{Program, ValidatedProgram, ProgramError, Operation};
141pub use proof::{prove, verify, Error, Proof};