use bitcoin::key::{Keypair, XOnlyPublicKey};
use bitcoin::secp256k1::schnorr::Signature;
use bitcoin::secp256k1::{Message, SecretKey};
use bitcoin::ScriptBuf;
use hmac::{Hmac, Mac};
use sha2::Sha256;
use sidestr_core::address::script_to_address;
use sidestr_core::block::{challenge_for, key_from_hex, secp};
use crate::error::{Error, Result};
pub trait SpendSigner {
fn pubkey(&self) -> XOnlyPublicKey;
fn sign_key_path(&self, sighash: &[u8; 32]) -> Result<Signature>;
fn script(&self) -> ScriptBuf {
script_for(&self.pubkey())
}
fn signs_elsewhere(&self) -> bool {
false
}
}
#[derive(Clone)]
pub struct PlainKey {
key: SecretKey,
keypair: Keypair,
}
impl core::fmt::Debug for PlainKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PlainKey")
.field("pubkey", &self.pubkey())
.finish_non_exhaustive()
}
}
impl PlainKey {
pub fn new(key: SecretKey) -> Self {
Self {
key,
keypair: Keypair::from_secret_key(secp(), &key),
}
}
pub fn from_hex(text: &str) -> Result<Self> {
Ok(Self::new(key_from_hex(text)?))
}
pub fn secret_key(&self) -> &SecretKey {
&self.key
}
}
impl SpendSigner for PlainKey {
fn pubkey(&self) -> XOnlyPublicKey {
self.keypair.x_only_public_key().0
}
fn sign_key_path(&self, sighash: &[u8; 32]) -> Result<Signature> {
Ok(secp().sign_schnorr_with_aux_rand(
&Message::from_digest(*sighash),
&self.keypair,
&[0u8; 32],
))
}
}
pub fn script_for(pubkey: &XOnlyPublicKey) -> ScriptBuf {
challenge_for(pubkey)
}
pub fn address_for(pubkey: &XOnlyPublicKey, hrp: &str) -> Option<String> {
script_to_address(&script_for(pubkey), hrp)
}
pub fn derive_subkey(root: &SecretKey, tag: &str) -> Result<SecretKey> {
let mut mac = Hmac::<Sha256>::new_from_slice(&root.secret_bytes())
.map_err(|e| Error::Signer(format!("hmac: {e}")))?;
mac.update(tag.as_bytes());
let out = mac.finalize().into_bytes();
Ok(SecretKey::from_slice(&out)?)
}
pub fn spend_tag(genesis_hash: &str, epoch: u32) -> String {
format!(
"sidestr/v1/spend/{}/{epoch}",
genesis_hash.trim().to_ascii_lowercase()
)
}
pub fn derive_spend_key(root: &SecretKey, genesis_hash: &str, epoch: u32) -> Result<SecretKey> {
let g = genesis_hash.trim();
if g.len() != 64 || !g.bytes().all(|b| b.is_ascii_hexdigit()) {
return Err(Error::Encoding(
"a genesis hash is 32 bytes of hex".to_string(),
));
}
derive_subkey(root, &spend_tag(g, epoch))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_key_signs_valid_schnorr() {
let k = PlainKey::new(SecretKey::from_slice(&[7u8; 32]).unwrap());
let digest = [0xabu8; 32];
let sig = k.sign_key_path(&digest).unwrap();
assert!(secp()
.verify_schnorr(&sig, &Message::from_digest(digest), &k.pubkey())
.is_ok());
assert_eq!(sig, k.sign_key_path(&digest).unwrap());
assert!(!format!("{k:?}").contains("0707"));
assert_eq!(k.script().as_bytes()[..2], [0x51, 0x20]);
}
#[test]
fn derivation_separates_domains() {
let root = SecretKey::from_slice(&[0x42u8; 32]).unwrap();
let g = "a".repeat(64);
let a = derive_spend_key(&root, &g, 0).unwrap();
let b = derive_spend_key(&root, &g, 1).unwrap();
let c = derive_spend_key(&root, &"b".repeat(64), 0).unwrap();
assert_ne!(a.secret_bytes(), b.secret_bytes());
assert_ne!(a.secret_bytes(), c.secret_bytes());
assert_ne!(a.secret_bytes(), root.secret_bytes());
assert_eq!(
a.secret_bytes(),
derive_spend_key(&root, &g.to_uppercase(), 0)
.unwrap()
.secret_bytes()
);
assert!(derive_spend_key(&root, "abc", 0).is_err());
assert_eq!(spend_tag(&g, 3), format!("sidestr/v1/spend/{g}/3"));
}
}