use bitcoin::secp256k1::{schnorr::Signature, Keypair, Message, SecretKey, XOnlyPublicKey};
use bitcoin::Txid;
use sidestr_core::block::secp;
use sidestr_nostr::event::{SignRequest, Signer};
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy)]
pub struct PartialRequest<'a> {
chain_id: &'a str,
height: u32,
template_id: [u8; 32],
digest: [u8; 32],
}
impl<'a> PartialRequest<'a> {
pub(crate) fn new(
chain_id: &'a str,
height: u32,
template_id: [u8; 32],
digest: [u8; 32],
) -> Self {
Self {
chain_id,
height,
template_id,
digest,
}
}
pub fn chain_id(&self) -> &str {
self.chain_id
}
pub fn height(&self) -> u32 {
self.height
}
pub fn template_id(&self) -> &[u8; 32] {
&self.template_id
}
pub fn digest(&self) -> &[u8; 32] {
&self.digest
}
}
#[derive(Debug, Clone, Copy)]
pub struct PegoutSignRequest<'a> {
chain_id: &'a str,
burn: &'a str,
unsigned_txid: Txid,
input: usize,
digest: [u8; 32],
}
impl<'a> PegoutSignRequest<'a> {
pub(crate) fn new(
chain_id: &'a str,
burn: &'a str,
unsigned_txid: Txid,
input: usize,
digest: [u8; 32],
) -> Self {
Self {
chain_id,
burn,
unsigned_txid,
input,
digest,
}
}
pub fn chain_id(&self) -> &str {
self.chain_id
}
pub fn burn(&self) -> &str {
self.burn
}
pub fn unsigned_txid(&self) -> Txid {
self.unsigned_txid
}
pub fn input(&self) -> usize {
self.input
}
pub fn digest(&self) -> &[u8; 32] {
&self.digest
}
}
pub trait BlockSigner {
fn pubkey(&self) -> XOnlyPublicKey;
fn sign_partial(&self, request: &PartialRequest<'_>) -> Result<Signature>;
fn sign_pegout_input(&self, request: &PegoutSignRequest<'_>) -> Result<Signature>;
}
pub trait RoundSigner: Signer + BlockSigner {}
impl<T: Signer + BlockSigner + ?Sized> RoundSigner for T {}
pub struct LocalKey {
keypair: Keypair,
}
impl core::fmt::Debug for LocalKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("LocalKey")
.field("pubkey", &self.pubkey())
.finish_non_exhaustive()
}
}
impl LocalKey {
pub fn new(key: SecretKey) -> Self {
Self {
keypair: Keypair::from_secret_key(secp(), &key),
}
}
pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self> {
Ok(Self::new(
SecretKey::from_slice(bytes).map_err(|e| Error::Key(e.to_string()))?,
))
}
pub fn from_hex(text: &str) -> Result<Self> {
Ok(Self::new(sidestr_core::block::key_from_hex(text)?))
}
pub fn pubkey_hex(&self) -> String {
hex::encode(self.pubkey().serialize())
}
fn sign_digest(&self, digest: &[u8; 32]) -> Signature {
secp().sign_schnorr_with_aux_rand(&Message::from_digest(*digest), &self.keypair, &[0u8; 32])
}
}
impl Signer for LocalKey {
fn pubkey_hex(&self) -> sidestr_nostr::Result<String> {
Ok(LocalKey::pubkey_hex(self))
}
fn sign(&self, request: &SignRequest<'_>) -> sidestr_nostr::Result<[u8; 64]> {
Ok(*self.sign_digest(request.id()).as_ref())
}
}
impl BlockSigner for LocalKey {
fn pubkey(&self) -> XOnlyPublicKey {
self.keypair.x_only_public_key().0
}
fn sign_partial(&self, request: &PartialRequest<'_>) -> Result<Signature> {
Ok(self.sign_digest(request.digest()))
}
fn sign_pegout_input(&self, request: &PegoutSignRequest<'_>) -> Result<Signature> {
Ok(self.sign_digest(request.digest()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_local_key_is_deterministic_and_never_displays_itself() {
let k = LocalKey::from_hex(&format!(" {} \n", "07".repeat(32))).unwrap();
assert_eq!(k.pubkey_hex().len(), 64);
assert!(!format!("{k:?}").contains(&"07".repeat(32)));
let r = PartialRequest::new("sidestr:t", 1, [1u8; 32], [2u8; 32]);
assert_eq!(k.sign_partial(&r).unwrap(), k.sign_partial(&r).unwrap());
assert!(LocalKey::from_hex("zz").is_err());
assert!(LocalKey::from_bytes(&[0u8; 32]).is_err());
}
}