|
5 | 5 | #![cfg(feature = "full")] |
6 | 6 |
|
7 | 7 | use { |
8 | | - crate::{feature_set::FeatureSet, instruction::Instruction, precompiles::PrecompileError}, |
| 8 | + crate::{ |
| 9 | + feature_set::{ed25519_precompile_verify_strict, FeatureSet}, |
| 10 | + instruction::Instruction, |
| 11 | + precompiles::PrecompileError, |
| 12 | + }, |
9 | 13 | bytemuck::bytes_of, |
10 | 14 | bytemuck_derive::{Pod, Zeroable}, |
11 | 15 | ed25519_dalek::{ed25519::signature::Signature, Signer, Verifier}, |
@@ -86,7 +90,7 @@ pub fn new_ed25519_instruction(keypair: &ed25519_dalek::Keypair, message: &[u8]) |
86 | 90 | pub fn verify( |
87 | 91 | data: &[u8], |
88 | 92 | instruction_datas: &[&[u8]], |
89 | | - _feature_set: &FeatureSet, |
| 93 | + feature_set: &FeatureSet, |
90 | 94 | ) -> Result<(), PrecompileError> { |
91 | 95 | if data.len() < SIGNATURE_OFFSETS_START { |
92 | 96 | return Err(PrecompileError::InvalidInstructionDataSize); |
@@ -145,9 +149,15 @@ pub fn verify( |
145 | 149 | offsets.message_data_size as usize, |
146 | 150 | )?; |
147 | 151 |
|
148 | | - publickey |
149 | | - .verify(message, &signature) |
150 | | - .map_err(|_| PrecompileError::InvalidSignature)?; |
| 152 | + if feature_set.is_active(&ed25519_precompile_verify_strict::id()) { |
| 153 | + publickey |
| 154 | + .verify_strict(message, &signature) |
| 155 | + .map_err(|_| PrecompileError::InvalidSignature)?; |
| 156 | + } else { |
| 157 | + publickey |
| 158 | + .verify(message, &signature) |
| 159 | + .map_err(|_| PrecompileError::InvalidSignature)?; |
| 160 | + } |
151 | 161 | } |
152 | 162 | Ok(()) |
153 | 163 | } |
@@ -189,9 +199,64 @@ pub mod test { |
189 | 199 | signature::{Keypair, Signer}, |
190 | 200 | transaction::Transaction, |
191 | 201 | }, |
| 202 | + hex, |
192 | 203 | rand0_7::{thread_rng, Rng}, |
193 | 204 | }; |
194 | 205 |
|
| 206 | + pub fn new_ed25519_instruction_raw( |
| 207 | + pubkey: &[u8], |
| 208 | + signature: &[u8], |
| 209 | + message: &[u8], |
| 210 | + ) -> Instruction { |
| 211 | + assert_eq!(pubkey.len(), PUBKEY_SERIALIZED_SIZE); |
| 212 | + assert_eq!(signature.len(), SIGNATURE_SERIALIZED_SIZE); |
| 213 | + |
| 214 | + let mut instruction_data = Vec::with_capacity( |
| 215 | + DATA_START |
| 216 | + .saturating_add(SIGNATURE_SERIALIZED_SIZE) |
| 217 | + .saturating_add(PUBKEY_SERIALIZED_SIZE) |
| 218 | + .saturating_add(message.len()), |
| 219 | + ); |
| 220 | + |
| 221 | + let num_signatures: u8 = 1; |
| 222 | + let public_key_offset = DATA_START; |
| 223 | + let signature_offset = public_key_offset.saturating_add(PUBKEY_SERIALIZED_SIZE); |
| 224 | + let message_data_offset = signature_offset.saturating_add(SIGNATURE_SERIALIZED_SIZE); |
| 225 | + |
| 226 | + // add padding byte so that offset structure is aligned |
| 227 | + instruction_data.extend_from_slice(bytes_of(&[num_signatures, 0])); |
| 228 | + |
| 229 | + let offsets = Ed25519SignatureOffsets { |
| 230 | + signature_offset: signature_offset as u16, |
| 231 | + signature_instruction_index: u16::MAX, |
| 232 | + public_key_offset: public_key_offset as u16, |
| 233 | + public_key_instruction_index: u16::MAX, |
| 234 | + message_data_offset: message_data_offset as u16, |
| 235 | + message_data_size: message.len() as u16, |
| 236 | + message_instruction_index: u16::MAX, |
| 237 | + }; |
| 238 | + |
| 239 | + instruction_data.extend_from_slice(bytes_of(&offsets)); |
| 240 | + |
| 241 | + debug_assert_eq!(instruction_data.len(), public_key_offset); |
| 242 | + |
| 243 | + instruction_data.extend_from_slice(pubkey); |
| 244 | + |
| 245 | + debug_assert_eq!(instruction_data.len(), signature_offset); |
| 246 | + |
| 247 | + instruction_data.extend_from_slice(signature); |
| 248 | + |
| 249 | + debug_assert_eq!(instruction_data.len(), message_data_offset); |
| 250 | + |
| 251 | + instruction_data.extend_from_slice(message); |
| 252 | + |
| 253 | + Instruction { |
| 254 | + program_id: solana_sdk::ed25519_program::id(), |
| 255 | + accounts: vec![], |
| 256 | + data: instruction_data, |
| 257 | + } |
| 258 | + } |
| 259 | + |
195 | 260 | fn test_case( |
196 | 261 | num_signatures: u16, |
197 | 262 | offsets: &Ed25519SignatureOffsets, |
@@ -380,4 +445,50 @@ pub mod test { |
380 | 445 | ); |
381 | 446 | assert!(tx.verify_precompiles(&feature_set).is_err()); |
382 | 447 | } |
| 448 | + |
| 449 | + #[test] |
| 450 | + fn test_ed25519_malleability() { |
| 451 | + solana_logger::setup(); |
| 452 | + let mint_keypair = Keypair::new(); |
| 453 | + |
| 454 | + // sig created via ed25519_dalek: both pass |
| 455 | + let privkey = ed25519_dalek::Keypair::generate(&mut thread_rng()); |
| 456 | + let message_arr = b"hello"; |
| 457 | + let instruction = new_ed25519_instruction(&privkey, message_arr); |
| 458 | + let tx = Transaction::new_signed_with_payer( |
| 459 | + &[instruction.clone()], |
| 460 | + Some(&mint_keypair.pubkey()), |
| 461 | + &[&mint_keypair], |
| 462 | + Hash::default(), |
| 463 | + ); |
| 464 | + |
| 465 | + let feature_set = FeatureSet::default(); |
| 466 | + assert!(tx.verify_precompiles(&feature_set).is_ok()); |
| 467 | + |
| 468 | + let feature_set = FeatureSet::all_enabled(); |
| 469 | + assert!(tx.verify_precompiles(&feature_set).is_ok()); |
| 470 | + |
| 471 | + // malleable sig: verify_strict does NOT pass |
| 472 | + // for example, test number 5: |
| 473 | + // https://github.com/C2SP/CCTV/tree/main/ed25519 |
| 474 | + // R has low order (in fact R == 0) |
| 475 | + let pubkey = |
| 476 | + &hex::decode("10eb7c3acfb2bed3e0d6ab89bf5a3d6afddd1176ce4812e38d9fd485058fdb1f") |
| 477 | + .unwrap(); |
| 478 | + let signature = &hex::decode("00000000000000000000000000000000000000000000000000000000000000009472a69cd9a701a50d130ed52189e2455b23767db52cacb8716fb896ffeeac09").unwrap(); |
| 479 | + let message = b"ed25519vectors 3"; |
| 480 | + let instruction = new_ed25519_instruction_raw(pubkey, signature, message); |
| 481 | + let tx = Transaction::new_signed_with_payer( |
| 482 | + &[instruction.clone()], |
| 483 | + Some(&mint_keypair.pubkey()), |
| 484 | + &[&mint_keypair], |
| 485 | + Hash::default(), |
| 486 | + ); |
| 487 | + |
| 488 | + let feature_set = FeatureSet::default(); |
| 489 | + assert!(tx.verify_precompiles(&feature_set).is_ok()); |
| 490 | + |
| 491 | + let feature_set = FeatureSet::all_enabled(); |
| 492 | + assert!(tx.verify_precompiles(&feature_set).is_err()); // verify_strict does NOT pass |
| 493 | + } |
383 | 494 | } |
0 commit comments