Skip to content

Commit ddda6c3

Browse files
[ed25519] Use verify_strict for signature verification in ed25519 precompile (anza-xyz#1876)
* use `verify_strict` for signature verification in ed25519 precompile * add test * clippy * increase ed25519 precompile cost constant by 5% * put ed25519 strict verification cost change under feature gate --------- Co-authored-by: Emanuele Cesena <ecesena@jumptrading.com>
1 parent d441c0f commit ddda6c3

File tree

4 files changed

+139
-9
lines changed

4 files changed

+139
-9
lines changed

cost-model/src/block_cost_limits.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ pub const SIGNATURE_COST: u64 = COMPUTE_UNIT_TO_US_RATIO * 24;
2828
pub const SECP256K1_VERIFY_COST: u64 = COMPUTE_UNIT_TO_US_RATIO * 223;
2929
/// Number of compute units for one ed25519 signature verification.
3030
pub const ED25519_VERIFY_COST: u64 = COMPUTE_UNIT_TO_US_RATIO * 76;
31+
/// Number of compute units for one ed25519 strict signature verification.
32+
pub const ED25519_VERIFY_STRICT_COST: u64 = COMPUTE_UNIT_TO_US_RATIO * 80;
3133
/// Number of compute units for one write lock
3234
pub const WRITE_LOCK_UNITS: u64 = COMPUTE_UNIT_TO_US_RATIO * 10;
3335
/// Number of data bytes per compute units

cost-model/src/cost_model.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ impl CostModel {
4040
} else {
4141
let mut tx_cost = UsageCostDetails::new_with_default_capacity();
4242

43-
Self::get_signature_cost(&mut tx_cost, transaction);
43+
Self::get_signature_cost(&mut tx_cost, transaction, feature_set);
4444
Self::get_write_lock_cost(&mut tx_cost, transaction, feature_set);
4545
Self::get_transaction_cost(&mut tx_cost, transaction, feature_set);
4646
tx_cost.allocated_accounts_data_size =
@@ -66,7 +66,7 @@ impl CostModel {
6666
} else {
6767
let mut tx_cost = UsageCostDetails::new_with_default_capacity();
6868

69-
Self::get_signature_cost(&mut tx_cost, transaction);
69+
Self::get_signature_cost(&mut tx_cost, transaction, feature_set);
7070
Self::get_write_lock_cost(&mut tx_cost, transaction, feature_set);
7171
Self::get_instructions_data_cost(&mut tx_cost, transaction);
7272
tx_cost.allocated_accounts_data_size =
@@ -82,13 +82,25 @@ impl CostModel {
8282
}
8383
}
8484

85-
fn get_signature_cost(tx_cost: &mut UsageCostDetails, transaction: &SanitizedTransaction) {
85+
fn get_signature_cost(
86+
tx_cost: &mut UsageCostDetails,
87+
transaction: &SanitizedTransaction,
88+
feature_set: &FeatureSet,
89+
) {
8690
let signatures_count_detail = transaction.message().get_signature_details();
8791
tx_cost.num_transaction_signatures = signatures_count_detail.num_transaction_signatures();
8892
tx_cost.num_secp256k1_instruction_signatures =
8993
signatures_count_detail.num_secp256k1_instruction_signatures();
9094
tx_cost.num_ed25519_instruction_signatures =
9195
signatures_count_detail.num_ed25519_instruction_signatures();
96+
97+
let ed25519_verify_cost =
98+
if feature_set.is_active(&feature_set::ed25519_precompile_verify_strict::id()) {
99+
ED25519_VERIFY_STRICT_COST
100+
} else {
101+
ED25519_VERIFY_COST
102+
};
103+
92104
tx_cost.signature_cost = signatures_count_detail
93105
.num_transaction_signatures()
94106
.saturating_mul(SIGNATURE_COST)
@@ -100,7 +112,7 @@ impl CostModel {
100112
.saturating_add(
101113
signatures_count_detail
102114
.num_ed25519_instruction_signatures()
103-
.saturating_mul(ED25519_VERIFY_COST),
115+
.saturating_mul(ed25519_verify_cost),
104116
);
105117
}
106118

sdk/src/ed25519_instruction.rs

Lines changed: 116 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
#![cfg(feature = "full")]
66

77
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+
},
913
bytemuck::bytes_of,
1014
bytemuck_derive::{Pod, Zeroable},
1115
ed25519_dalek::{ed25519::signature::Signature, Signer, Verifier},
@@ -86,7 +90,7 @@ pub fn new_ed25519_instruction(keypair: &ed25519_dalek::Keypair, message: &[u8])
8690
pub fn verify(
8791
data: &[u8],
8892
instruction_datas: &[&[u8]],
89-
_feature_set: &FeatureSet,
93+
feature_set: &FeatureSet,
9094
) -> Result<(), PrecompileError> {
9195
if data.len() < SIGNATURE_OFFSETS_START {
9296
return Err(PrecompileError::InvalidInstructionDataSize);
@@ -145,9 +149,15 @@ pub fn verify(
145149
offsets.message_data_size as usize,
146150
)?;
147151

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+
}
151161
}
152162
Ok(())
153163
}
@@ -189,9 +199,64 @@ pub mod test {
189199
signature::{Keypair, Signer},
190200
transaction::Transaction,
191201
},
202+
hex,
192203
rand0_7::{thread_rng, Rng},
193204
};
194205

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+
195260
fn test_case(
196261
num_signatures: u16,
197262
offsets: &Ed25519SignatureOffsets,
@@ -380,4 +445,50 @@ pub mod test {
380445
);
381446
assert!(tx.verify_precompiles(&feature_set).is_err());
382447
}
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+
}
383494
}

sdk/src/feature_set.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,10 @@ pub mod move_stake_and_move_lamports_ixs {
837837
solana_sdk::declare_id!("7bTK6Jis8Xpfrs8ZoUfiMDPazTcdPcTWheZFJTA5Z6X4");
838838
}
839839

840+
pub mod ed25519_precompile_verify_strict {
841+
solana_sdk::declare_id!("ed9tNscbWLYBooxWA7FE2B5KHWs8A6sxfY8EzezEcoo");
842+
}
843+
840844
lazy_static! {
841845
/// Map of feature identifiers to user-visible description
842846
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@@ -1041,6 +1045,7 @@ lazy_static! {
10411045
(zk_elgamal_proof_program_enabled::id(), "Enable ZkElGamalProof program SIMD-0153"),
10421046
(verify_retransmitter_signature::id(), "Verify retransmitter signature #1840"),
10431047
(move_stake_and_move_lamports_ixs::id(), "Enable MoveStake and MoveLamports stake program instructions #1610"),
1048+
(ed25519_precompile_verify_strict::id(), "Use strict verification in ed25519 precompile SIMD-0152"),
10441049
/*************** ADD NEW FEATURES HERE ***************/
10451050
]
10461051
.iter()

0 commit comments

Comments
 (0)