Skip to content

Commit eaef18d

Browse files
Add Leaf and Protoleaf structs
Add a Protoleaf struct to represent the payload of an add-request call to a sigsum log, with corresponding from_ascii() and to_ascii() functions. Add a Leaf struct as well, for completion. (Note that leaves are only serialized in the get-leaves endpoint, where a single ascii document reperesents multiple leaves. Figuring out a proper interface to do this is TBD.) Add missing to_ascii() functions for SignedTreeHead, InclusionProof and SigsumSignature.
1 parent a37796d commit eaef18d

15 files changed

Lines changed: 343 additions & 208 deletions

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ sha2 = "0.10.8"
2020

2121
[dev-dependencies]
2222
hex-literal = "1.0.0"
23-
insta = "1.43.2"
23+
lazy_static = "1.5.0"
2424
serde = { version = "1.0.190", features = ["derive"] }
2525
serde_json = "1.0.107"

src/io/ascii/mod.rs

Lines changed: 264 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
mod parser;
22

3-
use crate::{Hash, InclusionProof, Signature, SignedTreeHead, SigsumSignature, WitnessCosignature};
3+
// This needs to be in scope to write!() to a String
4+
use std::fmt::Write as _;
5+
6+
use crate::{
7+
Hash, InclusionProof, Protoleaf, Signature, SignedTreeHead, SigsumSignature, WitnessCosignature,
8+
};
49
pub use parser::ParseAsciiError;
510
use parser::{Parser, Result};
611

@@ -27,6 +32,22 @@ impl SignedTreeHead {
2732
cosignatures,
2833
})
2934
}
35+
36+
#[allow(unused_must_use)]
37+
pub fn to_ascii(&self) -> String {
38+
let mut ascii = String::new();
39+
writeln!(ascii, "size={}", self.size);
40+
writeln!(ascii, "root_hash={:x}", self.root_hash);
41+
writeln!(ascii, "signature={:x}", self.signature);
42+
for cosig in self.cosignatures.iter() {
43+
writeln!(
44+
ascii,
45+
"cosignature={:x} {} {:x}",
46+
cosig.keyhash, cosig.timestamp, cosig.cosignature
47+
);
48+
}
49+
ascii
50+
}
3051
}
3152

3253
impl InclusionProof {
@@ -43,6 +64,39 @@ impl InclusionProof {
4364
node_hashes,
4465
})
4566
}
67+
68+
#[allow(unused_must_use)]
69+
pub fn to_ascii(&self) -> String {
70+
let mut ascii = String::new();
71+
writeln!(ascii, "leaf_index={}", self.leaf_index);
72+
for h in self.node_hashes.iter() {
73+
writeln!(ascii, "node_hash={h:x}");
74+
}
75+
ascii
76+
}
77+
}
78+
79+
impl Protoleaf {
80+
pub fn from_ascii(input: &str) -> Result<Self> {
81+
let mut p = Parser::new(input);
82+
let message = p.parse("message")?;
83+
let signature = p.parse("signature")?;
84+
let public_key = p.parse::<[u8; 32]>("public_key")?.into();
85+
Ok(Protoleaf {
86+
message,
87+
signature,
88+
public_key,
89+
})
90+
}
91+
92+
#[allow(unused_must_use)]
93+
pub fn to_ascii(&self) -> String {
94+
let mut ascii = String::new();
95+
writeln!(ascii, "message={:x}", self.message);
96+
writeln!(ascii, "signature={:x}", self.signature);
97+
writeln!(ascii, "public_key={:x}", self.public_key);
98+
ascii
99+
}
46100
}
47101

48102
impl SigsumSignature {
@@ -76,67 +130,239 @@ impl SigsumSignature {
76130
proof,
77131
})
78132
}
133+
134+
#[allow(unused_must_use)]
135+
pub fn to_ascii(&self) -> String {
136+
let mut ascii = String::new();
137+
writeln!(ascii, "version=2");
138+
writeln!(ascii, "log={:x}", self.log_keyhash);
139+
writeln!(
140+
ascii,
141+
"leaf={:x} {:x}",
142+
self.leaf_keyhash, self.leaf_signature
143+
);
144+
writeln!(ascii);
145+
writeln!(ascii, "size={}", self.sth.size);
146+
writeln!(ascii, "root_hash={:x}", self.sth.root_hash);
147+
writeln!(ascii, "signature={:x}", self.sth.signature);
148+
for cosig in self.sth.cosignatures.iter() {
149+
writeln!(
150+
ascii,
151+
"cosignature={:x} {} {:x}",
152+
cosig.keyhash, cosig.timestamp, cosig.cosignature
153+
);
154+
}
155+
writeln!(ascii);
156+
writeln!(ascii, "leaf_index={}", self.proof.leaf_index);
157+
for h in self.proof.node_hashes.iter() {
158+
writeln!(ascii, "node_hash={h:x}");
159+
}
160+
ascii
161+
}
79162
}
80163

