flexible_transcript/
lib.rs1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3#![no_std]
4
5#[allow(unused_imports)]
6use std_shims::prelude::*;
7
8use zeroize::Zeroize;
9
10use digest::{
11 typenum::{
12 consts::U32, marker_traits::NonZero, type_operators::IsGreaterOrEqual, operator_aliases::GrEq,
13 },
14 core_api::BlockSizeUser,
15 Digest, Output, HashMarker,
16};
17
18#[cfg(feature = "merlin")]
19mod merlin;
20#[cfg(feature = "merlin")]
21pub use crate::merlin::MerlinTranscript;
22
23#[cfg(any(test, feature = "tests"))]
25pub mod tests;
26
27pub trait Transcript: Send + Clone {
29 type Challenge: Send + Sync + Clone + AsRef<[u8]>;
30
31 fn new(name: &'static [u8]) -> Self;
33
34 fn domain_separate(&mut self, label: &'static [u8]);
36
37 fn append_message<M: AsRef<[u8]>>(&mut self, label: &'static [u8], message: M);
39
40 fn challenge(&mut self, label: &'static [u8]) -> Self::Challenge;
45
46 fn rng_seed(&mut self, label: &'static [u8]) -> [u8; 32];
54}
55
56#[derive(Clone, Copy)]
57enum DigestTranscriptMember {
58 Name,
59 Domain,
60 Label,
61 Value,
62 Challenge,
63 Continued,
64 Challenged,
65}
66
67impl DigestTranscriptMember {
68 fn as_u8(&self) -> u8 {
69 match self {
70 DigestTranscriptMember::Name => 0,
71 DigestTranscriptMember::Domain => 1,
72 DigestTranscriptMember::Label => 2,
73 DigestTranscriptMember::Value => 3,
74 DigestTranscriptMember::Challenge => 4,
75 DigestTranscriptMember::Continued => 5,
76 DigestTranscriptMember::Challenged => 6,
77 }
78 }
79}
80
81pub trait SecureDigest: Digest + HashMarker {}
84impl<D: Digest + HashMarker> SecureDigest for D
85where
86 D::OutputSize: IsGreaterOrEqual<U32>,
88 GrEq<D::OutputSize, U32>: NonZero,
91{
92}
93
94#[derive(Clone, Debug)]
96pub struct DigestTranscript<D: Send + Clone + SecureDigest>(D);
97
98impl<D: Send + Clone + SecureDigest> DigestTranscript<D> {
99 fn append(&mut self, kind: DigestTranscriptMember, value: &[u8]) {
100 self.0.update([kind.as_u8()]);
101 self.0.update(u64::try_from(value.len()).unwrap().to_le_bytes());
103 self.0.update(value);
104 }
105}
106
107impl<D: Send + Clone + SecureDigest> Transcript for DigestTranscript<D> {
108 type Challenge = Output<D>;
109
110 fn new(name: &'static [u8]) -> Self {
111 let mut res = DigestTranscript(D::new());
112 res.append(DigestTranscriptMember::Name, name);
113 res
114 }
115
116 fn domain_separate(&mut self, label: &'static [u8]) {
117 self.append(DigestTranscriptMember::Domain, label);
118 }
119
120 fn append_message<M: AsRef<[u8]>>(&mut self, label: &'static [u8], message: M) {
121 self.append(DigestTranscriptMember::Label, label);
122 self.append(DigestTranscriptMember::Value, message.as_ref());
123 }
124
125 fn challenge(&mut self, label: &'static [u8]) -> Self::Challenge {
126 self.append(DigestTranscriptMember::Challenge, label);
127 let mut cloned = self.0.clone();
128
129 self.0.update([DigestTranscriptMember::Continued.as_u8()]);
132 cloned.update([DigestTranscriptMember::Challenged.as_u8()]);
133 cloned.finalize()
134 }
135
136 fn rng_seed(&mut self, label: &'static [u8]) -> [u8; 32] {
137 let mut seed = [0; 32];
138 seed.copy_from_slice(&self.challenge(label)[.. 32]);
139 seed
140 }
141}
142
143impl<D: Send + Clone + SecureDigest> Zeroize for DigestTranscript<D>
147where
148 D: BlockSizeUser,
149{
150 fn zeroize(&mut self) {
151 const WORD_SIZE: usize = 4;
153
154 let words = D::block_size().div_ceil(WORD_SIZE);
157 for _ in 0 .. (2 * words) {
158 self.0.update([255; WORD_SIZE]);
159 }
160
161 fn mark_read<D: Send + Clone + SecureDigest>(transcript: &DigestTranscript<D>) {
166 let mut challenge = core::hint::black_box(transcript.0.clone().finalize());
168 challenge.as_mut().zeroize();
169 }
170
171 mark_read(self)
172 }
173}
174
175#[cfg(feature = "recommended")]
177pub type RecommendedTranscript = DigestTranscript<blake2::Blake2b512>;