Skip to content

Commit 90372ba

Browse files
committed
ed25519: add instruction builder for multiple verifications
1 parent 558a507 commit 90372ba

File tree

1 file changed

+113
-0
lines changed

1 file changed

+113
-0
lines changed

ed25519-program/src/lib.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,29 @@ pub struct Ed25519SignatureOffsets {
3030
message_instruction_index: u16, // index of instruction data to get message data
3131
}
3232

33+
/// Convenience function to convert signature offsets into a single ed25519 instruction
34+
/// The caller can choose to extend the data buffer and write the verification data at
35+
/// `DATA_START` or store the verification data in a different instruction.
36+
pub fn offsets_to_ed25519_instruction(offsets: &[Ed25519SignatureOffsets]) -> Instruction {
37+
let mut instruction_data = Vec::with_capacity(
38+
SIGNATURE_OFFSETS_START
39+
.saturating_add(SIGNATURE_OFFSETS_SERIALIZED_SIZE.saturating_mul(offsets.len())),
40+
);
41+
42+
let num_signatures = offsets.len() as u16;
43+
instruction_data.extend_from_slice(&num_signatures.to_le_bytes());
44+
45+
for offsets in offsets {
46+
instruction_data.extend_from_slice(bytes_of(offsets));
47+
}
48+
49+
Instruction {
50+
program_id: solana_sdk_ids::ed25519_program::id(),
51+
accounts: vec![],
52+
data: instruction_data,
53+
}
54+
}
55+
3356
pub fn new_ed25519_instruction(keypair: &ed25519_dalek::Keypair, message: &[u8]) -> Instruction {
3457
let signature = keypair.sign(message).to_bytes();
3558
let pubkey = keypair.public.to_bytes();
@@ -83,6 +106,59 @@ pub fn new_ed25519_instruction(keypair: &ed25519_dalek::Keypair, message: &[u8])
83106
}
84107
}
85108

109+
/// Creates an ed25519 instruction for verifying multiple messages signed by `keypair`
110+
/// The verification is stored in the instruction data, so it is up to the caller to check
111+
/// that the messages will fit within the maximum transaction size.
112+
pub fn new_multi_ed25519_instruction(
113+
keypair: &ed25519_dalek::Keypair,
114+
messages: &[&[u8]],
115+
) -> Instruction {
116+
let data_start = messages
117+
.len()
118+
.saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
119+
.saturating_add(SIGNATURE_OFFSETS_START);
120+
let mut data_offset = data_start.saturating_add(PUBKEY_SERIALIZED_SIZE);
121+
let (offsets, signature_messages): (Vec<_>, Vec<_>) = messages
122+
.iter()
123+
.map(|message| {
124+
let signature = keypair.sign(message).to_bytes();
125+
126+
assert_eq!(signature.len(), SIGNATURE_SERIALIZED_SIZE);
127+
128+
let signature_offset = data_offset;
129+
let message_data_offset = signature_offset.saturating_add(SIGNATURE_SERIALIZED_SIZE);
130+
data_offset = data_offset
131+
.saturating_add(SIGNATURE_SERIALIZED_SIZE)
132+
.saturating_add(message.len());
133+
134+
let offsets = Ed25519SignatureOffsets {
135+
signature_offset: signature_offset as u16,
136+
signature_instruction_index: u16::MAX,
137+
public_key_offset: data_start as u16,
138+
public_key_instruction_index: u16::MAX,
139+
message_data_offset: message_data_offset as u16,
140+
message_data_size: message.len() as u16,
141+
message_instruction_index: u16::MAX,
142+
};
143+
144+
(offsets, (signature, message))
145+
})
146+
.unzip();
147+
148+
let mut instruction = offsets_to_ed25519_instruction(&offsets);
149+
150+
let pubkey = keypair.public.as_ref();
151+
assert_eq!(pubkey.len(), PUBKEY_SERIALIZED_SIZE);
152+
instruction.data.extend_from_slice(pubkey);
153+
154+
for (signature, message) in signature_messages {
155+
instruction.data.extend_from_slice(&signature);
156+
instruction.data.extend_from_slice(message);
157+
}
158+
159+
instruction
160+
}
161+
86162
pub fn verify(
87163
data: &[u8],
88164
instruction_datas: &[&[u8]],
@@ -440,6 +516,43 @@ pub mod test {
440516
assert!(tx.verify_precompiles(&feature_set).is_err());
441517
}
442518

519+
#[test]
520+
fn test_multi_ed25519() {
521+
solana_logger::setup();
522+
523+
let privkey = ed25519_dalek::Keypair::generate(&mut thread_rng());
524+
let messages: [&[u8]; 3] = [b"hello", b"IBRL", b"goodbye"];
525+
let mut instruction = new_multi_ed25519_instruction(&privkey, &messages);
526+
let mint_keypair = Keypair::new();
527+
let feature_set = FeatureSet::all_enabled();
528+
529+
let tx = Transaction::new_signed_with_payer(
530+
&[instruction.clone()],
531+
Some(&mint_keypair.pubkey()),
532+
&[&mint_keypair],
533+
Hash::default(),
534+
);
535+
536+
assert!(tx.verify_precompiles(&feature_set).is_ok());
537+
538+
let index = loop {
539+
let index = thread_rng().gen_range(0, instruction.data.len());
540+
// byte 1 is not used, so this would not cause the verify to fail
541+
if index != 1 {
542+
break index;
543+
}
544+
};
545+
546+
instruction.data[index] = instruction.data[index].wrapping_add(12);
547+
let tx = Transaction::new_signed_with_payer(
548+
&[instruction],
549+
Some(&mint_keypair.pubkey()),
550+
&[&mint_keypair],
551+
Hash::default(),
552+
);
553+
assert!(tx.verify_precompiles(&feature_set).is_err());
554+
}
555+
443556
#[test]
444557
fn test_ed25519_malleability() {
445558
solana_logger::setup();

0 commit comments

Comments
 (0)