Merge rust-bitcoin/rust-secp256k1#406: Use fixed width serde impls for keys

3ca7f499e0 Add fixed-width-serde integration tests (Tobin Harding)
bf9f556225 Add rustdocs describing fixed width serde (Tobin Harding)
c28808c5a4 Improve rustdocs for KeyPair (Tobin Harding)
6842383161 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 3ca7f499e0

Tree-SHA512: 77babce74fa9f0981bb3b869c4e77a68a4d1ec28d22d2c3be4305e27ef01d4828dac210e20b968cbbe5de8a0563cd985d7969bccf75cfe627a34a116fed1a5df
This commit is contained in:
Andrew Poelstra 2022-06-15 15:20:57 +00:00
commit 613d7dc1cb
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
5 changed files with 282 additions and 36 deletions

View File

@ -50,6 +50,12 @@ rand = "0.8"
rand_core = "0.6" rand_core = "0.6"
serde_test = "1.0" serde_test = "1.0"
bitcoin_hashes = "0.10" bitcoin_hashes = "0.10"
bincode = "1.3.3"
# cbor does not build on WASM, we use it in a single trivial test (an example of when
# fixed-width-serde breaks down). Just run the test when on an x86_64 machine.
[target.'cfg(target_arch = "x86_64")'.dependencies]
cbor = "0.4.1"
[target.wasm32-unknown-unknown.dev-dependencies] [target.wasm32-unknown-unknown.dev-dependencies]
wasm-bindgen-test = "0.3" wasm-bindgen-test = "0.3"

View File

