use crate::primitives::base_point::BasePoint;
use crate::primitives::big_number::{BigNumber, Endian};
use crate::primitives::curve::Curve;
use crate::primitives::error::PrimitivesError;
use crate::primitives::point::Point;
use crate::primitives::public_key::PublicKey;
#[derive(Clone, Debug)]
pub struct Signature {
r: BigNumber,
s: BigNumber,
}
impl Signature {
pub fn new(r: BigNumber, s: BigNumber) -> Self {
Signature { r, s }
}
pub fn r(&self) -> &BigNumber {
&self.r
}
pub fn s(&self) -> &BigNumber {
&self.s
}
pub fn from_der(data: &[u8]) -> Result<Self, PrimitivesError> {
if data.is_empty() {
return Err(PrimitivesError::InvalidSignature(
"empty DER data".to_string(),
));
}
let mut pos = 0;
if data[pos] != 0x30 {
return Err(PrimitivesError::InvalidSignature(
"DER signature must start with 0x30".to_string(),
));
}
pos += 1;
let total_len = Self::read_der_length(data, &mut pos)?;
if total_len + pos != data.len() {
return Err(PrimitivesError::InvalidSignature(
"DER signature length mismatch".to_string(),
));
}
if pos >= data.len() || data[pos] != 0x02 {
return Err(PrimitivesError::InvalidSignature(
"DER signature invalid: expected 0x02 for r".to_string(),
));
}
pos += 1;
let r_len = Self::read_der_length(data, &mut pos)?;
if pos + r_len > data.len() {
return Err(PrimitivesError::InvalidSignature(
"DER signature truncated at r".to_string(),
));
}
let mut r_bytes = &data[pos..pos + r_len];
pos += r_len;
if pos >= data.len() || data[pos] != 0x02 {
return Err(PrimitivesError::InvalidSignature(
"DER signature invalid: expected 0x02 for s".to_string(),
));
}
pos += 1;
let s_len = Self::read_der_length(data, &mut pos)?;
if data.len() != s_len + pos {
return Err(PrimitivesError::InvalidSignature(
"DER signature has trailing data".to_string(),
));
}
let mut s_bytes = &data[pos..pos + s_len];
if !r_bytes.is_empty() && r_bytes[0] == 0 {
if r_bytes.len() > 1 && (r_bytes[1] & 0x80) != 0 {
r_bytes = &r_bytes[1..];
} else if r_bytes.len() > 1 {
return Err(PrimitivesError::InvalidSignature(
"invalid R-value in DER: unnecessary leading zero".to_string(),
));
}
}
if !s_bytes.is_empty() && s_bytes[0] == 0 {
if s_bytes.len() > 1 && (s_bytes[1] & 0x80) != 0 {
s_bytes = &s_bytes[1..];
} else if s_bytes.len() > 1 {
return Err(PrimitivesError::InvalidSignature(
"invalid S-value in DER: unnecessary leading zero".to_string(),
));
}
}
let r = BigNumber::from_bytes(r_bytes, Endian::Big);
let s = BigNumber::from_bytes(s_bytes, Endian::Big);
Ok(Signature { r, s })
}
pub fn from_hex(hex: &str) -> Result<Self, PrimitivesError> {
let bytes = hex_to_bytes(hex)?;
Self::from_der(&bytes)
}
pub fn to_der(&self) -> Vec<u8> {
let r_bytes = self.canonicalize_int(&self.r);
let s_bytes = self.canonicalize_int(&self.s);
let inner_len = 2 + r_bytes.len() + 2 + s_bytes.len();
let mut result = Vec::with_capacity(inner_len + 2);
result.push(0x30);
Self::write_der_length(&mut result, inner_len);
result.push(0x02);
Self::write_der_length(&mut result, r_bytes.len());
result.extend_from_slice(&r_bytes);
result.push(0x02);
Self::write_der_length(&mut result, s_bytes.len());
result.extend_from_slice(&s_bytes);
result
}
pub fn to_hex(&self) -> String {
bytes_to_hex(&self.to_der())
}
fn canonicalize_int(&self, num: &BigNumber) -> Vec<u8> {
let mut bytes = num.to_array(Endian::Big, None);
if bytes.is_empty() {
return vec![0x00];
}
let mut start = 0;
while start < bytes.len() - 1 && bytes[start] == 0 && (bytes[start + 1] & 0x80) == 0 {
start += 1;
}
bytes = bytes[start..].to_vec();
if !bytes.is_empty() && (bytes[0] & 0x80) != 0 {
let mut padded = Vec::with_capacity(bytes.len() + 1);
padded.push(0x00);
padded.extend_from_slice(&bytes);
return padded;
}
bytes
}
fn read_der_length(data: &[u8], pos: &mut usize) -> Result<usize, PrimitivesError> {
if *pos >= data.len() {
return Err(PrimitivesError::InvalidSignature(
"DER length truncated".to_string(),
));
}
let initial = data[*pos];
*pos += 1;
if (initial & 0x80) != 0 {
return Err(PrimitivesError::InvalidSignature(
"DER entity length must be < 0x80".to_string(),
));
}
Ok(initial as usize)
}
fn write_der_length(buf: &mut Vec<u8>, len: usize) {
assert!(len < 0x80, "DER length must be < 0x80");
buf.push(len as u8);
}
pub fn from_compact(data: &[u8]) -> Result<Self, PrimitivesError> {
if data.len() != 64 {
return Err(PrimitivesError::InvalidSignature(format!(
"compact signature must be 64 bytes, got {}",
data.len()
)));
}
let r = BigNumber::from_bytes(&data[0..32], Endian::Big);
let s = BigNumber::from_bytes(&data[32..64], Endian::Big);
Ok(Signature { r, s })
}
pub fn to_compact(&self) -> Vec<u8> {
let mut result = Vec::with_capacity(64);
result.extend_from_slice(&self.r.to_array(Endian::Big, Some(32)));
result.extend_from_slice(&self.s.to_array(Endian::Big, Some(32)));
result
}
pub fn has_low_s(&self) -> bool {
let curve = Curve::secp256k1();
self.s.cmp(&curve.half_n) <= 0
}
pub fn to_low_s(&self) -> Signature {
if self.has_low_s() {
return self.clone();
}
let curve = Curve::secp256k1();
let new_s = curve.n.sub(&self.s);
Signature {
r: self.r.clone(),
s: new_s,
}
}
pub fn to_compact_bsm(&self, recovery: u8, compressed: bool) -> Vec<u8> {
let mut compact_byte = 27 + recovery;
if compressed {
compact_byte += 4;
}
let mut result = Vec::with_capacity(65);
result.push(compact_byte);
result.extend_from_slice(&self.r.to_array(Endian::Big, Some(32)));
result.extend_from_slice(&self.s.to_array(Endian::Big, Some(32)));
result
}
pub fn from_compact_bsm(data: &[u8]) -> Result<(Self, u8, bool), PrimitivesError> {
if data.len() != 65 {
return Err(PrimitivesError::InvalidSignature(
"compact BSM signature must be 65 bytes".to_string(),
));
}
let compact_byte = data[0];
if !(27..35).contains(&compact_byte) {
return Err(PrimitivesError::InvalidSignature(
"invalid compact byte (must be 27-34)".to_string(),
));
}
let compressed = compact_byte >= 31;
let recovery = if compressed {
compact_byte - 31
} else {
compact_byte - 27
};
let r = BigNumber::from_bytes(&data[1..33], Endian::Big);
let s = BigNumber::from_bytes(&data[33..65], Endian::Big);
Ok((Signature::new(r, s), recovery, compressed))
}
pub fn recover_public_key(
&self,
recovery: u8,
message_hash: &BigNumber,
) -> Result<PublicKey, PrimitivesError> {
let curve = Curve::secp256k1();
let n = &curve.n;
let is_y_odd = (recovery & 1) != 0;
let is_second_key = (recovery >> 1) != 0;
let x = if is_second_key {
self.r.add(n)
} else {
self.r.clone()
};
let r_point = Point::from_x(&x, is_y_odd)?;
let n_r = r_point.mul(n);
if !n_r.is_infinity() {
return Err(PrimitivesError::InvalidSignature(
"nR is not at infinity".to_string(),
));
}
let e_neg = message_hash
.neg()
.umod(n)
.map_err(|e| PrimitivesError::InvalidSignature(format!("umod failed: {}", e)))?;
let r_inv = self
.r
.invm(n)
.map_err(|e| PrimitivesError::InvalidSignature(format!("invm failed: {}", e)))?;
let sr = r_point.mul(&self.s);
let eg = BasePoint::instance().mul(&e_neg);
let q = sr.add(&eg).mul(&r_inv);
if q.is_infinity() {
return Err(PrimitivesError::InvalidSignature(
"recovered point is at infinity".to_string(),
));
}
Ok(PublicKey::from_point(q))
}
pub fn calculate_recovery_factor(
&self,
pubkey: &PublicKey,
message_hash: &BigNumber,
) -> Result<u8, PrimitivesError> {
for recovery in 0..4u8 {
if let Ok(recovered) = self.recover_public_key(recovery, message_hash) {
if pubkey == &recovered {
return Ok(recovery);
}
}
}
Err(PrimitivesError::InvalidSignature(
"unable to find valid recovery factor".to_string(),
))
}
}
fn hex_to_bytes(hex: &str) -> Result<Vec<u8>, PrimitivesError> {
if hex.len() & 1 != 0 {
return Err(PrimitivesError::InvalidHex(
"odd-length hex string".to_string(),
));
}
let mut bytes = Vec::with_capacity(hex.len() / 2);
for i in (0..hex.len()).step_by(2) {
let byte = u8::from_str_radix(&hex[i..i + 2], 16)
.map_err(|e| PrimitivesError::InvalidHex(e.to_string()))?;
bytes.push(byte);
}
Ok(bytes)
}
fn bytes_to_hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_signature_new() {
let r = BigNumber::one();
let s = BigNumber::one();
let sig = Signature::new(r.clone(), s.clone());
assert_eq!(sig.r().cmp(&r), 0);
assert_eq!(sig.s().cmp(&s), 0);
}
#[test]
fn test_der_encode_r1_s1() {
let sig = Signature::new(BigNumber::one(), BigNumber::one());
let der = sig.to_der();
assert_eq!(bytes_to_hex(&der), "3006020101020101");
}
#[test]
fn test_der_encode_high_bit_r_s() {
let r = BigNumber::from_hex("ff").unwrap();
let s = BigNumber::from_hex("ff").unwrap();
let sig = Signature::new(r, s);
let der = sig.to_der();
assert_eq!(bytes_to_hex(&der), "3008020200ff020200ff");
}
#[test]
fn test_der_encode_real_bitcoin_sig() {
let r =
BigNumber::from_hex("4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41")
.unwrap();
let s =
BigNumber::from_hex("181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09")
.unwrap();
let sig = Signature::new(r, s);
let der_hex = sig.to_hex();
assert_eq!(
der_hex,
"304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09"
);
}
#[test]
fn test_der_encode_r_with_high_bit() {
let r =
BigNumber::from_hex("813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc9832365")
.unwrap();
let s =
BigNumber::from_hex("6ff18a52dcc0336f7af62400a6dd9b810732baf1ff758000d6f613a556eb31ba")
.unwrap();
let sig = Signature::new(r, s);
let der_hex = sig.to_hex();
assert_eq!(
der_hex,
"3045022100813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc983236502206ff18a52dcc0336f7af62400a6dd9b810732baf1ff758000d6f613a556eb31ba"
);
}
#[test]
fn test_der_encode_both_high_bits() {
let r =
BigNumber::from_hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140")
.unwrap();
let s =
BigNumber::from_hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140")
.unwrap();
let sig = Signature::new(r, s);
let der_hex = sig.to_hex();
assert_eq!(
der_hex,
"3046022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140"
);
}
#[test]
fn test_der_round_trip_r1_s1() {
let sig = Signature::new(BigNumber::one(), BigNumber::one());
let der = sig.to_der();
let recovered = Signature::from_der(&der).unwrap();
assert_eq!(recovered.r().cmp(sig.r()), 0);
assert_eq!(recovered.s().cmp(sig.s()), 0);
}
#[test]
fn test_der_round_trip_real_sig() {
let r =
BigNumber::from_hex("4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41")
.unwrap();
let s =
BigNumber::from_hex("181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09")
.unwrap();
let sig = Signature::new(r, s);
let der = sig.to_der();
let recovered = Signature::from_der(&der).unwrap();
assert_eq!(recovered.r().cmp(sig.r()), 0);
assert_eq!(recovered.s().cmp(sig.s()), 0);
}
#[test]
fn test_der_round_trip_high_bits() {
let r =
BigNumber::from_hex("813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc9832365")
.unwrap();
let s =
BigNumber::from_hex("6ff18a52dcc0336f7af62400a6dd9b810732baf1ff758000d6f613a556eb31ba")
.unwrap();
let sig = Signature::new(r, s);
let der = sig.to_der();
let recovered = Signature::from_der(&der).unwrap();
assert_eq!(recovered.r().cmp(sig.r()), 0);
assert_eq!(recovered.s().cmp(sig.s()), 0);
}
#[test]
fn test_der_from_hex() {
let sig = Signature::from_hex(
"304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09",
).unwrap();
assert_eq!(
sig.r().to_hex(),
"4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41"
);
assert_eq!(
sig.s().to_hex(),
"181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09"
);
}
#[test]
fn test_der_invalid_tag() {
let result = Signature::from_der(&[0x31, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01]);
assert!(result.is_err());
}
#[test]
fn test_der_invalid_length() {
let result = Signature::from_der(&[0x30, 0x07, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01]);
assert!(result.is_err());
}
#[test]
fn test_der_empty() {
let result = Signature::from_der(&[]);
assert!(result.is_err());
}
#[test]
fn test_compact_round_trip() {
let r =
BigNumber::from_hex("4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41")
.unwrap();
let s =
BigNumber::from_hex("181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09")
.unwrap();
let sig = Signature::new(r, s);
let compact = sig.to_compact();
assert_eq!(compact.len(), 64);
let recovered = Signature::from_compact(&compact).unwrap();
assert_eq!(recovered.r().cmp(sig.r()), 0);
assert_eq!(recovered.s().cmp(sig.s()), 0);
}
#[test]
fn test_compact_encoding_hex() {
let r =
BigNumber::from_hex("4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41")
.unwrap();
let s =
BigNumber::from_hex("181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09")
.unwrap();
let sig = Signature::new(r, s);
let compact = sig.to_compact();
let hex = bytes_to_hex(&compact);
assert_eq!(
hex,
"4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09"
);
}
#[test]
fn test_compact_invalid_length() {
let result = Signature::from_compact(&[0; 63]);
assert!(result.is_err());
let result = Signature::from_compact(&[0; 65]);
assert!(result.is_err());
}
#[test]
fn test_low_s_already_low() {
let r =
BigNumber::from_hex("4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41")
.unwrap();
let s =
BigNumber::from_hex("181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09")
.unwrap();
let sig = Signature::new(r, s);
assert!(sig.has_low_s());
let normalized = sig.to_low_s();
assert_eq!(normalized.s().cmp(sig.s()), 0);
}
#[test]
fn test_low_s_needs_normalization() {
let r =
BigNumber::from_hex("4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41")
.unwrap();
let s =
BigNumber::from_hex("e7eadd137135f821b79f5b5322ed6f6137921779f39c5a19b7b03ce459a92438")
.unwrap();
let sig = Signature::new(r, s);
assert!(!sig.has_low_s());
let normalized = sig.to_low_s();
assert!(normalized.has_low_s());
assert_eq!(
normalized.s().to_hex(),
"181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09"
);
}
#[test]
fn test_low_s_boundary() {
let curve = Curve::secp256k1();
let sig = Signature::new(BigNumber::one(), curve.half_n.clone());
assert!(sig.has_low_s());
}
#[test]
fn test_low_s_boundary_plus_one() {
let curve = Curve::secp256k1();
let s = curve.half_n.addn(1);
let sig = Signature::new(BigNumber::one(), s);
assert!(!sig.has_low_s());
}
#[test]
fn test_compact_bsm_round_trip_compressed() {
let r =
BigNumber::from_hex("4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41")
.unwrap();
let s =
BigNumber::from_hex("181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09")
.unwrap();
let sig = Signature::new(r, s);
let compact = sig.to_compact_bsm(0, true);
assert_eq!(compact.len(), 65);
assert_eq!(compact[0], 31);
let (recovered_sig, recovery, compressed) = Signature::from_compact_bsm(&compact).unwrap();
assert_eq!(recovery, 0);
assert!(compressed);
assert_eq!(recovered_sig.r().cmp(sig.r()), 0);
assert_eq!(recovered_sig.s().cmp(sig.s()), 0);
}
#[test]
fn test_compact_bsm_round_trip_uncompressed() {
let r =
BigNumber::from_hex("4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41")
.unwrap();
let s =
BigNumber::from_hex("181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09")
.unwrap();
let sig = Signature::new(r, s);
let compact = sig.to_compact_bsm(1, false);
assert_eq!(compact.len(), 65);
assert_eq!(compact[0], 28);
let (recovered_sig, recovery, compressed) = Signature::from_compact_bsm(&compact).unwrap();
assert_eq!(recovery, 1);
assert!(!compressed);
assert_eq!(recovered_sig.r().cmp(sig.r()), 0);
assert_eq!(recovered_sig.s().cmp(sig.s()), 0);
}
#[test]
fn test_compact_bsm_all_recovery_values() {
let sig = Signature::new(BigNumber::one(), BigNumber::one());
for rec in 0..4u8 {
for &comp in &[true, false] {
let compact = sig.to_compact_bsm(rec, comp);
let (_, got_rec, got_comp) = Signature::from_compact_bsm(&compact).unwrap();
assert_eq!(
got_rec, rec,
"recovery mismatch for rec={} comp={}",
rec, comp
);
assert_eq!(
got_comp, comp,
"compressed mismatch for rec={} comp={}",
rec, comp
);
}
}
}
#[test]
fn test_compact_bsm_invalid_length() {
assert!(Signature::from_compact_bsm(&[0; 64]).is_err());
assert!(Signature::from_compact_bsm(&[0; 66]).is_err());
}
#[test]
fn test_compact_bsm_invalid_compact_byte() {
let mut data = [0u8; 65];
data[0] = 26; assert!(Signature::from_compact_bsm(&data).is_err());
data[0] = 35; assert!(Signature::from_compact_bsm(&data).is_err());
}
#[test]
fn test_recover_public_key() {
use crate::primitives::hash::sha256;
use crate::primitives::private_key::PrivateKey;
let priv_key = PrivateKey::from_hex("1").unwrap();
let pub_key = PublicKey::from_private_key(&priv_key);
let message = b"test message for recovery";
let msg_hash = sha256(message);
let msg_bn = BigNumber::from_bytes(&msg_hash, Endian::Big);
let sig = priv_key.sign(message, true).unwrap();
let recovery = sig.calculate_recovery_factor(&pub_key, &msg_bn).unwrap();
let recovered = sig.recover_public_key(recovery, &msg_bn).unwrap();
assert_eq!(pub_key, recovered, "recovered key should match original");
}
#[test]
fn test_recover_public_key_multiple_keys() {
use crate::primitives::hash::sha256;
use crate::primitives::private_key::PrivateKey;
let keys = [
"0000000000000000000000000000000000000000000000000000000000000001",
"00000000000000000000000000000000000000000000000000000000000000ff",
"00000000000000000000000000000000000000000000000000000000000003e8",
"00000000000000000000000000000000000000000000000000000000deadbeef",
];
for (i, key_hex) in keys.iter().enumerate() {
let priv_key = PrivateKey::from_hex(key_hex).unwrap();
let pub_key = PublicKey::from_private_key(&priv_key);
let message = format!("test message for recovery key {}", i);
let msg_hash = sha256(message.as_bytes());
let msg_bn = BigNumber::from_bytes(&msg_hash, Endian::Big);
let sig = priv_key.sign(message.as_bytes(), true).unwrap();
let recovery = sig.calculate_recovery_factor(&pub_key, &msg_bn).unwrap();
let recovered = sig.recover_public_key(recovery, &msg_bn).unwrap();
assert_eq!(
pub_key, recovered,
"recovered key should match for key {}",
key_hex
);
}
}
#[test]
fn test_calculate_recovery_factor_fails_with_wrong_key() {
use crate::primitives::hash::sha256;
use crate::primitives::private_key::PrivateKey;
let priv_key1 = PrivateKey::from_hex("1").unwrap();
let priv_key2 = PrivateKey::from_hex("2").unwrap();
let wrong_pub_key = PublicKey::from_private_key(&priv_key2);
let message = b"test message";
let msg_hash = sha256(message);
let msg_bn = BigNumber::from_bytes(&msg_hash, Endian::Big);
let sig = priv_key1.sign(message, true).unwrap();
let result = sig.calculate_recovery_factor(&wrong_pub_key, &msg_bn);
assert!(result.is_err(), "should fail with wrong public key");
}
#[test]
fn test_der_compact_same_values() {
let r =
BigNumber::from_hex("4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41")
.unwrap();
let s =
BigNumber::from_hex("181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09")
.unwrap();
let sig = Signature::new(r, s);
let from_der = Signature::from_der(&sig.to_der()).unwrap();
let from_compact = Signature::from_compact(&sig.to_compact()).unwrap();
assert_eq!(from_der.r().cmp(from_compact.r()), 0);
assert_eq!(from_der.s().cmp(from_compact.s()), 0);
}
#[test]
fn test_all_der_vectors() {
let vectors: Vec<(&str, &str, &str)> = vec![
("01", "01", "3006020101020101"),
("ff", "ff", "3008020200ff020200ff"),
(
"4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41",
"181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09",
"304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09",
),
(
"813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc9832365",
"6ff18a52dcc0336f7af62400a6dd9b810732baf1ff758000d6f613a556eb31ba",
"3045022100813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc983236502206ff18a52dcc0336f7af62400a6dd9b810732baf1ff758000d6f613a556eb31ba",
),
];
for (r_hex, s_hex, expected_der) in vectors {
let r = BigNumber::from_hex(r_hex).unwrap();
let s = BigNumber::from_hex(s_hex).unwrap();
let sig = Signature::new(r, s);
let der_hex = sig.to_hex();
assert_eq!(
der_hex, expected_der,
"DER encoding mismatch for r={}",
r_hex
);
let recovered = Signature::from_der(&sig.to_der()).unwrap();
assert_eq!(
recovered.r().cmp(sig.r()),
0,
"round-trip r mismatch for r={}",
r_hex
);
assert_eq!(
recovered.s().cmp(sig.s()),
0,
"round-trip s mismatch for r={}",
r_hex
);
}
}
}