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                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 none of the possible derived addresses match we need to return that this is invalid
136        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    // Building the zklogin verifier when the signature can actually contain a zklogin signature.
152    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    // If jwks from the request is empty we load the current set of active jwks that are onchain
189    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    // Get circuit mode from protocol config and set to verifier.
227    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}