@ -25,6 +25,9 @@ use crate::Error::{self, InvalidPublicKey, InvalidPublicKeySum, InvalidSecretKey
use crate::ffi::{self, CPtr, impl_array_newtype}; use crate::ffi::{self, CPtr, impl_array_newtype};
use crate::ffi::types::c_uint; use crate::ffi::types::c_uint;
#[cfg(feature = "serde")]
use serde::ser::SerializeTuple;
#[cfg(feature = "global-context")] #[cfg(feature = "global-context")]
use crate::{Message, ecdsa, SECP256K1}; use crate::{Message, ecdsa, SECP256K1};
#[cfg(all(feature = "global-context", feature = "rand-std"))] #[cfg(all(feature = "global-context", feature = "rand-std"))]
@ -33,6 +36,12 @@ use crate::Scalar;
/// Secret 256-bit key used as `x` in an ECDSA signature. /// Secret 256-bit key used as `x` in an ECDSA signature.
/// ///
/// # Serde support
///
/// Implements de/serialization with the `serde` feature enabled. We treat the byte value as a tuple
/// of 32 `u8`s for non-human-readable formats. This representation is optimal for for some formats
/// (e.g. [`bincode`]) however other formats may be less optimal (e.g. [`cbor`]).
///
/// # Examples /// # Examples
/// ///
/// Basic usage: /// Basic usage:
@ -45,6 +54,8 @@ use crate::Scalar;
/// let secret_key = SecretKey::new(&mut rand::thread_rng()); /// let secret_key = SecretKey::new(&mut rand::thread_rng());
/// # } /// # }
/// ``` /// ```
/// [`bincode`]: https://docs.rs/bincode
/// [`cbor`]: https://docs.rs/cbor
pub struct SecretKey([u8; constants::SECRET_KEY_SIZE]); pub struct SecretKey([u8; constants::SECRET_KEY_SIZE]);
impl_array_newtype!(SecretKey, u8, constants::SECRET_KEY_SIZE); impl_array_newtype!(SecretKey, u8, constants::SECRET_KEY_SIZE);
impl_display_secret!(SecretKey); impl_display_secret!(SecretKey);
@ -68,6 +79,12 @@ pub const ONE_KEY: SecretKey = SecretKey([0, 0, 0, 0, 0, 0, 0, 0,
/// A Secp256k1 public key, used for verification of signatures. /// A Secp256k1 public key, used for verification of signatures.
/// ///
/// # Serde support
///
/// Implements de/serialization with the `serde` feature enabled. We treat the byte value as a tuple
/// of 33 `u8`s for non-human-readable formats. This representation is optimal for for some formats
/// (e.g. [`bincode`]) however other formats may be less optimal (e.g. [`cbor`]).
///
/// # Examples /// # Examples
/// ///
/// Basic usage: /// Basic usage:
@ -81,6 +98,8 @@ pub const ONE_KEY: SecretKey = SecretKey([0, 0, 0, 0, 0, 0, 0, 0,
/// let public_key = PublicKey::from_secret_key(&secp, &secret_key); /// let public_key = PublicKey::from_secret_key(&secp, &secret_key);
/// # } /// # }
/// ``` /// ```
/// [`bincode`]: https://docs.rs/bincode
/// [`cbor`]: https://docs.rs/cbor
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
#[repr(transparent)] #[repr(transparent)]
pub struct PublicKey(ffi::PublicKey); pub struct PublicKey(ffi::PublicKey);
@ -315,7 +334,11 @@ impl serde::Serialize for SecretKey {
let mut buf = [0u8; constants::SECRET_KEY_SIZE * 2]; let mut buf = [0u8; constants::SECRET_KEY_SIZE * 2];
s.serialize_str(crate::to_hex(&self.0, &mut buf).expect("fixed-size hex serialization")) s.serialize_str(crate::to_hex(&self.0, &mut buf).expect("fixed-size hex serialization"))
} else { } else {
s.serialize_bytes(&self[..]) let mut tuple = s.serialize_tuple(constants::SECRET_KEY_SIZE)?;
for byte in self.0.iter() {
tuple.serialize_element(byte)?;
}
tuple.end()
} }
} }
} }
@ -329,10 +352,11 @@ impl<'de> serde::Deserialize<'de> for SecretKey {
"a hex string representing 32 byte SecretKey" "a hex string representing 32 byte SecretKey"
)) ))
} else { } else {
d.deserialize_bytes(super::serde_util::BytesVisitor::new( let visitor = super::serde_util::Tuple32Visitor::new(
"raw 32 bytes SecretKey", "raw 32 bytes SecretKey",
SecretKey::from_slice SecretKey::from_slice
)) );
d.deserialize_tuple(constants::SECRET_KEY_SIZE, visitor)
} }
} }
} }
@ -649,7 +673,11 @@ impl serde::Serialize for PublicKey {
if s.is_human_readable() { if s.is_human_readable() {
s.collect_str(self) s.collect_str(self)
} else { } else {
s.serialize_bytes(&self.serialize()) let mut tuple = s.serialize_tuple(constants::PUBLIC_KEY_SIZE)?;
for byte in self.serialize().iter() { // Serialize in compressed form.
tuple.serialize_element(&byte)?;
}
tuple.end()
} }
} }
} }
@ -663,10 +691,11 @@ impl<'de> serde::Deserialize<'de> for PublicKey {
"an ASCII hex string representing a public key" "an ASCII hex string representing a public key"
)) ))
} else { } else {
d.deserialize_bytes(super::serde_util::BytesVisitor::new( let visitor = super::serde_util::Tuple33Visitor::new(
"a bytestring representing a public key", "33 bytes compressed public key",
PublicKey::from_slice PublicKey::from_slice
)) );
d.deserialize_tuple(constants::PUBLIC_KEY_SIZE, visitor)
} }
} }
} }
@ -687,13 +716,10 @@ impl Ord for PublicKey {
/// ///
/// # Serde support /// # Serde support
/// ///
/// [`Serialize`] and [`Deserialize`] are not implemented for this type, even with the `serde` /// Implements de/serialization with the `serde` and_`global-context` features enabled. Serializes
/// feature active. This is due to security considerations, see the [`serde_keypair`] documentation /// the secret bytes only. We treat the byte value as a tuple of 32 `u8`s for non-human-readable
/// for details. /// formats. This representation is optimal for for some formats (e.g. [`bincode`]) however other
/// /// formats may be less optimal (e.g. [`cbor`]). For human-readable formats we use a hex string.
/// If the `serde` and `global-context` features are active `KeyPair`s can be serialized and
/// deserialized by annotating them with `#[serde(with = "secp256k1::serde_keypair")]`
/// inside structs or enums for which [`Serialize`] and [`Deserialize`] are being derived.
/// ///
/// # Examples /// # Examples
/// ///
@ -708,8 +734,8 @@ impl Ord for PublicKey {
/// let key_pair = KeyPair::from_secret_key(&secp, &secret_key); /// let key_pair = KeyPair::from_secret_key(&secp, &secret_key);
/// # } /// # }
/// ``` /// ```
/// [`Deserialize`]: serde::Deserialize /// [`bincode`]: https://docs.rs/bincode
/// [`Serialize`]: serde::Serialize /// [`cbor`]: https://docs.rs/cbor
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct KeyPair(ffi::KeyPair); pub struct KeyPair(ffi::KeyPair);
impl_display_secret!(KeyPair); impl_display_secret!(KeyPair);
@ -966,7 +992,11 @@ impl serde::Serialize for KeyPair {
s.serialize_str(crate::to_hex(&self.secret_bytes(), &mut buf) s.serialize_str(crate::to_hex(&self.secret_bytes(), &mut buf)
.expect("fixed-size hex serialization")) .expect("fixed-size hex serialization"))
} else { } else {
s.serialize_bytes(&self.0[..]) let mut tuple = s.serialize_tuple(constants::SECRET_KEY_SIZE)?;
for byte in self.secret_bytes().iter() {
tuple.serialize_element(&byte)?;
}
tuple.end()
} }
} }
} }
@ -980,19 +1010,26 @@ impl<'de> serde::Deserialize<'de> for KeyPair {
"a hex string representing 32 byte KeyPair" "a hex string representing 32 byte KeyPair"
)) ))
} else { } else {
d.deserialize_bytes(super::serde_util::BytesVisitor::new( let visitor = super::serde_util::Tuple32Visitor::new(
"raw 32 bytes KeyPair", "raw 32 bytes KeyPair",
|data| unsafe { |data| unsafe {
let ctx = Secp256k1::from_raw_all(ffi::secp256k1_context_no_precomp as *mut ffi::Context); let ctx = Secp256k1::from_raw_all(ffi::secp256k1_context_no_precomp as *mut ffi::Context);
KeyPair::from_seckey_slice(&ctx, data) KeyPair::from_seckey_slice(&ctx, data)
} }
)) );
d.deserialize_tuple(constants::SECRET_KEY_SIZE, visitor)
} }
} }
} }
/// An x-only public key, used for verification of Schnorr signatures and serialized according to BIP-340. /// An x-only public key, used for verification of Schnorr signatures and serialized according to BIP-340.
/// ///
/// # Serde support
///
/// Implements de/serialization with the `serde` feature enabled. We treat the byte value as a tuple
/// of 32 `u8`s for non-human-readable formats. This representation is optimal for for some formats
/// (e.g. [`bincode`]) however other formats may be less optimal (e.g. [`cbor`]).
///
/// # Examples /// # Examples
/// ///
/// Basic usage: /// Basic usage:
@ -1006,6 +1043,8 @@ impl<'de> serde::Deserialize<'de> for KeyPair {
/// let xonly = XOnlyPublicKey::from_keypair(&key_pair); /// let xonly = XOnlyPublicKey::from_keypair(&key_pair);
/// # } /// # }
/// ``` /// ```
/// [`bincode`]: https://docs.rs/bincode
/// [`cbor`]: https://docs.rs/cbor
#[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)] #[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)]
pub struct XOnlyPublicKey(ffi::XOnlyPublicKey); pub struct XOnlyPublicKey(ffi::XOnlyPublicKey);
@ -1429,7 +1468,11 @@ impl serde::Serialize for XOnlyPublicKey {
if s.is_human_readable() { if s.is_human_readable() {
s.collect_str(self) s.collect_str(self)
} else { } else {
s.serialize_bytes(&self.serialize()) let mut tuple = s.serialize_tuple(constants::SCHNORR_PUBLIC_KEY_SIZE)?;
for byte in self.serialize().iter() {
tuple.serialize_element(&byte)?;
}
tuple.end()
} }
} }
} }
@ -1443,10 +1486,11 @@ impl<'de> serde::Deserialize<'de> for XOnlyPublicKey {
"a hex string representing 32 byte schnorr public key" "a hex string representing 32 byte schnorr public key"
)) ))
} else { } else {
d.deserialize_bytes(super::serde_util::BytesVisitor::new( let visitor = super::serde_util::Tuple32Visitor::new(
"raw 32 bytes schnorr public key", "raw 32 bytes schnorr public key",
XOnlyPublicKey::from_slice XOnlyPublicKey::from_slice
)) );
d.deserialize_tuple(constants::SCHNORR_PUBLIC_KEY_SIZE, visitor)
} }
} }
} }
@ -1492,6 +1536,8 @@ pub mod serde_keypair {
#[cfg(test)] #[cfg(test)]
#[allow(unused_imports)] #[allow(unused_imports)]
mod test { mod test {
use super::*;
use core::str::FromStr; use core::str::FromStr;
#[cfg(any(feature = "alloc", feature = "std"))] #[cfg(any(feature = "alloc", feature = "std"))]
@ -1949,6 +1995,7 @@ mod test {
static SK_STR: &'static str = "\ static SK_STR: &'static str = "\
01010101010101010001020304050607ffff0000ffff00006363636363636363\ 01010101010101010001020304050607ffff0000ffff00006363636363636363\
"; ";
#[cfg(fuzzing)]
static PK_BYTES: [u8; 33] = [ static PK_BYTES: [u8; 33] = [
0x02, 0x02,
0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f, 0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f,
@ -1971,22 +2018,32 @@ mod test {
#[cfg(fuzzing)] #[cfg(fuzzing)]
let pk = PublicKey::from_slice(&PK_BYTES).expect("pk"); let pk = PublicKey::from_slice(&PK_BYTES).expect("pk");
assert_tokens(&sk.compact(), &[Token::BorrowedBytes(&SK_BYTES[..])]); assert_tokens(&sk.compact(), &[
assert_tokens(&sk.compact(), &[Token::Bytes(&SK_BYTES)]); Token::Tuple{ len: 32 },
assert_tokens(&sk.compact(), &[Token::ByteBuf(&SK_BYTES)]); Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1),
Token::U8(0), Token::U8(1), Token::U8(2), Token::U8(3), Token::U8(4), Token::U8(5), Token::U8(6), Token::U8(7),
Token::U8(0xff), Token::U8(0xff), Token::U8(0), Token::U8(0), Token::U8(0xff), Token::U8(0xff), Token::U8(0), Token::U8(0),
Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99),
Token::TupleEnd
]);
assert_tokens(&sk.readable(), &[Token::BorrowedStr(SK_STR)]); assert_tokens(&sk.readable(), &[Token::BorrowedStr(SK_STR)]);
assert_tokens(&sk.readable(), &[Token::Str(SK_STR)]); assert_tokens(&sk.readable(), &[Token::Str(SK_STR)]);
assert_tokens(&sk.readable(), &[Token::String(SK_STR)]); assert_tokens(&sk.readable(), &[Token::String(SK_STR)]);
assert_tokens(&pk.compact(), &[Token::BorrowedBytes(&PK_BYTES[..])]); assert_tokens(&pk.compact(), &[
assert_tokens(&pk.compact(), &[Token::Bytes(&PK_BYTES)]); Token::Tuple{ len: 33 },
assert_tokens(&pk.compact(), &[Token::ByteBuf(&PK_BYTES)]); Token::U8(0x02),
Token::U8(0x18), Token::U8(0x84), Token::U8(0x57), Token::U8(0x81), Token::U8(0xf6), Token::U8(0x31), Token::U8(0xc4), Token::U8(0x8f),
Token::U8(0x1c), Token::U8(0x97), Token::U8(0x09), Token::U8(0xe2), Token::U8(0x30), Token::U8(0x92), Token::U8(0x06), Token::U8(0x7d),
Token::U8(0x06), Token::U8(0x83), Token::U8(0x7f), Token::U8(0x30), Token::U8(0xaa), Token::U8(0x0c), Token::U8(0xd0), Token::U8(0x54),
Token::U8(0x4a), Token::U8(0xc8), Token::U8(0x87), Token::U8(0xfe), Token::U8(0x91), Token::U8(0xdd), Token::U8(0xd1), Token::U8(0x66),
Token::TupleEnd
]);
assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]); assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]);
assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]); assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]);
assert_tokens(&pk.readable(), &[Token::String(PK_STR)]); assert_tokens(&pk.readable(), &[Token::String(PK_STR)]);
} }
#[test] #[test]
@ -2066,10 +2123,14 @@ mod test {
"; ";
let sk = KeyPairWrapper(KeyPair::from_seckey_slice(&crate::SECP256K1, &SK_BYTES).unwrap()); let sk = KeyPairWrapper(KeyPair::from_seckey_slice(&crate::SECP256K1, &SK_BYTES).unwrap());
assert_tokens(&sk.compact(), &[
assert_tokens(&sk.compact(), &[Token::BorrowedBytes(&SK_BYTES[..])]); Token::Tuple{ len: 32 },
assert_tokens(&sk.compact(), &[Token::Bytes(&SK_BYTES)]); Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1),
assert_tokens(&sk.compact(), &[Token::ByteBuf(&SK_BYTES)]); Token::U8(0), Token::U8(1), Token::U8(2), Token::U8(3), Token::U8(4), Token::U8(5), Token::U8(6), Token::U8(7),
Token::U8(0xff), Token::U8(0xff), Token::U8(0), Token::U8(0), Token::U8(0xff), Token::U8(0xff), Token::U8(0), Token::U8(0),
Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99), Token::U8(99),
Token::TupleEnd
]);
assert_tokens(&sk.readable(), &[Token::BorrowedStr(SK_STR)]); assert_tokens(&sk.readable(), &[Token::BorrowedStr(SK_STR)]);
assert_tokens(&sk.readable(), &[Token::Str(SK_STR)]); assert_tokens(&sk.readable(), &[Token::Str(SK_STR)]);
@ -2221,4 +2282,38 @@ mod test {
assert_eq!(got, want) assert_eq!(got, want)
} }
#[test]
#[cfg(not(fuzzing))]
#[cfg(all(feature = "global-context", feature = "serde"))]
fn test_serde_x_only_pubkey() {
use serde_test::{Configure, Token, assert_tokens};
static SK_BYTES: [u8; 32] = [
1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 2, 3, 4, 5, 6, 7,
0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0,
99, 99, 99, 99, 99, 99, 99, 99
];
static PK_STR: &'static str = "\
18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166\
";
let kp = KeyPair::from_seckey_slice(&crate::SECP256K1, &SK_BYTES).unwrap();
let (pk, _parity) = XOnlyPublicKey::from_keypair(&kp);
assert_tokens(&pk.compact(), &[
Token::Tuple{ len: 32 },
Token::U8(0x18), Token::U8(0x84), Token::U8(0x57), Token::U8(0x81), Token::U8(0xf6), Token::U8(0x31), Token::U8(0xc4), Token::U8(0x8f),
Token::U8(0x1c), Token::U8(0x97), Token::U8(0x09), Token::U8(0xe2), Token::U8(0x30), Token::U8(0x92), Token::U8(0x06), Token::U8(0x7d),
Token::U8(0x06), Token::U8(0x83), Token::U8(0x7f), Token::U8(0x30), Token::U8(0xaa), Token::U8(0x0c), Token::U8(0xd0), Token::U8(0x54),
Token::U8(0x4a), Token::U8(0xc8), Token::U8(0x87), Token::U8(0xfe), Token::U8(0x91), Token::U8(0xdd), Token::U8(0xd1), Token::U8(0x66),
Token::TupleEnd
]);
assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]);
assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]);
assert_tokens(&pk.readable(), &[Token::String(PK_STR)]);
}
} }

