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 _ => {
103 return Err(RpcError::new(
104 tonic::Code::Internal,
105 "unknown signature scheme",
106 ));
107 }
108 },
109 sui_sdk_types::UserSignature::Multisig(multisig) => {
110 [Some(multisig.committee().derive_address()), None]
111 }
112 sui_sdk_types::UserSignature::ZkLogin(z) => {
113 let id = z.inputs.public_identifier();
114 [
115 Some(id.derive_address_padded()),
116 Some(id.derive_address_unpadded()),
117 ]
118 }
119 sui_sdk_types::UserSignature::Passkey(p) => {
120 [Some(p.public_key().derive_address()), None]
121 }
122 _ => {
123 return Err(RpcError::new(
124 tonic::Code::Internal,
125 "unknown signature scheme",
126 ));
127 }
128 };
129
130 let first_derived_address = derived_addresses[0].unwrap();
131
132 if !derived_addresses
134 .into_iter()
135 .flatten()
136 .any(|derived_address| derived_address == address)
137 {
138 let mut message = VerifySignatureResponse::default();
139 message.is_valid = Some(false);
140 message.reason = Some(format!(
141 "provided address `{}` does not match derived address `{}`",
142 address, first_derived_address
143 ));
144 return Ok(message);
145 }
146 }
147
148 let mut verifier = sui_crypto::UserSignatureVerifier::new();
150 if signature_may_contain_zklogin(&signature) {
151 verifier.with_zklogin_verifier(build_zklogin_verifier(service, &request.jwks)?);
152 }
153
154 let mut message = VerifySignatureResponse::default();
155 match verifier.verify(&signing_digest, &signature) {
156 Ok(()) => message.is_valid = Some(true),
157 Err(error) => {
158 message.is_valid = Some(false);
159 message.reason = Some(error.to_string());
160 }
161 }
162
163 Ok(message)
164}
165
166fn signature_may_contain_zklogin(signature: &sui_sdk_types::UserSignature) -> bool {
167 match signature {
168 sui_sdk_types::UserSignature::ZkLogin(_) => true,
169 sui_sdk_types::UserSignature::Multisig(multisig) => {
170 multisig.committee().members().iter().any(|member| {
171 matches!(
172 member.public_key(),
173 sui_sdk_types::MultisigMemberPublicKey::ZkLogin(_)
174 )
175 })
176 }
177 _ => false,
178 }
179}
180
181fn build_zklogin_verifier(
182 service: &RpcService,
183 request_jwks: &[sui_rpc::proto::sui::rpc::v2::ActiveJwk],
184) -> Result<sui_crypto::zklogin::ZkloginVerifier> {
185 let jwks = {
187 let mut jwks = request_jwks
188 .iter()
189 .enumerate()
190 .map(|(i, jwk)| {
191 let jwk = sui_sdk_types::ActiveJwk::try_from(jwk).map_err(|e| {
192 FieldViolation::new_at("jwks", i)
193 .with_description(e.to_string())
194 .with_reason(ErrorReason::FieldInvalid)
195 })?;
196 Ok((jwk.jwk_id, jwk.jwk))
197 })
198 .collect::<Result<HashMap<JwkId, Jwk>>>()?;
199
200 if jwks.is_empty()
201 && let Some(authenticator_state) = service.reader.get_authenticator_state()?
202 {
203 jwks.extend(
204 authenticator_state
205 .active_jwks
206 .into_iter()
207 .map(sui_sdk_types::ActiveJwk::from)
208 .map(|active_jwk| (active_jwk.jwk_id, active_jwk.jwk)),
209 );
210 }
211
212 jwks
213 };
214
215 let chain = service.chain_id().chain();
216 let mut zklogin_verifier = match chain {
217 sui_protocol_config::Chain::Mainnet | sui_protocol_config::Chain::Testnet => {
218 sui_crypto::zklogin::ZkloginVerifier::new_mainnet()
219 }
220 sui_protocol_config::Chain::Unknown => sui_crypto::zklogin::ZkloginVerifier::new_dev(),
221 };
222
223 let system_state = service.reader.get_system_state_summary()?;
225 let circuit_mode =
226 ProtocolConfig::get_for_version_if_supported(system_state.protocol_version.into(), chain)
227 .map(|config| config.zklogin_circuit_mode());
228 zklogin_verifier.set_circuit_mode(match circuit_mode {
229 Some(0) => ZkLoginCircuitMode::V1Only,
230 Some(1) => ZkLoginCircuitMode::Both,
231 Some(2) => ZkLoginCircuitMode::V2Only,
232 None => {
233 return Err(RpcError::new(
234 tonic::Code::Internal,
235 format!(
236 "protocol version {} is not supported",
237 system_state.protocol_version
238 ),
239 ));
240 }
241 Some(mode) => {
242 return Err(RpcError::new(
243 tonic::Code::Internal,
244 format!("invalid zklogin circuit mode in protocol config: {mode}"),
245 ));
246 }
247 });
248 *zklogin_verifier.jwks_mut() = jwks;
249 Ok(zklogin_verifier)
250}