boo_hoo/
proof.rs

1use std::fmt;
2use std::io::Read;
3use std::io::Write;
4
5use crate::bits::*;
6use crate::commitment;
7use crate::commitment::Commitment;
8use crate::commitment::Decommitment;
9use crate::constants::{CHALLENGE_CONTEXT, REPETITIONS};
10use crate::program::*;
11use crate::rng::{BitPRNG, Seed};
12use bincode::decode_from_std_read;
13use bincode::encode_into_slice;
14use bincode::encode_to_vec;
15use bincode::error::DecodeError;
16use bincode::error::EncodeError;
17use bincode::Decode;
18use bincode::Encode;
19use bincode::{config, encode_into_std_write};
20use rand_core::{CryptoRng, RngCore};
21
22/// Split a BitBuf into 3 shares which xor to form the original input.
23fn split<R: RngCore + CryptoRng>(rng: &mut R, input: BitBuf) -> [BitBuf; 3] {
24    let len = input.len();
25    let len_bytes = (7 + len) / 8;
26    let mut bytes = vec![0u8; len_bytes];
27
28    rng.fill_bytes(&mut bytes);
29    let mut buf0 = BitBuf::from_bytes(&bytes);
30    buf0.resize(input.len());
31
32    rng.fill_bytes(&mut bytes);
33    let mut buf1 = BitBuf::from_bytes(&bytes);
34    buf1.resize(input.len());
35
36    // Now, as a result, some of the upper bits in buf2 may be different,
37    // but our program has been validated, and will never access these.
38    let mut buf2 = input;
39    buf2.xor(&buf0);
40    buf2.xor(&buf1);
41
42    [buf0, buf1, buf2]
43}
44
45/// The and function between two parties.
46///
47/// When computing an and operation, each party needs the input from the party
48/// adjacent to them, and the mask bit from the party adjacent to them. This
49/// results in their secret share of the and operation.
50fn and(input_a: (Bit, Bit), input_b: (Bit, Bit), mask_a: Bit, mask_b: Bit) -> Bit {
51    (input_a.0 & input_a.1) ^ (input_a.0 & input_b.1) ^ (input_b.0 & input_a.1) ^ mask_a ^ mask_b
52}
53
54/// Represents the view of a single party in the MPC protocol.
55#[derive(Clone, Default, Debug, Encode, Decode)]
56struct View {
57    seed: Seed,
58    input: BitBuf,
59    messages: BitBuf,
60}
61
62/// The result of running the simulation on three parties.
63struct TriSimulation {
64    views: [View; 3],
65    outputs: [BitBuf; 3],
66}
67
68/// A simulator running the MPC protocol over three virtual parties.
69struct TriSimulator {
70    seeds: [Seed; 3],
71    rngs: [BitPRNG; 3],
72    machines: [Machine; 3],
73    messages: [BitBuf; 3],
74}
75
76impl TriSimulator {
77    /// Create a new trisimulator, initialized with some secret input.
78    pub fn create<R: RngCore + CryptoRng>(rng: &mut R, input: BitBuf) -> Self {
79        let seeds = [(); 3].map(|_| Seed::random(rng));
80        let rngs = [0, 1, 2].map(|i| BitPRNG::seeded(&seeds[i]));
81        let inputs = split(rng, input);
82        let machines = inputs.map(Machine::new);
83        let messages = [(); 3].map(|_| BitBuf::new());
84        Self {
85            seeds,
86            rngs,
87            machines,
88            messages,
89        }
90    }
91
92    fn and(&mut self) {
93        let mask_bits = [0, 1, 2].map(|i| self.rngs[i].next_bit());
94        let inputs = [0, 1, 2].map(|i| {
95            let machine = &mut self.machines[i];
96            let bit0 = machine.pop();
97            let bit1 = machine.pop();
98            (bit0, bit1)
99        });
100
101        for i0 in 0..3 {
102            let i1 = (i0 + 1) % 3;
103
104            let res = and(inputs[i0], inputs[i1], mask_bits[i0], mask_bits[i1]);
105
106            // Since this involves "communication" between the simulated parties,
107            // we record having received this message
108            self.messages[i0].push(res);
109            self.machines[i0].push(res);
110        }
111    }
112
113    fn op(&mut self, op: Operation) {
114        match op {
115            Operation::Not => {
116                for machine in &mut self.machines {
117                    machine.not();
118                }
119            }
120            Operation::And => self.and(),
121            Operation::Xor => {
122                for machine in &mut self.machines {
123                    machine.xor();
124                }
125            }
126            Operation::PushArg(i) => {
127                let i_usize = i as usize;
128                for machine in &mut self.machines {
129                    machine.push_arg(i_usize);
130                }
131            }
132            Operation::PushLocal(i) => {
133                let i_usize = i as usize;
134                for machine in &mut self.machines {
135                    machine.push_local(i_usize);
136                }
137            }
138            Operation::PopOutput => {
139                for machine in &mut self.machines {
140                    machine.pop_output();
141                }
142            }
143        }
144    }
145
146    /// Run the simulation, producing the result.
147    ///
148    /// The result contains the views of each party, along with the outputs.
149    pub fn run(mut self, program: &ValidatedProgram) -> TriSimulation {
150        for op in &program.operations {
151            self.op(*op);
152        }
153        let mut views = Vec::with_capacity(3);
154        let mut outputs = Vec::with_capacity(3);
155        for ((seed, machine), messages) in self
156            .seeds
157            .into_iter()
158            .zip(self.machines.into_iter())
159            .zip(self.messages.into_iter())
160        {
161            let (input, output) = machine.input_output();
162            views.push(View {
163                seed,
164                input: input,
165                messages,
166            });
167            outputs.push(output);
168        }
169        let views = views.try_into().unwrap();
170        let outputs = outputs.try_into().unwrap();
171        TriSimulation { views, outputs }
172    }
173}
174
175/// Represents a proof of knowledge of a pre-image of some arbitrary program.
176///
177/// This is generated through the `prove` function, and should be treated as
178/// an opaque blob which can be verified through the `verify` function.
179///
180/// For serialization and deserialization, the Encode and Decode traits are provided,
181/// which can be combined with [bincode](https://docs.rs/bincode/2.0.0-rc.1/bincode/index.html)
182/// in order to serialize to bytes, or an arbitrary IO object.
183///
184/// This object also provides wrapper methods to implement those functions.
185#[derive(Clone, Encode, Decode)]
186pub struct Proof {
187    commitments: Vec<Commitment>,
188    outputs: Vec<BitBuf>,
189    decommitments: Vec<Decommitment>,
190    views: Vec<View>,
191}
192
193impl Proof {
194    /// Encode this proof into a vector of bytes.
195    ///
196    /// In principle, this method shouldn't fail.
197    pub fn encode_to_vec(&self) -> Result<Vec<u8>, EncodeError> {
198        encode_to_vec(self, config::standard())
199    }
200
201    /// Encode this proof into an arbitrary object implementing `Write`.
202    ///
203    /// This will return the number of bytes written.
204    pub fn encode_to_write<W: Write>(&self, dst: &mut W) -> Result<usize, EncodeError> {
205        encode_into_std_write(self, dst, config::standard())
206    }
207
208    /// Decode this proof from an arbitrary object implementing `Read`.
209    ///
210    /// Note that this function will also work with `&[u8]`, since that implements `Read`.
211    pub fn decode_from_read<R: Read>(src: &mut R) -> Result<Self, DecodeError> {
212        decode_from_std_read(src, config::standard())
213    }
214}
215
216/// Our challenge is a series of trits, which we draw from a PRNG.
217fn challenge(
218    ctx: &[u8],
219    program: &ValidatedProgram,
220    output: &BitBuf,
221    commitments: &Vec<Commitment>,
222    outputs: &Vec<BitBuf>,
223) -> BitPRNG {
224    fn update<E: Encode>(hasher: &mut blake3::Hasher, e: E) {
225        encode_into_std_write(e, hasher, config::standard()).unwrap();
226    }
227
228    let mut hasher = blake3::Hasher::new_derive_key(CHALLENGE_CONTEXT);
229    update(&mut hasher, ctx);
230    update(&mut hasher, &program.operations);
231    update(&mut hasher, output);
232    update(&mut hasher, REPETITIONS);
233    update(&mut hasher, commitments);
234    update(&mut hasher, outputs);
235
236    BitPRNG::from_hasher(hasher)
237}
238
239/// An internal function for creating a proof, after validkkoating inputs.
240///
241/// The buffers for input and output must have the exact right length for the program.
242fn do_prove<R: RngCore + CryptoRng>(
243    rng: &mut R,
244    ctx: &[u8],
245    program: &ValidatedProgram,
246    input: &BitBuf,
247    output: &BitBuf,
248) -> Proof {
249    let mut commitments = Vec::with_capacity(REPETITIONS * 3);
250    let mut outputs = Vec::with_capacity(REPETITIONS * 3);
251    let mut all_decommitments = Vec::with_capacity(REPETITIONS * 3);
252    let mut all_views = Vec::with_capacity(REPETITIONS * 3);
253
254    for _ in 0..REPETITIONS {
255        let simulation = TriSimulator::create(rng, input.clone()).run(program);
256
257        for output in simulation.outputs {
258            outputs.push(output);
259        }
260
261        for view in simulation.views {
262            let (com, decom) = commitment::commit(rng, &view);
263            commitments.push(com);
264            all_decommitments.push(decom);
265            all_views.push(view);
266        }
267    }
268
269    let mut bit_rng = challenge(ctx, program, output, &commitments, &outputs);
270
271    let mut decommitments = Vec::with_capacity(REPETITIONS * 2);
272    let mut views = Vec::with_capacity(REPETITIONS * 2);
273
274    for i in (0..REPETITIONS).map(|i| i * 3) {
275        let trit = bit_rng.next_trit() as usize;
276        let i0 = i + trit;
277        let i1 = i + ((trit + 1) % 3);
278        for ij in [i0, i1] {
279            decommitments.push(std::mem::take(&mut all_decommitments[ij]));
280            views.push(std::mem::take(&mut all_views[ij]));
281        }
282    }
283
284    Proof {
285        commitments,
286        outputs,
287        decommitments,
288        views,
289    }
290}
291
292/// Represents the partial simulation over the two revealed parties of the protocol.
293struct ReSimulation {
294    primary_messages: BitBuf,
295    outputs: [BitBuf; 2],
296}
297
298/// The ReSimulator partially simulates the MPC protocol over only two parties.
299/// 
300/// Since we have only two parties, we can't do a full simulation. Instead, we trust
301/// the claimed messages for the second party, and re-simulate the behavior of the first
302/// party given those messages.
303struct ReSimulator<'a> {
304    /// The new messages reproduced for the first party.
305    primary_messages: BitBuf,
306    /// The claimed messages for the second party.
307    secondary_messages: &'a BitBuf,
308    /// The current index into those messages.
309    secondary_messages_i: usize,
310    /// The RNGs for each party.
311    rngs: [BitPRNG; 2],
312    /// The machine for the state of each party.
313    machines: [Machine; 2],
314}
315
316impl<'a> ReSimulator<'a> {
317    /// Create a new ReSimulator given the inputs, seeds, and the messages of the second party.
318    fn new(inputs: [&'a BitBuf; 2], seeds: [&'a Seed; 2], secondary_messages: &'a BitBuf) -> Self {
319        Self {
320            primary_messages: BitBuf::new(),
321            secondary_messages,
322            secondary_messages_i: 0,
323            rngs: seeds.map(BitPRNG::seeded),
324            machines: inputs.map(|input| Machine::new(input.clone())),
325        }
326    }
327
328    fn next_secondary_message(&mut self) -> Option<Bit> {
329        let out = self.secondary_messages.get(self.secondary_messages_i);
330        self.secondary_messages_i += 1;
331        out
332    }
333
334    fn and(&mut self) -> Option<()> {
335        let masks = [0, 1].map(|i| self.rngs[i].next_bit());
336        let inputs = [0, 1].map(|i| {
337            let machine = &mut self.machines[i];
338            let bit0 = machine.pop();
339            let bit1 = machine.pop();
340            (bit0, bit1)
341        });
342
343        let res = and(inputs[0], inputs[1], masks[0], masks[1]);
344        self.machines[0].push(res);
345        self.primary_messages.push(res);
346        // We just trust the secondary messages to be correct
347        let next_message = self.next_secondary_message()?;
348        self.machines[1].push(next_message);
349        Some(())
350    }
351
352    fn op(&mut self, op: Operation) -> Option<()> {
353        match op {
354            Operation::Not => {
355                for machine in &mut self.machines {
356                    machine.not();
357                }
358            }
359            Operation::And => self.and()?,
360            Operation::Xor => {
361                for machine in &mut self.machines {
362                    machine.xor();
363                }
364            }
365            Operation::PushArg(i) => {
366                for machine in &mut self.machines {
367                    machine.push_arg(i as usize)
368                }
369            }
370            Operation::PushLocal(i) => {
371                for machine in &mut self.machines {
372                    machine.push_local(i as usize)
373                }
374            }
375            Operation::PopOutput => {
376                for machine in &mut self.machines {
377                    machine.pop_output();
378                }
379            }
380        }
381        Some(())
382    }
383
384    /// Run the resimulation on a given program.
385    ///
386    /// This can potentially fail, if not enough messages are passed to the simulator.
387    /// This indicates a bad proof.
388    pub fn run(mut self, program: &ValidatedProgram) -> Option<ReSimulation> {
389        for op in &program.operations {
390            self.op(*op)?;
391        }
392
393        let primary_messages = self.primary_messages;
394        let outputs = self.machines.map(|machine| machine.input_output().1);
395        Some(ReSimulation {
396            primary_messages,
397            outputs,
398        })
399    }
400}
401
402/// Verify a single repetition of the proof.
403///
404/// The slices `commitments` and `outputs` should have 3 elements, and
405/// `decommitments` and `views` should have 2 elements.
406fn verify_repetition(
407    program: &ValidatedProgram,
408    output: &BitBuf,
409    commitments: &[Commitment],
410    outputs: &[BitBuf],
411    trit: u8,
412    decommitments: &[Decommitment],
413    views: &[View],
414) -> bool {
415    // Check that the output is correct
416    let mut actual_output = outputs[0].clone();
417    actual_output.xor(&outputs[1]);
418    actual_output.xor(&outputs[2]);
419
420    if actual_output != *output {
421        return false;
422    }
423    // Check input lengths
424    if !(0..2).all(|i| views[i].input.len() == program.input_count) {
425        return false;
426    }
427
428    let i = [trit as usize, ((trit + 1) % 3) as usize];
429
430    // Check that the commitments are valid
431    if !(0..2).all(|j| {
432        let i_j = i[j];
433        commitment::decommit(&views[j], &commitments[i_j], &decommitments[j])
434    }) {
435        return false;
436    }
437
438    // Check that the views are coherent, and produce the right output
439    let re_simulation_result = ReSimulator::new(
440        [&views[0].input, &views[1].input],
441        [&views[0].seed, &views[1].seed],
442        &views[1].messages,
443    )
444    .run(program);
445    let re_simulation = match re_simulation_result {
446        Some(x) => x,
447        None => return false,
448    };
449
450    if re_simulation.primary_messages != views[0].messages {
451        return false;
452    }
453    (0..2).all(|j| re_simulation.outputs[j] == outputs[i[j]])
454}
455
456fn do_verify(ctx: &[u8], program: &ValidatedProgram, output: &BitBuf, proof: &Proof) -> bool {
457    // Check that the proof has enough content
458    if proof.commitments.len() != 3 * REPETITIONS {
459        return false;
460    }
461    if proof.outputs.len() != 3 * REPETITIONS {
462        return false;
463    }
464    if proof.decommitments.len() != 2 * REPETITIONS {
465        return false;
466    }
467    if proof.views.len() != 2 * REPETITIONS {
468        return false;
469    }
470
471    let mut bit_rng = challenge(ctx, program, output, &proof.commitments, &proof.outputs);
472
473    (0..REPETITIONS).all(|i| {
474        let trit = bit_rng.next_trit();
475        verify_repetition(
476            program,
477            output,
478            &proof.commitments[3 * i..3 * (i + 1)],
479            &proof.outputs[3 * i..3 * (i + 1)],
480            trit,
481            &proof.decommitments[2 * i..2 * (i + 1)],
482            &proof.views[2 * i..2 * (i + 1)],
483        )
484    })
485}
486
487/// Represents an error that can happen when proving or verifying.
488///
489/// At the moment, errors only happen because the size of the input or the output
490/// was insufficient for what the program expected. If too little input our output
491/// is provided, then proving and verifying will fail.
492#[derive(Clone, Debug, PartialEq)]
493pub enum Error {
494    InsufficientInput(usize),
495    InsufficientOutput(usize),
496}
497
498impl fmt::Display for Error {
499    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
500        match self {
501            Error::InsufficientInput(i) => write!(f, "insufficient input of size {}", i),
502            Error::InsufficientOutput(i) => write!(f, "insufficient output of size {}", i),
503        }
504    }
505}
506
507impl std::error::Error for Error {}
508
509/// Create a proof that running a program on an input produces the provided output.
510///
511/// This proof will let other people independently verify that we know an input producing
512/// that output after running the program, without revealing what that secret input is.
513///
514/// This proof also commits to the program. A proof will only verify with the exact
515/// program used to create the proof. Any modification to the program, even if it
516/// results in equivalent functionality, will fail to verify.
517///
518/// This proving function takes an additional context, which can be used to bind
519/// the proof to a given context. The verifier will need to pass in the same context,
520/// and will fail to verify if their context is different. Among other things,
521/// this allows you to potentially turn these proofs into a signature scheme, by using
522/// a message as a context.
523///
524/// The program needs to be validated, to make sure that it isn't malformed. Furthermore,
525/// this proving can fail if the input or output doesn't match the size of the proof.
526/// Providing additional input or output will work, and any extra data beyond the number
527/// of bits used by the program is ignored completely. Bits are read from least to
528/// most significant.
529pub fn prove<R: RngCore + CryptoRng>(
530    rng: &mut R,
531    ctx: &[u8],
532    program: &ValidatedProgram,
533    input: &[u8],
534    output: &[u8],
535) -> Result<Proof, Error> {
536    let mut input_buf = BitBuf::from_bytes(input);
537    let mut output_buf = BitBuf::from_bytes(output);
538    if input_buf.len() < program.input_count {
539        return Err(Error::InsufficientInput(input_buf.len()));
540    }
541    if output_buf.len() < program.output_count {
542        return Err(Error::InsufficientOutput(output_buf.len()));
543    }
544    input_buf.resize(program.input_count);
545    output_buf.resize(program.output_count);
546    Ok(do_prove(rng, ctx, program, &input_buf, &output_buf))
547}
548
549/// Verify a proof, demonstrating knowledge of some input for which the program returns this output.
550///
551/// This function returns `true` if the proof is correct.
552///
553/// For the proof to successfully verify, the prover needs to have known an input which
554/// produces this exact output when used to run the program. Furthermore, the program
555/// needs to exactly match the program used to create the proof. Finally, the context
556/// data used to produce the proof needs to be the exact same as passed to verify.
557///
558/// Note that the output needs to be at least as long as the output produced by the program.
559/// Any additional bits in the output will be ignored. Bits are read from least significant
560/// to most significant. If the output isn't large enough, this function will fail.
561pub fn verify(
562    ctx: &[u8],
563    program: &ValidatedProgram,
564    output: &[u8],
565    proof: &Proof,
566) -> Result<bool, Error> {
567    let mut output_buf = BitBuf::from_bytes(output);
568    if output_buf.len() < program.output_count {
569        return Err(Error::InsufficientOutput(output_buf.len()));
570    }
571    output_buf.resize(program.output_count);
572
573    Ok(do_verify(ctx, program, &output_buf, proof))
574}
575
576#[cfg(test)]
577mod test {
578    use rand_core::OsRng;
579
580    use super::*;
581    use crate::program::generators::arb_program_and_inputs;
582    use proptest::prelude::*;
583
584    fn simple_program() -> ValidatedProgram {
585        use Operation::*;
586
587        Program::new([
588            PushArg(0),
589            PushArg(1),
590            PushArg(2),
591            PushArg(3),
592            Xor,
593            Xor,
594            Xor,
595            PushArg(4),
596            PushArg(5),
597            PushArg(6),
598            PushArg(7),
599            Xor,
600            Xor,
601            Xor,
602            And,
603            PopOutput,
604        ])
605        .validate()
606        .unwrap()
607    }
608
609    const TEST_CTX: &[u8] = b"test context";
610
611    #[test]
612    fn test_simple_program_proof_succeeds() {
613        let input = &[0b0111_1110];
614        let output = &[1];
615        let program = simple_program();
616        let proof = prove(&mut OsRng, TEST_CTX, &program, input, output);
617        assert!(proof.is_ok());
618        assert_eq!(
619            Ok(true),
620            verify(TEST_CTX, &program, output, &proof.unwrap())
621        );
622    }
623
624    proptest! {
625        // This test is slow, so should be run with --include-ignore
626        #[test]
627        #[ignore]
628        fn test_program_proofs_succeed((program, input, output) in arb_program_and_inputs()) {
629            let proof = prove(&mut OsRng, TEST_CTX, &program, &input, &[output]);
630            assert!(proof.is_ok());
631            assert_eq!(Ok(true), verify(TEST_CTX, &program, &[output], &proof.unwrap()));
632        }
633    }
634}