solana/core/src/sigverify.rs

487 lines
15 KiB
Rust
Raw Normal View History

2018-06-07 13:51:29 -07:00
//! The `sigverify` module provides digital signature verification functions.
//! By default, signatures are verified in parallel using all available CPU
2019-09-26 13:36:51 -07:00
//! cores. When perf-libs are available signature verification is offloaded
//! to the GPU.
2018-06-07 13:51:29 -07:00
//!
use crate::cuda_runtime::PinnedVec;
use crate::packet::{Packet, Packets};
2019-09-26 13:36:51 -07:00
use crate::perf_libs;
use crate::recycler::Recycler;
2018-12-07 19:16:27 -08:00
use crate::result::Result;
use bincode::serialized_size;
use rayon::ThreadPool;
use solana_metrics::inc_new_counter_debug;
use solana_sdk::message::MessageHeader;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::short_vec::decode_len;
2018-12-03 10:26:28 -08:00
use solana_sdk::signature::Signature;
2018-11-29 16:18:47 -08:00
#[cfg(test)]
use solana_sdk::transaction::Transaction;
2018-04-06 14:43:05 -07:00
use std::mem::size_of;
2018-03-26 21:07:11 -07:00
2019-09-12 11:39:39 -07:00
use solana_rayon_threadlimit::get_thread_count;
pub const NUM_THREADS: u32 = 10;
use std::cell::RefCell;
thread_local!(static PAR_THREAD_POOL: RefCell<ThreadPool> = RefCell::new(rayon::ThreadPoolBuilder::new()
2019-09-12 11:39:39 -07:00
.num_threads(get_thread_count())
.build()
.unwrap()));
pub type TxOffset = PinnedVec<u32>;
type TxOffsets = (TxOffset, TxOffset, TxOffset, TxOffset, Vec<Vec<u32>>);
2018-07-14 15:58:08 -07:00
pub fn init() {
2019-09-26 13:36:51 -07:00
if let Some(api) = perf_libs::api() {
unsafe {
(api.ed25519_set_verbose)(true);
if !(api.ed25519_init)() {
panic!("ed25519_init() failed");
}
(api.ed25519_set_verbose)(false);
}
}
2018-07-14 15:58:08 -07:00
}
2018-03-26 21:07:11 -07:00
fn verify_packet(packet: &Packet) -> u8 {
let (sig_len, sig_start, msg_start, pubkey_start) = get_packet_offsets(packet, 0);
let mut sig_start = sig_start as usize;
let mut pubkey_start = pubkey_start as usize;
let msg_start = msg_start as usize;
2018-03-26 21:07:11 -07:00
2018-04-06 14:24:15 -07:00
if packet.meta.size <= msg_start {
2018-03-26 21:07:11 -07:00
return 0;
}
2018-04-06 14:24:15 -07:00
let msg_end = packet.meta.size;
for _ in 0..sig_len {
let pubkey_end = pubkey_start as usize + size_of::<Pubkey>();
let sig_end = sig_start as usize + size_of::<Signature>();
if pubkey_end >= packet.meta.size || sig_end >= packet.meta.size {
return 0;
}
let signature = Signature::new(&packet.data[sig_start..sig_end]);
if !signature.verify(
&packet.data[pubkey_start..pubkey_end],
&packet.data[msg_start..msg_end],
) {
return 0;
}
pubkey_start += size_of::<Pubkey>();
sig_start += size_of::<Signature>();
}
1
2018-03-26 21:07:11 -07:00
}
fn batch_size(batches: &[Packets]) -> usize {
batches.iter().map(|p| p.packets.len()).sum()
2018-05-24 23:18:41 -07:00
}
pub fn get_packet_offsets(packet: &Packet, current_offset: u32) -> (u32, u32, u32, u32) {
let (sig_len, sig_size) = decode_len(&packet.data);
let msg_start_offset = sig_size + sig_len * size_of::<Signature>();
2019-03-24 22:51:56 -07:00
let (_pubkey_len, pubkey_size) = decode_len(&packet.data[msg_start_offset..]);
2019-03-24 22:51:56 -07:00
let sig_start = current_offset as usize + sig_size;
let msg_start = current_offset as usize + msg_start_offset;
let pubkey_start =
msg_start + serialized_size(&MessageHeader::default()).unwrap() as usize + pubkey_size;
2019-03-24 22:51:56 -07:00
(
sig_len as u32,
sig_start as u32,
msg_start as u32,
pubkey_start as u32,
)
}
pub fn generate_offsets(batches: &[Packets], recycler: &Recycler<TxOffset>) -> Result<TxOffsets> {
debug!("allocating..");
let mut signature_offsets: PinnedVec<_> = recycler.allocate("sig_offsets");
signature_offsets.set_pinnable();
let mut pubkey_offsets: PinnedVec<_> = recycler.allocate("pubkey_offsets");
pubkey_offsets.set_pinnable();
let mut msg_start_offsets: PinnedVec<_> = recycler.allocate("msg_start_offsets");
msg_start_offsets.set_pinnable();
let mut msg_sizes: PinnedVec<_> = recycler.allocate("msg_size_offsets");
msg_sizes.set_pinnable();
let mut current_packet = 0;
let mut v_sig_lens = Vec::new();
batches.iter().for_each(|p| {
let mut sig_lens = Vec::new();
p.packets.iter().for_each(|packet| {
let current_offset = current_packet as u32 * size_of::<Packet>() as u32;
let (sig_len, sig_start, msg_start_offset, pubkey_offset) =
get_packet_offsets(packet, current_offset);
let mut pubkey_offset = pubkey_offset;
sig_lens.push(sig_len);
trace!("pubkey_offset: {}", pubkey_offset);
let mut sig_offset = sig_start;
for _ in 0..sig_len {
signature_offsets.push(sig_offset);
sig_offset += size_of::<Signature>() as u32;
pubkey_offsets.push(pubkey_offset);
pubkey_offset += size_of::<Pubkey>() as u32;
msg_start_offsets.push(msg_start_offset);
msg_sizes.push(current_offset + (packet.meta.size as u32) - msg_start_offset);
}
current_packet += 1;
});
v_sig_lens.push(sig_lens);
});
Ok((
signature_offsets,
pubkey_offsets,
msg_start_offsets,
msg_sizes,
v_sig_lens,
))
}
pub fn ed25519_verify_cpu(batches: &[Packets]) -> Vec<Vec<u8>> {
2018-04-06 14:43:05 -07:00
use rayon::prelude::*;
2018-05-30 21:24:21 -07:00
let count = batch_size(batches);
2019-04-16 18:25:53 -07:00
debug!("CPU ECDSA for {}", batch_size(batches));
let rv = PAR_THREAD_POOL.with(|thread_pool| {
thread_pool.borrow().install(|| {
batches
.into_par_iter()
.map(|p| p.packets.par_iter().map(verify_packet).collect())
.collect()
})
});
inc_new_counter_debug!("ed25519_verify_cpu", count);
2018-05-30 21:24:21 -07:00
rv
2018-03-26 21:07:11 -07:00
}
pub fn ed25519_verify_disabled(batches: &[Packets]) -> Vec<Vec<u8>> {
use rayon::prelude::*;
let count = batch_size(batches);
2019-04-16 18:25:53 -07:00
debug!("disabled ECDSA for {}", batch_size(batches));
let rv = batches
.into_par_iter()
.map(|p| vec![1u8; p.packets.len()])
.collect();
inc_new_counter_debug!("ed25519_verify_disabled", count);
rv
}
pub fn ed25519_verify(
batches: &[Packets],
recycler: &Recycler<TxOffset>,
recycler_out: &Recycler<PinnedVec<u8>>,
) -> Vec<Vec<u8>> {
2019-09-26 13:36:51 -07:00
let api = perf_libs::api();
if api.is_none() {
return ed25519_verify_cpu(batches);
}
let api = api.unwrap();
use crate::packet::PACKET_DATA_SIZE;
2018-05-30 21:24:21 -07:00
let count = batch_size(batches);
// micro-benchmarks show GPU time for smallest batch around 15-20ms
// and CPU speed for 64-128 sigverifies around 10-20ms. 64 is a nice
// power-of-two number around that accounting for the fact that the CPU
// may be busy doing other things while being a real fullnode
// TODO: dynamically adjust this crossover
if count < 64 {
return ed25519_verify_cpu(batches);
}
let (signature_offsets, pubkey_offsets, msg_start_offsets, msg_sizes, sig_lens) =
generate_offsets(batches, recycler).unwrap();
2019-04-16 18:25:53 -07:00
debug!("CUDA ECDSA for {}", batch_size(batches));
debug!("allocating out..");
let mut out = recycler_out.allocate("out_buffer");
out.set_pinnable();
2018-03-26 21:07:11 -07:00
let mut elems = Vec::new();
let mut rvs = Vec::new();
let mut num_packets = 0;
for p in batches {
2019-09-26 13:36:51 -07:00
elems.push(perf_libs::Elems {
2018-03-26 21:07:11 -07:00
elems: p.packets.as_ptr(),
num: p.packets.len() as u32,
});
let mut v = Vec::new();
v.resize(p.packets.len(), 0);
rvs.push(v);
num_packets += p.packets.len();
2018-03-26 21:07:11 -07:00
}
out.resize(signature_offsets.len(), 0);
trace!("Starting verify num packets: {}", num_packets);
2018-03-26 21:07:11 -07:00
trace!("elem len: {}", elems.len() as u32);
trace!("packet sizeof: {}", size_of::<Packet>() as u32);
trace!("len offset: {}", PACKET_DATA_SIZE as u32);
const USE_NON_DEFAULT_STREAM: u8 = 1;
2018-03-26 21:07:11 -07:00
unsafe {
2019-09-26 13:36:51 -07:00
let res = (api.ed25519_verify_many)(
2018-03-26 21:07:11 -07:00
elems.as_ptr(),
elems.len() as u32,
size_of::<Packet>() as u32,
num_packets as u32,
signature_offsets.len() as u32,
msg_sizes.as_ptr(),
pubkey_offsets.as_ptr(),
signature_offsets.as_ptr(),
msg_start_offsets.as_ptr(),
2018-03-26 21:07:11 -07:00
out.as_mut_ptr(),
USE_NON_DEFAULT_STREAM,
2018-03-26 21:07:11 -07:00
);
if res != 0 {
trace!("RETURN!!!: {}", res);
}
}
trace!("done verify");
let mut num = 0;
for (vs, sig_vs) in rvs.iter_mut().zip(sig_lens.iter()) {
for (v, sig_v) in vs.iter_mut().zip(sig_vs.iter()) {
let mut vout = 1;
for _ in 0..*sig_v {
if 0 == out[num] {
vout = 0;
}
num += 1;
}
*v = vout;
2018-03-26 21:07:11 -07:00
if *v != 0 {
trace!("VERIFIED PACKET!!!!!");
}
}
}
inc_new_counter_debug!("ed25519_verify_gpu", count);
recycler_out.recycle(out);
recycler.recycle(signature_offsets);
recycler.recycle(pubkey_offsets);
recycler.recycle(msg_sizes);
recycler.recycle(msg_start_offsets);
2018-03-26 21:07:11 -07:00
rvs
}
2018-04-11 12:18:00 -07:00
#[cfg(test)]
pub fn make_packet_from_transaction(tx: Transaction) -> Packet {
use bincode::serialize;
let tx_bytes = serialize(&tx).unwrap();
let mut packet = Packet::default();
packet.meta.size = tx_bytes.len();
packet.data[..packet.meta.size].copy_from_slice(&tx_bytes);
return packet;
}
2018-04-11 12:18:00 -07:00
#[cfg(test)]
mod tests {
use crate::packet::{Packet, Packets};
use crate::recycler::Recycler;
2018-12-07 19:16:27 -08:00
use crate::sigverify;
2019-03-28 18:11:16 -07:00
use crate::test_tx::{test_multisig_tx, test_tx};
2018-12-04 15:37:11 -08:00
use bincode::{deserialize, serialize};
2019-03-23 20:12:27 -07:00
use solana_sdk::transaction::Transaction;
const SIG_OFFSET: usize = 1;
2018-12-04 15:37:11 -08:00
pub fn memfind<A: Eq>(a: &[A], b: &[A]) -> Option<usize> {
assert!(a.len() >= b.len());
let end = a.len() - b.len() + 1;
for i in 0..end {
if a[i..i + b.len()] == b[..] {
return Some(i);
}
}
None
}
#[test]
fn test_layout() {
2018-05-25 15:05:37 -07:00
let tx = test_tx();
let tx_bytes = serialize(&tx).unwrap();
let packet = serialize(&tx).unwrap();
assert_matches!(memfind(&packet, &tx_bytes), Some(0));
assert_matches!(memfind(&packet, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), None);
}
2018-04-11 12:18:00 -07:00
2018-12-04 15:37:11 -08:00
#[test]
fn test_system_transaction_layout() {
let tx = test_tx();
let tx_bytes = serialize(&tx).unwrap();
let message_data = tx.message_data();
2018-12-04 15:37:11 -08:00
let packet = sigverify::make_packet_from_transaction(tx.clone());
let (sig_len, sig_start, msg_start_offset, pubkey_offset) =
sigverify::get_packet_offsets(&packet, 0);
assert_eq!(
memfind(&tx_bytes, &tx.signatures[0].as_ref()),
Some(SIG_OFFSET)
);
assert_eq!(
2019-03-29 09:05:06 -07:00
memfind(&tx_bytes, &tx.message().account_keys[0].as_ref()),
2018-12-04 15:37:11 -08:00
Some(pubkey_offset as usize)
);
assert_eq!(
memfind(&tx_bytes, &message_data),
2018-12-04 15:37:11 -08:00
Some(msg_start_offset as usize)
);
assert_eq!(
memfind(&tx_bytes, &tx.signatures[0].as_ref()),
Some(sig_start as usize)
);
assert_eq!(sig_len, 1);
}
#[test]
fn test_system_transaction_data_layout() {
2018-12-07 19:16:27 -08:00
use crate::packet::PACKET_DATA_SIZE;
2018-12-04 15:37:11 -08:00
let mut tx0 = test_tx();
2019-03-29 09:05:06 -07:00
tx0.message.instructions[0].data = vec![1, 2, 3];
let message0a = tx0.message_data();
2018-12-04 15:37:11 -08:00
let tx_bytes = serialize(&tx0).unwrap();
assert!(tx_bytes.len() < PACKET_DATA_SIZE);
assert_eq!(
memfind(&tx_bytes, &tx0.signatures[0].as_ref()),
Some(SIG_OFFSET)
);
let tx1 = deserialize(&tx_bytes).unwrap();
assert_eq!(tx0, tx1);
2019-03-29 09:05:06 -07:00
assert_eq!(tx1.message().instructions[0].data, vec![1, 2, 3]);
2018-12-04 15:37:11 -08:00
2019-03-29 09:05:06 -07:00
tx0.message.instructions[0].data = vec![1, 2, 4];
let message0b = tx0.message_data();
2019-01-25 22:41:20 -08:00
assert_ne!(message0a, message0b);
2018-12-04 15:37:11 -08:00
}
2019-03-28 18:11:16 -07:00
// Just like get_packet_offsets, but not returning redundant information.
fn get_packet_offsets_from_tx(tx: Transaction, current_offset: u32) -> (u32, u32, u32, u32) {
let packet = sigverify::make_packet_from_transaction(tx);
let (sig_len, sig_start, msg_start_offset, pubkey_offset) =
2019-03-28 18:11:16 -07:00
sigverify::get_packet_offsets(&packet, current_offset);
(
sig_len,
sig_start - current_offset,
msg_start_offset - sig_start,
pubkey_offset - msg_start_offset,
)
}
#[test]
fn test_get_packet_offsets() {
assert_eq!(get_packet_offsets_from_tx(test_tx(), 0), (1, 1, 64, 4));
assert_eq!(get_packet_offsets_from_tx(test_tx(), 100), (1, 1, 64, 4));
// Ensure we're not indexing packet by the `current_offset` parameter.
assert_eq!(
get_packet_offsets_from_tx(test_tx(), 1_000_000),
(1, 1, 64, 4)
);
// Ensure we're returning sig_len, not sig_size.
2019-03-28 18:11:16 -07:00
assert_eq!(
get_packet_offsets_from_tx(test_multisig_tx(), 0),
(2, 1, 128, 4)
2019-03-28 18:11:16 -07:00
);
}
2018-04-11 12:18:00 -07:00
fn generate_packet_vec(
packet: &Packet,
num_packets_per_batch: usize,
num_batches: usize,
) -> Vec<Packets> {
2018-04-11 12:18:00 -07:00
// generate packet vector
let batches: Vec<_> = (0..num_batches)
.map(|_| {
let mut packets = Packets::default();
packets.packets.resize(0, Packet::default());
for _ in 0..num_packets_per_batch {
packets.packets.push(packet.clone());
}
assert_eq!(packets.packets.len(), num_packets_per_batch);
packets
})
.collect();
assert_eq!(batches.len(), num_batches);
batches
}
fn test_verify_n(n: usize, modify_data: bool) {
let tx = test_tx();
let mut packet = sigverify::make_packet_from_transaction(tx);
// jumble some data to test failure
if modify_data {
packet.data[20] = packet.data[20].wrapping_add(10);
}
let batches = generate_packet_vec(&packet, n, 2);
2018-04-11 12:18:00 -07:00
let recycler = Recycler::default();
let recycler_out = Recycler::default();
2018-04-11 12:18:00 -07:00
// verify packets
let ans = sigverify::ed25519_verify(&batches, &recycler, &recycler_out);
2018-04-11 12:18:00 -07:00
// check result
let ref_ans = if modify_data { 0u8 } else { 1u8 };
assert_eq!(ans, vec![vec![ref_ans; n], vec![ref_ans; n]]);
}
#[test]
fn test_verify_zero() {
test_verify_n(0, false);
}
#[test]
fn test_verify_one() {
test_verify_n(1, false);
}
#[test]
fn test_verify_seventy_one() {
test_verify_n(71, false);
}
#[test]
2019-03-28 18:11:16 -07:00
fn test_verify_multisig() {
solana_logger::setup();
2019-03-28 18:11:16 -07:00
let tx = test_multisig_tx();
let mut packet = sigverify::make_packet_from_transaction(tx);
let n = 4;
let num_batches = 3;
let mut batches = generate_packet_vec(&packet, n, num_batches);
packet.data[40] = packet.data[40].wrapping_add(8);
batches[0].packets.push(packet);
let recycler = Recycler::default();
let recycler_out = Recycler::default();
// verify packets
let ans = sigverify::ed25519_verify(&batches, &recycler, &recycler_out);
// check result
let ref_ans = 1u8;
let mut ref_vec = vec![vec![ref_ans; n]; num_batches];
ref_vec[0].push(0u8);
assert_eq!(ans, ref_vec);
}
2018-04-11 12:18:00 -07:00
#[test]
fn test_verify_fail() {
test_verify_n(5, true);
}
}