#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::{string::String, vec::Vec};
use bech32::{Bech32m, Hrp};
use ripemd::Ripemd160;
use sha2::{Digest, Sha256};
use signer_btc::bitcoin_message_digest;
pub use signer_primitives::{self, Sign, SignError, SignMessage, SignOutput};
use signer_primitives::{Secp256k1Signer, delegate_secp256k1_ctors};
const BIP137_COMPRESSED_P2PKH_OFFSET: u8 = 31;
const SPARK_HRP: &str = "spark";
#[derive(Debug)]
pub struct Signer(Secp256k1Signer);
impl Signer {
delegate_secp256k1_ctors!();
#[must_use]
pub fn address(&self) -> String {
let pubkey = self.0.compressed_public_key();
let hash160 = Ripemd160::digest(Sha256::digest(&pubkey));
let hrp = Hrp::parse_unchecked(SPARK_HRP);
#[allow(
clippy::expect_used,
reason = "HRP and 20-byte hash160 are always valid bech32m inputs"
)]
bech32::encode::<Bech32m>(hrp, &hash160).expect("valid bech32m")
}
#[must_use]
pub fn public_key_bytes(&self) -> Vec<u8> {
self.0.compressed_public_key()
}
#[must_use]
pub fn public_key_hex(&self) -> String {
hex::encode(self.0.compressed_public_key())
}
pub fn verify_hash(&self, hash: &[u8; 32], signature: &[u8]) -> Result<(), SignError> {
self.0.verify_prehash_any(hash, signature)
}
pub fn sign_transaction(&self, tx_bytes: &[u8]) -> Result<SignOutput, SignError> {
let digest: [u8; 32] = Sha256::digest(Sha256::digest(tx_bytes)).into();
self.0.sign_prehash_recoverable(&digest)
}
}
impl Sign for Signer {
type Error = SignError;
fn sign_hash(&self, hash: &[u8; 32]) -> Result<SignOutput, SignError> {
self.0.sign_prehash_recoverable(hash)
}
}
impl SignMessage for Signer {
fn sign_message(&self, message: &[u8]) -> Result<SignOutput, SignError> {
let digest = bitcoin_message_digest(message);
Ok(self
.0
.sign_prehash_recoverable(&digest)?
.with_v_offset(BIP137_COMPRESSED_P2PKH_OFFSET))
}
}
#[cfg(feature = "kobe")]
impl Signer {
pub fn from_derived(account: &kobe_spark::DerivedAccount) -> Result<Self, SignError> {
Self::from_bytes(account.private_key_bytes())
}
}
#[cfg(test)]
mod tests;