View File

@ -569,9 +569,14 @@ mod tests {
assert_tokens(&sig.readable(), &[Token::Str(SIG_STR)]); assert_tokens(&sig.readable(), &[Token::Str(SIG_STR)]);
assert_tokens(&sig.readable(), &[Token::String(SIG_STR)]); assert_tokens(&sig.readable(), &[Token::String(SIG_STR)]);
assert_tokens(&pk.compact(), &[Token::BorrowedBytes(&PK_BYTES[..])]); assert_tokens(&pk.compact(), &[
assert_tokens(&pk.compact(), &[Token::Bytes(&PK_BYTES[..])]); Token::Tuple{ len: 32 },
assert_tokens(&pk.compact(), &[Token::ByteBuf(&PK_BYTES[..])]); Token::U8(24), Token::U8(132), Token::U8(87), Token::U8(129), Token::U8(246), Token::U8(49), Token::U8(196), Token::U8(143),
Token::U8(28), Token::U8(151), Token::U8(9), Token::U8(226), Token::U8(48), Token::U8(146), Token::U8(6), Token::U8(125),
Token::U8(6), Token::U8(131), Token::U8(127), Token::U8(48), Token::U8(170), Token::U8(12), Token::U8(208), Token::U8(84),
Token::U8(74), Token::U8(200), Token::U8(135), Token::U8(254), Token::U8(145), Token::U8(221), Token::U8(209), Token::U8(102),
Token::TupleEnd
]);
assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]); assert_tokens(&pk.readable(), &[Token::BorrowedStr(PK_STR)]);
assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]); assert_tokens(&pk.readable(), &[Token::Str(PK_STR)]);

