cdk_signatory/proto/
client.rs1use 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#[allow(missing_debug_implementations)]
15pub struct SignatoryRpcClient {
16 client: SignatoryClient<InterceptedService<Channel, VersionInterceptor>>,
17 url: String,
18}
19
20#[derive(thiserror::Error, Debug)]
21pub enum ClientError {
23 #[error(transparent)]
25 Transport(#[from] tonic::transport::Error),
26
27 #[error(transparent)]
29 Io(#[from] std::io::Error),
30
31 #[error(transparent)]
33 Signatory(#[from] cdk_common::error::Error),
34
35 #[error("Invalid URL")]
37 InvalidUrl,
38}
39
40impl SignatoryRpcClient {
41 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}