feat: add ed25519 signature verify program
Solang requires a method for verify ed25519 signatures. Add a new builtin program at address Ed25519SigVerify111111111111111111111111111 which takes any number of ed25519 signature, public key, and message. If any of the signatures fails to verify, an error is returned. The changes for the web3.js package will go into another commit, since the tests test against a released solana node. Adding web3.js ed25519 testing will break CI.
This commit is contained in:
328
sdk/src/ed25519_instruction.rs
Normal file
328
sdk/src/ed25519_instruction.rs
Normal file
@@ -0,0 +1,328 @@
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
use crate::{decode_error::DecodeError, instruction::Instruction};
|
||||
use bytemuck::{bytes_of, Pod, Zeroable};
|
||||
use ed25519_dalek::{ed25519::signature::Signature, Signer, Verifier};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, Clone, PartialEq)]
|
||||
pub enum Ed25519Error {
|
||||
#[error("ed25519 public key is not valid")]
|
||||
InvalidPublicKey,
|
||||
#[error("ed25519 signature is not valid")]
|
||||
InvalidSignature,
|
||||
#[error("offset not valid")]
|
||||
InvalidDataOffsets,
|
||||
#[error("instruction is incorrect size")]
|
||||
InvalidInstructionDataSize,
|
||||
}
|
||||
|
||||
impl<T> DecodeError<T> for Ed25519Error {
|
||||
fn type_of() -> &'static str {
|
||||
"Ed25519Error"
|
||||
}
|
||||
}
|
||||
|
||||
pub const PUBKEY_SERIALIZED_SIZE: usize = 32;
|
||||
pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
|
||||
pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 14;
|
||||
// bytemuck requires structures to be aligned
|
||||
pub const SIGNATURE_OFFSETS_START: usize = 2;
|
||||
pub const DATA_START: usize = SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START;
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct Ed25519SignatureOffsets {
|
||||
signature_offset: u16, // offset to ed25519 signature of 64 bytes
|
||||
signature_instruction_index: u16, // instruction index to find signature
|
||||
public_key_offset: u16, // offset to public key of 32 bytes
|
||||
public_key_instruction_index: u16, // instruction index to find public key
|
||||
message_data_offset: u16, // offset to start of message data
|
||||
message_data_size: u16, // size of message data
|
||||
message_instruction_index: u16, // index of instruction data to get message data
|
||||
}
|
||||
|
||||
pub fn new_ed25519_instruction(keypair: &ed25519_dalek::Keypair, message: &[u8]) -> Instruction {
|
||||
let signature = keypair.sign(message).to_bytes();
|
||||
let pubkey = keypair.public.to_bytes();
|
||||
|
||||
assert_eq!(pubkey.len(), PUBKEY_SERIALIZED_SIZE);
|
||||
assert_eq!(signature.len(), SIGNATURE_SERIALIZED_SIZE);
|
||||
|
||||
let mut instruction_data = Vec::with_capacity(
|
||||
DATA_START
|
||||
.saturating_add(SIGNATURE_SERIALIZED_SIZE)
|
||||
.saturating_add(PUBKEY_SERIALIZED_SIZE)
|
||||
.saturating_add(message.len()),
|
||||
);
|
||||
|
||||
let num_signatures: u8 = 1;
|
||||
let public_key_offset = DATA_START;
|
||||
let signature_offset = public_key_offset.saturating_add(PUBKEY_SERIALIZED_SIZE);
|
||||
let message_data_offset = signature_offset.saturating_add(SIGNATURE_SERIALIZED_SIZE);
|
||||
|
||||
// add padding byte so that offset structure is aligned
|
||||
instruction_data.extend_from_slice(bytes_of(&[num_signatures, 0]));
|
||||
|
||||
let offsets = Ed25519SignatureOffsets {
|
||||
signature_offset: signature_offset as u16,
|
||||
signature_instruction_index: 0,
|
||||
public_key_offset: public_key_offset as u16,
|
||||
public_key_instruction_index: 0,
|
||||
message_data_offset: message_data_offset as u16,
|
||||
message_data_size: message.len() as u16,
|
||||
message_instruction_index: 0,
|
||||
};
|
||||
|
||||
instruction_data.extend_from_slice(bytes_of(&offsets));
|
||||
|
||||
debug_assert_eq!(instruction_data.len(), public_key_offset);
|
||||
|
||||
instruction_data.extend_from_slice(&pubkey);
|
||||
|
||||
debug_assert_eq!(instruction_data.len(), signature_offset);
|
||||
|
||||
instruction_data.extend_from_slice(&signature);
|
||||
|
||||
debug_assert_eq!(instruction_data.len(), message_data_offset);
|
||||
|
||||
instruction_data.extend_from_slice(message);
|
||||
|
||||
Instruction {
|
||||
program_id: solana_sdk::ed25519_program::id(),
|
||||
accounts: vec![],
|
||||
data: instruction_data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_signatures(data: &[u8], instruction_datas: &[&[u8]]) -> Result<(), Ed25519Error> {
|
||||
if data.len() < SIGNATURE_OFFSETS_START {
|
||||
return Err(Ed25519Error::InvalidInstructionDataSize);
|
||||
}
|
||||
let num_signatures = data[0] as usize;
|
||||
if num_signatures == 0 && data.len() > SIGNATURE_OFFSETS_START {
|
||||
return Err(Ed25519Error::InvalidInstructionDataSize);
|
||||
}
|
||||
let expected_data_size = num_signatures
|
||||
.saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
|
||||
.saturating_add(SIGNATURE_OFFSETS_START);
|
||||
if data.len() < expected_data_size {
|
||||
return Err(Ed25519Error::InvalidInstructionDataSize);
|
||||
}
|
||||
for i in 0..num_signatures {
|
||||
let start = i
|
||||
.saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
|
||||
.saturating_add(SIGNATURE_OFFSETS_START);
|
||||
let end = start.saturating_add(SIGNATURE_OFFSETS_SERIALIZED_SIZE);
|
||||
|
||||
// bytemuck wants structures aligned
|
||||
let offsets: &Ed25519SignatureOffsets = bytemuck::try_from_bytes(&data[start..end])
|
||||
.map_err(|_| Ed25519Error::InvalidDataOffsets)?;
|
||||
|
||||
// Parse out signature
|
||||
let signature_index = offsets.signature_instruction_index as usize;
|
||||
if signature_index >= instruction_datas.len() {
|
||||
return Err(Ed25519Error::InvalidDataOffsets);
|
||||
}
|
||||
let signature_instruction = instruction_datas[signature_index];
|
||||
let sig_start = offsets.signature_offset as usize;
|
||||
let sig_end = sig_start.saturating_add(SIGNATURE_SERIALIZED_SIZE);
|
||||
if sig_end >= signature_instruction.len() {
|
||||
return Err(Ed25519Error::InvalidDataOffsets);
|
||||
}
|
||||
|
||||
let signature =
|
||||
ed25519_dalek::Signature::from_bytes(&signature_instruction[sig_start..sig_end])
|
||||
.map_err(|_| Ed25519Error::InvalidSignature)?;
|
||||
|
||||
// Parse out pubkey
|
||||
let pubkey = get_data_slice(
|
||||
instruction_datas,
|
||||
offsets.public_key_instruction_index,
|
||||
offsets.public_key_offset,
|
||||
PUBKEY_SERIALIZED_SIZE,
|
||||
)?;
|
||||
|
||||
let publickey = ed25519_dalek::PublicKey::from_bytes(pubkey)
|
||||
.map_err(|_| Ed25519Error::InvalidPublicKey)?;
|
||||
|
||||
// Parse out message
|
||||
let message = get_data_slice(
|
||||
instruction_datas,
|
||||
offsets.message_instruction_index,
|
||||
offsets.message_data_offset,
|
||||
offsets.message_data_size as usize,
|
||||
)?;
|
||||
|
||||
publickey
|
||||
.verify(message, &signature)
|
||||
.map_err(|_| Ed25519Error::InvalidSignature)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_data_slice<'a>(
|
||||
instruction_datas: &'a [&[u8]],
|
||||
instruction_index: u16,
|
||||
offset_start: u16,
|
||||
size: usize,
|
||||
) -> Result<&'a [u8], Ed25519Error> {
|
||||
let signature_index = instruction_index as usize;
|
||||
if signature_index >= instruction_datas.len() {
|
||||
return Err(Ed25519Error::InvalidDataOffsets);
|
||||
}
|
||||
let signature_instruction = &instruction_datas[signature_index];
|
||||
let start = offset_start as usize;
|
||||
let end = start.saturating_add(size);
|
||||
if end > signature_instruction.len() {
|
||||
return Err(Ed25519Error::InvalidDataOffsets);
|
||||
}
|
||||
|
||||
Ok(&instruction_datas[signature_index][start..end])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
|
||||
fn test_case(
|
||||
num_signatures: u16,
|
||||
offsets: &Ed25519SignatureOffsets,
|
||||
) -> Result<(), Ed25519Error> {
|
||||
assert_eq!(
|
||||
bytemuck::bytes_of(offsets).len(),
|
||||
SIGNATURE_OFFSETS_SERIALIZED_SIZE
|
||||
);
|
||||
|
||||
let mut instruction_data = vec![0u8; DATA_START];
|
||||
instruction_data[0..SIGNATURE_OFFSETS_START].copy_from_slice(bytes_of(&num_signatures));
|
||||
instruction_data[SIGNATURE_OFFSETS_START..DATA_START].copy_from_slice(bytes_of(offsets));
|
||||
|
||||
verify_signatures(&instruction_data, &[&[0u8; 100]])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_offsets() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mut instruction_data = vec![0u8; DATA_START];
|
||||
let offsets = Ed25519SignatureOffsets::default();
|
||||
instruction_data[0..SIGNATURE_OFFSETS_START].copy_from_slice(bytes_of(&1u16));
|
||||
instruction_data[SIGNATURE_OFFSETS_START..DATA_START].copy_from_slice(bytes_of(&offsets));
|
||||
instruction_data.truncate(instruction_data.len() - 1);
|
||||
|
||||
assert_eq!(
|
||||
verify_signatures(&instruction_data, &[&[0u8; 100]]),
|
||||
Err(Ed25519Error::InvalidInstructionDataSize)
|
||||
);
|
||||
|
||||
let offsets = Ed25519SignatureOffsets {
|
||||
signature_instruction_index: 1,
|
||||
..Ed25519SignatureOffsets::default()
|
||||
};
|
||||
assert_eq!(
|
||||
test_case(1, &offsets),
|
||||
Err(Ed25519Error::InvalidDataOffsets)
|
||||
);
|
||||
|
||||
let offsets = Ed25519SignatureOffsets {
|
||||
message_instruction_index: 1,
|
||||
..Ed25519SignatureOffsets::default()
|
||||
};
|
||||
assert_eq!(
|
||||
test_case(1, &offsets),
|
||||
Err(Ed25519Error::InvalidDataOffsets)
|
||||
);
|
||||
|
||||
let offsets = Ed25519SignatureOffsets {
|
||||
public_key_instruction_index: 1,
|
||||
..Ed25519SignatureOffsets::default()
|
||||
};
|
||||
assert_eq!(
|
||||
test_case(1, &offsets),
|
||||
Err(Ed25519Error::InvalidDataOffsets)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_data_offsets() {
|
||||
let offsets = Ed25519SignatureOffsets {
|
||||
message_data_offset: 99,
|
||||
message_data_size: 1,
|
||||
..Ed25519SignatureOffsets::default()
|
||||
};
|
||||
assert_eq!(test_case(1, &offsets), Err(Ed25519Error::InvalidSignature));
|
||||
|
||||
let offsets = Ed25519SignatureOffsets {
|
||||
message_data_offset: 100,
|
||||
message_data_size: 1,
|
||||
..Ed25519SignatureOffsets::default()
|
||||
};
|
||||
assert_eq!(
|
||||
test_case(1, &offsets),
|
||||
Err(Ed25519Error::InvalidDataOffsets)
|
||||
);
|
||||
|
||||
let offsets = Ed25519SignatureOffsets {
|
||||
message_data_offset: 100,
|
||||
message_data_size: 1000,
|
||||
..Ed25519SignatureOffsets::default()
|
||||
};
|
||||
assert_eq!(
|
||||
test_case(1, &offsets),
|
||||
Err(Ed25519Error::InvalidDataOffsets)
|
||||
);
|
||||
|
||||
let offsets = Ed25519SignatureOffsets {
|
||||
message_data_offset: std::u16::MAX,
|
||||
message_data_size: std::u16::MAX,
|
||||
..Ed25519SignatureOffsets::default()
|
||||
};
|
||||
assert_eq!(
|
||||
test_case(1, &offsets),
|
||||
Err(Ed25519Error::InvalidDataOffsets)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pubkey_offset() {
|
||||
let offsets = Ed25519SignatureOffsets {
|
||||
public_key_offset: std::u16::MAX,
|
||||
..Ed25519SignatureOffsets::default()
|
||||
};
|
||||
assert_eq!(
|
||||
test_case(1, &offsets),
|
||||
Err(Ed25519Error::InvalidDataOffsets)
|
||||
);
|
||||
|
||||
let offsets = Ed25519SignatureOffsets {
|
||||
public_key_offset: 100 - PUBKEY_SERIALIZED_SIZE as u16 + 1,
|
||||
..Ed25519SignatureOffsets::default()
|
||||
};
|
||||
assert_eq!(
|
||||
test_case(1, &offsets),
|
||||
Err(Ed25519Error::InvalidDataOffsets)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signature_offset() {
|
||||
let offsets = Ed25519SignatureOffsets {
|
||||
signature_offset: std::u16::MAX,
|
||||
..Ed25519SignatureOffsets::default()
|
||||
};
|
||||
assert_eq!(
|
||||
test_case(1, &offsets),
|
||||
Err(Ed25519Error::InvalidDataOffsets)
|
||||
);
|
||||
|
||||
let offsets = Ed25519SignatureOffsets {
|
||||
signature_offset: 100 - SIGNATURE_SERIALIZED_SIZE as u16 + 1,
|
||||
..Ed25519SignatureOffsets::default()
|
||||
};
|
||||
assert_eq!(
|
||||
test_case(1, &offsets),
|
||||
Err(Ed25519Error::InvalidDataOffsets)
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user