1use bitcoin::secp256k1::{schnorr::Signature, Keypair, Message, SecretKey, XOnlyPublicKey};
21use bitcoin::Txid;
22use sidestr_core::block::secp;
23use sidestr_nostr::event::{SignRequest, Signer};
24
25use crate::error::{Error, Result};
26
27#[derive(Debug, Clone, Copy)]
30pub struct PartialRequest<'a> {
31 chain_id: &'a str,
32 height: u32,
33 template_id: [u8; 32],
34 digest: [u8; 32],
35}
36
37impl<'a> PartialRequest<'a> {
38 pub(crate) fn new(
39 chain_id: &'a str,
40 height: u32,
41 template_id: [u8; 32],
42 digest: [u8; 32],
43 ) -> Self {
44 Self {
45 chain_id,
46 height,
47 template_id,
48 digest,
49 }
50 }
51 pub fn chain_id(&self) -> &str {
53 self.chain_id
54 }
55 pub fn height(&self) -> u32 {
57 self.height
58 }
59 pub fn template_id(&self) -> &[u8; 32] {
62 &self.template_id
63 }
64 pub fn digest(&self) -> &[u8; 32] {
66 &self.digest
67 }
68}
69
70#[derive(Debug, Clone, Copy)]
74pub struct PegoutSignRequest<'a> {
75 chain_id: &'a str,
76 burn: &'a str,
77 unsigned_txid: Txid,
78 input: usize,
79 digest: [u8; 32],
80}
81
82impl<'a> PegoutSignRequest<'a> {
83 pub(crate) fn new(
84 chain_id: &'a str,
85 burn: &'a str,
86 unsigned_txid: Txid,
87 input: usize,
88 digest: [u8; 32],
89 ) -> Self {
90 Self {
91 chain_id,
92 burn,
93 unsigned_txid,
94 input,
95 digest,
96 }
97 }
98 pub fn chain_id(&self) -> &str {
100 self.chain_id
101 }
102 pub fn burn(&self) -> &str {
104 self.burn
105 }
106 pub fn unsigned_txid(&self) -> Txid {
108 self.unsigned_txid
109 }
110 pub fn input(&self) -> usize {
112 self.input
113 }
114 pub fn digest(&self) -> &[u8; 32] {
116 &self.digest
117 }
118}
119
120pub trait BlockSigner {
123 fn pubkey(&self) -> XOnlyPublicKey;
125 fn sign_partial(&self, request: &PartialRequest<'_>) -> Result<Signature>;
127 fn sign_pegout_input(&self, request: &PegoutSignRequest<'_>) -> Result<Signature>;
129}
130
131pub trait RoundSigner: Signer + BlockSigner {}
134impl<T: Signer + BlockSigner + ?Sized> RoundSigner for T {}
135
136pub struct LocalKey {
139 keypair: Keypair,
140}
141
142impl core::fmt::Debug for LocalKey {
143 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
144 f.debug_struct("LocalKey")
145 .field("pubkey", &self.pubkey())
146 .finish_non_exhaustive()
147 }
148}
149
150impl LocalKey {
151 pub fn new(key: SecretKey) -> Self {
153 Self {
154 keypair: Keypair::from_secret_key(secp(), &key),
155 }
156 }
157 pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self> {
159 Ok(Self::new(
160 SecretKey::from_slice(bytes).map_err(|e| Error::Key(e.to_string()))?,
161 ))
162 }
163 pub fn from_hex(text: &str) -> Result<Self> {
166 Ok(Self::new(sidestr_core::block::key_from_hex(text)?))
167 }
168 pub fn pubkey_hex(&self) -> String {
170 hex::encode(self.pubkey().serialize())
171 }
172 fn sign_digest(&self, digest: &[u8; 32]) -> Signature {
173 secp().sign_schnorr_with_aux_rand(&Message::from_digest(*digest), &self.keypair, &[0u8; 32])
174 }
175}
176
177impl Signer for LocalKey {
178 fn pubkey_hex(&self) -> sidestr_nostr::Result<String> {
179 Ok(LocalKey::pubkey_hex(self))
180 }
181 fn sign(&self, request: &SignRequest<'_>) -> sidestr_nostr::Result<[u8; 64]> {
182 Ok(*self.sign_digest(request.id()).as_ref())
183 }
184}
185
186impl BlockSigner for LocalKey {
187 fn pubkey(&self) -> XOnlyPublicKey {
188 self.keypair.x_only_public_key().0
189 }
190 fn sign_partial(&self, request: &PartialRequest<'_>) -> Result<Signature> {
191 Ok(self.sign_digest(request.digest()))
192 }
193 fn sign_pegout_input(&self, request: &PegoutSignRequest<'_>) -> Result<Signature> {
194 Ok(self.sign_digest(request.digest()))
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn a_local_key_is_deterministic_and_never_displays_itself() {
204 let k = LocalKey::from_hex(&format!(" {} \n", "07".repeat(32))).unwrap();
205 assert_eq!(k.pubkey_hex().len(), 64);
206 assert!(!format!("{k:?}").contains(&"07".repeat(32)));
207 let r = PartialRequest::new("sidestr:t", 1, [1u8; 32], [2u8; 32]);
208 assert_eq!(k.sign_partial(&r).unwrap(), k.sign_partial(&r).unwrap());
209 assert!(LocalKey::from_hex("zz").is_err());
210 assert!(LocalKey::from_bytes(&[0u8; 32]).is_err());
211 }
212}