81164
#[cfg(test)]
82165
mod tests {
83166
use super::*;
167+
use hex_literal::hex;
168+
use lazy_static::lazy_static;
169+
170+
lazy_static! {
171+
static ref PROTOLEAF: Protoleaf = Protoleaf {
172+
message: hex!("084c799cd551dd1d8d5c5f9a5d593b2e931f5e36122ee5c793c1d08a19839cc0").into(),
173+
signature: hex!("e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900").into(),
174+
public_key: hex!("16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245").into(),
175+
};
176+
}
177+
178+
const PROTOLEAF_ASCII: &str = "\
179+
message=084c799cd551dd1d8d5c5f9a5d593b2e931f5e36122ee5c793c1d08a19839cc0\n\
180+
signature=e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n\
181+
public_key=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245\n\
182+
";
183+
184+
#[test]
185+
fn protoleaf_from_ascii() {
186+
assert_eq!(*PROTOLEAF, Protoleaf::from_ascii(PROTOLEAF_ASCII).unwrap());
187+
}
188+
189+
#[test]
190+
fn protoleaf_to_ascii() {
191+
assert_eq!(PROTOLEAF_ASCII, (*PROTOLEAF).to_ascii());
192+
}
193+
194+
lazy_static!{
195+
static ref STH: SignedTreeHead = SignedTreeHead {
196+
size: 1285,
197+
root_hash: hex!("8100f29c0e9017a7512dab0911bf06a4b5b99cd77d8c710635307b5d217af1f6").into(),
198+
signature: hex!("e327fe13e5c3d2043cbf69fe1b778f77cb10a8e14fc09309dd375c9af25903f9ec35906cfb2c36ab2d210329eb538a6673487d2d101800370c978634b6f9f70d").into(),
199+
cosignatures: vec![
200+
WitnessCosignature {
201+
keyhash: hex!("1a450ecf1f49a4e4580c35e4d83316a74deda949dbb7d338e89d4315764d88de").into(),
202+
timestamp: 1687170591,
203+
cosignature: hex!("cacc54d315609b796f72ac1d71d1bbc15667853ed980bd3e0f957de7a875b84bd2dcde6489fc3ed66428190ce588ac1061b0d5748e73cfb887ebf38d0b53060a").into(),
204+
},
205+
WitnessCosignature {
206+
keyhash: hex!("73b6cbe5e3c8e679fb5967b78c59e95db2969a5c13b3423b5e69523e3d52f531").into(),
207+
timestamp: 1687170591,
208+
cosignature: hex!("7f568da17c57ea322a9c2668ae9fc2c1d6ab5556d9a997e7bfa1cbc4dc5cf7b94e0cead42d481bf0d3d90ad2ee0d272e9e687f8f82fddf76d37d722c6815fe0f").into(),
209+
},
210+
],
211+
};
212+
}
213+
214+
const STH_ASCII: &str = "\
215+
size=1285\n\
216+
root_hash=8100f29c0e9017a7512dab0911bf06a4b5b99cd77d8c710635307b5d217af1f6\n\
217+
signature=e327fe13e5c3d2043cbf69fe1b778f77cb10a8e14fc09309dd375c9af25903f9ec35906cfb2c36ab2d210329eb538a6673487d2d101800370c978634b6f9f70d\n\
218+
cosignature=1a450ecf1f49a4e4580c35e4d83316a74deda949dbb7d338e89d4315764d88de 1687170591 cacc54d315609b796f72ac1d71d1bbc15667853ed980bd3e0f957de7a875b84bd2dcde6489fc3ed66428190ce588ac1061b0d5748e73cfb887ebf38d0b53060a\n\
219+
cosignature=73b6cbe5e3c8e679fb5967b78c59e95db2969a5c13b3423b5e69523e3d52f531 1687170591 7f568da17c57ea322a9c2668ae9fc2c1d6ab5556d9a997e7bfa1cbc4dc5cf7b94e0cead42d481bf0d3d90ad2ee0d272e9e687f8f82fddf76d37d722c6815fe0f\n\
220+
";
84221

85222
#[test]
86223
fn sth_from_ascii() {
87-
insta::assert_debug_snapshot!(SignedTreeHead::from_ascii(include_str!(
88-
"testdata/tree-head.ascii"
89-
))
90-
.unwrap());
224+
assert_eq!(*STH, SignedTreeHead::from_ascii(STH_ASCII).unwrap());
91225
}
92226

93227
#[test]
94-
fn inclusionproof_from_ascii() {
95-
insta::assert_debug_snapshot!(InclusionProof::from_ascii(include_str!(
96-
"testdata/inclusion-proof.ascii"
97-
))
98-
.unwrap());
228+
fn sth_to_ascii() {
229+
assert_eq!(STH_ASCII, (*STH).to_ascii());
230+
}
231+
232+
lazy_static! {
233+
static ref PROOF: InclusionProof = InclusionProof {
234+
leaf_index: 2,
235+
node_hashes: vec![
236+
hex!("35fd6eb70d46d60679775c346225688e6e84c02c3c7978e5c51daf8decc22d2f").into(),
237+
hex!("11a2b46fb34efed4abbd144f8666bda8b83ee2ee6f7685062ed5cd68d616412a").into(),
238+
],
239+
};
240+
}
241+
242+
const PROOF_ASCII: &str = "\
243+
leaf_index=2\n\
244+
node_hash=35fd6eb70d46d60679775c346225688e6e84c02c3c7978e5c51daf8decc22d2f\n\
245+
node_hash=11a2b46fb34efed4abbd144f8666bda8b83ee2ee6f7685062ed5cd68d616412a\n\
246+
";
247+
248+
#[test]
249+
fn proof_from_ascii() {
250+
assert_eq!(*PROOF, InclusionProof::from_ascii(PROOF_ASCII).unwrap());
99251
}
100252

253+
#[test]
254+
fn proof_to_ascii() {
255+
assert_eq!(PROOF_ASCII, (*PROOF).to_ascii());
256+
}
257+
258+
lazy_static! {
259+
static ref SSIG: SigsumSignature = SigsumSignature {
260+
log_keyhash: hex!("4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d")
261+
.into(),
262+
leaf_keyhash: hex!("16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245").into(),
263+
leaf_signature: hex!("e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900").into(),
264+
sth: (*STH).clone(),
265+
proof: (*PROOF).clone(),
266+
};
267+
}
268+
269+
const SSIG_ASCII:&str = "\
270+
version=2\n\
271+
log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n\
272+
leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n\
273+
\n\
274+
size=1285\n\
275+
root_hash=8100f29c0e9017a7512dab0911bf06a4b5b99cd77d8c710635307b5d217af1f6\n\
276+
signature=e327fe13e5c3d2043cbf69fe1b778f77cb10a8e14fc09309dd375c9af25903f9ec35906cfb2c36ab2d210329eb538a6673487d2d101800370c978634b6f9f70d\n\
277+
cosignature=1a450ecf1f49a4e4580c35e4d83316a74deda949dbb7d338e89d4315764d88de 1687170591 cacc54d315609b796f72ac1d71d1bbc15667853ed980bd3e0f957de7a875b84bd2dcde6489fc3ed66428190ce588ac1061b0d5748e73cfb887ebf38d0b53060a\n\
278+
cosignature=73b6cbe5e3c8e679fb5967b78c59e95db2969a5c13b3423b5e69523e3d52f531 1687170591 7f568da17c57ea322a9c2668ae9fc2c1d6ab5556d9a997e7bfa1cbc4dc5cf7b94e0cead42d481bf0d3d90ad2ee0d272e9e687f8f82fddf76d37d722c6815fe0f\n\
279+
\n\
280+
leaf_index=2\n\
281+
node_hash=35fd6eb70d46d60679775c346225688e6e84c02c3c7978e5c51daf8decc22d2f\n\
282+
node_hash=11a2b46fb34efed4abbd144f8666bda8b83ee2ee6f7685062ed5cd68d616412a\n\
283+
";
284+
101285
#[test]
102286
fn sigsumsig_from_ascii() {
103-
insta::assert_debug_snapshot!(SigsumSignature::from_ascii(include_str!(
104-
"testdata/sigsum-signature.ascii"
105-
))
106-
.unwrap());
287+
assert_eq!(*SSIG, SigsumSignature::from_ascii(SSIG_ASCII).unwrap());
288+
}
289+
290+
#[test]
291+
fn sigsumsig_to_ascii() {
292+
assert_eq!(SSIG_ASCII, (*SSIG).to_ascii());
107293
}
108294

109-
macro_rules! test_parse_error {
110-
($testname:ident, $file:literal, $errormsg:literal) => {
295+
macro_rules! test_ssig_parse_error {
296+
($testname:ident, $errormsg:literal, $input:literal) => {
111297
#[test]
112298
fn $testname() {
113-
let input = include_str!($file);
114299
assert_eq!(
115300
$errormsg,
116-
SigsumSignature::from_ascii(input).unwrap_err().to_string()
301+
SigsumSignature::from_ascii($input).unwrap_err().to_string()
117302
);
118303
}
119304
};
120305
}
121306

122-
test_parse_error!(
123-
sigsum_signature_missing_part,
124-
"testdata/sigsum-signature_missing-part.ascii",
125-
"parse error: expected 3 parts, got 2"
126-
);
127-
test_parse_error!(
128-
sigsum_signature_too_many_parts,
129-
"testdata/sigsum-signature_too-many-parts.ascii",
130-
"parse error: expected 3 parts, got 4"
131-
);
132-
test_parse_error!(
133-
sigsum_signature_version_not_supported,
134-
"testdata/sigsum-signature_version-not-supported.ascii",
135-
"parse error: version 666 not supported"
136-
);
137-
test_parse_error!(
138-
sigsum_signature_extra_line,
139-
"testdata/sigsum-signature_extra-line.ascii",
140-
"parse error: expected an empty line after 'leaf'"
141-
);
307+
test_ssig_parse_error! {
308+
ssig_missing_part,
309+
"expected 3 parts, got 2",
310+
"\
311+
version=2\n\
312+
log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n\
313+
leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n\
314+
\n\
315+
size=4898\n\
316+
root_hash=5a2221ac3be281d2403abb44f9b6a8b1b1b2db97e5838d029a2df19db5e708bd\n\
317+
signature=67de0c13f8ae8a13ca2ad2aaab35326ba55186d240cbb48f4872a79847a41b5d2b43820dc8f4ae43030d432bbf753c365ba7a248fc12ea7be53f05bb72829409\n\
318+
"
319+
}
320+
321+
test_ssig_parse_error! { sigsum_signature_too_many_parts,
322+
"expected 3 parts, got 4",
323+
"\
324+
version=2\n\
325+
log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n\
326+
leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n\
327+
\n\
328+
size=1\n\
329+
root_hash=5a2221ac3be281d2403abb44f9b6a8b1b1b2db97e5838d029a2df19db5e708bd\n\
330+
signature=67de0c13f8ae8a13ca2ad2aaab35326ba55186d240cbb48f4872a79847a41b5d2b43820dc8f4ae43030d432bbf753c365ba7a248fc12ea7be53f05bb72829409\n\
331+
\n\
332+
leaf_index=1\n\
333+
\n\
334+
abc=123\n\
335+
"
336+
}
337+
338+
test_ssig_parse_error! { sigsum_signature_version_not_supported,
339+
"version 666 not supported",
340+
"\
341+
version=666\n\
342+
log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n\
343+
leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n\
344+
\n\
345+
size=1\n\
346+
root_hash=5a2221ac3be281d2403abb44f9b6a8b1b1b2db97e5838d029a2df19db5e708bd\n\
347+
signature=67de0c13f8ae8a13ca2ad2aaab35326ba55186d240cbb48f4872a79847a41b5d2b43820dc8f4ae43030d432bbf753c365ba7a248fc12ea7be53f05bb72829409\n\
348+
\n\
349+
leaf_index=1\n\
350+
"
351+
}
352+
353+
test_ssig_parse_error! { sigsum_signature_extra_line,
354+
"expected an empty line after 'leaf'",
355+
"\
356+
version=2\n\
357+
log=4e89cc51651f0d95f3c6127c15e1a42e3ddf7046c5b17b752689c402e773bb4d\n\
358+
leaf=16e8e5005d909e941b34d040b646dba5b6608e5e3353c7db860f9b8849eae245 e82c007b640da6375657e95bd9e3768017cd9b9478dffb7496a7de69b2e8608ceb35d336b44a3b1a3b0d4493cbc694bf87daf279684473024557dd427d34d900\n\
359+
abc=123\n\
360+
\n\
361+
size=1\n\
362+
root_hash=5a2221ac3be281d2403abb44f9b6a8b1b1b2db97e5838d029a2df19db5e708bd\n\
363+
signature=67de0c13f8ae8a13ca2ad2aaab35326ba55186d240cbb48f4872a79847a41b5d2b43820dc8f4ae43030d432bbf753c365ba7a248fc12ea7be53f05bb72829409\n\
364+
\n\
365+
leaf_index=1\n\
366+
"
367+
}
142368
}

0 commit comments

Comments
 (0)