|
| 1 | +use std::fmt::Debug; |
| 2 | +use std::sync::Arc; |
| 3 | +use std::time::{SystemTime, UNIX_EPOCH}; |
| 4 | + |
| 5 | +use apollo_network_types::network_types::PeerId; |
| 6 | +use apollo_protobuf::protobuf::{PublicKeyAndChallenge, SignedChallengeAndIdentity}; |
| 7 | +use apollo_signature_manager::signature_manager::{verify_identity, SignatureVerificationError}; |
| 8 | +use apollo_signature_manager_types::{SharedSignatureManagerClient, SignatureManagerClientError}; |
| 9 | +use async_trait::async_trait; |
| 10 | +use mockall::automock; |
| 11 | +use rand::rngs::OsRng; |
| 12 | +use rand::RngCore; |
| 13 | +use starknet_api::crypto::utils::{PublicKey, RawSignature}; |
| 14 | +use starknet_types_core::felt::Felt; |
| 15 | +use thiserror::Error; |
| 16 | +use tokio::task; |
| 17 | + |
| 18 | +use crate::authentication::negotiator::{ |
| 19 | + ConnectionReceiver, |
| 20 | + ConnectionSender, |
| 21 | + NegotiationSide, |
| 22 | + Negotiator, |
| 23 | + NegotiatorOutput, |
| 24 | +}; |
| 25 | + |
| 26 | +pub type StarkAuthNegotiatorResult<T> = Result<T, StarkAuthNegotiatorError>; |
| 27 | + |
| 28 | +#[cfg_attr(any(feature = "testing", test), automock)] |
| 29 | +#[async_trait] |
| 30 | +pub trait ChallengeGenerator: Send + Sync { |
| 31 | + async fn generate(&self) -> Vec<u8>; |
| 32 | +} |
| 33 | + |
| 34 | +struct OsRngChallengeGenerator; |
| 35 | + |
| 36 | +#[async_trait] |
| 37 | +impl ChallengeGenerator for OsRngChallengeGenerator { |
| 38 | + async fn generate(&self) -> Vec<u8> { |
| 39 | + task::block_in_place(|| { |
| 40 | + let first_u64 = OsRng.next_u64(); |
| 41 | + let second_u64 = OsRng.next_u64(); |
| 42 | + let combined_u128 = (first_u64 as u128) << 64 | (second_u64 as u128); |
| 43 | + |
| 44 | + // TODO(noam.s): should this be little endian? |
| 45 | + combined_u128.to_be_bytes().to_vec() |
| 46 | + }) |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +struct TimeStampChallengeGenerator; |
| 51 | + |
| 52 | +#[async_trait] |
| 53 | +impl ChallengeGenerator for TimeStampChallengeGenerator { |
| 54 | + async fn generate(&self) -> Vec<u8> { |
| 55 | + // Return current system time in nanoseconds. |
| 56 | + let timestamp = SystemTime::now() |
| 57 | + .duration_since(UNIX_EPOCH) |
| 58 | + .expect("System time set to before UNIX EPOCH") |
| 59 | + .as_nanos(); |
| 60 | + // TODO(noam.s): should this be little endian? |
| 61 | + timestamp.to_be_bytes().to_vec() |
| 62 | + } |
| 63 | +} |
| 64 | + |
| 65 | +type SharedChallengeGenerator = Arc<dyn ChallengeGenerator>; |
| 66 | + |
| 67 | +#[derive(Debug, Error)] |
| 68 | +pub enum StarkAuthNegotiatorError { |
| 69 | + #[error("Other side sent invalid data: {0}")] |
| 70 | + InvalidData(String), |
| 71 | + #[error(transparent)] |
| 72 | + Io(#[from] std::io::Error), |
| 73 | + #[error(transparent)] |
| 74 | + Decode(#[from] prost::DecodeError), |
| 75 | + #[error(transparent)] |
| 76 | + SignatureManager(#[from] SignatureManagerClientError), |
| 77 | + #[error(transparent)] |
| 78 | + SignatureVerification(#[from] SignatureVerificationError), |
| 79 | + #[error("Verification failed")] |
| 80 | + VerificationFailure, |
| 81 | +} |
| 82 | + |
| 83 | +#[derive(Clone)] |
| 84 | +pub struct StarkAuthNegotiator { |
| 85 | + my_stark_public_key: PublicKey, |
| 86 | + signer: SharedSignatureManagerClient, |
| 87 | + challenge_generator: SharedChallengeGenerator, |
| 88 | +} |
| 89 | + |
| 90 | +impl StarkAuthNegotiator { |
| 91 | + pub fn new( |
| 92 | + my_stark_public_key: PublicKey, |
| 93 | + signer: SharedSignatureManagerClient, |
| 94 | + challenge_generator: SharedChallengeGenerator, |
| 95 | + ) -> Self { |
| 96 | + Self { my_stark_public_key, signer, challenge_generator } |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +impl StarkAuthNegotiator { |
| 101 | + async fn negotiate_connection( |
| 102 | + &mut self, |
| 103 | + my_peer_id: PeerId, |
| 104 | + other_peer_id: PeerId, |
| 105 | + connection_sender: &mut dyn ConnectionSender, |
| 106 | + connection_receiver: &mut dyn ConnectionReceiver, |
| 107 | + _side: NegotiationSide, |
| 108 | + ) -> Result<NegotiatorOutput, StarkAuthNegotiatorError> { |
| 109 | + // 1. Send my public key and challenge and receive other's public key and challenge. |
| 110 | + let my_challenge = self.challenge_generator.generate().await; |
| 111 | + let my_key_and_challenge = PublicKeyAndChallenge { |
| 112 | + public_key: Some(self.my_stark_public_key.0.into()), |
| 113 | + challenge: my_challenge, |
| 114 | + }; |
| 115 | + let (_, other_key_and_challenge) = tokio::try_join!( |
| 116 | + connection_sender.send(my_key_and_challenge.into()), |
| 117 | + connection_receiver.receive() |
| 118 | + )?; |
| 119 | + |
| 120 | + // 2. Verify other's public key and challenge are valid. |
| 121 | + let other_key_and_challenge = PublicKeyAndChallenge::try_from(other_key_and_challenge)?; |
| 122 | + |
| 123 | + let other_public_key = PublicKey( |
| 124 | + other_key_and_challenge |
| 125 | + .public_key |
| 126 | + .ok_or(StarkAuthNegotiatorError::InvalidData("public_key".to_string()))? |
| 127 | + .try_into() |
| 128 | + .map_err(|e| { |
| 129 | + StarkAuthNegotiatorError::InvalidData( |
| 130 | + "PublicKeyAndChallenge::public_key".to_string(), |
| 131 | + ) |
| 132 | + })?, |
| 133 | + ); |
| 134 | + |
| 135 | + let other_challenge = other_key_and_challenge.challenge; |
| 136 | + |
| 137 | + // 3. Send my signature for the challenge and receive other's signature for the challenge. |
| 138 | + let signature = self.signer.identify(my_peer_id, other_challenge.clone()).await?; |
| 139 | + let signature_message = SignedChallengeAndIdentity { |
| 140 | + // Convert from Felt to Felt252 (proto type). |
| 141 | + signature: signature.0.iter().map(|stark_felt| (*stark_felt).into()).collect(), |
| 142 | + }; |
| 143 | + let (_, other_signature) = tokio::try_join!( |
| 144 | + connection_sender.send(signature_message.into()), |
| 145 | + connection_receiver.receive() |
| 146 | + )?; |
| 147 | + |
| 148 | + let other_signature = SignedChallengeAndIdentity::try_from(other_signature)?; |
| 149 | + |
| 150 | + let other_raw_signature = RawSignature( |
| 151 | + other_signature |
| 152 | + .signature |
| 153 | + .iter() |
| 154 | + .map(|felt252| Felt::try_from(felt252.clone())) |
| 155 | + .collect::<Result<Vec<Felt>, _>>() |
| 156 | + .map_err(|e| { |
| 157 | + StarkAuthNegotiatorError::InvalidData( |
| 158 | + "SignedChallengeAndIdentity::signature".to_string(), |
| 159 | + ) |
| 160 | + })?, |
| 161 | + ); |
| 162 | + |
| 163 | + // 4. Verify other's signature. |
| 164 | + match verify_identity( |
| 165 | + other_peer_id, |
| 166 | + other_challenge, |
| 167 | + other_raw_signature, |
| 168 | + other_public_key, |
| 169 | + )? { |
| 170 | + true => Ok(NegotiatorOutput::None), |
| 171 | + false => Err(StarkAuthNegotiatorError::VerificationFailure), |
| 172 | + } |
| 173 | + } |
| 174 | +} |
| 175 | + |
| 176 | +#[async_trait] |
| 177 | +impl Negotiator for StarkAuthNegotiator { |
| 178 | + type Error = StarkAuthNegotiatorError; |
| 179 | + |
| 180 | + fn protocol_name(&self) -> &'static str { |
| 181 | + "verify_staker" |
| 182 | + } |
| 183 | + |
| 184 | + async fn negotiate_connection( |
| 185 | + &mut self, |
| 186 | + my_peer_id: PeerId, |
| 187 | + other_peer_id: PeerId, |
| 188 | + connection_sender: &mut dyn ConnectionSender, |
| 189 | + connection_receiver: &mut dyn ConnectionReceiver, |
| 190 | + side: NegotiationSide, |
| 191 | + ) -> Result<NegotiatorOutput, Self::Error> { |
| 192 | + self.negotiate_connection( |
| 193 | + my_peer_id, |
| 194 | + other_peer_id, |
| 195 | + connection_sender, |
| 196 | + connection_receiver, |
| 197 | + side, |
| 198 | + ) |
| 199 | + .await |
| 200 | + } |
| 201 | +} |
0 commit comments