Skip to content

Commit 613d7dc

Browse files
committed
Merge #406: Use fixed width serde impls for keys
3ca7f49 Add fixed-width-serde integration tests (Tobin Harding) bf9f556 Add rustdocs describing fixed width serde (Tobin Harding) c28808c Improve rustdocs for KeyPair (Tobin Harding) 6842383 Use fixed width serde impls for keys (Tobin Harding) Pull request description: Currently we serialize keys using the `BytesVisitor`, this causes the serialized data to contain additional metadata encoding the length (an extra 8 bytes) when serialized with [bincode.](https://docs.rs/bincode/latest/bincode/index.html). This extra data is unnecessary since we know in advance the length of these two types. We do not control the data output by serialization of our types because it depends on which crate is used to do the serialization. This PR improves the situation for serialization using the `bincode` crate, and this PR introduces mentions of `bincode` in the rustdocs, is this acceptable? See below for a table that describes binary serialization by other crates. Implement a sequence based visitor that encodes the keys as fixed width data for: - `SecretKey` - `PublicKey` - `KeyPair` - `XOnlyPublicKey` Fixes: #295 **Question**: PR only does keys, do we want to do signatures as well? ACKs for top commit: apoelstra: ACK 3ca7f49 Tree-SHA512: 77babce74fa9f0981bb3b869c4e77a68a4d1ec28d22d2c3be4305e27ef01d4828dac210e20b968cbbe5de8a0563cd985d7969bccf75cfe627a34a116fed1a5df
2 parents 73ad30d + 3ca7f49 commit 613d7dc

File tree

5 files changed

+282
-36
lines changed

5 files changed

+282
-36
lines changed

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ rand = "0.8"
5050
rand_core = "0.6"
5151
serde_test = "1.0"
5252
bitcoin_hashes = "0.10"
53+
bincode = "1.3.3"
54+
55+
# cbor does not build on WASM, we use it in a single trivial test (an example of when
56+
# fixed-width-serde breaks down). Just run the test when on an x86_64 machine.
57+
[target.'cfg(target_arch = "x86_64")'.dependencies]
58+
cbor = "0.4.1"
5359

5460
[target.wasm32-unknown-unknown.dev-dependencies]
5561
wasm-bindgen-test = "0.3"

src/key.rs

Lines changed: 128 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ use crate::Error::{self, InvalidPublicKey, InvalidPublicKeySum, InvalidSecretKey
2525
use crate::ffi::{self, CPtr, impl_array_newtype};
2626
use crate::ffi::types::c_uint;
2727

28+
#[cfg(feature = "serde")]
29+
use serde::ser::SerializeTuple;
30+
2831
#[cfg(feature = "global-context")]
2932
use crate::{Message, ecdsa, SECP256K1};
3033
#[cfg(all(feature = "global-context", feature = "rand-std"))]
@@ -33,6 +36,12 @@ use crate::Scalar;
3336

3437
/// Secret 256-bit key used as `x` in an ECDSA signature.
3538
///
39+
/// # Serde support
40+
///
41+
/// Implements de/serialization with the `serde` feature enabled. We treat the byte value as a tuple
42+
/// of 32 `u8`s for non-human-readable formats. This representation is optimal for for some formats
43+
/// (e.g. [`bincode`]) however other formats may be less optimal (e.g. [`cbor`]).
44+
///
3645
/// # Examples
3746
///
3847
/// Basic usage:
@@ -45,6 +54,8 @@ use crate::Scalar;
4554
/// let secret_key = SecretKey::new(&mut rand::thread_rng());
4655
/// # }
4756
/// ```
57+
/// [`bincode`]: https://docs.rs/bincode
58+
/// [`cbor`]: https://docs.rs/cbor
4859
pub struct SecretKey([u8; constants::SECRET_KEY_SIZE]);
4960
impl_array_newtype!(SecretKey, u8, constants::SECRET_KEY_SIZE);
5061
impl_display_secret!(SecretKey);
@@ -68,6 +79,12 @@ pub const ONE_KEY: SecretKey = SecretKey([0, 0, 0, 0, 0, 0, 0, 0,
6879

6980
/// A Secp256k1 public key, used for verification of signatures.
7081
///
82+
/// # Serde support
83+
///
84+
/// Implements de/serialization with the `serde` feature enabled. We treat the byte value as a tuple
85+
/// of 33 `u8`s for non-human-readable formats. This representation is optimal for for some formats
86+
/// (e.g. [`bincode`]) however other formats may be less optimal (e.g. [`cbor`]).
87+
///
7188
/// # Examples
7289
///
7390
/// Basic usage:
@@ -81,6 +98,8 @@ pub const ONE_KEY: SecretKey = SecretKey([0, 0, 0, 0, 0, 0, 0, 0,
8198
/// let public_key = PublicKey::from_secret_key(&secp, &secret_key);
8299
/// # }
83100
/// ```
101+
/// [`bincode`]: https://docs.rs/bincode
102+
/// [`cbor`]: https://docs.rs/cbor
84103
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
85104
#[repr(transparent)]
86105
pub struct PublicKey(ffi::PublicKey);
@@ -315,7 +334,11 @@ impl serde::Serialize for SecretKey {
315334
let mut buf = [0u8; constants::SECRET_KEY_SIZE * 2];
316335
s.serialize_str(crate::to_hex(&self.0, &mut buf).expect("fixed-size hex serialization"))
317336
} else {
318-
s.serialize_bytes(&self[..])
337+
let mut tuple = s.serialize_tuple(constants::SECRET_KEY_SIZE)?;
338+
for byte in self.0.iter() {
339+
tuple.serialize_element(byte)?;
340+
}
341+
tuple.end()
319342
}
320343
}
321344
}
@@ -329,10 +352,11 @@ impl<'de> serde::Deserialize<'de> for SecretKey {
329352
"a hex string representing 32 byte SecretKey"
330353
))
331354
} else {
332-
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
355+
let visitor = super::serde_util::Tuple32Visitor::new(
333356
"raw 32 bytes SecretKey",
334357
SecretKey::from_slice
335-
))
358+
);
359+
d.deserialize_tuple(constants::SECRET_KEY_SIZE, visitor)
336360
}
337361
}
338362
}
@@ -649,7 +673,11 @@ impl serde::Serialize for PublicKey {
649673
if s.is_human_readable() {
650674
s.collect_str(self)
651675
} else {
652-
s.serialize_bytes(&self.serialize())
676+
let mut tuple = s.serialize_tuple(constants::PUBLIC_KEY_SIZE)?;
677+
for byte in self.serialize().iter() { // Serialize in compressed form.
678+
tuple.serialize_element(&byte)?;
679+
}
680+
tuple.end()
653681
}
654682
}
655683
}
@@ -663,10 +691,11 @@ impl<'de> serde::Deserialize<'de> for PublicKey {
663691
"an ASCII hex string representing a public key"
664692
))
665693
} else {
666-
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
667-
"a bytestring representing a public key",
694+
let visitor = super::serde_util::Tuple33Visitor::new(
695+
"33 bytes compressed public key",
668696
PublicKey::from_slice
669-
))
697+
);
698+
d.deserialize_tuple(constants::PUBLIC_KEY_SIZE, visitor)
670699
}
671700
}
672701
}
@@ -687,13 +716,10 @@ impl Ord for PublicKey {
687716
///
688717
/// # Serde support
689718
///
690-
/// [`Serialize`] and [`Deserialize`] are not implemented for this type, even with the `serde`
691-
/// feature active. This is due to security considerations, see the [`serde_keypair`] documentation
692-
/// for details.
693-
///
694-
/// If the `serde` and `global-context` features are active `KeyPair`s can be serialized and
695-
/// deserialized by annotating them with `#[serde(with = "secp256k1::serde_keypair")]`
696-
/// inside structs or enums for which [`Serialize`] and [`Deserialize`] are being derived.
719+
/// Implements de/serialization with the `serde` and_`global-context` features enabled. Serializes
720+
/// the secret bytes only. We treat the byte value as a tuple of 32 `u8`s for non-human-readable
721+
/// formats. This representation is optimal for for some formats (e.g. [`bincode`]) however other
722+
/// formats may be less optimal (e.g. [`cbor`]). For human-readable formats we use a hex string.
697723
///
698724
/// # Examples
699725
///
@@ -708,8 +734,8 @@ impl Ord for PublicKey {
708734
/// let key_pair = KeyPair::from_secret_key(&secp, &secret_key);
709735
/// # }
710736
/// ```
711-
/// [`Deserialize`]: serde::Deserialize
712-
/// [`Serialize`]: serde::Serialize
737+
/// [`bincode`]: https://docs.rs/bincode
738+
/// [`cbor`]: https://docs.rs/cbor
713739
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
714740
pub struct KeyPair(ffi::KeyPair);
715741
impl_display_secret!(KeyPair);
@@ -966,7 +992,11 @@ impl serde::Serialize for KeyPair {
966992
s.serialize_str(crate::to_hex(&self.secret_bytes(), &mut buf)
967993
.expect("fixed-size hex serialization"))
968994
} else {
969-
s.serialize_bytes(&self.0[..])
995+
let mut tuple = s.serialize_tuple(constants::SECRET_KEY_SIZE)?;
996+
for byte in self.secret_bytes().iter() {
997+
tuple.serialize_element(&byte)?;
998+
}
999+
tuple.end()
9701000
}
9711001
}
9721002
}
@@ -980,19 +1010,26 @@ impl<'de> serde::Deserialize<'de> for KeyPair {
9801010
"a hex string representing 32 byte KeyPair"
9811011
))
9821012
} else {
983-
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
1013+
let visitor = super::serde_util::Tuple32Visitor::new(
9841014
"raw 32 bytes KeyPair",
9851015
|data| unsafe {
9861016
let ctx = Secp256k1::from_raw_all(ffi::secp256k1_context_no_precomp as *mut ffi::Context);
9871017
KeyPair::from_seckey_slice(&ctx, data)
9881018
}
989-
))
1019+
);
1020+
d.deserialize_tuple(constants::SECRET_KEY_SIZE, visitor)
9901021
}
9911022
}
9921023
}
9931024

