Skip to main content

sl_transcript/
lib.rs

1#![no_std]
2
3use elliptic_curve::{
4    ff::PrimeField,
5    ops::Reduce,
6    sec1::{ModulusSize, ToEncodedPoint},
7    CurveArithmetic, FieldBytes,
8};
9
10#[cfg(feature = "merlin")]
11pub use merlin_imp::Transcript;
12
13#[cfg(all(not(feature = "merlin"), feature = "shake128"))]
14pub use shake128::Transcript;
15
16/// Common transcript interface used by protocol code.
17///
18/// Implementations provide labeled absorption and challenge generation.
19/// The primitive methods are `new`, `append_message`, `append_u64`, and
20/// `challenge_bytes`; the curve helpers have default implementations.
21pub trait TranscriptProtocol {
22    /// Create a new transcript with the given domain-separation label.
23    fn new(label: &'static [u8]) -> Self
24    where
25        Self: Sized;
26
27    /// Absorb an arbitrary byte string under a label.
28    fn append_message(&mut self, label: &'static [u8], message: &[u8]);
29
30    /// Absorb a `u64` value under a label.
31    fn append_u64(&mut self, label: &'static [u8], value: u64);
32
33    /// Fill `dest` with challenge bytes derived from the transcript state.
34    fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]);
35
36    /// Absorb an elliptic-curve point in compressed form.
37    fn append_point<C>(
38        &mut self,
39        label: &'static [u8],
40        point: &C::ProjectivePoint,
41    ) where
42        C: CurveArithmetic,
43        C::FieldBytesSize: ModulusSize,
44        C::ProjectivePoint: ToEncodedPoint<C>,
45    {
46        self.append_message(label, point.to_encoded_point(true).as_ref())
47    }
48
49    /// Absorb an elliptic-curve scalar in canonical representation.
50    fn append_scalar<C>(&mut self, label: &'static [u8], scalar: &C::Scalar)
51    where
52        C: CurveArithmetic,
53        C::FieldBytesSize: ModulusSize,
54        C::ProjectivePoint: ToEncodedPoint<C>,
55    {
56        let bytes = scalar.to_repr();
57        self.append_message(label, bytes.as_ref())
58    }
59
60    /// Derive a scalar challenge from the transcript.
61    fn challenge_scalar<C>(&mut self, label: &'static [u8]) -> C::Scalar
62    where
63        C: CurveArithmetic,
64        C::FieldBytesSize: ModulusSize,
65        C::ProjectivePoint: ToEncodedPoint<C>,
66    {
67        let mut buf = FieldBytes::<C>::default();
68        self.challenge_bytes(label, &mut buf);
69        C::Scalar::reduce_bytes(&buf)
70    }
71
72    /// Construct the transcript context used by DLog proofs.
73    fn new_dlog_proof(
74        session_id: &[u8],
75        party_id: usize,
76        action: &[u8],
77        label: &'static [u8],
78    ) -> Self
79    where
80        Self: Sized,
81    {
82        let mut transcript = Self::new(label);
83        transcript.append_message(b"session_id", session_id.as_ref());
84        transcript.append_u64(b"party_id", party_id as u64);
85        transcript.append_message(b"action", action);
86        transcript
87    }
88}
89
90#[cfg(feature = "merlin")]
91mod merlin_imp {
92    use super::*;
93
94    pub struct Transcript(merlin::Transcript);
95
96    impl Transcript {
97        pub fn new(label: &'static [u8]) -> Self {
98            Self(merlin::Transcript::new(label))
99        }
100    }
101
102    impl TranscriptProtocol for Transcript {
103        fn new(label: &'static [u8]) -> Self {
104            Self::new(label)
105        }
106
107        fn append_message(&mut self, label: &'static [u8], message: &[u8]) {
108            self.0.append_message(label, message);
109        }
110
111        fn append_u64(&mut self, label: &'static [u8], value: u64) {
112            self.0.append_u64(label, value)
113        }
114
115        fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]) {
116            self.0.challenge_bytes(label, dest);
117        }
118    }
119}
120
121#[cfg(feature = "shake128")]
122mod shake128 {
123    use super::TranscriptProtocol;
124
125    use sha3::{
126        digest::{ExtendableOutput, Update, XofReader},
127        Shake128,
128    };
129
130    pub struct Transcript(Shake128);
131
132    impl Transcript {
133        pub fn new(label: &'static [u8]) -> Self {
134            let mut t = Shake128::default();
135            t.update(&(label.len() as u64).to_le_bytes());
136            t.update(label);
137            Self(t)
138        }
139    }
140
141    impl TranscriptProtocol for Transcript {
142        fn new(label: &'static [u8]) -> Self {
143            Self::new(label)
144        }
145
146        fn append_message(&mut self, label: &'static [u8], message: &[u8]) {
147            self.0.update(&(label.len() as u64).to_le_bytes());
148            self.0.update(label);
149            self.0.update(&(message.len() as u64).to_le_bytes());
150            self.0.update(message);
151        }
152
153        fn append_u64(&mut self, label: &'static [u8], value: u64) {
154            self.append_message(label, &value.to_le_bytes());
155        }
156
157        fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]) {
158            self.0.update(&(label.len() as u32).to_le_bytes());
159            self.0.update(label);
160            self.0.update(&(dest.len() as u32).to_le_bytes());
161
162            self.0.clone().finalize_xof().read(dest);
163        }
164    }
165}