Skip to main content

sui_rpc_api/grpc/v2/
signature_verification_service.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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        //TODO add function in sui_sdk_types crate to do this
91        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 none of the possible derived addresses match we need to return that this is invalid
133        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    // Building the zklogin verifier when the signature can actually contain a zklogin signature.
149    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    // If jwks from the request is empty we load the current set of active jwks that are onchain
186    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    // Get circuit mode from protocol config and set to verifier.
224    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}