9941025
/// An x-only public key, used for verification of Schnorr signatures and serialized according to BIP-340.
9951026
///
1027+
/// # Serde support
1028+
///
1029+
/// Implements de/serialization with the `serde` feature enabled. We treat the byte value as a tuple
1030+
/// of 32 `u8`s for non-human-readable formats. This representation is optimal for for some formats
1031+
/// (e.g. [`bincode`]) however other formats may be less optimal (e.g. [`cbor`]).
1032+
///
9961033
/// # Examples
9971034
///
9981035
/// Basic usage:
@@ -1006,6 +1043,8 @@ impl<'de> serde::Deserialize<'de> for KeyPair {
10061043
/// let xonly = XOnlyPublicKey::from_keypair(&key_pair);
10071044
/// # }
10081045
/// ```
1046+
/// [`bincode`]: https://docs.rs/bincode
1047+
/// [`cbor`]: https://docs.rs/cbor
10091048
#[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)]
10101049
pub struct XOnlyPublicKey(ffi::XOnlyPublicKey);
10111050

@@ -1429,7 +1468,11 @@ impl serde::Serialize for XOnlyPublicKey {
14291468
if s.is_human_readable() {
14301469
s.collect_str(self)
14311470
} else {
1432-
s.serialize_bytes(&self.serialize())
1471+
let mut tuple = s.serialize_tuple(constants::SCHNORR_PUBLIC_KEY_SIZE)?;
1472+
for byte in self.serialize().iter() {
1473+
tuple.serialize_element(&byte)?;
1474+
}
1475+
tuple.end()
14331476
}
14341477
}
14351478
}
@@ -1443,10 +1486,11 @@ impl<'de> serde::Deserialize<'de> for XOnlyPublicKey {
14431486
"a hex string representing 32 byte schnorr public key"
14441487
))
14451488
} else {
1446-
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
1489+
let visitor = super::serde_util::Tuple32Visitor::new(
14471490
"raw 32 bytes schnorr public key",
14481491
XOnlyPublicKey::from_slice
1449-
))
1492+
);
1493+
d.deserialize_tuple(constants::SCHNORR_PUBLIC_KEY_SIZE, visitor)
14501494
}
14511495
}
14521496
}
@@ -1492,6 +1536,8 @@ pub mod serde_keypair {
14921536
#[cfg(test)]
14931537
#[allow(unused_imports)]
14941538
mod test {
1539+
use super::*;
1540+
14951541
use core::str::FromStr;
14961542

14971543
#[cfg(any(feature = "alloc", feature = "std"))]
@@ -1949,6 +1995,7 @@ mod test {
19491995
static SK_STR: &'static str = "\
19501996
01010101010101010001020304050607ffff0000ffff00006363636363636363\
19511997
";
1998+
#[cfg(fuzzing)]
19521999
static PK_BYTES: [u8; 33] = [
19532000
0x02,
19542001
0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f,
@@ -1971,22 +2018,32 @@ mod test {
19712018
#[cfg(fuzzing)]
19722019
let pk = PublicKey::from_slice(&PK_BYTES).expect("pk");
19732020

1974-
assert_tokens(&sk.compact(), &[Token::BorrowedBytes(&SK_BYTES[..])]);
1975-
assert_tokens(&sk.compact(), &[Token::Bytes(&SK_BYTES)]);
1976-
assert_tokens(&sk.compact(), &[Token::ByteBuf(&SK_BYTES)]);
2021+
assert_tokens(&sk.compact(), &[
2022+
Token::Tuple{ len: 32 },
2023+
Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1),
2024+
Token::U8(0), Token::U8(1), Token::U8(2), Token::U8(3), Token::U8(4), Token::U8(5), Token::U8(6), Token::U8(7),
2025+
Token::U8(0xff), Token::U8(0xff), Token::U8(0), Token::U8(0), Token::U8(0xff), Token::U8(0xff), Token::U8(0), Token::U8(0),
2026+
Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99),
2027+
Token::TupleEnd
2028+
]);
19772029

19782030
assert_tokens(&sk.readable(), &[Token::BorrowedStr(SK_STR)]);
19792031
assert_tokens(&sk.readable(), &[Token::Str(SK_STR)]);
19802032
assert_tokens(&sk.readable(), &[Token::String(SK_STR)]);
19812033

1982-
assert_tokens(&pk.compact(), &[Token::BorrowedBytes(&PK_BYTES[..])]);
1983-
assert_tokens(&pk.compact(), &[Token::Bytes(&PK_BYTES)]);
1984-
assert_tokens(&pk.compact(), &[Token::ByteBuf(&PK_BYTES)]);
2034+
assert_tokens(&pk.compact(), &[
2035+
Token::Tuple{ len: 33 },
2036+
Token::U8(0x02),
2037+
Token::U8(0x18), Token::U8(0x84), Token::U8(0x57), Token::U8(0x81), Token::U8(0xf6), Token::U8(0x31), Token::U8(0xc4), Token::U8(0x8f),
2038+
Token::U8(0x1c), Token::U8(0x97), Token::U8(0x09), Token::U8(0xe2), Token::U8(0x30), Token::U8(0x92), Token::U8(0x06), Token::U8(0x7d),
2039+
Token::U8(0x06), Token::U8(0x83), Token::U8(0x7f), Token::U8(0x30), Token::U8(0xaa), Token::U8(0x0c), Token::U8(0xd0), Token::U8(0x54),
2040+
Token::U8(0x4a), Token::U8(0xc8), Token::U8(0x87), Token::U8(0xfe), Token::U8(0x91), Token::U8(0xdd), Token::U8(0xd1), Token::U8(0x66),
2041+
Token::TupleEnd
2042+
]);
19852043

19862044
assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]);
19872045
assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]);
19882046
assert_tokens(&pk.readable(), &[Token::String(PK_STR)]);
1989-
19902047
}
19912048

