Skip to content

Commit 1c94c64

Browse files
committed
Add serde feature and serde for Signatures
1 parent 8fa4be9 commit 1c94c64

File tree

6 files changed

+173
-4
lines changed

6 files changed

+173
-4
lines changed

Cargo.lock

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

contrib/test.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
3+
if [ -z "$1" ]; then
4+
echo This test script runs the workspace tests and the -sys tests with the
5+
echo serde feature enabled. This is necessary because you can not test
6+
echo features on the workspace level. The script also checks if the files are
7+
echo rustfmt\'d.
8+
echo
9+
echo "ERROR: \$1 parameter must be the workspace directory"
10+
exit 1
11+
fi
12+
DIR=$1
13+
14+
shopt -s globstar
15+
16+
(
17+
cd "$DIR"
18+
set -e
19+
cargo test
20+
(
21+
cd secp256k1-zkp-sys
22+
cargo test --features serde
23+
)
24+
rustfmt --check -- **/*.rs
25+
)
26+
27+
if [ $? -ne 0 ]; then
28+
echo ERROR: $0 failed
29+
exit 1
30+
fi
31+

secp256k1-zkp-sys/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@ keywords = [ "crypto", "ECDSA", "Schnorr", "secp256k1", "libsecp256k1", "secp256
1111
readme = "README.md"
1212
build = "build.rs"
1313

14+
[package.metadata.docs.rs]
15+
features = [ "serde" ]
16+
all-features = true
17+
1418
[build-dependencies]
1519
cc = ">= 1.0.28, <= 1.0.35"
1620

1721
[dev-dependencies]
1822
rand = "0.6"
23+
serde_test = "1.0"
1924

2025
[dev-dependencies.secp256k1-zkp-dev]
2126
version = "0.1"
@@ -25,3 +30,8 @@ path = "../secp256k1-zkp-dev/"
2530
version = "0.12"
2631
# TODO
2732
path = "../../rust-secp256k1/"
33+
34+
[dependencies.serde]
35+
version = "1.0"
36+
optional = true
37+
default-features = false

secp256k1-zkp-sys/src/lib.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
#[cfg(any(test))]
3333
pub extern crate rand;
3434

35+
#[cfg(feature = "serde")]
36+
pub extern crate serde;
37+
#[cfg(all(feature = "serde", test))]
38+
pub extern crate serde_test;
39+
3540
#[cfg(test)]
3641
extern crate core;
3742
#[cfg(test)]
@@ -74,6 +79,33 @@ impl Drop for ScratchSpace {
7479
}
7580
}
7681

82+
/// Utility function used to parse hex into a target u8 buffer. Returns
83+
/// the number of bytes converted or an error if it encounters an invalid
84+
/// character or unexpected end of string.
85+
fn from_hex(hex: &str, target: &mut [u8]) -> Result<usize, ()> {
86+
if hex.len() % 2 == 1 || hex.len() > target.len() * 2 {
87+
return Err(());
88+
}
89+
90+
let mut b = 0;
91+
let mut idx = 0;
92+
for c in hex.bytes() {
93+
b <<= 4;
94+
match c {
95+
b'A'...b'F' => b |= c - b'A' + 10,
96+
b'a'...b'f' => b |= c - b'a' + 10,
97+
b'0'...b'9' => b |= c - b'0',
98+
_ => return Err(()),
99+
}
100+
if (idx & 1) == 1 {
101+
target[idx / 2] = b;
102+
b = 0;
103+
}
104+
idx += 1;
105+
}
106+
Ok(idx / 2)
107+
}
108+
77109
#[cfg(test)]
78110
mod tests {
79111
use super::ScratchSpace;

secp256k1-zkp-sys/src/schnorrsig/mod.rs

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
1919
mod ffi;
2020

21-
use core::{fmt, ptr};
21+
use core::{fmt, ptr, str};
2222

2323
use secp256k1::key::{PublicKey, SecretKey};
2424
use secp256k1::{self, Message, Secp256k1, Signing, Verification};
2525

26-
use super::ScratchSpace;
26+
use super::{from_hex, ScratchSpace};
2727

2828
/// A Schnorrsig error. This does not implement `std:error::Error` because it's not available with
2929
/// `no_std`.
@@ -61,7 +61,7 @@ impl fmt::Display for Error {
6161
}
6262

6363
/// A Schnorr signature
64-
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
64+
#[derive(Copy, Clone, PartialEq, Eq)]
6565
pub struct Signature(ffi::SchnorrSignature);
6666
impl Signature {
6767
#[inline]
@@ -113,6 +113,53 @@ impl From<ffi::SchnorrSignature> for Signature {
113113
}
114114
}
115115

116+
impl fmt::Debug for Signature {
117+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118+
fmt::Display::fmt(self, f)
119+
}
120+
}
121+
122+
impl fmt::Display for Signature {
123+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
124+
let v = self.serialize();
125+
for ch in &v[..] {
126+
write!(f, "{:02x}", *ch)?;
127+
}
128+
Ok(())
129+
}
130+
}
131+
132+
impl str::FromStr for Signature {
133+
type Err = Error;
134+
fn from_str(s: &str) -> Result<Signature, Error> {
135+
let mut res = [0; 64];
136+
if s.len() != 64 * 2 {
137+
return Err(Error::InvalidSignature);
138+
}
139+
match from_hex(s, &mut res) {
140+
Ok(x) => Signature::parse(&res[0..x]),
141+
_ => Err(Error::InvalidSignature),
142+
}
143+
}
144+
}
145+
146+
#[cfg(feature = "serde")]
147+
impl ::serde::Serialize for Signature {
148+
fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
149+
s.serialize_bytes(&self.serialize())
150+
}
151+
}
152+
153+
#[cfg(feature = "serde")]
154+
impl<'de> ::serde::Deserialize<'de> for Signature {
155+
fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Signature, D::Error> {
156+
use serde::de::Error;
157+
158+
let sl: &[u8] = ::serde::Deserialize::deserialize(d)?;
159+
Signature::parse(sl).map_err(D::Error::custom)
160+
}
161+
}
162+
116163
/// Schnorrsig signing trait
117164
pub trait Sign {
118165
/// Creates a Schnorr signature as defined by BIP-schnorr from a message and a secret key.
@@ -221,6 +268,7 @@ impl<C: Verification> Verify for Secp256k1<C> {
221268
#[cfg(test)]
222269
mod tests {
223270
use super::{Error, ScratchSpace, Sign, Signature, Verify};
271+
use core::str::FromStr;
224272
use rand::{thread_rng, RngCore};
225273
use secp256k1::{Message, Secp256k1};
226274
use secp256k1_zkp_dev::GenerateKeypair;
@@ -247,7 +295,35 @@ mod tests {
247295
0xA1, 0xA6, 0x4D, 0xC5, 0x9F, 0x70, 0x13, 0xFD,
248296
];
249297
// Shouldn't return error right now
250-
Signature::parse(&ser_sig).unwrap();
298+
let sig = Signature::parse(&ser_sig).unwrap();
299+
300+
let hex_sig = format!("{}", sig);
301+
assert_eq!(sig, Signature::from_str(&hex_sig).unwrap());
302+
let hex_sig = "";
303+
assert_eq!(Signature::from_str(&hex_sig), Err(Error::InvalidSignature));
304+
let hex_sig = "x";
305+
assert_eq!(Signature::from_str(&hex_sig), Err(Error::InvalidSignature));
306+
}
307+
308+
#[cfg(feature = "serde")]
309+
#[test]
310+
fn test_signature_serde() {
311+
use secp256k1::SecretKey;
312+
use serde_test::{assert_tokens, Token};
313+
314+
let s = Secp256k1::new();
315+
316+
let msg = Message::from_slice(&[1; 32]).unwrap();
317+
let sk = SecretKey::from_slice(&[2; 32]).unwrap();
318+
let sig = s.schnorrsig_sign(&msg, &sk);
319+
static SIG_BYTES: [u8; 64] = [
320+
0x9D, 0x0B, 0xAD, 0x57, 0x67, 0x19, 0xD3, 0x2A, 0xE7, 0x6B, 0xED, 0xB3, 0x4C, 0x77,
321+
0x48, 0x66, 0x67, 0x3C, 0xBD, 0xE3, 0xF4, 0xE1, 0x29, 0x51, 0x55, 0x5C, 0x94, 0x08,
322+
0xE6, 0xCE, 0x77, 0x4B, 0xA2, 0x9B, 0x2E, 0x41, 0x74, 0x30, 0x82, 0x17, 0x1E, 0x35,
323+
0xD5, 0x63, 0xC9, 0x9D, 0x0F, 0xCE, 0xB6, 0xC4, 0x0B, 0x9A, 0xC2, 0x80, 0x77, 0x33,
324+
0x59, 0xE7, 0xB5, 0x6C, 0x09, 0x41, 0x8D, 0x6D,
325+
];
326+
assert_tokens(&sig, &[Token::BorrowedBytes(&SIG_BYTES[..])]);
251327
}
252328

253329
#[test]

secp256k1-zkp/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ description = "Rust bindings for the Elements Project's `libsecp256k1-zkp` libra
1010
keywords = [ "crypto", "ECDSA", "Schnorr", "secp256k1", "libsecp256k1", "secp256k1-zkp", "libsecp256k1-zkp", "bitcoin" ]
1111
readme = "README.md"
1212

13+
[features]
14+
serde = ["secp256k1-zkp-sys/serde"]
15+
1316
[dev-dependencies]
1417
rand = "0.6"
1518

0 commit comments

Comments
 (0)