View File

@ -67,3 +67,56 @@ where
(self.parse_fn)(v).map_err(E::custom) (self.parse_fn)(v).map_err(E::custom)
} }
} }
macro_rules! impl_tuple_visitor {
($thing:ident, $len:expr) => {
pub(crate) struct $thing<F> {
expectation: &'static str,
parse_fn: F,
}
impl<F, T, E> $thing<F>
where
F: FnOnce(&[u8]) -> Result<T, E>,
E: fmt::Display,
{
pub fn new(expectation: &'static str, parse_fn: F) -> Self {
$thing {
expectation,
parse_fn,
}
}
}
impl<'de, F, T, E> de::Visitor<'de> for $thing<F>
where
F: FnOnce(&[u8]) -> Result<T, E>,
E: fmt::Display,
{
type Value = T;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(self.expectation)
}
fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
where
V: de::SeqAccess<'de>,
{
let mut bytes = [0u8; $len];
for (i, byte) in bytes.iter_mut().enumerate() {
if let Some(value) = seq.next_element()? {
*byte = value;
} else {
return Err(de::Error::invalid_length(i, &self));
}
}
(self.parse_fn)(&bytes).map_err(de::Error::custom)
}
}
}
}
impl_tuple_visitor!(Tuple32Visitor, 32);
impl_tuple_visitor!(Tuple33Visitor, 33);