19922049
#[test]
@@ -2066,10 +2123,14 @@ mod test {
20662123
";
20672124

20682125
let sk = KeyPairWrapper(KeyPair::from_seckey_slice(&crate::SECP256K1, &SK_BYTES).unwrap());
2069-
2070-
assert_tokens(&sk.compact(), &[Token::BorrowedBytes(&SK_BYTES[..])]);
2071-
assert_tokens(&sk.compact(), &[Token::Bytes(&SK_BYTES)]);
2072-
assert_tokens(&sk.compact(), &[Token::ByteBuf(&SK_BYTES)]);
2126+
assert_tokens(&sk.compact(), &[
2127+
Token::Tuple{ len: 32 },
2128+
Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1),
2129+
Token::U8(0), Token::U8(1), Token::U8(2), Token::U8(3), Token::U8(4), Token::U8(5), Token::U8(6), Token::U8(7),
2130+
Token::U8(0xff), Token::U8(0xff), Token::U8(0), Token::U8(0), Token::U8(0xff), Token::U8(0xff), Token::U8(0), Token::U8(0),
2131+
Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99),
2132+
Token::TupleEnd
2133+
]);
20732134

20742135
assert_tokens(&sk.readable(), &[Token::BorrowedStr(SK_STR)]);
20752136
assert_tokens(&sk.readable(), &[Token::Str(SK_STR)]);
@@ -2221,4 +2282,38 @@ mod test {
22212282

22222283
assert_eq!(got, want)
22232284
}
2285+
2286+
#[test]
2287+
#[cfg(not(fuzzing))]
2288+
#[cfg(all(feature = "global-context", feature = "serde"))]
2289+
fn test_serde_x_only_pubkey() {
2290+
use serde_test::{Configure, Token, assert_tokens};
2291+
2292+
static SK_BYTES: [u8; 32] = [
2293+
1, 1, 1, 1, 1, 1, 1, 1,
2294+
0, 1, 2, 3, 4, 5, 6, 7,
2295+
0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0,
2296+
99, 99, 99, 99, 99, 99, 99, 99
2297+
];
2298+
2299+
static PK_STR: &'static str = "\
2300+
18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166\
2301+
";
2302+
2303+
let kp = KeyPair::from_seckey_slice(&crate::SECP256K1, &SK_BYTES).unwrap();
2304+
let (pk, _parity) = XOnlyPublicKey::from_keypair(&kp);
2305+
2306+
assert_tokens(&pk.compact(), &[
2307+
Token::Tuple{ len: 32 },
2308+
Token::U8(0x18), Token::U8(0x84), Token::U8(0x57), Token::U8(0x81), Token::U8(0xf6), Token::U8(0x31), Token::U8(0xc4), Token::U8(0x8f),
2309+
Token::U8(0x1c), Token::U8(0x97), Token::U8(0x09), Token::U8(0xe2), Token::U8(0x30), Token::U8(0x92), Token::U8(0x06), Token::U8(0x7d),
2310+
Token::U8(0x06), Token::U8(0x83), Token::U8(0x7f), Token::U8(0x30), Token::U8(0xaa), Token::U8(0x0c), Token::U8(0xd0), Token::U8(0x54),
2311+
Token::U8(0x4a), Token::U8(0xc8), Token::U8(0x87), Token::U8(0xfe), Token::U8(0x91), Token::U8(0xdd), Token::U8(0xd1), Token::U8(0x66),
2312+
Token::TupleEnd
2313+
]);
2314+
2315+
assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]);
2316+
assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]);
2317+
assert_tokens(&pk.readable(), &[Token::String(PK_STR)]);
2318+
}
22242319
}

