sui_rpc_api/grpc/v2/
signature_verification_service.rs1use std::collections::HashMap;
5use sui_crypto::Verifier;
6use sui_crypto::zklogin::ZkLoginCircuitMode;
7use sui_protocol_config::ProtocolConfig;
8use sui_sdk_types::Jwk;
9use sui_sdk_types::JwkId;
10use tap::Pipe;
11
12use crate::ErrorReason;
13use crate::Result;
14use crate::RpcError;
15use crate::RpcService;
16use sui_rpc::proto::google::rpc::bad_request::FieldViolation;
17use sui_rpc::proto::sui::rpc::v2::VerifySignatureRequest;
18use sui_rpc::proto::sui::rpc::v2::VerifySignatureResponse;
19use sui_rpc::proto::sui::rpc::v2::signature_verification_service_server::SignatureVerificationService;
20
21#[tonic::async_trait]
22impl SignatureVerificationService for RpcService {
23 async fn verify_signature(
24 &self,
25 request: tonic::Request<VerifySignatureRequest>,
26 ) -> Result<tonic::Response<VerifySignatureResponse>, tonic::Status> {
27 verify_signature(self, request.into_inner())
28 .map(tonic::Response::new)
29 .map_err(Into::into)
30 }
31}
32
33#[tracing::instrument(skip(service))]
34fn verify_signature(
35 service: &RpcService,
36 request: VerifySignatureRequest,
37) -> Result<VerifySignatureResponse> {
38 let signature = request
39 .signature
40 .as_ref()
41 .ok_or_else(|| FieldViolation::new("signature").with_reason(ErrorReason::FieldMissing))?
42 .pipe(sui_sdk_types::UserSignature::try_from)
43 .map_err(|e| {
44 FieldViolation::new("signature")
45 .with_description(format!("invalid signature: {e}"))
46 .with_reason(ErrorReason::FieldInvalid)
47 })?;
48
49 let signing_digest = {
50 let bcs = request
51 .message
52 .ok_or_else(|| FieldViolation::new("message").with_reason(ErrorReason::FieldMissing))?;
53
54 match bcs.name() {
55 "TransactionData" => bcs
56 .deserialize::<sui_sdk_types::Transaction>()?
57 .signing_digest(),
58 "PersonalMessage" => bcs
59 .deserialize::<&[u8]>()
60 .map(|slice| sui_sdk_types::PersonalMessage(slice.into()))?
61 .signing_digest(),
62 _ => {
63 if let Ok(personal_message) = bcs
64 .deserialize::<&[u8]>()
65 .map(|slice| sui_sdk_types::PersonalMessage(slice.into()))
66 {
67 personal_message.signing_digest()
68 } else if let Ok(transaction) = bcs.deserialize::<sui_sdk_types::Transaction>() {
69 transaction.signing_digest()
70 } else {
71 return Err(FieldViolation::new("message")
72 .with_description("invalid message")
73 .with_reason(ErrorReason::FieldInvalid)
74 .into());
75 }
76 }
77 }
78 };
79
80 if let Some(address) = request
81 .address
82 .map(|address| address.parse::<sui_sdk_types::Address>())
83 .transpose()
84 .map_err(|e| {
85 FieldViolation::new("address")
86 .with_description(format!("invalid address: {e}"))
87 .with_reason(ErrorReason::FieldInvalid)
88 })?
89 {
90 let derived_addresses = match &signature {
92 sui_sdk_types::UserSignature::Simple(simple_signature) => match simple_signature {
93 sui_sdk_types::SimpleSignature::Ed25519 { public_key, .. } => {
94 [Some(public_key.derive_address()), None]
95 }
96 sui_sdk_types::SimpleSignature::Secp256k1 { public_key, .. } => {
97 [Some(public_key.derive_address()), None]
98 }
99 sui_sdk_types::SimpleSignature::Secp256r1 { public_key, .. } => {
100 [Some(public_key.derive_address()), None]
101 }
102 sui_sdk_types::SimpleSignature::MlDsa65 { public_key, .. } => {
103 [Some(public_key.derive_address()), None]
104 }
105 _ => {
106 return Err(RpcError::new(
107 tonic::Code::Internal,
108 "unknown signature scheme",
109 ));
110 }
111 },
112 sui_sdk_types::UserSignature::Multisig(multisig) => {
113 [Some(multisig.committee().derive_address()), None]
114 }
115 sui_sdk_types::UserSignature::ZkLogin(z) => {
116 let id = z.inputs.public_identifier();
117 [
118 Some(id.derive_address_padded()),
119 Some(id.derive_address_unpadded()),
120 ]
121 }
122 sui_sdk_types::UserSignature::Passkey(p) => {
123 [Some(p.public_key().derive_address()), None]
124 }
125 _ => {
126 return Err(RpcError::new(
127 tonic::Code::Internal,
128 "unknown signature scheme",
129 ));
130 }
131 };
132
133 let first_derived_address = derived_addresses[0].unwrap();
134
135 if !derived_addresses
137 .into_iter()
138 .flatten()
139 .any(|derived_address| derived_address == address)
140 {
141 let mut message = VerifySignatureResponse::default();
142 message.is_valid = Some(false);
143 message.reason = Some(format!(
144 "provided address `{}` does not match derived address `{}`",
145 address, first_derived_address
146 ));
147 return Ok(message);
148 }
149 }
150
151 let mut verifier = sui_crypto::UserSignatureVerifier::new();
153 if signature_may_contain_zklogin(&signature) {
154 verifier.with_zklogin_verifier(build_zklogin_verifier(service, &request.jwks)?);
155 }
156
157 let mut message = VerifySignatureResponse::default();
158 match verifier.verify(&signing_digest, &signature) {
159 Ok(()) => message.is_valid = Some(true),
160 Err(error) => {
161 message.is_valid = Some(false);
162 message.reason = Some(error.to_string());
163 }
164 }
165
166 Ok(message)
167}
168
169fn signature_may_contain_zklogin(signature: &sui_sdk_types::UserSignature) -> bool {
170 match signature {
171 sui_sdk_types::UserSignature::ZkLogin(_) => true,
172 sui_sdk_types::UserSignature::Multisig(multisig) => {
173 multisig.committee().members().iter().any(|member| {
174 matches!(
175 member.public_key(),
176 sui_sdk_types::MultisigMemberPublicKey::ZkLogin(_)
177 )
178 })
179 }
180 _ => false,
181 }
182}
183
184fn build_zklogin_verifier(
185 service: &RpcService,
186 request_jwks: &[sui_rpc::proto::sui::rpc::v2::ActiveJwk],
187) -> Result<sui_crypto::zklogin::ZkloginVerifier> {
188 let jwks = {
190 let mut jwks = request_jwks
191 .iter()
192 .enumerate()
193 .map(|(i, jwk)| {
194 let jwk = sui_sdk_types::ActiveJwk::try_from(jwk).map_err(|e| {
195 FieldViolation::new_at("jwks", i)
196 .with_description(e.to_string())
197 .with_reason(ErrorReason::FieldInvalid)
198 })?;
199 Ok((jwk.jwk_id, jwk.jwk))
200 })
201 .collect::<Result<HashMap<JwkId, Jwk>>>()?;
202
203 if jwks.is_empty()
204 && let Some(authenticator_state) = service.reader.get_authenticator_state()?
205 {
206 jwks.extend(
207 authenticator_state
208 .active_jwks
209 .into_iter()
210 .map(sui_sdk_types::ActiveJwk::from)
211 .map(|active_jwk| (active_jwk.jwk_id, active_jwk.jwk)),
212 );
213 }
214
215 jwks
216 };
217
218 let chain = service.chain_id().chain();
219 let mut zklogin_verifier = match chain {
220 sui_protocol_config::Chain::Mainnet | sui_protocol_config::Chain::Testnet => {
221 sui_crypto::zklogin::ZkloginVerifier::new_mainnet()
222 }
223 sui_protocol_config::Chain::Unknown => sui_crypto::zklogin::ZkloginVerifier::new_dev(),
224 };
225
226 let system_state = service.reader.get_system_state_summary()?;
228 let circuit_mode =
229 ProtocolConfig::get_for_version_if_supported(system_state.protocol_version.into(), chain)
230 .map(|config| config.zklogin_circuit_mode());
231 zklogin_verifier.set_circuit_mode(match circuit_mode {
232 Some(0) => ZkLoginCircuitMode::V1Only,
233 Some(1) => ZkLoginCircuitMode::Both,
234 Some(2) => ZkLoginCircuitMode::V2Only,
235 None => {
236 return Err(RpcError::new(
237 tonic::Code::Internal,
238 format!(
239 "protocol version {} is not supported",
240 system_state.protocol_version
241 ),
242 ));
243 }
244 Some(mode) => {
245 return Err(RpcError::new(
246 tonic::Code::Internal,
247 format!("invalid zklogin circuit mode in protocol config: {mode}"),
248 ));
249 }
250 });
251 *zklogin_verifier.jwks_mut() = jwks;
252 Ok(zklogin_verifier)
253}