1use crate::{get_hash_digest, BSVErrors, PublicKey, SigHash, SigningHash, ECDSA};
2use k256::{ecdsa::recoverable, ecdsa::Signature as SecpSignature, FieldBytes};
3use num_traits::FromPrimitive;
4#[cfg(target_arch = "wasm32")]
5use wasm_bindgen::{prelude::*, throw_str};
6
7#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen)]
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct RecoveryInfo {
10 is_y_odd: bool,
11 is_x_reduced: bool,
12 is_pubkey_compressed: bool,
13}
14
15#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen)]
16impl RecoveryInfo {
17 pub fn new(is_y_odd: bool, is_x_reduced: bool, is_pubkey_compressed: bool) -> RecoveryInfo {
18 RecoveryInfo {
19 is_y_odd,
20 is_x_reduced,
21 is_pubkey_compressed,
22 }
23 }
24
25 pub fn from_byte(recovery_byte: u8, is_pubkey_compressed: bool) -> RecoveryInfo {
26 RecoveryInfo {
27 is_x_reduced: (recovery_byte & 0b10) != 0,
28 is_y_odd: (recovery_byte & 1) != 0,
29 is_pubkey_compressed,
30 }
31 }
32
33 pub fn default() -> RecoveryInfo {
34 RecoveryInfo {
35 is_y_odd: false,
36 is_x_reduced: false,
37 is_pubkey_compressed: false,
38 }
39 }
40}
41
42#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen)]
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct Signature {
45 pub(crate) sig: k256::ecdsa::Signature,
46 pub(crate) recovery: Option<RecoveryInfo>,
47}
48
49impl Signature {
53 pub(crate) fn from_der_impl(bytes: &[u8]) -> Result<Signature, BSVErrors> {
54 let sighash_stripped_bytes = match bytes.last().and_then(|v| SigHash::from_u8(*v)) {
55 Some(_v) => bytes[0..bytes.len() - 1].to_vec(),
56 _ => bytes.to_vec(),
57 };
58
59 let sig = SecpSignature::from_der(&sighash_stripped_bytes)?;
60
61 Ok(Signature { sig, recovery: None })
62 }
63
64 pub(crate) fn from_hex_der_impl(hex: &str) -> Result<Signature, BSVErrors> {
65 let bytes = hex::decode(hex)?;
66 Signature::from_der_impl(&bytes)
67 }
68
69 pub(crate) fn get_public_key(&self, message: &[u8], hash_algo: SigningHash) -> Result<PublicKey, BSVErrors> {
70 let recovery = match &self.recovery {
71 Some(v) => v,
72 None => {
73 return Err(BSVErrors::PublicKeyRecoveryError(
74 "No recovery info is provided in this signature, unable to recover private key. Use compact byte serialisation instead.".into(),
75 ecdsa::Error::new(),
76 ))
77 }
78 };
79
80 let id = ecdsa::RecoveryId::new(recovery.is_y_odd, recovery.is_x_reduced);
81 let k256_recovery = id.try_into().map_err(|e| BSVErrors::PublicKeyRecoveryError("".into(), e))?;
82
83 let recoverable_sig = recoverable::Signature::new(&self.sig, k256_recovery)?;
84 let message_digest = get_hash_digest(hash_algo, message);
85 let verify_key = match recoverable_sig.recover_verify_key_from_digest(message_digest) {
86 Ok(v) => v,
87 Err(e) => {
88 return Err(BSVErrors::PublicKeyRecoveryError(format!("Signature Hex: {} Id: {:?}", self.to_der_hex(), recovery), e));
89 }
90 };
91
92 let pub_key = PublicKey::from_bytes_impl(&verify_key.to_bytes())?;
93
94 Ok(pub_key)
95 }
96
97 pub(crate) fn from_compact_impl(compact_bytes: &[u8]) -> Result<Signature, BSVErrors> {
98 let (recovery, is_compressed) = match (compact_bytes[0] - 27) as i8 - 4 {
101 x if x < 0 => (x + 4, false),
102 x => (x, true),
103 };
104
105 if recovery > 3 {
107 return Err(BSVErrors::SignatureError("Cannot have recovery byte that is larger than 3."));
108 }
109
110 let r = *FieldBytes::from_slice(&compact_bytes[1..33]);
111 let s = *FieldBytes::from_slice(&compact_bytes[33..65]);
112
113 let sig = SecpSignature::from_scalars(r, s)?;
114
115 Ok(Signature {
116 sig,
117 recovery: Some(RecoveryInfo::from_byte(recovery as u8, is_compressed)),
118 })
119 }
120}
121
122#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen)]
123impl Signature {
124 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen(js_name = toHex))]
126 pub fn to_der_hex(&self) -> String {
127 let bytes = self.sig.to_der();
128
129 hex::encode(bytes)
130 }
131
132 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen(js_name = toBytes))]
134 pub fn to_der_bytes(&self) -> Vec<u8> {
135 let bytes = self.sig.to_der();
136
137 bytes.as_bytes().to_vec()
138 }
139
140 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen(js_name = toCompactBytes))]
142 pub fn to_compact_bytes(&self, recovery_info: Option<RecoveryInfo>) -> Vec<u8> {
143 let RecoveryInfo {
145 is_y_odd,
146 is_x_reduced,
147 is_pubkey_compressed,
148 } = recovery_info.or_else(|| self.recovery.clone()).unwrap_or_else(RecoveryInfo::default);
149
150 let mut recovery = ((is_x_reduced as u8) << 1 | (is_y_odd as u8)) + 27 + 4;
151
152 if !is_pubkey_compressed {
153 recovery -= 4
154 }
155
156 let mut compact_buf = vec![recovery];
157 let r_bytes = &*self.sig.r().to_bytes();
158 compact_buf.extend_from_slice(r_bytes);
159
160 let s_bytes = &*self.sig.s().to_bytes();
161 compact_buf.extend_from_slice(s_bytes);
162
163 compact_buf
164 }
165
166 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen(js_name = toCompactHex))]
168 pub fn to_compact_hex(&self, recovery_info: Option<RecoveryInfo>) -> String {
169 hex::encode(self.to_compact_bytes(recovery_info))
170 }
171
172 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen(js_name = verifyMessage))]
173 pub fn verify_message(&self, message: &[u8], pub_key: &PublicKey) -> bool {
174 ECDSA::verify_digest_impl(message, pub_key, self, SigningHash::Sha256).is_ok()
175 }
176}
177
178#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen)]
182#[cfg(target_arch = "wasm32")]
183impl Signature {
184 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen(js_name = fromDER))]
185 pub fn from_der(bytes: &[u8]) -> Result<Signature, JsValue> {
186 Signature::from_der_impl(bytes).map_err(|e| JsValue::from_str(&e.to_string()))
187 }
188
189 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen(js_name = fromHexDER))]
190 pub fn from_hex_der(hex: &str) -> Result<Signature, JsValue> {
191 match Signature::from_hex_der_impl(hex) {
192 Ok(v) => Ok(v),
193 Err(e) => Err(JsValue::from_str(&e.to_string())),
194 }
195 }
196
197 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen(js_name = fromCompactBytes))]
198 pub fn from_compact_bytes(compact_bytes: &[u8]) -> Result<Signature, JsValue> {
199 match Signature::from_compact_impl(compact_bytes) {
200 Ok(v) => Ok(v),
201 Err(e) => Err(JsValue::from_str(&e.to_string())),
202 }
203 }
204
205 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-signature"), wasm_bindgen(js_name = recoverPublicKey))]
206 pub fn recover_public_key(&self, message: &[u8], hash_algo: SigningHash) -> Result<PublicKey, JsValue> {
207 match Signature::get_public_key(&self, &message, hash_algo) {
208 Ok(v) => Ok(v),
209 Err(e) => Err(JsValue::from_str(&e.to_string())),
210 }
211 }
212}
213
214#[cfg(not(target_arch = "wasm32"))]
218impl Signature {
219 #[cfg(not(target_arch = "wasm32"))]
220 pub fn from_der(bytes: &[u8]) -> Result<Signature, BSVErrors> {
221 Signature::from_der_impl(bytes)
222 }
223
224 #[cfg(not(target_arch = "wasm32"))]
225 pub fn from_hex_der(hex: &str) -> Result<Signature, BSVErrors> {
226 Signature::from_hex_der_impl(hex)
227 }
228
229 pub fn from_compact_bytes(compact_bytes: &[u8]) -> Result<Signature, BSVErrors> {
230 Signature::from_compact_impl(compact_bytes)
231 }
232
233 #[cfg(not(target_arch = "wasm32"))]
234 pub fn recover_public_key(&self, message: &[u8], hash_algo: SigningHash) -> Result<PublicKey, BSVErrors> {
235 Signature::get_public_key(self, message, hash_algo)
236 }
237}