src/schnorr.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -569,9 +569,14 @@ mod tests {
569569
assert_tokens(&sig.readable(), &[Token::Str(SIG_STR)]);
570570
assert_tokens(&sig.readable(), &[Token::String(SIG_STR)]);
571571

572-
assert_tokens(&pk.compact(), &[Token::BorrowedBytes(&PK_BYTES[..])]);
573-
assert_tokens(&pk.compact(), &[Token::Bytes(&PK_BYTES[..])]);
574-
assert_tokens(&pk.compact(), &[Token::ByteBuf(&PK_BYTES[..])]);
572+
assert_tokens(&pk.compact(), &[
573+
Token::Tuple{ len: 32 },
574+
Token::U8(24), Token::U8(132), Token::U8(87), Token::U8(129), Token::U8(246), Token::U8(49), Token::U8(196), Token::U8(143),
575+
Token::U8(28), Token::U8(151), Token::U8(9), Token::U8(226), Token::U8(48), Token::U8(146), Token::U8(6), Token::U8(125),
576+
Token::U8(6), Token::U8(131), Token::U8(127), Token::U8(48), Token::U8(170), Token::U8(12), Token::U8(208), Token::U8(84),
577+
Token::U8(74), Token::U8(200), Token::U8(135), Token::U8(254), Token::U8(145), Token::U8(221), Token::U8(209), Token::U8(102),
578+
Token::TupleEnd
579+
]);
575580

576581
assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]);
577582
assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]);

0 commit comments

Comments
 (0)