Skip to main content

cdk_signatory/
signatory.rs

1//! Signatory mod
2//!
3//! This module abstract all the key related operations, defining an interface for the necessary
4//! operations, to be implemented by the different signatory implementations.
5//!
6//! There is an in memory implementation, when the keys are stored in memory, in the same process,
7//! but it is isolated from the rest of the application, and they communicate through a channel with
8//! the defined API.
9use cdk_common::common::IssuerVersion;
10use cdk_common::error::Error;
11use cdk_common::mint::MintKeySetInfo;
12use cdk_common::nuts::nut02::KeySetVersion;
13use cdk_common::{
14    BlindSignature, BlindedMessage, CurrencyUnit, Id, KeySet, Keys, MintKeySet, Proof, PublicKey,
15};
16
17#[derive(Debug)]
18/// Type alias to make the keyset info API more useful, queryable by unit and Id
19pub enum KeysetIdentifier {
20    /// Mint Keyset by unit
21    Unit(CurrencyUnit),
22    /// Mint Keyset by Id
23    Id(Id),
24}
25
26impl From<Id> for KeysetIdentifier {
27    fn from(id: Id) -> Self {
28        Self::Id(id)
29    }
30}
31
32impl From<CurrencyUnit> for KeysetIdentifier {
33    fn from(unit: CurrencyUnit) -> Self {
34        Self::Unit(unit)
35    }
36}
37
38/// RotateKeyArguments
39///
40/// This struct is used to pass the arguments to the rotate_keyset function
41///
42/// TODO: Change argument to accept a vector of Amount instead of max_order.
43#[derive(Debug, Clone)]
44pub struct RotateKeyArguments {
45    /// Unit
46    pub unit: CurrencyUnit,
47    /// List of amounts to support
48    pub amounts: Vec<u64>,
49    /// Input fee
50    pub input_fee_ppk: u64,
51    /// KeySet Version
52    pub keyset_id_type: KeySetVersion,
53    /// FinalExpiry
54    pub final_expiry: Option<u64>,
55}
56
57#[derive(Debug, Clone)]
58/// Signatory keysets
59pub struct SignatoryKeysets {
60    /// The public key
61    pub pubkey: PublicKey,
62    /// The list of keysets
63    pub keysets: Vec<SignatoryKeySet>,
64}
65
66#[derive(Debug, Clone)]
67/// SignatoryKeySet
68///
69/// This struct is used to represent a keyset and its info, pretty much all the information but the
70/// private key, that will never leave the signatory
71pub struct SignatoryKeySet {
72    /// The keyset Id
73    pub id: Id,
74    /// The Currency Unit
75    pub unit: CurrencyUnit,
76    /// Whether to set it as active or not
77    pub active: bool,
78    /// The list of public keys
79    pub keys: Keys,
80    /// Amounts supported by the keyset
81    pub amounts: Vec<u64>,
82    /// Input fee for the keyset (parts per thousand)
83    pub input_fee_ppk: u64,
84    /// Final expiry of the keyset (unix timestamp in the future)
85    pub final_expiry: Option<u64>,
86    /// Issuer Version
87    pub issuer_version: Option<IssuerVersion>,
88    /// Version is the derivation_path_index
89    pub version: u32,
90}
91
92impl SignatoryKeySet {
93    /// Returns true if `final_expiry` is set and strictly in the past.
94    pub fn is_expired(&self) -> bool {
95        self.final_expiry
96            .is_some_and(|expiry| expiry < cdk_common::util::unix_time())
97    }
98}
99
100impl From<&SignatoryKeySet> for KeySet {
101    fn from(val: &SignatoryKeySet) -> Self {
102        val.to_owned().into()
103    }
104}
105
106impl From<SignatoryKeySet> for KeySet {
107    fn from(val: SignatoryKeySet) -> Self {
108        KeySet {
109            id: val.id,
110            unit: val.unit,
111            active: Some(val.active),
112            keys: val.keys,
113            input_fee_ppk: val.input_fee_ppk,
114            final_expiry: val.final_expiry,
115        }
116    }
117}
118
119impl From<&SignatoryKeySet> for MintKeySetInfo {
120    fn from(val: &SignatoryKeySet) -> Self {
121        val.to_owned().into()
122    }
123}
124
125impl From<SignatoryKeySet> for MintKeySetInfo {
126    fn from(val: SignatoryKeySet) -> Self {
127        MintKeySetInfo {
128            id: val.id,
129            unit: val.unit,
130            active: val.active,
131            input_fee_ppk: val.input_fee_ppk,
132            derivation_path: Default::default(),
133            derivation_path_index: Default::default(),
134            amounts: val.amounts,
135            final_expiry: val.final_expiry,
136            issuer_version: val.issuer_version,
137            valid_from: 0,
138        }
139    }
140}
141
142impl From<&(MintKeySetInfo, MintKeySet)> for SignatoryKeySet {
143    fn from((info, key): &(MintKeySetInfo, MintKeySet)) -> Self {
144        Self {
145            id: info.id,
146            unit: key.unit.clone(),
147            active: info.active,
148            input_fee_ppk: info.input_fee_ppk,
149            amounts: info.amounts.clone(),
150            keys: key.keys.clone().into(),
151            version: info.derivation_path_index.unwrap_or(1),
152            final_expiry: key.final_expiry,
153            issuer_version: info.issuer_version.clone(),
154        }
155    }
156}
157
158#[async_trait::async_trait]
159/// Signatory trait
160pub trait Signatory {
161    /// The Signatory implementation name. This may be exposed, so being as discrete as possible is
162    /// advised.
163    fn name(&self) -> String;
164
165    /// Blind sign a message.
166    ///
167    /// The message can be for a coin or an auth token.
168    async fn blind_sign(
169        &self,
170        blinded_messages: Vec<BlindedMessage>,
171    ) -> Result<Vec<BlindSignature>, Error>;
172
173    /// Verify [`Proof`] meets conditions and is signed by the mint (ignores P2PK/HTLC signatures"
174    async fn verify_proofs(&self, proofs: Vec<Proof>) -> Result<(), Error>;
175
176    /// Retrieve the list of all mint keysets
177    async fn keysets(&self) -> Result<SignatoryKeysets, Error>;
178
179    /// Add current keyset to inactive keysets
180    /// Generate new keyset
181    async fn rotate_keyset(&self, args: RotateKeyArguments) -> Result<SignatoryKeySet, Error>;
182}
183
184#[cfg(test)]
185mod tests {
186    use std::collections::BTreeMap;
187    use std::str::FromStr;
188
189    use cdk_common::nuts::nut01::Keys;
190    use cdk_common::util::unix_time;
191    use cdk_common::{CurrencyUnit, Id};
192
193    use super::*;
194
195    fn dummy_signatory_keyset(final_expiry: Option<u64>) -> SignatoryKeySet {
196        SignatoryKeySet {
197            id: Id::from_str("009a1f293253e41e").unwrap(),
198            unit: CurrencyUnit::Sat,
199            active: true,
200            keys: Keys::new(BTreeMap::new()),
201            amounts: vec![1, 2, 4, 8, 16, 32, 64, 128, 256, 512],
202            input_fee_ppk: 0,
203            final_expiry,
204            issuer_version: None,
205            version: 0,
206        }
207    }
208
209    #[test]
210    fn test_is_expired_none() {
211        let ks = dummy_signatory_keyset(None);
212        assert!(!ks.is_expired());
213    }
214
215    #[test]
216    fn test_is_expired_far_future() {
217        let ks = dummy_signatory_keyset(Some(unix_time() + 1_000_000));
218        assert!(!ks.is_expired());
219    }
220
221    #[test]
222    fn test_is_expired_exactly_now_is_not_expired() {
223        // strict less-than: expiry == now is not yet expired
224        let ks = dummy_signatory_keyset(Some(unix_time()));
225        assert!(!ks.is_expired());
226    }
227
228    #[test]
229    fn test_is_expired_one_second_ago() {
230        let ks = dummy_signatory_keyset(Some(unix_time() - 1));
231        assert!(ks.is_expired());
232    }
233
234    #[test]
235    fn test_is_expired_zero() {
236        let ks = dummy_signatory_keyset(Some(0));
237        assert!(ks.is_expired());
238    }
239}