triptych/
transcript.rs

1// Copyright (c) 2024, The Tari Project
2// SPDX-License-Identifier: BSD-3-Clause
3
4use alloc::vec::Vec;
5
6use curve25519_dalek::{RistrettoPoint, Scalar};
7use merlin::TranscriptRng;
8use rand_core::CryptoRngCore;
9
10use crate::{proof::ProofError, Transcript, TriptychParameters, TriptychStatement, TriptychWitness};
11
12/// A Triptych proof transcript.
13pub(crate) struct ProofTranscript<'a, R: CryptoRngCore> {
14    transcript: &'a mut Transcript,
15    witness: Option<&'a TriptychWitness>,
16    transcript_rng: TranscriptRng,
17    external_rng: &'a mut R,
18}
19
20impl<'a, R: CryptoRngCore> ProofTranscript<'a, R> {
21    // Domain separator used for hashing
22    const DOMAIN: &'static str = "Triptych proof";
23    // Version identifier used for hashing
24    const VERSION: u64 = 0;
25
26    /// Initialize a transcript.
27    pub(crate) fn new(
28        transcript: &'a mut Transcript,
29        statement: &TriptychStatement,
30        external_rng: &'a mut R,
31        witness: Option<&'a TriptychWitness>,
32    ) -> Self {
33        // Update the transcript
34        transcript.append_message(b"dom-sep", Self::DOMAIN.as_bytes());
35        transcript.append_u64(b"version", Self::VERSION);
36        transcript.append_message(b"statement", statement.get_hash());
37
38        // Set up the transcript generator
39        let transcript_rng = Self::build_transcript_rng(transcript, witness, external_rng);
40
41        Self {
42            transcript,
43            witness,
44            transcript_rng,
45            external_rng,
46        }
47    }
48
49    /// Run the Fiat-Shamir commitment phase and produce challenge powers
50    #[allow(non_snake_case, clippy::too_many_arguments)]
51    pub(crate) fn commit(
52        &mut self,
53        params: &TriptychParameters,
54        A: &RistrettoPoint,
55        B: &RistrettoPoint,
56        C: &RistrettoPoint,
57        D: &RistrettoPoint,
58        X: &Vec<RistrettoPoint>,
59        Y: &Vec<RistrettoPoint>,
60    ) -> Result<Vec<Scalar>, ProofError> {
61        let m = params.get_m() as usize;
62
63        // Update the transcript
64        self.transcript.append_message(b"A", A.compress().as_bytes());
65        self.transcript.append_message(b"B", B.compress().as_bytes());
66        self.transcript.append_message(b"C", C.compress().as_bytes());
67        self.transcript.append_message(b"D", D.compress().as_bytes());
68        for X_item in X {
69            self.transcript.append_message(b"X", X_item.compress().as_bytes());
70        }
71        for Y_item in Y {
72            self.transcript.append_message(b"Y", Y_item.compress().as_bytes());
73        }
74
75        // Update the transcript generator
76        self.transcript_rng = Self::build_transcript_rng(self.transcript, self.witness, self.external_rng);
77
78        // Get the initial challenge using wide reduction
79        let mut xi_bytes = [0u8; 64];
80        self.transcript.challenge_bytes(b"xi", &mut xi_bytes);
81        let xi = Scalar::from_bytes_mod_order_wide(&xi_bytes);
82
83        // Get powers of the challenge and confirm they are nonzero
84        let mut xi_powers = Vec::with_capacity(m.checked_add(1).ok_or(ProofError::InvalidParameter)?);
85        let mut xi_power = Scalar::ONE;
86        for _ in 0..=m {
87            if xi_power == Scalar::ZERO {
88                return Err(ProofError::InvalidChallenge);
89            }
90
91            xi_powers.push(xi_power);
92            xi_power *= xi;
93        }
94
95        Ok(xi_powers)
96    }
97
98    /// Run the Fiat-Shamir response phase
99    #[allow(non_snake_case)]
100    pub(crate) fn response(mut self, f: &Vec<Vec<Scalar>>, z_A: &Scalar, z_C: &Scalar, z: &Scalar) -> TranscriptRng {
101        // Update the transcript
102        for f_row in f {
103            for f in f_row {
104                self.transcript.append_message(b"f", f.as_bytes());
105            }
106        }
107        self.transcript.append_message(b"z_A", z_A.as_bytes());
108        self.transcript.append_message(b"z_C", z_C.as_bytes());
109        self.transcript.append_message(b"z", z.as_bytes());
110
111        // Update the transcript generator
112        self.transcript_rng = Self::build_transcript_rng(self.transcript, self.witness, self.external_rng);
113
114        self.transcript_rng
115    }
116
117    /// Get a mutable reference to the transcript generator
118    pub(crate) fn as_mut_rng(&mut self) -> &mut TranscriptRng {
119        &mut self.transcript_rng
120    }
121
122    /// Build a random number generator from a transcript, optionally binding in witness data.
123    fn build_transcript_rng(
124        transcript: &Transcript,
125        witness: Option<&TriptychWitness>,
126        external_rng: &mut R,
127    ) -> TranscriptRng {
128        if let Some(witness) = witness {
129            transcript
130                .build_rng()
131                .rekey_with_witness_bytes(b"l", &witness.get_l().to_le_bytes())
132                .rekey_with_witness_bytes(b"r", witness.get_r().as_bytes())
133                .finalize(external_rng)
134        } else {
135            transcript.build_rng().finalize(external_rng)
136        }
137    }
138}