Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests(app): 🙅 app rejects invalid auth signatures #4182

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use {
self::common::BuilderExt,
cnidarium::TempStorage,
decaf377_rdsa::{SigningKey, SpendAuth, VerificationKey},
penumbra_app::{genesis::AppState, server::consensus::Consensus},
penumbra_keys::test_keys,
penumbra_mock_client::MockClient,
penumbra_mock_consensus::TestNode,
penumbra_proto::DomainType,
penumbra_stake::{
component::validator_handler::ValidatorDataRead as _, validator::Validator, FundingStreams,
GovernanceKey, IdentityKey,
},
rand_core::OsRng,
tap::Tap,
tracing::{error_span, info, Instrument},
};

mod common;

/// Show that the application rejects validator definitions with an invalid auth signature.
#[tokio::test]
async fn app_rejects_validator_definitions_with_invalid_auth_sigs() -> anyhow::Result<()> {
// Install a test logger, and acquire some temporary storage.
let guard = common::set_tracing_subscriber();
let storage = TempStorage::new().await?;

// Start the test node.
let mut node = {
let consensus = Consensus::new(storage.as_ref().clone());
let app_state = AppState::default();
TestNode::builder()
.single_validator()
.with_penumbra_auto_app_state(app_state)?
.init_chain(consensus)
.await
}?;

// Assert that there is only a single validator definition present, before we go any further.
assert_eq!(
storage
.latest_snapshot()
.validator_definitions()
.await?
.len(),
1,
"invalid validator definition transactions should not be accepted"
);

// Sync the mock client, using the test wallet's spend key, to the latest snapshot.
let client = MockClient::new(test_keys::SPEND_KEY.clone())
.with_sync_to_storage(&storage)
.await?
.tap(|c| info!(client.notes = %c.notes.len(), "mock client synced to test storage"));

// To define a validator, we need to define two keypairs: an identity key
// for the Penumbra application and a consensus key for cometbft.
let new_validator_id_sk = SigningKey::<SpendAuth>::new(OsRng);
let new_validator_id = IdentityKey(VerificationKey::from(&new_validator_id_sk).into());
let new_validator_consensus_sk = ed25519_consensus::SigningKey::new(OsRng);
let new_validator_consensus = new_validator_consensus_sk.verification_key();

// Create a different signing key, which we will use to create a forged authentication
// signature in our validator definition transaction.
let different_signing_key = SigningKey::<SpendAuth>::new(OsRng);

// Insert the validator's consensus keypair into the keyring so it can be used to sign blocks.
node.keyring_mut()
// Keyring should just be a BTreeMap rather than creating a new API
.insert(new_validator_consensus, new_validator_consensus_sk);

// Now define the validator's configuration data.
let new_validator = Validator {
identity_key: new_validator_id.clone(),
// TODO: when https://github.com/informalsystems/tendermint-rs/pull/1401 is released,
// replace this with a direct `Into::into()` call. at the time of writing, v0.35.0 is the
// latest version. check for new releases at https://crates.io/crates/tendermint/versions.
consensus_key: tendermint::PublicKey::from_raw_ed25519(&new_validator_consensus.to_bytes())
.expect("consensus key is valid"),
governance_key: GovernanceKey(new_validator_id_sk.into()),
enabled: true,
sequence_number: 0,
name: "test validator".to_string(),
website: String::default(),
description: String::default(),
funding_streams: FundingStreams::default(),
};

// Make a transaction that defines a new validator, providing an invalid signature.
let plan = {
use {
penumbra_stake::validator,
penumbra_transaction::{ActionPlan, TransactionParameters, TransactionPlan},
rand_core::OsRng,
};
let bytes = new_validator.encode_to_vec();
// NB: we do NOT use the validator's signing key here. this transaction will contain an
// invalid authentication signature.
let auth_sig = different_signing_key.sign(OsRng, &bytes);
let action = ActionPlan::ValidatorDefinition(validator::Definition {
validator: new_validator.clone(),
auth_sig,
});
let mut plan = TransactionPlan {
actions: vec![action.into()],
// Now fill out the remaining parts of the transaction needed for verification:
memo: None,
detection_data: None, // We'll set this automatically below
transaction_parameters: TransactionParameters {
chain_id: TestNode::<()>::CHAIN_ID.to_string(),
..Default::default()
},
};
plan.populate_detection_data(rand_core::OsRng, 0);
plan
};
let tx = client.witness_auth_build(&plan).await?;

// Execute the transaction, applying it to the chain state.
node.block()
.add_tx(tx.encode_to_vec())
.execute()
.instrument(error_span!(
"executing block with validator definition transaction"
))
.await?;
let post_tx_snapshot = storage.latest_snapshot();

// Assert that there is still only a single validator definition present, and that this
// invalid transaction did not cause a new definition to appear.
assert_eq!(
post_tx_snapshot.validator_definitions().await?.len(),
1,
"invalid validator definition transactions should not be accepted"
);

// The test passed. Free our temporary storage and drop our tracing subscriber.
Ok(())
.tap(|_| drop(node))
.tap(|_| drop(storage))
.tap(|_| drop(guard))
}
Loading