Skip to content

Commit

Permalink
feat: verify particle signatures [NET-539] (#1811)
Browse files Browse the repository at this point in the history
  • Loading branch information
justprosh authored Oct 11, 2023
1 parent 2c6b6e3 commit 188d833
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 8 deletions.
12 changes: 12 additions & 0 deletions aquamarine/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
use humantime::FormattedDuration;
use thiserror::Error;

use particle_protocol::ParticleError;

#[derive(Debug, Error)]
pub enum AquamarineApiError {
#[error("AquamarineApiError::ParticleExpired: particle_id = {particle_id}")]
Expand Down Expand Up @@ -44,6 +46,11 @@ pub enum AquamarineApiError {
"AquamarineApiError::AquamarineQueueFull: can't send particle {particle_id:?} to Aquamarine"
)]
AquamarineQueueFull { particle_id: Option<String> },
#[error("AquamarineApiError::SignatureVerificationFailed: particle_id = {particle_id}, error = {err}")]
SignatureVerificationFailed {
particle_id: String,
err: ParticleError,
},
}

impl AquamarineApiError {
Expand All @@ -52,6 +59,11 @@ impl AquamarineApiError {
AquamarineApiError::ParticleExpired { particle_id } => Some(particle_id),
AquamarineApiError::OneshotCancelled { particle_id } => Some(particle_id),
AquamarineApiError::ExecutionTimedOut { particle_id, .. } => Some(particle_id),
// Should it be `None` considering usage of signature as particle id?
// It can compromise valid particles into thinking they are invalid.
// But still there can be a case when signature was generated wrong
// and client will never know about it.
AquamarineApiError::SignatureVerificationFailed { .. } => None,
AquamarineApiError::AquamarineDied { particle_id } => particle_id,
AquamarineApiError::AquamarineQueueFull { particle_id, .. } => particle_id,
}
Expand Down
10 changes: 10 additions & 0 deletions aquamarine/src/plumber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ impl<RT: AquaRuntime, F: ParticleFunctionStatic> Plumber<RT, F> {
return;
}

if let Err(err) = particle.verify() {
tracing::warn!(target: "signature", particle_id = particle.id, "Particle signature verification failed: {err:?}");
self.events
.push_back(Err(AquamarineApiError::SignatureVerificationFailed {
particle_id: particle.id,
err,
}));
return;
}

let builtins = &self.builtins;
let key = (particle.id.clone(), worker_id);
let entry = self.actors.entry(key);
Expand Down
8 changes: 6 additions & 2 deletions crates/local-vm/src/local_vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,15 +303,19 @@ pub fn make_particle(

tracing::info!(particle_id = id, "Made a particle");

Particle {
let mut particle = Particle {
id,
init_peer_id: peer_id,
timestamp,
ttl,
script,
signature: vec![],
data: particle_data,
}
};

particle.sign(key_pair).expect("sign particle");

particle
}

pub fn read_args(
Expand Down
2 changes: 1 addition & 1 deletion crates/nox-tests/tests/local_vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ fn make() {
&mut local_vm_a,
false,
Duration::from_secs(20),
&keypair_b,
&keypair_a,
);

let args = read_args(particle, client_b, &mut local_vm_b, &keypair_b)
Expand Down
2 changes: 1 addition & 1 deletion crates/system-services/src/distro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl SystemServiceDistros {
fn versions_from(packages: &HashMap<String, PackageDistro>) -> Versions {
let mut versions = Self::default_versions();
for (name, package) in packages {
match ServiceKey::from_string(&name) {
match ServiceKey::from_string(name) {
Some(AquaIpfs) => {
versions.aqua_ipfs_version = package.version;
}
Expand Down
1 change: 0 additions & 1 deletion nox/src/effectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ impl Effectors {
let sent = connectivity.send(contact, particle).await;
if sent {
// resolved and sent, exit
return;
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion particle-protocol/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ pub enum ParticleError {
err: SigningError,
particle_id: String,
},
#[error("Failed to verify particle {particle_id} signature: {err}")]
#[error("Failed to verify particle {particle_id} by {peer_id} with signature: {err}")]
SignatureVerificationFailed {
#[source]
err: VerificationError,
particle_id: String,
peer_id: String,
},
#[error("Failed to decode public key from init_peer_id of particle {particle_id}: {err}")]
DecodingError {
Expand Down
54 changes: 52 additions & 2 deletions particle-protocol/src/particle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,12 @@ impl Particle {
/// return immutable particle fields in bytes for signing
/// concatenation of:
/// - id as bytes
/// - init_peer_id in base58 as bytes
/// - timestamp u64 as little-endian bytes
/// - ttl u32 as little-endian bytes
/// - script as bytes
fn as_bytes(&self) -> Vec<u8> {
let mut bytes = vec![];
bytes.extend(self.id.as_bytes());
bytes.extend(self.init_peer_id.to_base58().as_bytes());
bytes.extend(self.timestamp.to_le_bytes());
bytes.extend(self.ttl.to_le_bytes());
bytes.extend(self.script.as_bytes());
Expand Down Expand Up @@ -135,6 +133,7 @@ impl Particle {
.map_err(|err| SignatureVerificationFailed {
err,
particle_id: self.id.clone(),
peer_id: self.init_peer_id.to_base58(),
})
}
}
Expand All @@ -159,3 +158,54 @@ impl std::fmt::Display for Particle {
)
}
}

#[cfg(test)]
mod tests {
use crate::Particle;
use base64::{engine::general_purpose::STANDARD as base64, Engine};
use fluence_keypair::{KeyFormat, KeyPair};

#[test]
fn test_signature() {
let kp_bytes = base64
.decode("7h48PQ/f1rS9TxacmgODxbD42Il9B3KC117jvOPppPE=")
.unwrap();
assert_eq!(kp_bytes.len(), 32);

let kp = KeyPair::from_secret_key(kp_bytes, KeyFormat::Ed25519).unwrap();

// assert peer id
assert_eq!(
kp.get_peer_id().to_base58(),
"12D3KooWANqfCDrV79MZdMnMqTvDdqSAPSxdgFY1L6DCq2DVGB4D"
);

// test simple signature
let message = "message".to_string();

let signature = kp.sign(message.as_bytes()).unwrap();
assert!(kp.public().verify(message.as_bytes(), &signature).is_ok());
assert_eq!(base64.encode(signature.to_vec()), "sBW7H6/1fwAwF86ldwVm9BDu0YH3w30oFQjTWX0Tiu9yTVZHmxkV2OX4GL5jn0Iz0CrasGcOfozzkZwtJBPMBg==");

// test particle signature
let mut p = Particle {
id: "2883f959-e9e7-4843-8c37-205d393ca372".to_string(),
init_peer_id: kp.get_peer_id(),
timestamp: 1696934545662,
ttl: 7000,
script: "abc".to_string(),
signature: vec![],
data: vec![],
};

let particle_bytes = p.as_bytes();
assert_eq!(
base64.encode(&particle_bytes),
"Mjg4M2Y5NTktZTllNy00ODQzLThjMzctMjA1ZDM5M2NhMzcy/kguGYsBAABYGwAAYWJj"
);

p.sign(&kp).unwrap();
assert!(p.verify().is_ok());
assert_eq!(base64.encode(&p.signature), "KceXDnOfqe0dOnAxiDsyWBIvUq6WHoT0ge+VMHXOZsjZvCNH7/10oufdlYfcPomfv28On6E87ZhDcHGBZcb7Bw==");
}
}

0 comments on commit 188d833

Please sign in to comment.