diff --git a/Cargo.lock b/Cargo.lock index 3d0ac22ee7..f2ea0ac724 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4678,6 +4678,7 @@ name = "solana-perf" version = "1.2.0" dependencies = [ "bincode", + "curve25519-dalek 2.0.0", "dlopen", "dlopen_derive", "lazy_static", diff --git a/fetch-perf-libs.sh b/fetch-perf-libs.sh index 30bcfd46b8..4d28761b9f 100755 --- a/fetch-perf-libs.sh +++ b/fetch-perf-libs.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -PERF_LIBS_VERSION=v0.18.0 +PERF_LIBS_VERSION=v0.18.1 VERSION=$PERF_LIBS_VERSION-1 set -e diff --git a/perf/Cargo.toml b/perf/Cargo.toml index a66b7f18f7..61ce3c7bd5 100644 --- a/perf/Cargo.toml +++ b/perf/Cargo.toml @@ -22,6 +22,7 @@ solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.2.0" } solana-budget-program = { path = "../programs/budget", version = "1.2.0" } solana-logger = { path = "../logger", version = "1.2.0" } solana-metrics = { path = "../metrics", version = "1.2.0" } +curve25519-dalek = { version = "2" } [lib] name = "solana_perf" diff --git a/perf/src/perf_libs.rs b/perf/src/perf_libs.rs index 73a9e9dd3d..0891068198 100644 --- a/perf/src/perf_libs.rs +++ b/perf/src/perf_libs.rs @@ -91,6 +91,12 @@ pub struct Api<'a> { Symbol<'a, unsafe extern "C" fn(ptr: *mut c_void, size: usize, flags: c_uint) -> c_int>, pub cuda_host_unregister: Symbol<'a, unsafe extern "C" fn(ptr: *mut c_void) -> c_int>, + + pub ed25519_get_checked_scalar: + Symbol<'a, unsafe extern "C" fn(out_scalar: *mut u8, in_scalar: *const u8) -> c_int>, + + pub ed25519_check_packed_ge_small_order: + Symbol<'a, unsafe extern "C" fn(packed_ge: *const u8) -> c_int>, } static mut API: Option> = None; diff --git a/perf/src/sigverify.rs b/perf/src/sigverify.rs index 82f6fb4743..0de8aa215a 100644 --- a/perf/src/sigverify.rs +++ b/perf/src/sigverify.rs @@ -293,6 +293,34 @@ pub fn copy_return_values(sig_lens: &[Vec], out: &PinnedVec, rvs: &mut } } +// return true for success, i.e ge unpacks and !ge.is_small_order() +pub fn check_packed_ge_small_order(ge: &[u8; 32]) -> bool { + if let Some(api) = perf_libs::api() { + unsafe { + // Returns 1 == fail, 0 == success + let res = (api.ed25519_check_packed_ge_small_order)(ge.as_ptr()); + + return res == 0; + } + } + false +} + +pub fn get_checked_scalar(scalar: &[u8; 32]) -> Result<[u8; 32], PacketError> { + let mut out = [0u8; 32]; + if let Some(api) = perf_libs::api() { + unsafe { + let res = (api.ed25519_get_checked_scalar)(out.as_mut_ptr(), scalar.as_ptr()); + if res == 0 { + return Ok(out); + } else { + return Err(PacketError::InvalidLen); + } + } + } + Ok(out) +} + pub fn ed25519_verify( batches: &[Packets], recycler: &Recycler, @@ -312,7 +340,7 @@ pub fn ed25519_verify( // power-of-two number around that accounting for the fact that the CPU // may be busy doing other things while being a real validator // TODO: dynamically adjust this crossover - if count < std::usize::MAX { + if count < 64 { return ed25519_verify_cpu(batches); } @@ -721,8 +749,131 @@ mod tests { assert_eq!(ans, ref_vec); } + #[test] + fn test_verify_fuzz() { + use rand::{thread_rng, Rng}; + solana_logger::setup(); + + let tx = test_multisig_tx(); + let packet = sigverify::make_packet_from_transaction(tx); + + let recycler = Recycler::default(); + let recycler_out = Recycler::default(); + for _ in 0..50 { + let n = thread_rng().gen_range(1, 30); + let num_batches = thread_rng().gen_range(2, 30); + let mut batches = generate_packet_vec(&packet, n, num_batches); + + let num_modifications = thread_rng().gen_range(0, 5); + for _ in 0..num_modifications { + let batch = thread_rng().gen_range(0, batches.len()); + let packet = thread_rng().gen_range(0, batches[batch].packets.len()); + let offset = thread_rng().gen_range(0, batches[batch].packets[packet].meta.size); + let add = thread_rng().gen_range(0, 255); + batches[batch].packets[packet].data[offset] = + batches[batch].packets[packet].data[offset].wrapping_add(add); + } + + // verify packets + let ans = sigverify::ed25519_verify(&batches, &recycler, &recycler_out); + + let cpu_ref = ed25519_verify_cpu(&batches); + + debug!("ans: {:?} ref: {:?}", ans, cpu_ref); + // check result + assert_eq!(ans, cpu_ref); + } + } + #[test] fn test_verify_fail() { test_verify_n(5, true); } + + #[test] + fn test_get_checked_scalar() { + solana_logger::setup(); + use curve25519_dalek::scalar::Scalar; + use rand::{thread_rng, Rng}; + use rayon::prelude::*; + use std::sync::atomic::{AtomicU64, Ordering}; + + if perf_libs::api().is_none() { + return; + } + + let passed_g = AtomicU64::new(0); + let failed_g = AtomicU64::new(0); + (0..4).into_par_iter().for_each(|_| { + let mut input = [0u8; 32]; + let mut passed = 0; + let mut failed = 0; + for _ in 0..1_000_000 { + thread_rng().fill(&mut input); + let ans = get_checked_scalar(&input); + let ref_ans = Scalar::from_canonical_bytes(input); + if let Some(ref_ans) = ref_ans { + passed += 1; + assert_eq!(ans.unwrap(), ref_ans.to_bytes()); + } else { + failed += 1; + assert!(ans.is_err()); + } + } + passed_g.fetch_add(passed, Ordering::Relaxed); + failed_g.fetch_add(failed, Ordering::Relaxed); + }); + info!( + "passed: {} failed: {}", + passed_g.load(Ordering::Relaxed), + failed_g.load(Ordering::Relaxed) + ); + } + + #[test] + fn test_ge_small_order() { + solana_logger::setup(); + use curve25519_dalek::edwards::CompressedEdwardsY; + use rand::{thread_rng, Rng}; + use rayon::prelude::*; + use std::sync::atomic::{AtomicU64, Ordering}; + + if perf_libs::api().is_none() { + return; + } + + let passed_g = AtomicU64::new(0); + let failed_g = AtomicU64::new(0); + (0..4).into_par_iter().for_each(|_| { + let mut input = [0u8; 32]; + let mut passed = 0; + let mut failed = 0; + for _ in 0..1_000_000 { + thread_rng().fill(&mut input); + let ans = check_packed_ge_small_order(&input); + let ref_ge = CompressedEdwardsY::from_slice(&input); + if let Some(ref_element) = ref_ge.decompress() { + if ref_element.is_small_order() { + assert!(!ans); + } else { + assert!(ans); + } + } else { + assert!(!ans); + } + if ans { + passed += 1; + } else { + failed += 1; + } + } + passed_g.fetch_add(passed, Ordering::Relaxed); + failed_g.fetch_add(failed, Ordering::Relaxed); + }); + info!( + "passed: {} failed: {}", + passed_g.load(Ordering::Relaxed), + failed_g.load(Ordering::Relaxed) + ); + } }