87
tests/serde.rs Normal file
View File

@ -0,0 +1,87 @@
#![cfg(feature = "serde")]
extern crate bincode;
#[cfg(target_arch = "x86_64")]
extern crate cbor;
extern crate secp256k1;
use secp256k1::{PublicKey, SecretKey, XOnlyPublicKey};
#[cfg(feature = "global-context")]
use secp256k1::{Secp256k1, KeyPair};
// Arbitrary key data.
static SK_BYTES: [u8; 32] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23,
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31,
0x0f, 0x10, 0x1f, 0xa0, 0xa9, 0xaa, 0xaf, 0xff,
];
static PK_BYTES: [u8; 33] = [
0x02,
0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f,
0x1c, 0x97, 0x09, 0xe2, 0x30, 0x92, 0x06, 0x7d,
0x06, 0x83, 0x7f, 0x30, 0xaa, 0x0c, 0xd0, 0x54,
0x4a, 0xc8, 0x87, 0xfe, 0x91, 0xdd, 0xd1, 0x66,
];
static XONLY_PK_BYTES: [u8; 32] = [
0x18, 0x84, 0x57, 0x81, 0xf6, 0x31, 0xc4, 0x8f,
0x1c, 0x97, 0x09, 0xe2, 0x30, 0x92, 0x06, 0x7d,
0x06, 0x83, 0x7f, 0x30, 0xaa, 0x0c, 0xd0, 0x54,
0x4a, 0xc8, 0x87, 0xfe, 0x91, 0xdd, 0xd1, 0x66,
];
fn secret_key() -> SecretKey {
SecretKey::from_slice(&SK_BYTES).expect("failed to create sk from slice")
}
// Our current serde serialization implementation is only guaranteed to be fixed
// width for bincode. https://docs.rs/bincode/latest/bincode/index.html
#[test]
fn bincode_secret_key() {
let sk = secret_key();
let ser = bincode::serialize(&sk).unwrap();
assert_eq!(ser, SK_BYTES);
}
#[test]
fn bincode_public_key() {
let pk = PublicKey::from_slice(&PK_BYTES).expect("failed to create pk from slice");
let ser = bincode::serialize(&pk).unwrap();
assert_eq!(ser, &PK_BYTES as &[u8])
}
#[test]
#[cfg(feature = "global-context")]
fn bincode_key_pair() {
let secp = Secp256k1::new();
let kp = KeyPair::from_seckey_slice(&secp, &SK_BYTES).expect("failed to create keypair");
let ser = bincode::serialize(&kp).unwrap();
assert_eq!(ser, SK_BYTES);
}
#[test]
fn bincode_x_only_public_key() {
let pk = XOnlyPublicKey::from_slice(&XONLY_PK_BYTES).expect("failed to create xonly pk from slice");
let ser = bincode::serialize(&pk).unwrap();
assert_eq!(ser, XONLY_PK_BYTES);
}
// cbor adds an additional byte of metadata to certain byte values (byte_value < 24).
#[test]
#[cfg(target_arch = "x86_64")]
fn cbor() {
let sk = secret_key();
let mut e = cbor::Encoder::from_memory();
e.encode(sk.as_ref()).unwrap();
// 52 because there are 22 bytes in the key for which cbor adds metadata.
assert_eq!(e.as_bytes().len(), 52);
}