Skip to content

Commit

Permalink
feat(interpreter-data)!: allow only deterministic signature algorithms (
Browse files Browse the repository at this point in the history
#734)

Some public signature algorithms require a RNG, but it is not
available in certain environments like smartcontracts.
  • Loading branch information
monoid authored Nov 2, 2023
1 parent 55da7a6 commit 15ce40a
Show file tree
Hide file tree
Showing 20 changed files with 243 additions and 35 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions air/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ fluence-app-service = "0.29.0"
marine-rs-sdk = { version = "0.10.0", features = ["logger"] }

borsh = "0.10.3"
bs58 = "0.5.0"
# the feature just silence a warning in the criterion 0.3.x.
criterion = { version = "0.3.3", features = ["html_reports"] }
csv = "1.1.5"
Expand Down
4 changes: 2 additions & 2 deletions air/src/farewell_step/outcome.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ use crate::INTERPRETER_SUCCESS;

use air_interpreter_data::InterpreterData;
use air_interpreter_interface::CallRequests;
use air_interpreter_signatures::KeyPair;
use air_utils::measure;
use fluence_keypair::error::SigningError;
use fluence_keypair::KeyPair;

use std::fmt::Debug;
use std::hash::Hash;
Expand Down Expand Up @@ -138,7 +138,7 @@ fn sign_result(exec_ctx: &mut ExecutionCtx<'_>, keypair: &KeyPair) -> Result<(),
.map_err(signing_error_into_outcome)?;

let current_pubkey = keypair.public();
exec_ctx.signature_store.put(current_pubkey.into(), current_signature);
exec_ctx.signature_store.put(current_pubkey, current_signature);

Ok(())
}
Expand Down
7 changes: 2 additions & 5 deletions air/src/preparation_step/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,8 @@ pub enum PreparationError {
},

/// Malformed keypair format data.
#[error("malformed keypair format: {error:?}")]
MalformedKeyPairData {
#[from]
error: fluence_keypair::error::DecodingError,
},
#[error("malformed keypair format: {0}")]
MalformedKeyPairData(#[from] air_interpreter_signatures::KeyError),

/// Failed to verify CidStore contents of the current data.
#[error(transparent)]
Expand Down
5 changes: 3 additions & 2 deletions air/src/preparation_step/preparation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ use crate::execution_step::TraceHandler;

use air_interpreter_data::InterpreterData;
use air_interpreter_interface::RunParameters;
use air_interpreter_signatures::KeyError;
use air_interpreter_signatures::KeyPair;
use air_interpreter_signatures::SignatureStore;
use air_parser::ast::Instruction;
use fluence_keypair::KeyFormat;
use fluence_keypair::KeyPair;

use std::convert::TryFrom;

Expand Down Expand Up @@ -88,7 +89,7 @@ pub(crate) fn prepare<'i>(
)?;
let trace_handler = TraceHandler::from_trace(prev_data.trace, current_data.trace);

let key_format = KeyFormat::try_from(run_parameters.key_format)?;
let key_format = KeyFormat::try_from(run_parameters.key_format).map_err(KeyError::from)?;
let keypair = KeyPair::from_secret_key(run_parameters.secret_key_bytes, key_format)?;

let result = PreparationDescriptor {
Expand Down
8 changes: 5 additions & 3 deletions air/src/signing_step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@

use crate::ExecutionError;

use air_interpreter_signatures::{PeerCidTracker, SignatureStore};
use air_interpreter_signatures::KeyPair;
use air_interpreter_signatures::PeerCidTracker;
use air_interpreter_signatures::SignatureStore;

#[cfg(feature = "gen_signatures")]
#[tracing::instrument(skip_all)]
pub(crate) fn sign_produced_cids(
signature_tracker: &mut PeerCidTracker,
signature_store: &mut SignatureStore,
salt: &str,
keypair: &fluence_keypair::KeyPair,
keypair: &KeyPair,
) -> Result<(), ExecutionError> {
use crate::UncatchableError;

Expand All @@ -42,7 +44,7 @@ pub(crate) fn sign_produced_cids(
_signature_tracker: &mut PeerCidTracker,
_signature_store: &mut SignatureStore,
_salt: &str,
_keypair: &fluence_keypair::KeyPair,
_keypair: &KeyPair,
) -> Result<(), ExecutionError> {
Ok(())
}
2 changes: 2 additions & 0 deletions air/tests/test_module/features/signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* limitations under the License.
*/

#[cfg(feature = "check_signatures")]
mod algorithms;
#[cfg(feature = "check_signatures")]
mod attacks;
#[cfg(feature = "check_signatures")]
Expand Down
95 changes: 95 additions & 0 deletions air/tests/test_module/features/signatures/algorithms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

use air::{min_supported_version, PreparationError};
use air_interpreter_data::{verification::DataVerifierError, InterpreterData};
use air_interpreter_signatures::KeyError;
use air_test_utils::{
assert_error_eq,
prelude::{request_sent_by, unit_call_service},
test_runner::{create_avm, create_avm_with_key, NativeAirRunner, TestRunParameters},
};
use fluence_keypair::KeyFormat;
use serde_json::json;

/// Checking that other peers' key algorithms are valid.
#[test]
fn test_banned_signature() {
let air_script = r#"(call "other_peer_id" ("" "") [])"#;

let bad_algo_keypair = fluence_keypair::KeyPair::generate_secp256k1();
let bad_algo_pk = bad_algo_keypair.public();
let bad_algo_signature: air_interpreter_signatures::Signature =
air_interpreter_signatures::sign_cids(vec![], "particle_id", &bad_algo_keypair)
.unwrap()
.into();

let bad_algo_pk_ser = bs58::encode(bad_algo_pk.encode()).into_string();
let bad_signature_store = json!({
bad_algo_pk_ser: bad_algo_signature,
});
let bad_peer_id = bad_algo_pk.to_peer_id().to_string();

let trace = vec![request_sent_by("init_peer_fake_id")];

let mut data = serde_json::to_value(InterpreterData::from_execution_result(
trace.into(),
<_>::default(),
<_>::default(),
<_>::default(),
min_supported_version().clone(),
))
.unwrap();

data["signatures"] = bad_signature_store;

let current_data = data.to_string();

let mut avm = create_avm(unit_call_service(), "other_peer_id");
let res = avm
.call(
air_script,
"",
current_data,
TestRunParameters::from_init_peer_id("init_peer_fake_id"),
)
.unwrap();

assert_error_eq!(
&res,
PreparationError::DataSignatureCheckError(DataVerifierError::MalformedKey {
error: KeyError::AlgorithmNotWhitelisted(KeyFormat::Secp256k1),
peer_id: bad_peer_id
})
);
}

/// Checking that local key is valid.
#[test]
fn test_banned_signing_key() {
let air_script = "(null)";
let bad_algo_keypair = fluence_keypair::KeyPair::generate_secp256k1();

let mut avm = create_avm_with_key::<NativeAirRunner>(bad_algo_keypair, unit_call_service());
let res = avm
.call(air_script, "", "", TestRunParameters::from_init_peer_id("init_peer_id"))
.unwrap();

assert_error_eq!(
&res,
PreparationError::MalformedKeyPairData(KeyError::AlgorithmNotWhitelisted(KeyFormat::Secp256k1))
);
}
2 changes: 1 addition & 1 deletion air/tests/test_module/issues/issue_632.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fn issue_310() {
0,
None,
call_results,
&key_pair,
key_pair.as_inner(),
particle_id.to_owned(),
)
.unwrap()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
use std::rc::Rc;

use air_interpreter_cid::CidRef;
use air_interpreter_signatures::KeyError;
use thiserror::Error as ThisError;
#[derive(Debug, ThisError)]
pub enum DataVerifierError {
#[error(transparent)]
MalformedKey(fluence_keypair::error::DecodingError),
#[error("malformed key at peer: {peer_id:?}: {error}")]
MalformedKey { error: KeyError, peer_id: String },

#[error(transparent)]
MalformedSignature(fluence_keypair::error::DecodingError),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ impl<'data> DataVerifier<'data> {
// it can be further optimized if only required parts are passed
// SignatureStore is not used elsewhere
pub fn new(data: &'data InterpreterData, salt: &'data str) -> Result<Self, DataVerifierError> {
// validate key algoritms
for (public_key, _) in data.signatures.iter() {
public_key
.validate()
.map_err(|error| DataVerifierError::MalformedKey {
error,
peer_id: public_key.to_peer_id(),
})?;
}

// it contains signature too; if we try to add a value to a peer w/o signature, it is an immediate error
let mut grouped_cids: HashMap<Box<str>, PeerInfo<'data>> = data
.signatures
Expand Down
5 changes: 5 additions & 0 deletions crates/air-lib/interpreter-signatures/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ bs58 = "0.5.0"
borsh = { version = "0.10.3", features = ["rc"]}
borsh-derive = "0.10.3"
serde = { version = "1.0.190", features = ["derive"] }
thiserror = "1.0.49"

[features]
default = ["rand"]
rand = []
Loading

0 comments on commit 15ce40a

Please sign in to comment.