1use crate::compat::CompatError;
9use crate::primitives::big_number::{BigNumber, Endian};
10use crate::primitives::ecdsa::{ecdsa_sign, ecdsa_verify};
11use crate::primitives::hash::hash256;
12use crate::primitives::private_key::PrivateKey;
13use crate::primitives::public_key::PublicKey;
14use crate::primitives::signature::Signature;
15
16const BSM_PREFIX: &[u8] = b"Bitcoin Signed Message:\n";
17
18pub struct BSM;
23
24fn write_varint(buf: &mut Vec<u8>, value: usize) {
32 if value < 0xfd {
33 buf.push(value as u8);
34 } else if value <= 0xffff {
35 buf.push(0xfd);
36 buf.extend_from_slice(&(value as u16).to_le_bytes());
37 } else if value <= 0xffffffff {
38 buf.push(0xfe);
39 buf.extend_from_slice(&(value as u32).to_le_bytes());
40 } else {
41 buf.push(0xff);
42 buf.extend_from_slice(&(value as u64).to_le_bytes());
43 }
44}
45
46impl BSM {
47 pub fn magic_hash(message: &[u8]) -> [u8; 32] {
54 let mut buf = Vec::new();
55 write_varint(&mut buf, BSM_PREFIX.len());
56 buf.extend_from_slice(BSM_PREFIX);
57 write_varint(&mut buf, message.len());
58 buf.extend_from_slice(message);
59 hash256(&buf)
60 }
61
62 pub fn sign(message: &[u8], private_key: &PrivateKey) -> Result<Vec<u8>, CompatError> {
68 let msg_hash = Self::magic_hash(message);
69
70 let sig = ecdsa_sign(&msg_hash, private_key.bn(), true)?;
72
73 let pub_key = private_key.to_public_key();
75 let msg_bn = BigNumber::from_bytes(&msg_hash, Endian::Big);
76 let recovery = sig.calculate_recovery_factor(&pub_key, &msg_bn)?;
77
78 Ok(sig.to_compact_bsm(recovery, true))
80 }
81
82 pub fn verify(
87 message: &[u8],
88 sig_bytes: &[u8],
89 pub_key: &PublicKey,
90 ) -> Result<bool, CompatError> {
91 let (signature, _recovery, _compressed) = Signature::from_compact_bsm(sig_bytes)?;
92
93 let msg_hash = Self::magic_hash(message);
94
95 Ok(ecdsa_verify(&msg_hash, &signature, pub_key.point()))
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use crate::primitives::big_number::{BigNumber, Endian};
103
104 fn base64_decode(input: &str) -> Vec<u8> {
106 let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
107 let mut result = Vec::new();
108 let mut buf: u32 = 0;
109 let mut bits: u32 = 0;
110
111 for &byte in input.as_bytes() {
112 if byte == b'=' {
113 break;
114 }
115 let val = table.iter().position(|&b| b == byte);
116 if let Some(v) = val {
117 buf = (buf << 6) | (v as u32);
118 bits += 6;
119 if bits >= 8 {
120 bits -= 8;
121 result.push((buf >> bits) as u8);
122 buf &= (1 << bits) - 1;
123 }
124 }
125 }
126 result
127 }
128
129 fn base64_encode(data: &[u8]) -> String {
131 let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
132 let mut result = String::new();
133 let mut i = 0;
134 while i < data.len() {
135 let b0 = data[i] as u32;
136 let b1 = if i + 1 < data.len() {
137 data[i + 1] as u32
138 } else {
139 0
140 };
141 let b2 = if i + 2 < data.len() {
142 data[i + 2] as u32
143 } else {
144 0
145 };
146 let triple = (b0 << 16) | (b1 << 8) | b2;
147 result.push(table[((triple >> 18) & 0x3f) as usize] as char);
148 result.push(table[((triple >> 12) & 0x3f) as usize] as char);
149 if i + 1 < data.len() {
150 result.push(table[((triple >> 6) & 0x3f) as usize] as char);
151 } else {
152 result.push('=');
153 }
154 if i + 2 < data.len() {
155 result.push(table[(triple & 0x3f) as usize] as char);
156 } else {
157 result.push('=');
158 }
159 i += 3;
160 }
161 result
162 }
163
164 #[test]
165 fn test_magic_hash_produces_32_bytes() {
166 let hash = BSM::magic_hash(b"hello");
167 assert_eq!(hash.len(), 32);
168 }
169
170 #[test]
171 fn test_magic_hash_deterministic() {
172 let h1 = BSM::magic_hash(b"hello");
173 let h2 = BSM::magic_hash(b"hello");
174 assert_eq!(h1, h2);
175 }
176
177 #[test]
178 fn test_magic_hash_different_messages() {
179 let h1 = BSM::magic_hash(b"hello");
180 let h2 = BSM::magic_hash(b"world");
181 assert_ne!(h1, h2);
182 }
183
184 #[test]
185 fn test_sign_produces_65_bytes() {
186 let priv_key = PrivateKey::from_hex(
187 "0000000000000000000000000000000000000000000000000000000000000001",
188 )
189 .unwrap();
190 let sig = BSM::sign(b"test message", &priv_key).unwrap();
191 assert_eq!(sig.len(), 65, "BSM signature must be 65 bytes");
192 }
193
194 #[test]
195 fn test_verify_correct_signature() {
196 let priv_key = PrivateKey::from_hex(
197 "0000000000000000000000000000000000000000000000000000000000000001",
198 )
199 .unwrap();
200 let pub_key = priv_key.to_public_key();
201 let message = b"test message";
202 let sig = BSM::sign(message, &priv_key).unwrap();
203 let result = BSM::verify(message, &sig, &pub_key).unwrap();
204 assert!(result, "verify should return true for matching sig");
205 }
206
207 #[test]
208 fn test_verify_wrong_message() {
209 let priv_key = PrivateKey::from_hex(
210 "0000000000000000000000000000000000000000000000000000000000000001",
211 )
212 .unwrap();
213 let pub_key = priv_key.to_public_key();
214 let sig = BSM::sign(b"correct message", &priv_key).unwrap();
215 let result = BSM::verify(b"wrong message", &sig, &pub_key).unwrap();
216 assert!(!result, "verify should return false for wrong message");
217 }
218
219 #[test]
220 fn test_verify_wrong_public_key() {
221 let priv_key1 = PrivateKey::from_hex(
222 "0000000000000000000000000000000000000000000000000000000000000001",
223 )
224 .unwrap();
225 let priv_key2 = PrivateKey::from_hex(
226 "0000000000000000000000000000000000000000000000000000000000000002",
227 )
228 .unwrap();
229 let wrong_pub_key = priv_key2.to_public_key();
230 let sig = BSM::sign(b"test message", &priv_key1).unwrap();
231 let result = BSM::verify(b"test message", &sig, &wrong_pub_key).unwrap();
232 assert!(!result, "verify should return false for wrong public key");
233 }
234
235 #[test]
236 fn test_sign_verify_roundtrip_various_messages() {
237 let priv_key = PrivateKey::from_hex(
238 "00000000000000000000000000000000000000000000000000000000deadbeef",
239 )
240 .unwrap();
241 let pub_key = priv_key.to_public_key();
242
243 let messages: &[&[u8]] = &[
244 b"",
245 b"a",
246 b"hello world",
247 b"The quick brown fox jumps over the lazy dog",
248 &[0u8; 256],
249 ];
250
251 for msg in messages {
252 let sig = BSM::sign(msg, &priv_key).unwrap();
253 assert_eq!(sig.len(), 65);
254 let result = BSM::verify(msg, &sig, &pub_key).unwrap();
255 assert!(
256 result,
257 "round-trip failed for message of length {}",
258 msg.len()
259 );
260 }
261 }
262
263 #[test]
264 fn test_cross_sdk_sign_vector() {
265 let priv_key =
269 PrivateKey::from_wif("L211enC224G1kV8pyyq7bjVd9SxZebnRYEzzM3i7ZHCc1c5E7dQu").unwrap();
270 let message = b"hello world";
271 let sig = BSM::sign(message, &priv_key).unwrap();
272 let sig_b64 = base64_encode(&sig);
273 assert_eq!(
274 sig_b64,
275 "H4T8Asr0WkC6wYfBESR6pCAfECtdsPM4fwiSQ2qndFi8dVtv/mrOFaySx9xQE7j24ugoJ4iGnsRwAC8QwaoHOXk=",
276 "BSM signature should match TS SDK output"
277 );
278 }
279
280 #[test]
281 fn test_cross_sdk_verify_vector() {
282 let sig_b64 = "IAV89EkfHSzAIA8cEWbbKHUYzJqcShkpWaXGJ5+mf4+YIlf3XNlr0bj9X60sNe1A7+x9qyk+zmXropMDY4370n8=";
284 let sig_bytes = base64_decode(sig_b64);
285 let pub_key = PublicKey::from_string(
286 "03d4d1a6c5d8c03b0e671bc1891b69afaecb40c0686188fe9019f93581b43e8334",
287 )
288 .unwrap();
289 let message = b"Texas";
290 let result = BSM::verify(message, &sig_bytes, &pub_key).unwrap();
291 assert!(result, "should verify TS SDK BSM signature for 'Texas'");
292 }
293}