Files
serai/networks/ethereum/schnorr/src/tests/mod.rs
Luke Parker 184c02714a alloy-core 1.0, alloy 0.14, revm 0.22 (001)
This moves to Rust 1.86 as were prior on Rust 1.81, and the new alloy
dependencies require 1.82.

The revm API changes were notable for us. Instead of relying on a modified call
instruction (with deep introspection into the EVM design), we now use the more
recent and now more prominent Inspector API. This:

1) Lets us perform far less introspection
2) Forces us to rewrite the gas estimation code we just had audited

Thankfully, it itself should be much easier to read/review, and our existing
test suite has extensively validated it.

This resolves 001 which was a concern for if/when this upgrade occurs. By doing
it now, with a dedicated test case ensuring the issue we would have had with
alloy-core 0.8 and `validate=false` isn't actively an issue, we resolve it.
2025-04-12 08:09:09 -04:00

135 lines
3.9 KiB
Rust

use std::sync::Arc;
use rand_core::{RngCore, OsRng};
use group::ff::{Field, PrimeField};
use k256::{Scalar, ProjectivePoint};
use alloy_core::primitives::Address;
use alloy_sol_types::SolCall;
use alloy_simple_request_transport::SimpleRequest;
use alloy_rpc_types_eth::{TransactionInput, TransactionRequest};
use alloy_rpc_client::ClientBuilder;
use alloy_provider::{Provider, RootProvider};
use alloy_node_bindings::{Anvil, AnvilInstance};
use crate::{PublicKey, Signature};
mod public_key;
pub(crate) use public_key::test_key;
mod signature;
mod premise;
#[expect(warnings)]
#[expect(needless_pass_by_value)]
#[expect(clippy::all)]
#[expect(clippy::ignored_unit_patterns)]
#[expect(clippy::redundant_closure_for_method_calls)]
mod abi {
alloy_sol_types::sol!("contracts/tests/Schnorr.sol");
pub(crate) use TestSchnorr::*;
}
async fn setup_test() -> (AnvilInstance, Arc<RootProvider>, Address) {
let anvil = Anvil::new().spawn();
let provider = Arc::new(RootProvider::new(
ClientBuilder::default().transport(SimpleRequest::new(anvil.endpoint()), true),
));
let mut address = [0; 20];
OsRng.fill_bytes(&mut address);
let address = Address::from(address);
let _: () = provider
.raw_request(
"anvil_setCode".into(),
[
address.to_string(),
include_str!(concat!(
env!("OUT_DIR"),
"/ethereum-schnorr-contract/TestSchnorr.bin-runtime"
))
.to_string(),
],
)
.await
.unwrap();
(anvil, provider, address)
}
async fn call_verify(
provider: &RootProvider,
address: Address,
public_key: &PublicKey,
message: &[u8],
signature: &Signature,
) -> bool {
let public_key: [u8; 32] = public_key.eth_repr();
let c_bytes: [u8; 32] = signature.c().to_repr().into();
let s_bytes: [u8; 32] = signature.s().to_repr().into();
let call = TransactionRequest::default().to(address).input(TransactionInput::new(
abi::verifyCall::new((
public_key.into(),
message.to_vec().into(),
c_bytes.into(),
s_bytes.into(),
))
.abi_encode()
.into(),
));
let bytes = provider.call(call).await.unwrap();
abi::verifyCall::abi_decode_returns(&bytes).unwrap()
}
#[tokio::test]
async fn test_verify() {
let (_anvil, provider, address) = setup_test().await;
for _ in 0 .. 100 {
let (key, public_key) = test_key();
let nonce = Scalar::random(&mut OsRng);
let mut message = vec![0; 1 + usize::try_from(OsRng.next_u32() % 256).unwrap()];
OsRng.fill_bytes(&mut message);
let c = Signature::challenge(ProjectivePoint::GENERATOR * nonce, &public_key, &message);
let s = nonce + (c * key);
let sig = Signature::new(c, s).unwrap();
assert!(sig.verify(&public_key, &message));
assert!(call_verify(&provider, address, &public_key, &message, &sig).await);
// Test setting `s = 0` doesn't pass verification
{
let zero_s = Signature::new(c, Scalar::ZERO).unwrap();
assert!(!zero_s.verify(&public_key, &message));
assert!(!call_verify(&provider, address, &public_key, &message, &zero_s).await);
}
// Mutate the message and make sure the signature now fails to verify
{
let mut message = message.clone();
message[0] = message[0].wrapping_add(1);
assert!(!sig.verify(&public_key, &message));
assert!(!call_verify(&provider, address, &public_key, &message, &sig).await);
}
// Mutate c and make sure the signature now fails to verify
{
let mutated_c = Signature::new(c + Scalar::ONE, s).unwrap();
assert!(!mutated_c.verify(&public_key, &message));
assert!(!call_verify(&provider, address, &public_key, &message, &mutated_c).await);
}
// Mutate s and make sure the signature now fails to verify
{
let mutated_s = Signature::new(c, s + Scalar::ONE).unwrap();
assert!(!mutated_s.verify(&public_key, &message));
assert!(!call_verify(&provider, address, &public_key, &message, &mutated_s).await);
}
}
}