/*
* AquaVM Workflow Engine
*
* Copyright (C) 2024 Fluence DAO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation version 3 of the
* License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use air::{ExecutionCidState, PreparationError};
use air_interpreter_cid::CidRef;
use air_interpreter_signatures::{PeerCidTracker, PublicKey, SignatureStore};
use air_test_utils::key_utils::derive_dummy_keypair;
use air_test_utils::prelude::*;
use semver::Version;
use std::rc::Rc;
/// This testing modules assert AquaVM resistance to various attacks.
///
/// CID store manipulations are checked in the `corruption` module.
#[tokio::test]
async fn test_attack_injection_current_peer_scalar() {
// injecting a value that arrives to peer who does the next step
let (alice_keypair, alice_peer_id) = derive_dummy_keypair("alice_peer");
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair("mallory_peer");
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{alice_peer_id}" ("" "") [] z))
"#
);
let mut alice_cid_state = ExecutionCidState::new();
let mut alice_signature_tracker = PeerCidTracker::new(alice_peer_id.clone());
let mut alice_signature_store = SignatureStore::new();
let alice_call_1 = scalar_tracked!("good result", &mut alice_cid_state, peer = &alice_peer_id);
alice_signature_tracker.register(&*alice_peer_id, &extract_service_result_cid(&alice_call_1));
let alice_trace = vec![alice_call_1.clone()];
let alice_signature = alice_signature_tracker.gen_signature("", &alice_keypair).unwrap();
alice_signature_store.put(alice_keypair.public().into(), alice_signature);
let mut mallory_cid_state = alice_cid_state.clone();
let mut mallory_signature_tracker = PeerCidTracker::new(mallory_peer_id.clone());
let mut mallory_signature_store = alice_signature_store.clone();
let mallory_call_2 = scalar_tracked!("valid result", &mut mallory_cid_state, peer = &mallory_peer_id);
let fake_call_3 = scalar_tracked!("fake result", &mut mallory_cid_state, peer = &alice_peer_id);
mallory_signature_tracker.register(&*mallory_peer_id, &extract_service_result_cid(&mallory_call_2));
let mallory_trace = vec![alice_call_1, mallory_call_2, fake_call_3];
let mallory_signature = mallory_signature_tracker.gen_signature("", &mallory_keypair).unwrap();
mallory_signature_store.put(mallory_keypair.public().into(), mallory_signature);
let alice_data = InterpreterDataEnvelope::from_execution_result(
alice_trace.into(),
alice_cid_state.into(),
alice_signature_store,
2,
Version::new(1, 1, 1),
);
let mallory_data = InterpreterDataEnvelope::from_execution_result(
mallory_trace.into(),
mallory_cid_state.into(),
mallory_signature_store,
2,
Version::new(1, 1, 1),
);
let mut alice_avm =
create_avm_with_key::(alice_keypair, unit_call_service(), <_>::default()).await;
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = alice_data.serialize().unwrap();
let cur_data = mallory_data.serialize().unwrap();
let res = alice_avm
.call(&air_script, prev_data, cur_data, test_run_params)
.await
.unwrap();
assert_ne!(res.ret_code, 0);
}
#[tokio::test]
async fn test_attack_injection_current_peer_stream() {
// injecting a value that arrives to peer who does the next step
let alice_peer_id = "alice_peer";
let mallory_peer_id = "mallory_peer";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{alice_peer_id}" ("" "") [] $z))
"#
);
let mut alice_cid_state = ExecutionCidState::default();
let alice_call_1 = scalar_tracked!("good result", &mut alice_cid_state, peer = &alice_peer_id);
let mut alice_signature_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_signature_tracker.register(&*alice_peer_id, &extract_service_result_cid(&alice_call_1));
let mut alice_signature_store = SignatureStore::new();
let alice_signature = alice_signature_tracker.gen_signature("", &alice_keypair).unwrap();
alice_signature_store.put(alice_pk, alice_signature);
let alice_trace = vec![alice_call_1.clone()];
let mut mallory_cid_state = alice_cid_state.clone();
let mallory_call_2 = scalar_tracked!("valid result", &mut mallory_cid_state, peer = &mallory_peer_id);
let fake_call_3 = stream_tracked!("fake result", 0, &mut mallory_cid_state, peer = &alice_peer_id);
let mut mallory_signature_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_signature_tracker.register(&*mallory_peer_id, &extract_service_result_cid(&mallory_call_2));
let mut mallory_signature_store = SignatureStore::new();
let mallory_signature = mallory_signature_tracker.gen_signature("", &mallory_keypair).unwrap();
mallory_signature_store.put(mallory_pk, mallory_signature);
let mallory_trace = vec![alice_call_1, mallory_call_2, fake_call_3];
let alice_data = InterpreterDataEnvelope::from_execution_result(
alice_trace.into(),
alice_cid_state.into(),
alice_signature_store,
2,
Version::new(1, 1, 1),
);
let mallory_data = InterpreterDataEnvelope::from_execution_result(
mallory_trace.into(),
mallory_cid_state.into(),
mallory_signature_store,
2,
Version::new(1, 1, 1),
);
let mut alice_avm =
create_avm_with_key::(alice_keypair, unit_call_service(), <_>::default()).await;
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = alice_data.serialize().unwrap();
let cur_data = mallory_data.serialize().unwrap();
let res = alice_avm
.call(&air_script, prev_data, cur_data, test_run_params)
.await
.unwrap();
assert_ne!(res.ret_code, 0, "{}", res.error_message);
}
#[tokio::test]
async fn test_attack_injection_current_injection_unused() {
// injecting a value that arrives to peer who does the next step
let alice_peer_id = "alice_peer";
let mallory_peer_id = "mallory_peer";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{alice_peer_id}" ("" "") []))
"#
);
let mut alice_cid_state = ExecutionCidState::default();
let alice_call_1 = scalar_tracked!("good result", &mut alice_cid_state, peer = &alice_peer_id);
let alice_trace = vec![alice_call_1.clone()];
let mut mallory_cid_state = alice_cid_state.clone();
let mallory_call_2 = scalar_tracked!("valid result", &mut mallory_cid_state, peer = &mallory_peer_id);
let fake_call_3 = unused!("fake result", peer = &alice_peer_id);
let mallory_trace = vec![alice_call_1, mallory_call_2, fake_call_3];
let mut alice_signature_store = SignatureStore::new();
let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_cid_tracker.register(&alice_peer_id, &extract_service_result_cid(&mallory_trace[0]));
let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap();
alice_signature_store.put(alice_pk, alice_signature);
let mallory_signature_store = alice_signature_store.clone();
let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[1]));
let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap();
alice_signature_store.put(mallory_pk, mallory_signature);
let alice_data = InterpreterDataEnvelope::from_execution_result(
alice_trace.into(),
alice_cid_state.into(),
alice_signature_store,
2,
Version::new(1, 1, 1),
);
let mallory_data = InterpreterDataEnvelope::from_execution_result(
mallory_trace.into(),
mallory_cid_state.into(),
mallory_signature_store,
2,
Version::new(1, 1, 1),
);
let mut alice_avm =
create_avm_with_key::(alice_keypair, unit_call_service(), <_>::default()).await;
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = alice_data.serialize().unwrap();
let cur_data = mallory_data.serialize().unwrap();
let res = alice_avm
.call(&air_script, prev_data, cur_data, test_run_params)
.await
.unwrap();
assert_ne!(res.ret_code, 0, "{}", res.error_message);
}
#[tokio::test]
async fn test_attack_injection_other_peer_scalar() {
// injecting a value that arrives to peer who does the next step
let alice_peer_id = "alice_peer";
let bob_peer_id = "bob_peer";
let mallory_peer_id = "mallory_peer";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (bob_keypair, bob_peer_id) = derive_dummy_keypair(bob_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{bob_peer_id}" ("" "") [] z))
"#
);
let mut mallory_cid_state = ExecutionCidState::default();
let alice_call_1 = scalar_tracked!("good result", &mut mallory_cid_state, peer = &alice_peer_id);
let mallory_call_2 = scalar_tracked!("valid result", &mut mallory_cid_state, peer = &mallory_peer_id);
let fake_call_3 = scalar_tracked!("fake result", &mut mallory_cid_state, peer = &alice_peer_id);
let mallory_trace = vec![alice_call_1, mallory_call_2, fake_call_3];
let mut signature_store = SignatureStore::new();
let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_cid_tracker.register(&alice_peer_id, &extract_service_result_cid(&mallory_trace[0]));
let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap();
signature_store.put(alice_pk, alice_signature);
let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[1]));
let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap();
signature_store.put(mallory_pk, mallory_signature);
let mallory_data = InterpreterDataEnvelope::from_execution_result(
mallory_trace.into(),
mallory_cid_state.into(),
signature_store,
2,
Version::new(1, 1, 1),
);
let mut bob_avm = create_avm_with_key::(bob_keypair, unit_call_service(), <_>::default()).await;
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = "";
let cur_data = mallory_data.serialize().unwrap();
let res = bob_avm
.call(&air_script, prev_data, cur_data, test_run_params)
.await
.unwrap();
assert_ne!(res.ret_code, 0);
}
#[tokio::test]
async fn test_attack_injection_other_peer_stream() {
// injecting a value that arrives to peer who does the next step
let alice_peer_id = "alice_peer";
let bob_peer_id = "bob_peer";
let mallory_peer_id = "mallory_peer";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (bob_keypair, bob_peer_id) = derive_dummy_keypair(bob_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{bob_peer_id}" ("" "") [] $z))
"#
);
let mut mallory_cid_state = ExecutionCidState::default();
let alice_call_1 = scalar_tracked!("good result", &mut mallory_cid_state, peer = &alice_peer_id);
let mallory_call_2 = scalar_tracked!("valid result", &mut mallory_cid_state, peer = &mallory_peer_id);
let fake_call_3 = stream_tracked!("fake result", 0, &mut mallory_cid_state, peer = &alice_peer_id);
let mut signature_store = SignatureStore::new();
let mut alice_signature_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_signature_tracker.register(&*alice_peer_id, &extract_service_result_cid(&alice_call_1));
let alice_signature = alice_signature_tracker.gen_signature("", &alice_keypair).unwrap();
signature_store.put(alice_pk, alice_signature);
let mut mallory_signature_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_signature_tracker.register(&*mallory_peer_id, &extract_service_result_cid(&mallory_call_2));
let mallory_signature = mallory_signature_tracker.gen_signature("", &mallory_keypair).unwrap();
signature_store.put(mallory_pk, mallory_signature);
let mallory_trace = vec![alice_call_1, mallory_call_2, fake_call_3];
let mallory_data = InterpreterDataEnvelope::from_execution_result(
mallory_trace.into(),
mallory_cid_state.into(),
signature_store,
2,
Version::new(1, 1, 1),
);
let mut bob_avm = create_avm_with_key::(bob_keypair, unit_call_service(), <_>::default()).await;
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = "";
let cur_data = mallory_data.serialize().unwrap();
let res = bob_avm
.call(&air_script, prev_data, cur_data, test_run_params)
.await
.unwrap();
assert_ne!(res.ret_code, 0, "{}", res.error_message);
}
#[tokio::test]
async fn test_attack_injection_other_peer_unused() {
// injecting a value that arrives to peer who does the next step
let alice_peer_id = "alice_peer";
let bob_peer_id = "bob_peer";
let mallory_peer_id = "mallory_peer";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id);
let (bob_keypair, bob_peer_id) = derive_dummy_keypair(bob_peer_id);
let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id);
let alice_pk: PublicKey = alice_keypair.public().into();
let mallory_pk: PublicKey = mallory_keypair.public().into();
let air_script = format!(
r#"
(seq
(seq
(call "{alice_peer_id}" ("" "") [] x)
(call "{mallory_peer_id}" ("" "") [] y))
(call "{bob_peer_id}" ("" "") []))
"#
);
let mut mallory_cid_state = ExecutionCidState::default();
let alice_call_1 = scalar_tracked!("good result", &mut mallory_cid_state, peer = &alice_peer_id);
let mallory_call_2 = scalar_tracked!("valid result", &mut mallory_cid_state, peer = &mallory_peer_id);
let fake_call_3 = unused!("fake result", peer = &alice_peer_id);
let mut signature_store = SignatureStore::new();
let mut alice_signature_tracker = PeerCidTracker::new(alice_peer_id.clone());
alice_signature_tracker.register(&*alice_peer_id, &extract_service_result_cid(&alice_call_1));
let alice_signature = alice_signature_tracker.gen_signature("", &alice_keypair).unwrap();
signature_store.put(alice_pk, alice_signature);
let mut mallory_signature_tracker = PeerCidTracker::new(mallory_peer_id.clone());
mallory_signature_tracker.register(&*mallory_peer_id, &extract_service_result_cid(&mallory_call_2));
let mallory_signature = mallory_signature_tracker.gen_signature("", &mallory_keypair).unwrap();
signature_store.put(mallory_pk, mallory_signature);
let mallory_trace = vec![alice_call_1, mallory_call_2, fake_call_3];
let mallory_data = InterpreterDataEnvelope::from_execution_result(
mallory_trace.into(),
mallory_cid_state.into(),
signature_store,
2,
Version::new(1, 1, 1),
);
let mut bob_avm = create_avm_with_key::(bob_keypair, unit_call_service(), <_>::default()).await;
let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id);
let prev_data = "";
let cur_data = mallory_data.serialize().unwrap();
let res = bob_avm
.call(&air_script, prev_data, cur_data, test_run_params)
.await
.unwrap();
// please not that such injection is not caught
assert_eq!(res.ret_code, 0, "{}", res.error_message);
}
#[tokio::test]
async fn test_attack_replay() {
let alice_name = "alice_peer_id";
let bob_name = "bob_peer_id";
let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_name);
let (bob_keypair, _) = derive_dummy_keypair(bob_name);
let air_script = format!(
r#"(seq
(call "{alice_peer_id}" ("" "") [] y)
(call "bob" ("" "") [] z))"#
);
let mut alice_avm =
create_avm_with_key::(alice_keypair.clone(), unit_call_service(), <_>::default()).await;
let mut bob_avm =
create_avm_with_key::(bob_keypair.clone(), unit_call_service(), <_>::default()).await;
let run_params1 = TestRunParameters::from_init_peer_id(&alice_peer_id).with_particle_id("first_particle");
let run_params2 = run_params1.clone();
let res1 = alice_avm.call(&air_script, "", "", run_params1.clone()).await.unwrap();
let res2 = alice_avm.call(&air_script, "", "", run_params2).await.unwrap();
assert_eq!(res1.ret_code, 0, "test validity check failed: {}", res1.error_message);
assert_eq!(res1, res2, "test validity check failed");
let res_bob = bob_avm
.call(&air_script, "", res1.data.clone(), run_params1)
.await
.unwrap();
assert_eq!(
res_bob.ret_code, 0,
"test validity check failed: {}",
res_bob.error_message
);
let mallory_run_params = TestRunParameters::from_init_peer_id(&alice_peer_id).with_particle_id("second_particle");
let res_replay = bob_avm
.call(&air_script, "", res1.data, mallory_run_params)
.await
.unwrap();
let dalek_error = ed25519_dalek::ed25519::Error::from_source("Verification equation was not satisfied");
let nested_error = fluence_keypair::error::VerificationError::Ed25519(
dalek_error,
// will break if signed data format changes
"5KxZZzHbixcH8idzggBJYDgXP87qBBWAHLeFvPvCRVMqyAbf6mrqMdAa1P3iNjAaxXVFuJsxtrrQaL1MKw6C8pER".to_owned(),
"6m3zmtymxDL56KBpNgKqc7QiGRuWuxr82bG2q7dF5xCD".to_owned(),
);
let cids: Vec> = vec!["bagaaihrarsryjavaf4zikqilrc2hzph7rszpfyfxjpopfnczjxlqeb56nbhq".into()];
let expected = PreparationError::DataSignatureCheckError(verification::DataVerifierError::SignatureMismatch {
error: Box::new(nested_error.into()),
cids,
peer_id: alice_peer_id,
});
assert_error_eq!(&res_replay, expected);
}