Skip to content

Commit d2f1490

Browse files
apollo_network: implementation of stark authentication
1 parent 3329183 commit d2f1490

File tree

3 files changed

+207
-0
lines changed

3 files changed

+207
-0
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/apollo_network/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ testing = ["mockall"]
1313
apollo_config.workspace = true
1414
apollo_metrics.workspace = true
1515
apollo_network_types.workspace = true
16+
apollo_protobuf.workspace = true
17+
apollo_signature_manager.workspace = true
18+
apollo_signature_manager_types.workspace = true
1619
async-stream.workspace = true
1720
async-trait.workspace = true
1821
asynchronous-codec.workspace = true
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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

Comments
 (0)