tronz_primitives/
signature.rs1use core::fmt;
4
5use k256::ecdsa::{RecoveryId, Signature};
6
7use crate::error::SignatureError;
8
9pub const SIGNATURE_LEN: usize = 65;
11
12#[derive(Clone, Copy, PartialEq, Eq, Hash)]
18pub struct RecoverableSignature {
19 r: [u8; 32],
20 s: [u8; 32],
21 v: u8,
22}
23
24impl RecoverableSignature {
25 pub fn from_signature(sig: &Signature, recovery_id: RecoveryId) -> Self {
27 let bytes = sig.to_bytes();
28 let mut r = [0u8; 32];
29 let mut s = [0u8; 32];
30 r.copy_from_slice(&bytes[..32]);
31 s.copy_from_slice(&bytes[32..]);
32 Self { r, s, v: recovery_id.to_byte() }
33 }
34
35 pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignatureError> {
40 if bytes.len() != SIGNATURE_LEN {
41 return Err(SignatureError::BadLength(bytes.len()));
42 }
43 let v = match bytes[64] {
44 v @ (0 | 1) => v,
45 27 => 0,
46 28 => 1,
47 other => return Err(SignatureError::BadRecoveryId(other)),
48 };
49 let mut r = [0u8; 32];
50 let mut s = [0u8; 32];
51 r.copy_from_slice(&bytes[..32]);
52 s.copy_from_slice(&bytes[32..64]);
53 Ok(Self { r, s, v })
54 }
55
56 pub fn r(&self) -> &[u8; 32] {
58 &self.r
59 }
60
61 pub fn s(&self) -> &[u8; 32] {
63 &self.s
64 }
65
66 pub fn v(&self) -> u8 {
68 self.v
69 }
70
71 pub fn to_bytes(&self) -> [u8; SIGNATURE_LEN] {
73 let mut out = [0u8; SIGNATURE_LEN];
74 out[..32].copy_from_slice(&self.r);
75 out[32..64].copy_from_slice(&self.s);
76 out[64] = self.v;
77 out
78 }
79
80 pub fn split(&self) -> Result<(Signature, RecoveryId), SignatureError> {
82 let mut rs = [0u8; 64];
83 rs[..32].copy_from_slice(&self.r);
84 rs[32..].copy_from_slice(&self.s);
85 let sig = Signature::from_slice(&rs)?;
86 let recid = RecoveryId::from_byte(self.v).ok_or(SignatureError::BadRecoveryId(self.v))?;
87 Ok((sig, recid))
88 }
89}
90
91impl fmt::Debug for RecoverableSignature {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 write!(f, "RecoverableSignature(0x{})", hex::encode(self.to_bytes()))
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use k256::ecdsa::{SigningKey, signature::hazmat::PrehashSigner};
100
101 use super::*;
102
103 #[test]
104 fn bytes_roundtrip() {
105 let mut bytes = [7u8; SIGNATURE_LEN];
106 bytes[64] = 1;
107 let sig = RecoverableSignature::from_bytes(&bytes).unwrap();
108 assert_eq!(sig.to_bytes(), bytes);
109 assert_eq!(sig.v(), 1);
110 }
111
112 #[test]
113 fn normalises_eth_v() {
114 let mut bytes = [3u8; SIGNATURE_LEN];
115 bytes[64] = 28;
116 let sig = RecoverableSignature::from_bytes(&bytes).unwrap();
117 assert_eq!(sig.v(), 1);
118 }
119
120 #[test]
121 fn bad_length_and_recid() {
122 assert!(matches!(
123 RecoverableSignature::from_bytes(&[0u8; 10]),
124 Err(SignatureError::BadLength(10))
125 ));
126 let mut bytes = [0u8; SIGNATURE_LEN];
127 bytes[64] = 5;
128 assert!(matches!(
129 RecoverableSignature::from_bytes(&bytes),
130 Err(SignatureError::BadRecoveryId(5))
131 ));
132 }
133
134 #[test]
135 fn from_signature_and_split() {
136 let signing = SigningKey::from_bytes(&[1u8; 32].into()).unwrap();
137 let (sig, recid): (Signature, RecoveryId) = signing.sign_prehash(&[9u8; 32]).unwrap();
138 let rec = RecoverableSignature::from_signature(&sig, recid);
139 let (sig2, recid2) = rec.split().unwrap();
140 assert_eq!(sig, sig2);
141 assert_eq!(recid.to_byte(), recid2.to_byte());
142 }
143}