Skip to main content

cdk_signatory/proto/
client.rs

1use std::path::Path;
2
3use cdk_common::error::Error;
4use cdk_common::grpc::{VersionInterceptor, VERSION_SIGNATORY_HEADER};
5use cdk_common::{BlindSignature, BlindedMessage, Proof};
6use tonic::codegen::InterceptedService;
7use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
8
9use crate::proto;
10use crate::proto::signatory_client::SignatoryClient;
11use crate::signatory::{RotateKeyArguments, Signatory, SignatoryKeySet, SignatoryKeysets};
12
13/// A client for the Signatory service.
14#[allow(missing_debug_implementations)]
15pub struct SignatoryRpcClient {
16    client: SignatoryClient<InterceptedService<Channel, VersionInterceptor>>,
17    url: String,
18}
19
20#[derive(thiserror::Error, Debug)]
21/// Client Signatory Error
22pub enum ClientError {
23    /// Transport error
24    #[error(transparent)]
25    Transport(#[from] tonic::transport::Error),
26
27    /// IO-related errors
28    #[error(transparent)]
29    Io(#[from] std::io::Error),
30
31    /// Signatory Error
32    #[error(transparent)]
33    Signatory(#[from] cdk_common::error::Error),
34
35    /// Invalid URL
36    #[error("Invalid URL")]
37    InvalidUrl,
38}
39
40impl SignatoryRpcClient {
41    /// Create a new RemoteSigner from a tonic transport channel.
42    pub async fn new<A>(url: String, tls_dir: Option<A>) -> Result<Self, ClientError>
43    where
44        A: AsRef<Path>,
45    {
46        #[cfg(not(target_arch = "wasm32"))]
47        if rustls::crypto::CryptoProvider::get_default().is_none() {
48            let _ = rustls::crypto::ring::default_provider().install_default();
49        }
50
51        let channel = if let Some(tls_dir) = tls_dir {
52            let tls_dir = tls_dir.as_ref();
53            let server_root_ca_cert = std::fs::read_to_string(tls_dir.join("ca.pem"))?;
54            let server_root_ca_cert = Certificate::from_pem(server_root_ca_cert);
55            let client_cert = std::fs::read_to_string(tls_dir.join("client.pem"))?;
56            let client_key = std::fs::read_to_string(tls_dir.join("client.key"))?;
57            let client_identity = Identity::from_pem(client_cert, client_key);
58            let tls = ClientTlsConfig::new()
59                .ca_certificate(server_root_ca_cert)
60                .identity(client_identity);
61
62            Channel::from_shared(url.clone())
63                .map_err(|_| ClientError::InvalidUrl)?
64                .tls_config(tls)?
65                .connect()
66                .await?
67        } else {
68            Channel::from_shared(url.clone())
69                .map_err(|_| ClientError::InvalidUrl)?
70                .connect()
71                .await?
72        };
73
74        let version = (proto::Constants::SchemaVersion as u8).to_string();
75        let interceptor = VersionInterceptor::new(VERSION_SIGNATORY_HEADER, version);
76
77        Ok(Self {
78            client: SignatoryClient::with_interceptor(channel, interceptor),
79            url,
80        })
81    }
82}
83
84macro_rules! handle_error {
85    ($x:expr, $y:ident, scalar) => {{
86        let mut obj = $x.into_inner();
87        if let Some(err) = obj.error.take() {
88            return Err(err.into());
89        }
90
91        obj.$y
92    }};
93    ($x:expr, $y:ident) => {{
94        let mut obj = $x.into_inner();
95        if let Some(err) = obj.error.take() {
96            return Err(err.into());
97        }
98
99        obj.$y
100            .take()
101            .ok_or(Error::Custom("Internal error".to_owned()))?
102    }};
103}
104
105#[async_trait::async_trait]
106impl Signatory for SignatoryRpcClient {
107    fn name(&self) -> String {
108        format!("Rpc Signatory {}", self.url)
109    }
110
111    #[tracing::instrument(skip_all)]
112    async fn blind_sign(&self, request: Vec<BlindedMessage>) -> Result<Vec<BlindSignature>, Error> {
113        let req = super::BlindedMessages {
114            blinded_messages: request
115                .into_iter()
116                .map(|blind_message| blind_message.into())
117                .collect(),
118        };
119
120        self.client
121            .clone()
122            .blind_sign(tonic::Request::new(req))
123            .await
124            .map(|response| {
125                handle_error!(response, sigs)
126                    .blind_signatures
127                    .into_iter()
128                    .map(|blinded_signature| blinded_signature.try_into())
129                    .collect()
130            })
131            .map_err(|e| Error::Custom(e.to_string()))?
132    }
133
134    #[tracing::instrument(skip_all)]
135    async fn verify_proofs(&self, proofs: Vec<Proof>) -> Result<(), Error> {
136        let req: super::Proofs = proofs.into();
137        self.client
138            .clone()
139            .verify_proofs(tonic::Request::new(req))
140            .await
141            .map(|response| {
142                if handle_error!(response, success, scalar) {
143                    Ok(())
144                } else {
145                    Err(Error::SignatureMissingOrInvalid)
146                }
147            })
148            .map_err(|e| Error::Custom(e.to_string()))?
149    }
150
151    #[tracing::instrument(skip_all)]
152    async fn keysets(&self) -> Result<SignatoryKeysets, Error> {
153        self.client
154            .clone()
155            .keysets(tonic::Request::new(super::EmptyRequest {}))
156            .await
157            .map(|response| handle_error!(response, keysets).try_into())
158            .map_err(|e| Error::Custom(e.to_string()))?
159    }
160
161    #[tracing::instrument(skip(self))]
162    async fn rotate_keyset(&self, args: RotateKeyArguments) -> Result<SignatoryKeySet, Error> {
163        let req: super::RotationRequest = args.into();
164        self.client
165            .clone()
166            .rotate_keyset(tonic::Request::new(req))
167            .await
168            .map(|response| handle_error!(response, keyset).try_into())
169            .map_err(|e| Error::Custom(e.to_string()))?
170    }
171}