Skip to content

Commit d8a0399

Browse files
committed
Use fixed width serde impls for keys
Currently we serialize keys using the `BytesVisitor`, this causes the serialized data to contain additional metadata encoding the length (an extra 8 bytes). This extra data is unnecessary since we know in advance the length of these two types. Implement a sequence based visitor that encodes the keys as fixed width data. Do fixed width serde imples for: - SecretKey - PublicKey - KeyPair - XOnlyPublicKey
1 parent d0e5c54 commit d8a0399

File tree

8 files changed

+194
-57
lines changed

8 files changed

+194
-57
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ global-context = ["std"]
3232
secp256k1-sys = { version = "0.4.2", default-features = false, path = "./secp256k1-sys" }
3333
bitcoin_hashes = { version = "0.10", optional = true }
3434
rand = { version = "0.6", default-features = false, optional = true }
35+
# Our custom serde code requires an allocator, enable secp256k1 feature `std` or `alloc`.
3536
serde = { version = "1.0", default-features = false, optional = true }
3637

3738

contrib/test.sh

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/bin/sh -ex
22

33
# TODO: Add "alloc" once we bump MSRV to past 1.29
4-
FEATURES="bitcoin_hashes global-context lowmemory rand rand-std recovery serde std"
4+
FEATURES="bitcoin_hashes global-context lowmemory rand rand-std recovery std"
55

66
# Use toolchain if explicitly specified
77
if [ -n "$TOOLCHAIN" ]
@@ -24,8 +24,8 @@ if [ "$DO_FEATURE_MATRIX" = true ]; then
2424
cargo test --all --no-default-features
2525

2626
# All features
27-
cargo build --all --no-default-features --features="$FEATURES"
28-
cargo test --all --no-default-features --features="$FEATURES"
27+
cargo build --all --no-default-features --features="$FEATURES serde"
28+
cargo test --all --no-default-features --features="$FEATURES serde"
2929
# Single features
3030
for feature in ${FEATURES}
3131
do
@@ -35,9 +35,12 @@ if [ "$DO_FEATURE_MATRIX" = true ]; then
3535

3636
# Other combos
3737
RUSTFLAGS='--cfg=fuzzing' RUSTDOCFLAGS=$RUSTFLAGS cargo test --all
38-
RUSTFLAGS='--cfg=fuzzing' RUSTDOCFLAGS=$RUSTFLAGS cargo test --all --features="$FEATURES"
38+
RUSTFLAGS='--cfg=fuzzing' RUSTDOCFLAGS=$RUSTFLAGS cargo test --all --features="$FEATURES serde"
3939
cargo test --all --features="rand rand-std"
4040
cargo test --all --features="rand serde"
41+
# serde requires an allocator
42+
cargo build --all --no-default-features --features=alloc,serde
43+
cargo test --all --no-default-features --features=alloc,serde
4144

4245
if [ "$DO_BENCH" = true ]; then # proxy for us having a nightly compiler
4346
cargo test --all --all-features
@@ -52,7 +55,7 @@ fi
5255

5356
# Docs
5457
if [ "$DO_DOCS" = true ]; then
55-
cargo doc --all --features="$FEATURES"
58+
cargo doc --all --features="$FEATURES serde"
5659
fi
5760

5861
# Webassembly stuff
@@ -77,6 +80,7 @@ if [ "$DO_ASAN" = true ]; then
7780
cargo test --lib --all --features="$FEATURES" -Zbuild-std --target x86_64-unknown-linux-gnu &&
7881
cargo run --release --manifest-path=./no_std_test/Cargo.toml | grep -q "Verified Successfully"
7982
cargo run --release --features=alloc --manifest-path=./no_std_test/Cargo.toml | grep -q "Verified alloc Successfully"
83+
cargo run --release --features=use-serde --manifest-path=./no_std_test/Cargo.toml | grep -q "Verified Successfully"
8084
fi
8185

8286
# Test if panic in C code aborts the process (either with a real panic or with SIGILL)

no_std_test/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ authors = ["Elichai Turkel <[email protected]>"]
55

66
[features]
77
alloc = ["secp256k1/alloc", "wee_alloc"]
8+
use-serde = ["alloc", "secp256k1/serde"]
89

