1mod error;
25
26use core::ops::Deref;
27use std::sync::LazyLock;
28
29pub use bitcoin;
30use bitcoin::base64::Engine;
31use bitcoin::base64::engine::general_purpose::STANDARD;
32use bitcoin::hashes::{Hash, HashEngine, sha256d};
33pub use bitcoin::psbt::Psbt;
34pub use bitcoin::secp256k1;
35use bitcoin::secp256k1::{All, Message, Secp256k1, Signing};
36pub use bitcoin::{
37 Address, CompressedPublicKey, Network, NetworkKind, PrivateKey, PublicKey, Transaction,
38};
39pub use error::Error;
40
41static SECP: LazyLock<Secp256k1<All>> = LazyLock::new(Secp256k1::new);
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum AddressType {
46 P2pkh,
48 P2shP2wpkh,
50 P2wpkh,
52}
53
54#[derive(Debug, Clone)]
71pub struct Signer {
72 key: PrivateKey,
73}
74
75impl Deref for Signer {
76 type Target = PrivateKey;
77
78 #[inline]
79 fn deref(&self) -> &Self::Target {
80 &self.key
81 }
82}
83
84impl Drop for Signer {
85 fn drop(&mut self) {
86 self.key.inner.non_secure_erase();
87 }
88}
89
90impl Signer {
91 pub fn from_wif(wif: &str) -> Result<Self, Error> {
97 let key: PrivateKey = wif.parse()?;
98 Ok(Self { key })
99 }
100
101 pub fn from_hex(hex_str: &str, network: Network) -> Result<Self, Error> {
110 let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
111 let bytes: [u8; 32] = hex::decode(hex_str)?.try_into().map_err(|v: Vec<u8>| {
112 Error::InvalidKey(format!("expected 32 bytes, got {}", v.len()))
113 })?;
114 Self::from_bytes(&bytes, network)
115 }
116
117 pub fn from_bytes(bytes: &[u8; 32], network: Network) -> Result<Self, Error> {
123 let secret_key = secp256k1::SecretKey::from_slice(bytes)?;
124 Ok(Self {
125 key: PrivateKey::new(secret_key, network),
126 })
127 }
128
129 #[must_use]
131 pub fn random(network: Network) -> Self {
132 let (secret_key, _) = SECP.generate_keypair(&mut secp256k1::rand::thread_rng());
133 Self {
134 key: PrivateKey::new(secret_key, network),
135 }
136 }
137
138 #[must_use]
140 pub fn sign_ecdsa(&self, msg: &Message) -> secp256k1::ecdsa::Signature {
141 SECP.sign_ecdsa(msg, &self.key.inner)
142 }
143
144 #[must_use]
146 pub fn sign_schnorr(&self, msg: &Message) -> secp256k1::schnorr::Signature {
147 let keypair = secp256k1::Keypair::from_secret_key(&*SECP, &self.key.inner);
148 SECP.sign_schnorr(msg, &keypair)
149 }
150
151 pub fn sign_message(&self, msg: &str) -> Result<String, Error> {
157 self.sign_message_with_type(msg, AddressType::P2wpkh)
158 }
159
160 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
172 pub fn sign_message_with_type(
173 &self,
174 msg: &str,
175 addr_type: AddressType,
176 ) -> Result<String, Error> {
177 let secp_msg = Message::from_digest(Self::signed_msg_hash(msg));
178 let sig = SECP.sign_ecdsa_recoverable(&secp_msg, &self.key.inner);
179 let (recovery_id, sig_bytes) = sig.serialize_compact();
180
181 let flag_base: u8 = match addr_type {
182 AddressType::P2pkh if self.key.compressed => 31,
183 AddressType::P2pkh => 27,
184 AddressType::P2shP2wpkh => 35,
185 AddressType::P2wpkh => 39,
186 };
187
188 let mut buf = [0u8; 65];
189 buf[0] = flag_base + recovery_id.to_i32() as u8;
190 buf[1..].copy_from_slice(&sig_bytes);
191 Ok(STANDARD.encode(buf))
192 }
193
194 pub fn verify_message(
202 msg: &str,
203 signature_base64: &str,
204 expected_address: &Address,
205 network: Network,
206 ) -> Result<bool, Error> {
207 let raw = STANDARD
208 .decode(signature_base64)
209 .map_err(|e| Error::Signature(format!("invalid base64: {e}")))?;
210
211 if raw.len() != 65 {
212 return Err(Error::Signature(format!(
213 "expected 65 bytes, got {}",
214 raw.len()
215 )));
216 }
217
218 let flag = raw[0];
219 let recovery_id_raw = match flag {
220 27..=30 => flag - 27,
221 31..=34 => flag - 31,
222 35..=38 => flag - 35,
223 39..=42 => flag - 39,
224 _ => return Err(Error::Signature(format!("invalid flag byte: {flag}"))),
225 };
226
227 let recovery_id = secp256k1::ecdsa::RecoveryId::from_i32(i32::from(recovery_id_raw))?;
228 let recoverable =
229 secp256k1::ecdsa::RecoverableSignature::from_compact(&raw[1..], recovery_id)?;
230
231 let secp_msg = Message::from_digest(Self::signed_msg_hash(msg));
232 let recovered_pk = SECP.recover_ecdsa(&secp_msg, &recoverable)?;
233
234 let recovered_addr = match flag {
235 27..=30 => {
236 let pk = PublicKey::new_uncompressed(recovered_pk);
237 #[allow(deprecated)]
238 Address::p2pkh(pk, network)
239 }
240 31..=34 => {
241 let cpk = CompressedPublicKey(recovered_pk);
242 #[allow(deprecated)]
243 Address::p2pkh(PublicKey::from(cpk), network)
244 }
245 35..=38 => {
246 let cpk = CompressedPublicKey(recovered_pk);
247 Address::p2shwpkh(&cpk, network)
248 }
249 39..=42 => {
250 let cpk = CompressedPublicKey(recovered_pk);
251 Address::p2wpkh(&cpk, network)
252 }
253 _ => unreachable!(),
254 };
255
256 Ok(recovered_addr.script_pubkey() == expected_address.script_pubkey())
257 }
258
259 pub fn sign_psbt(&self, psbt: &mut Psbt) -> Result<(), Error> {
265 psbt.sign(&PsbtKey(self.key), &*SECP)
266 .map(|_| ())
267 .map_err(|(_, errors)| {
268 let msg: Vec<String> = errors
269 .iter()
270 .map(|(idx, err)| format!("input {idx}: {err}"))
271 .collect();
272 Error::Psbt(msg.join("; "))
273 })
274 }
275
276 #[must_use]
282 pub fn compressed_public_key(&self) -> CompressedPublicKey {
283 CompressedPublicKey::from_private_key(&*SECP, &self.key)
284 .expect("valid private key always produces valid public key")
285 }
286
287 #[must_use]
289 pub fn public_key(&self) -> PublicKey {
290 self.key.public_key(&*SECP)
291 }
292
293 #[inline]
295 #[must_use]
296 pub const fn network_kind(&self) -> NetworkKind {
297 self.key.network
298 }
299
300 #[must_use]
302 pub fn p2wpkh_address(&self, network: Network) -> Address {
303 Address::p2wpkh(&self.compressed_public_key(), network)
304 }
305
306 #[must_use]
308 pub fn p2tr_address(&self, network: Network) -> Address {
309 let keypair = secp256k1::Keypair::from_secret_key(&*SECP, &self.key.inner);
310 let (xonly, _) = keypair.x_only_public_key();
311 Address::p2tr(&*SECP, xonly, None, network)
312 }
313
314 #[must_use]
316 pub fn p2pkh_address(&self, network: Network) -> Address {
317 #[allow(deprecated)]
318 Address::p2pkh(self.public_key(), network)
319 }
320
321 #[must_use]
323 pub fn p2sh_p2wpkh_address(&self, network: Network) -> Address {
324 Address::p2shwpkh(&self.compressed_public_key(), network)
325 }
326
327 fn signed_msg_hash(msg: &str) -> [u8; 32] {
329 let mut engine = sha256d::Hash::engine();
330 engine.input(b"\x18Bitcoin Signed Message:\n");
331 let msg_bytes = msg.as_bytes();
332 Self::write_compact_size(&mut engine, msg_bytes.len());
333 engine.input(msg_bytes);
334 sha256d::Hash::from_engine(engine).to_byte_array()
335 }
336
337 #[allow(clippy::cast_possible_truncation)]
338 fn write_compact_size<E: HashEngine>(engine: &mut E, size: usize) {
339 if size < 253 {
340 engine.input(&[size as u8]);
341 } else if size <= 0xFFFF {
342 engine.input(&[253]);
343 engine.input(&(size as u16).to_le_bytes());
344 } else if size <= 0xFFFF_FFFF {
345 engine.input(&[254]);
346 engine.input(&(size as u32).to_le_bytes());
347 } else {
348 engine.input(&[255]);
349 engine.input(&(size as u64).to_le_bytes());
350 }
351 }
352}
353
354struct PsbtKey(PrivateKey);
356
357impl bitcoin::psbt::GetKey for PsbtKey {
358 type Error = bitcoin::psbt::GetKeyError;
359
360 fn get_key<C: Signing>(
361 &self,
362 key_request: bitcoin::psbt::KeyRequest,
363 secp: &Secp256k1<C>,
364 ) -> Result<Option<PrivateKey>, Self::Error> {
365 let our_pk = self.0.public_key(secp);
366 match key_request {
367 bitcoin::psbt::KeyRequest::Pubkey(ref pk) if our_pk.inner == pk.inner => {
368 Ok(Some(self.0))
369 }
370 bitcoin::psbt::KeyRequest::Bip32(_) => {
371 Ok(Some(self.0))
374 }
375 _ => Ok(None),
376 }
377 }
378}
379
380#[cfg(feature = "kobe")]
381impl Signer {
382 pub fn from_derived(
388 derived: &kobe_btc::DerivedAddress,
389 network: Network,
390 ) -> Result<Self, Error> {
391 Self::from_wif(&derived.private_key_wif)
392 .or_else(|_| Self::from_hex(&derived.private_key_hex, network))
393 }
394
395 pub fn from_standard_wallet(wallet: &kobe_btc::StandardWallet) -> Result<Self, Error> {
401 Self::from_wif(&wallet.to_wif())
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408
409 #[test]
410 fn assert_send_sync() {
411 fn assert<T: Send + Sync>() {}
412 assert::<Signer>();
413 }
414
415 #[test]
416 fn assert_clone() {
417 let s = Signer::random(Network::Bitcoin);
418 let s2 = s.clone();
419 assert_eq!(s.compressed_public_key(), s2.compressed_public_key());
420 }
421
422 #[test]
423 fn random_signer() {
424 let s = Signer::random(Network::Bitcoin);
425 assert!(!s.compressed_public_key().to_string().is_empty());
426 }
427
428 #[test]
429 fn wif_roundtrip() {
430 let s = Signer::random(Network::Bitcoin);
431 let wif = s.to_wif();
432 let restored = Signer::from_wif(&wif).unwrap();
433 assert_eq!(s.compressed_public_key(), restored.compressed_public_key());
434 }
435
436 #[test]
437 fn hex_roundtrip() {
438 let s = Signer::random(Network::Bitcoin);
439 let hex_key = hex::encode(s.key.inner.secret_bytes());
440 let restored = Signer::from_hex(&hex_key, Network::Bitcoin).unwrap();
441 assert_eq!(s.compressed_public_key(), restored.compressed_public_key());
442 }
443
444 #[test]
445 fn ecdsa_sign_verify() {
446 let s = Signer::random(Network::Bitcoin);
447 let msg = Message::from_digest([1u8; 32]);
448 let sig = s.sign_ecdsa(&msg);
449 SECP.verify_ecdsa(&msg, &sig, &s.public_key().inner)
450 .unwrap();
451 }
452
453 #[test]
454 fn schnorr_sign_verify() {
455 let s = Signer::random(Network::Bitcoin);
456 let msg = Message::from_digest([2u8; 32]);
457 let sig = s.sign_schnorr(&msg);
458 let keypair = secp256k1::Keypair::from_secret_key(&*SECP, &s.key.inner);
459 let (xonly, _) = keypair.x_only_public_key();
460 SECP.verify_schnorr(&sig, &msg, &xonly).unwrap();
461 }
462
463 #[test]
464 fn bip137_p2wpkh() {
465 let s = Signer::random(Network::Bitcoin);
466 let sig = s.sign_message("Hello, Bitcoin!").unwrap();
467 let addr = s.p2wpkh_address(Network::Bitcoin);
468 assert!(Signer::verify_message("Hello, Bitcoin!", &sig, &addr, Network::Bitcoin).unwrap());
469 }
470
471 #[test]
472 fn bip137_p2pkh() {
473 let s = Signer::random(Network::Bitcoin);
474 let sig = s
475 .sign_message_with_type("test", AddressType::P2pkh)
476 .unwrap();
477 let addr = s.p2pkh_address(Network::Bitcoin);
478 assert!(Signer::verify_message("test", &sig, &addr, Network::Bitcoin).unwrap());
479 }
480
481 #[test]
482 fn bip137_p2sh_p2wpkh() {
483 let s = Signer::random(Network::Bitcoin);
484 let sig = s
485 .sign_message_with_type("test", AddressType::P2shP2wpkh)
486 .unwrap();
487 let addr = s.p2sh_p2wpkh_address(Network::Bitcoin);
488 assert!(Signer::verify_message("test", &sig, &addr, Network::Bitcoin).unwrap());
489 }
490
491 #[test]
492 fn bip137_wrong_message_fails() {
493 let s = Signer::random(Network::Bitcoin);
494 let sig = s.sign_message("correct").unwrap();
495 let addr = s.p2wpkh_address(Network::Bitcoin);
496 assert!(!Signer::verify_message("wrong", &sig, &addr, Network::Bitcoin).unwrap());
497 }
498
499 #[test]
500 fn address_generation() {
501 let s = Signer::random(Network::Bitcoin);
502 assert!(!s.p2wpkh_address(Network::Bitcoin).to_string().is_empty());
503 assert!(!s.p2tr_address(Network::Bitcoin).to_string().is_empty());
504 assert!(!s.p2pkh_address(Network::Bitcoin).to_string().is_empty());
505 assert!(
506 !s.p2sh_p2wpkh_address(Network::Bitcoin)
507 .to_string()
508 .is_empty()
509 );
510 }
511
512 #[test]
513 fn network_kind() {
514 let s = Signer::random(Network::Testnet);
515 assert_eq!(s.network_kind(), NetworkKind::Test);
516 }
517
518 #[test]
519 fn deref_to_private_key() {
520 let s = Signer::random(Network::Bitcoin);
521 let _wif: String = s.to_wif();
522 }
523}