Skip to content
This repository was archived by the owner on Oct 17, 2022. It is now read-only.

Commit 6a28e2f

Browse files
authored
Update Ed25519Signature ser/de logic (#756)
* Update signature serde * fixup! Update signature serde * fix Ed25519Signature ser/de * add comment
1 parent edaab03 commit 6a28e2f

File tree

6 files changed

+139
-29
lines changed

6 files changed

+139
-29
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crypto/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ hkdf = { version = "0.12.3", features = ["std"] }
1616
rand = { version = "0.8.5", features = ["std"] }
1717
rust_secp256k1 = { version = "0.24.0", package = "secp256k1", features = ["recovery", "rand-std", "bitcoin_hashes", "global-context"] }
1818
serde = { version = "1.0.142", features = ["derive"] }
19+
serde_bytes = "0.11.7"
1920
serde_with = "2.0.0"
2021
signature = "1.6.0"
2122
tokio = { version = "1.20.1", features = ["sync", "rt", "macros"] }
2223
zeroize = "1.5.7"
2324

24-
# TODO: switch to https://github.com/celo-org/celo-bls-snark-rs
25+
# TODO: switch to https://github.com/celo-org/celo-bls-snark-rs
2526
# when https://github.com/celo-org/celo-bls-snark-rs/issues/228 is solved
2627
celo-bls = { git = "https://github.com/huitseeker/celo-bls-snark-rs", branch = "updates-2", package = "bls-crypto", optional = true }
2728

@@ -55,3 +56,4 @@ proptest = "1.0.0"
5556
proptest-derive = "0.3.0"
5657
serde_json = "1.0.83"
5758
sha3 = "0.10.2"
59+
serde-reflection = "0.3.6"

crypto/src/ed25519.rs

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
use base64ct::{Base64, Encoding};
44
use ed25519_consensus::{batch, VerificationKeyBytes};
55
use once_cell::sync::OnceCell;
6-
use serde::{de, Deserialize, Serialize};
6+
use serde::{
7+
de::{self, MapAccess, SeqAccess, Visitor},
8+
ser::SerializeStruct,
9+
Deserialize, Serialize,
10+
};
11+
use serde_bytes::{ByteBuf, Bytes};
712
use signature::{rand_core::OsRng, Signature, Signer, Verifier};
813
use std::{
914
fmt::{self, Display},
@@ -23,6 +28,9 @@ pub const ED25519_PRIVATE_KEY_LENGTH: usize = 32;
2328
pub const ED25519_PUBLIC_KEY_LENGTH: usize = 32;
2429
pub const ED25519_SIGNATURE_LENGTH: usize = 64;
2530

31+
const BASE64_FIELD_NAME: &str = "base64";
32+
const RAW_FIELD_NAME: &str = "raw";
33+
2634
///
2735
/// Define Structs
2836
///
@@ -46,6 +54,7 @@ pub struct Ed25519KeyPair {
4654
#[derive(Debug, Clone, PartialEq, Eq)]
4755
pub struct Ed25519Signature {
4856
pub sig: ed25519_consensus::Signature,
57+
// Helps implementing AsRef<[u8]>.
4958
pub bytes: OnceCell<[u8; ED25519_SIGNATURE_LENGTH]>,
5059
}
5160

@@ -246,16 +255,32 @@ impl Default for Ed25519Signature {
246255
}
247256
}
248257

258+
// Notes for Serialize and Deserialize implementations of Ed25519Signature:
259+
// - Since `bytes` field contains serialized `sig` field, it can be used directly for ser/de of
260+
// the Ed25519Signature struct.
261+
// - The `serialize_struct()` function and deserialization visitor add complexity, but they are necessary for
262+
// Ed25519Signature ser/de to work with `serde_reflection`.
263+
// `serde_reflection` works poorly [with aliases and nameless types](https://docs.rs/serde-reflection/latest/serde_reflection/index.html#unsupported-idioms).
264+
// - Serialization output and deserialization input support human readable (base64) and non-readable (binary) formats
265+
// separately (supported for other schemes since #460). Different struct field names ("base64" vs "raw") are used
266+
// to disambiguate the formats.
267+
// These notes may help if Ed25519Signature needs to change the struct layout, or its ser/de implementation.
249268
impl Serialize for Ed25519Signature {
250269
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
251270
where
252271
S: serde::Serializer,
253272
{
254-
if serializer.is_human_readable() {
255-
base64ct::Base64::encode_string(self.as_ref()).serialize(serializer)
273+
let readable = serializer.is_human_readable();
274+
let mut state = serializer.serialize_struct("Ed25519Signature", 1)?;
275+
if readable {
276+
state.serialize_field(
277+
BASE64_FIELD_NAME,
278+
&base64ct::Base64::encode_string(self.as_ref()),
279+
)?;
256280
} else {
257-
self.as_ref().serialize(serializer)
281+
state.serialize_field(RAW_FIELD_NAME, Bytes::new(self.as_ref()))?;
258282
}
283+
state.end()
259284
}
260285
}
261286

@@ -264,14 +289,73 @@ impl<'de> Deserialize<'de> for Ed25519Signature {
264289
where
265290
D: serde::Deserializer<'de>,
266291
{
267-
let bytes = if deserializer.is_human_readable() {
268-
let s = String::deserialize(deserializer)?;
269-
base64ct::Base64::decode_vec(&s).map_err(|e| de::Error::custom(e.to_string()))?
292+
struct Ed25519SignatureVisitor {
293+
readable: bool,
294+
}
295+
296+
impl<'de> Visitor<'de> for Ed25519SignatureVisitor {
297+
type Value = Ed25519Signature;
298+
299+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
300+
formatter.write_str("struct Ed25519Signature")
301+
}
302+
303+
fn visit_seq<V>(self, mut seq: V) -> Result<Ed25519Signature, V::Error>
304+
where
305+
V: SeqAccess<'de>,
306+
{
307+
if self.readable {
308+
let s = seq
309+
.next_element::<String>()?
310+
.ok_or_else(|| de::Error::missing_field(BASE64_FIELD_NAME))?;
311+
Ed25519Signature::decode_base64(&s)
312+
.map_err(|e| de::Error::custom(e.to_string()))
313+
} else {
314+
let b = seq
315+
.next_element::<ByteBuf>()?
316+
.ok_or_else(|| de::Error::missing_field(RAW_FIELD_NAME))?;
317+
<Ed25519Signature as Signature>::from_bytes(&b)
318+
.map_err(|e| de::Error::custom(e.to_string()))
319+
}
320+
}
321+
322+
fn visit_map<V>(self, mut map: V) -> Result<Ed25519Signature, V::Error>
323+
where
324+
V: MapAccess<'de>,
325+
{
326+
if self.readable {
327+
let entry = map
328+
.next_entry::<&str, String>()?
329+
.ok_or_else(|| de::Error::missing_field(BASE64_FIELD_NAME))?;
330+
if entry.0 != BASE64_FIELD_NAME {
331+
return Err(de::Error::unknown_field(entry.0, &[BASE64_FIELD_NAME]));
332+
}
333+
Ed25519Signature::decode_base64(&entry.1)
334+
.map_err(|e| de::Error::custom(e.to_string()))
335+
} else {
336+
let entry = map
337+
.next_entry::<&str, &[u8]>()?
338+
.ok_or_else(|| de::Error::missing_field(RAW_FIELD_NAME))?;
339+
if entry.0 != RAW_FIELD_NAME {
340+
return Err(de::Error::unknown_field(entry.0, &[RAW_FIELD_NAME]));
341+
}
342+
<Ed25519Signature as Signature>::from_bytes(entry.1)
343+
.map_err(|e| de::Error::custom(e.to_string()))
344+
}
345+
}
346+
}
347+
348+
let readable = deserializer.is_human_readable();
349+
let fields: &[&str; 1] = if readable {
350+
&[BASE64_FIELD_NAME]
270351
} else {
271-
Vec::deserialize(deserializer)?
352+
&[RAW_FIELD_NAME]
272353
};
273-
<Ed25519Signature as signature::Signature>::from_bytes(&bytes)
274-
.map_err(|e| de::Error::custom(e.to_string()))
354+
deserializer.deserialize_struct(
355+
"Ed25519Signature",
356+
fields,
357+
Ed25519SignatureVisitor { readable },
358+
)
275359
}
276360
}
277361

crypto/src/tests/ed25519_tests.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ use crate::{
1111
hkdf::hkdf_generate_from_ikm,
1212
traits::{AggregateAuthenticator, EncodeDecodeBase64, KeyPair, ToFromBytes, VerifyingKey},
1313
};
14-
1514
use blake2::digest::Update;
15+
use ed25519_consensus::VerificationKey;
1616
use rand::{rngs::StdRng, SeedableRng as _};
17+
use serde_reflection::{Samples, Tracer, TracerConfig};
1718
use sha3::Sha3_256;
1819
use signature::{Signer, Verifier};
1920

@@ -51,6 +52,38 @@ fn serialize_deserialize() {
5152
assert!(bincode::deserialize::<Ed25519PublicKey>(&bytes).is_err());
5253
}
5354

55+
#[test]
56+
fn custom_serde_reflection() {
57+
let config = TracerConfig::default()
58+
.record_samples_for_newtype_structs(true)
59+
.record_samples_for_structs(true)
60+
.record_samples_for_tuple_structs(true);
61+
let mut tracer = Tracer::new(config);
62+
let mut samples = Samples::new();
63+
64+
let message = b"hello, narwhal";
65+
let sig = keys().pop().unwrap().sign(message);
66+
tracer
67+
.trace_value(&mut samples, &sig)
68+
.expect("trace value Ed25519Signature");
69+
assert!(samples.value("Ed25519Signature").is_some());
70+
tracer
71+
.trace_type::<Ed25519Signature>(&samples)
72+
.expect("trace type Ed25519PublicKey");
73+
74+
let kpref = keys().pop().unwrap();
75+
let public_key = kpref.public();
76+
tracer
77+
.trace_value(&mut samples, public_key)
78+
.expect("trace value Ed25519PublicKey");
79+
assert!(samples.value("Ed25519PublicKey").is_some());
80+
// The Ed25519PublicKey struct and its ser/de implementation treats itself as a "newtype struct".
81+
// But `trace_type()` only supports the base type.
82+
tracer
83+
.trace_type::<VerificationKey>(&samples)
84+
.expect("trace type VerificationKey");
85+
}
86+
5487
#[test]
5588
fn test_serde_signatures_non_human_readable() {
5689
let message = b"hello, narwhal";
@@ -68,10 +101,9 @@ fn test_serde_signatures_human_readable() {
68101
let signature = kp.sign(message);
69102

70103
let serialized = serde_json::to_string(&signature).unwrap();
71-
println!("{:?}", serialized);
72104
assert_eq!(
73105
format!(
74-
"\"{}\"",
106+
r#"{{"base64":"{}"}}"#,
75107
base64ct::Base64::encode_string(&signature.sig.to_bytes())
76108
),
77109
serialized

node/src/generate_format.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -126,18 +126,6 @@ fn get_registry() -> Result<Registry> {
126126
tracer.trace_type::<HeaderDigest>(&samples)?;
127127
tracer.trace_type::<CertificateDigest>(&samples)?;
128128

129-
// Caveat: the following (trace_type) won't work, but not because of generics.
130-
//
131-
// Generic types instantiated multiple times in the same tracing session requires a work around.
132-
// https://docs.rs/serde-reflection/latest/serde_reflection/#features-and-limitations
133-
// but here we should be fine.
134-
//
135-
// This doesn't work because of the custom ser/de in PublicKey, which carries through to most top-level messages
136-
//
137-
// tracer.trace_type::<Header<Ed25519PublicKey>>(&samples)?;
138-
// tracer.trace_type::<Certificate<Ed25519PublicKey>>(&samples)?;
139-
// tracer.trace_type::<PrimaryWorkerMessage<Ed25519PublicKey>>(&samples)?;
140-
141129
// The final entry points that we must document
142130
tracer.trace_type::<WorkerPrimaryMessage>(&samples)?;
143131
tracer.trace_type::<WorkerPrimaryError>(&samples)?;

node/tests/staged/narwhal.yaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Certificate:
2626
SEQ:
2727
TUPLE:
2828
- TYPENAME: Ed25519PublicKey
29-
- SEQ: U8
29+
- TYPENAME: Ed25519Signature
3030
CertificateDigest:
3131
NEWTYPESTRUCT:
3232
TUPLEARRAY:
@@ -43,6 +43,9 @@ Committee:
4343
- epoch: U64
4444
Ed25519PublicKey:
4545
NEWTYPESTRUCT: STR
46+
Ed25519Signature:
47+
STRUCT:
48+
- raw: BYTES
4649
Header:
4750
STRUCT:
4851
- author:
@@ -60,7 +63,7 @@ Header:
6063
- id:
6164
TYPENAME: HeaderDigest
6265
- signature:
63-
SEQ: U8
66+
TYPENAME: Ed25519Signature
6467
HeaderDigest:
6568
NEWTYPESTRUCT:
6669
TUPLEARRAY:
@@ -152,4 +155,3 @@ WorkerPrimaryMessage:
152155
Reconfigure:
153156
NEWTYPE:
154157
TYPENAME: ReconfigureNotification
155-

0 commit comments

Comments
 (0)