910
[dependencies]
1011
wee_alloc = { version = "0.4.5", optional = true }
11-
secp256k1 = { path = "../", default-features = false, features = ["serde", "rand", "recovery"] }
12+
secp256k1 = { path = "../", default-features = false, features = ["rand", "recovery"] }
1213
libc = { version = "0.2", default-features = false }
1314
serde_cbor = { version = "0.10", default-features = false } # A random serializer that supports no-std.
1415

no_std_test/src/main.rs

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,10 @@ use core::fmt::{self, write, Write};
6565
use core::intrinsics;
6666
use core::panic::PanicInfo;
6767

68-
use secp256k1::ecdh::SharedSecret;
6968
use secp256k1::ffi::types::AlignedType;
7069
use secp256k1::rand::{self, RngCore};
71-
use secp256k1::serde::Serialize;
7270
use secp256k1::*;
7371

74-
use serde_cbor::de;
75-
use serde_cbor::ser::SliceWrite;
76-
use serde_cbor::Serializer;
77-
7872
struct FakeRng;
7973
impl RngCore for FakeRng {
8074
fn next_u32(&mut self) -> u32 {
@@ -116,25 +110,34 @@ fn start(_argc: isize, _argv: *const *const u8) -> isize {
116110
let new_rec_sig = ecdsa::RecoverableSignature::from_compact(&data, rec_id).unwrap();
117111
assert_eq!(rec_sig, new_rec_sig);
118112

119-
let mut cbor_ser = [0u8; 100];
120-
let writer = SliceWrite::new(&mut cbor_ser[..]);
121-
let mut ser = Serializer::new(writer);
122-
sig.serialize(&mut ser).unwrap();
123-
let size = ser.into_inner().bytes_written();
124-
let new_sig: ecdsa::Signature = de::from_mut_slice(&mut cbor_ser[..size]).unwrap();
125-
assert_eq!(sig, new_sig);
126-
127-
let _ = SharedSecret::new(&public_key, &secret_key);
128-
let mut x_arr = [0u8; 32];
129-
let y_arr = SharedSecret::new_with_hash(&public_key, &secret_key, |x,y| {
130-
x_arr = x;
131-
y.into()
132-
});
133-
assert_ne!(x_arr, [0u8; 32]);
134-
assert_ne!(&y_arr[..], &[0u8; 32][..]);
113+
#[cfg(feature = "use-serde")] {
114+
use secp256k1::serde::Serialize;
115+
use serde_cbor::de;
116+
use serde_cbor::ser::SliceWrite;
117+
use serde_cbor::Serializer;
118+
119+
let mut cbor_ser = [0u8; 100];
120+
let writer = SliceWrite::new(&mut cbor_ser[..]);
121+
let mut ser = Serializer::new(writer);
122+
sig.serialize(&mut ser).unwrap();
123+
let size = ser.into_inner().bytes_written();
124+
let new_sig: ecdsa::Signature = de::from_mut_slice(&mut cbor_ser[..size]).unwrap();
125+
assert_eq!(sig, new_sig);
126+
}
135127

136128
#[cfg(feature = "alloc")]
137129
{
130+
use secp256k1::ecdh::SharedSecret;
131+
132+
let _ = SharedSecret::new(&public_key, &secret_key);
133+
let mut x_arr = [0u8; 32];
134+
let y_arr = SharedSecret::new_with_hash(&public_key, &secret_key, |x,y| {
135+
x_arr = x;
136+
y.into()
137+
});
138+
assert_ne!(x_arr, [0u8; 32]);
139+
assert_ne!(&y_arr[..], &[0u8; 32][..]);
140+
138141
let secp_alloc = Secp256k1::new();
139142
let public_key = PublicKey::from_secret_key(&secp_alloc, &secret_key);
140143
let message = Message::from_slice(&[0xab; 32]).expect("32 bytes");

src/key.rs

Lines changed: 98 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ use Verification;
2828
use constants;
2929
use ffi::{self, CPtr};
3030

31+
#[cfg(feature = "serde")]
32+
use serde::ser::SerializeTuple;
33+
3134
#[cfg(feature = "global-context")]
3235
use {Message, ecdsa, SECP256K1};
3336
#[cfg(all(feature = "global-context", feature = "rand-std"))]
@@ -302,21 +305,25 @@ impl ::serde::Serialize for SecretKey {
302305
let mut buf = [0u8; 64];
303306
s.serialize_str(::to_hex(&self.0, &mut buf).expect("fixed-size hex serialization"))
304307
} else {
305-
s.serialize_bytes(&self[..])
308+
let mut seq = s.serialize_tuple(constants::SECRET_KEY_SIZE)?;
309+
for byte in self.0.iter() {
310+
seq.serialize_element(byte)?;
311+
}
312+
seq.end()
306313
}
307314
}
308315
}
309316

310-
#[cfg(feature = "serde")]
311-
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
317+
#[cfg(all(feature = "serde", any(feature = "std", feature = "alloc")))]
318+
#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", any(feature = "std", feature = "alloc")))))]
312319
impl<'de> ::serde::Deserialize<'de> for SecretKey {
313320
fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
314321
if d.is_human_readable() {
315322
d.deserialize_str(super::serde_util::FromStrVisitor::new(
316323
"a hex string representing 32 byte SecretKey"
317324
))
318325
} else {
319-
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
326+
d.deserialize_seq(super::serde_util::SeqVisitor::new(
320327
"raw 32 bytes SecretKey",
321328
SecretKey::from_slice
322329
))
@@ -615,24 +622,29 @@ impl From<ffi::PublicKey> for PublicKey {
615622
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
616623
impl ::serde::Serialize for PublicKey {
617624
fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
625+
// FIXME: human_readable serializes the whole 64 bytes but non-human-readable does 33 (compressed). Is this correct?
618626
if s.is_human_readable() {
619627
s.collect_str(self)
620628
} else {
621-
s.serialize_bytes(&self.serialize())
629+
let mut seq = s.serialize_tuple(constants::PUBLIC_KEY_SIZE)?;
630+
for byte in self.serialize().iter() { // Serialize in compressed form.
631+
seq.serialize_element(&byte)?;
632+
}
633+
seq.end()
622634
}
623635
}
624636
}
625637

626-
#[cfg(feature = "serde")]
627-
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
638+
#[cfg(all(feature = "serde", any(feature = "std", feature = "alloc")))]
639+
#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", any(feature = "std", feature = "alloc")))))]
628640
impl<'de> ::serde::Deserialize<'de> for PublicKey {
629641
fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<PublicKey, D::Error> {
630642
if d.is_human_readable() {
631643
d.deserialize_str(super::serde_util::FromStrVisitor::new(
632644
"an ASCII hex string representing a public key"
633645
))
634646
} else {
635-
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
647+
d.deserialize_seq(super::serde_util::SeqVisitor::new(
636648
"a bytestring representing a public key",
637649
PublicKey::from_slice
638650
))
@@ -929,21 +941,25 @@ impl ::serde::Serialize for KeyPair {
929941
s.serialize_str(::to_hex(&self.serialize_secret(), &mut buf)
930942
.expect("fixed-size hex serialization"))
931943
} else {
932-
s.serialize_bytes(&self.0[..])
944+
let mut seq = s.serialize_tuple(constants::SECRET_KEY_SIZE)?;
945+
for byte in self.serialize_secret().iter() {
946+
seq.serialize_element(&byte)?;
947+
}
948+
seq.end()
933949
}
934950
}
935951
}
936952

937-
#[cfg(feature = "serde")]
938-
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
953+
#[cfg(all(feature = "serde", any(feature = "std", feature = "alloc")))]
954+
#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", any(feature = "std", feature = "alloc")))))]
939955
impl<'de> ::serde::Deserialize<'de> for KeyPair {
940956
fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
941957
if d.is_human_readable() {
942958
d.deserialize_str(super::serde_util::FromStrVisitor::new(
943959
"a hex string representing 32 byte KeyPair"
944960
))
945961
} else {
946-
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
962+
d.deserialize_seq(super::serde_util::SeqVisitor::new(
947963
"raw 32 bytes KeyPair",
948964
|data| unsafe {
949965
let ctx = Secp256k1::from_raw_all(ffi::secp256k1_context_no_precomp as *mut ffi::Context);
@@ -1350,24 +1366,29 @@ impl From<::key::PublicKey> for XOnlyPublicKey {
13501366
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
13511367
impl ::serde::Serialize for XOnlyPublicKey {
13521368
fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1369+
// FIXME: human_readable serializes the whole 64 bytes but non-human-readable does 32 bytes (x co-ordinate). Is this correct?
13531370
if s.is_human_readable() {
13541371
s.collect_str(self)
13551372
} else {
1356-
s.serialize_bytes(&self.serialize())
1373+
let mut seq = s.serialize_tuple(constants::SCHNORRSIG_PUBLIC_KEY_SIZE)?;
1374+
for byte in self.serialize().iter() {
1375+
seq.serialize_element(&byte)?;
1376+
}
1377+
seq.end()
13571378
}
13581379
}
13591380
}
13601381

1361-
#[cfg(feature = "serde")]
1362-
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
1382+
#[cfg(all(feature = "serde", any(feature = "std", feature = "alloc")))]
1383+
#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", any(feature = "std", feature = "alloc")))))]
13631384
impl<'de> ::serde::Deserialize<'de> for XOnlyPublicKey {
13641385
fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
13651386
if d.is_human_readable() {
13661387
d.deserialize_str(super::serde_util::FromStrVisitor::new(
13671388
"a hex string representing 32 byte schnorr public key"
13681389
))
13691390
} else {
1370-
d.deserialize_bytes(super::serde_util::BytesVisitor::new(
1391+
d.deserialize_seq(super::serde_util::SeqVisitor::new(
13711392
"raw 32 bytes schnorr public key",
13721393
XOnlyPublicKey::from_slice
13731394
))
@@ -1911,6 +1932,7 @@ mod test {
19111932
static SK_STR: &'static str = "\
19121933
01010101010101010001020304050607ffff0000ffff00006363636363636363\
19131934
";
1935+
#[cfg(fuzzing)]
19141936
static PK_BYTES: [u8; 33] = [
19151937
0x02,
19161938
0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f,
@@ -1933,22 +1955,32 @@ mod test {
19331955
#[cfg(fuzzing)]
19341956
let pk = PublicKey::from_slice(&PK_BYTES).expect("pk");
19351957

1936-
assert_tokens(&sk.compact(), &[Token::BorrowedBytes(&SK_BYTES[..])]);
1937-
assert_tokens(&sk.compact(), &[Token::Bytes(&SK_BYTES)]);
1938-
assert_tokens(&sk.compact(), &[Token::ByteBuf(&SK_BYTES)]);
1958+
assert_tokens(&sk.compact(), &[
1959+
Token::Tuple{ len: 32 },
1960+
Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1),
1961+
Token::U8(0), Token::U8(1), Token::U8(2), Token::U8(3), Token::U8(4), Token::U8(5), Token::U8(6), Token::U8(7),
1962+
Token::U8(0xff), Token::U8(0xff), Token::U8(0), Token::U8(0), Token::U8(0xff), Token::U8(0xff), Token::U8(0), Token::U8(0),
1963+
Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99),
1964+
Token::TupleEnd
1965+
]);
19391966

19401967
assert_tokens(&sk.readable(), &[Token::BorrowedStr(SK_STR)]);
19411968
assert_tokens(&sk.readable(), &[Token::Str(SK_STR)]);
19421969
assert_tokens(&sk.readable(), &[Token::String(SK_STR)]);
19431970

1944-
assert_tokens(&pk.compact(), &[Token::BorrowedBytes(&PK_BYTES[..])]);
1945-
assert_tokens(&pk.compact(), &[Token::Bytes(&PK_BYTES)]);
1946-
assert_tokens(&pk.compact(), &[Token::ByteBuf(&PK_BYTES)]);
1971+
assert_tokens(&pk.compact(), &[
1972+
Token::Tuple{ len: 33 },
1973+
Token::U8(0x02),
1974+
Token::U8(0x18), Token::U8(0x84), Token::U8(0x57), Token::U8(0x81), Token::U8(0xf6), Token::U8(0x31), Token::U8(0xc4), Token::U8(0x8f),
1975+
Token::U8(0x1c), Token::U8(0x97), Token::U8(0x09), Token::U8(0xe2), Token::U8(0x30), Token::U8(0x92), Token::U8(0x06), Token::U8(0x7d),
1976+
Token::U8(0x06), Token::U8(0x83), Token::U8(0x7f), Token::U8(0x30), Token::U8(0xaa), Token::U8(0x0c), Token::U8(0xd0), Token::U8(0x54),
1977+
Token::U8(0x4a), Token::U8(0xc8), Token::U8(0x87), Token::U8(0xfe), Token::U8(0x91), Token::U8(0xdd), Token::U8(0xd1), Token::U8(0x66),
1978+
Token::TupleEnd
1979+
]);
19471980

19481981
assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]);
19491982
assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]);
19501983
assert_tokens(&pk.readable(), &[Token::String(PK_STR)]);
1951-
19521984
}
19531985

19541986
#[test]
@@ -2027,12 +2059,52 @@ mod test {
20272059

20282060
let sk = KeyPairWrapper(KeyPair::from_seckey_slice(&::SECP256K1, &SK_BYTES).unwrap());
20292061

2030-
assert_tokens(&sk.compact(), &[Token::BorrowedBytes(&SK_BYTES[..])]);
2031-
assert_tokens(&sk.compact(), &[Token::Bytes(&SK_BYTES)]);
2032-
assert_tokens(&sk.compact(), &[Token::ByteBuf(&SK_BYTES)]);
2062+
assert_tokens(&sk.compact(), &[
2063+
Token::Tuple{ len: 32 },
2064+
Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1),
2065+
Token::U8(0), Token::U8(1), Token::U8(2), Token::U8(3), Token::U8(4), Token::U8(5), Token::U8(6), Token::U8(7),
2066+
Token::U8(0xff), Token::U8(0xff), Token::U8(0), Token::U8(0), Token::U8(0xff), Token::U8(0xff), Token::U8(0), Token::U8(0),
2067+
Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99),
2068+
Token::TupleEnd
2069+
]);
20332070

20342071
assert_tokens(&sk.readable(), &[Token::BorrowedStr(SK_STR)]);
20352072
assert_tokens(&sk.readable(), &[Token::Str(SK_STR)]);
20362073
assert_tokens(&sk.readable(), &[Token::String(SK_STR)]);
20372074
}
2075+
2076+
#[test]
2077+
#[cfg(all(feature = "global-context", feature = "serde"))]
2078+
fn test_serde_x_only_pubkey() {
2079+
use serde_test::{Configure, Token, assert_tokens};
2080+
use key::KeyPair;
2081+
2082+
static SK_BYTES: [u8; 32] = [
2083+
1, 1, 1, 1, 1, 1, 1, 1,
2084+
0, 1, 2, 3, 4, 5, 6, 7,
2085+
0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0,
2086+
99, 99, 99, 99, 99, 99, 99, 99
2087+
];
2088+
2089+
static PK_STR: &'static str = "\
2090+
18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166\
2091+
";
2092+
2093+
// FIXME: Do we need to cfg fuzzing/not-fuzzing keypair?
2094+
let kp = KeyPair::from_seckey_slice(&::SECP256K1, &SK_BYTES).unwrap();
2095+
let pk = XOnlyPublicKey::from_keypair(&kp);
2096+
2097+
assert_tokens(&pk.compact(), &[
2098+
Token::Tuple{ len: 32 },
2099+
Token::U8(0x18), Token::U8(0x84), Token::U8(0x57), Token::U8(0x81), Token::U8(0xf6), Token::U8(0x31), Token::U8(0xc4), Token::U8(0x8f),
2100+
Token::U8(0x1c), Token::U8(0x97), Token::U8(0x09), Token::U8(0xe2), Token::U8(0x30), Token::U8(0x92), Token::U8(0x06), Token::U8(0x7d),
2101+
Token::U8(0x06), Token::U8(0x83), Token::U8(0x7f), Token::U8(0x30), Token::U8(0xaa), Token::U8(0x0c), Token::U8(0xd0), Token::U8(0x54),
2102+
Token::U8(0x4a), Token::U8(0xc8), Token::U8(0x87), Token::U8(0xfe), Token::U8(0x91), Token::U8(0xdd), Token::U8(0xd1), Token::U8(0x66),
2103+
Token::TupleEnd
2104+
]);
2105+
2106+
assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]);
2107+
assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]);
2108+
assert_tokens(&pk.readable(), &[Token::String(PK_STR)]);
2109+
}
20382110
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@
163163
#![cfg_attr(all(test, feature = "unstable"), feature(test))]
164164
#![cfg_attr(docsrs, feature(doc_cfg))]
165165

166+
#[cfg(all(feature = "serde", not(feature = "std"), not(feature = "alloc")))]
167+
compile_error!("Feature serde requires an allocator, please enable 'std' or 'alloc'");
168+
166169
#[macro_use]
167170
pub extern crate secp256k1_sys;
168171
pub use secp256k1_sys as ffi;

0 commit comments

Comments
 (0)