Skip to main content

lightcone_sdk/program/
ed25519.rs

1//! Ed25519 signature verification instruction helpers.
2//!
3//! This module provides functions to create Ed25519 verification instructions
4//! for order matching. Three strategies are supported:
5//!
6//! 1. **Individual Instructions** - One instruction per signature (simplest)
7//! 2. **Batch Verification** - One instruction for multiple signatures (more efficient)
8//! 3. **Cross-Instruction References** - References data in match instruction (most efficient)
9
10use solana_sdk::{
11    instruction::Instruction,
12    pubkey::Pubkey,
13};
14
15use crate::program::constants::ED25519_PROGRAM_ID;
16use crate::program::orders::FullOrder;
17
18// ============================================================================
19// Ed25519 Verification Parameters
20// ============================================================================
21
22/// Parameters for creating an Ed25519 verification instruction.
23#[derive(Debug, Clone)]
24pub struct Ed25519VerifyParams {
25    /// The public key that signed the message
26    pub pubkey: Pubkey,
27    /// The message that was signed (32-byte order hash)
28    pub message: [u8; 32],
29    /// The Ed25519 signature (64 bytes)
30    pub signature: [u8; 64],
31}
32
33impl Ed25519VerifyParams {
34    /// Create verify params from a full order.
35    pub fn from_order(order: &FullOrder) -> Self {
36        Self {
37            pubkey: order.maker,
38            message: order.hash(),
39            signature: order.signature,
40        }
41    }
42}
43
44// ============================================================================
45// Strategy 1: Individual Ed25519 Instructions
46// ============================================================================
47
48/// Create an Ed25519 signature verification instruction.
49///
50/// This instruction must precede the matchOrdersMulti instruction in the transaction.
51/// The Solana Ed25519 program will verify the signature and the matchOrdersMulti
52/// instruction will read the verification result from the instructions sysvar.
53///
54/// Ed25519 instruction data format (144 bytes):
55/// - Header (16 bytes):
56///   - num_signatures (u8): 1
57///   - padding (u8): 0
58///   - signature_offset (u16): 16
59///   - signature_instruction_index (u16): 0xFFFF (same instruction)
60///   - public_key_offset (u16): 80 (16 + 64)
61///   - public_key_instruction_index (u16): 0xFFFF
62///   - message_data_offset (u16): 112 (16 + 64 + 32)
63///   - message_data_size (u16): 32
64///   - message_instruction_index (u16): 0xFFFF
65/// - Signature (64 bytes)
66/// - Public Key (32 bytes)
67/// - Message (32 bytes)
68pub fn create_ed25519_verify_instruction(params: &Ed25519VerifyParams) -> Instruction {
69    let mut data = vec![0u8; 144];
70
71    // Header
72    data[0] = 1; // num_signatures
73    data[1] = 0; // padding
74
75    // signature_offset (u16 LE) - starts at byte 16
76    data[2..4].copy_from_slice(&16u16.to_le_bytes());
77    // signature_instruction_index (u16 LE) - 0xFFFF = same instruction
78    data[4..6].copy_from_slice(&0xFFFFu16.to_le_bytes());
79
80    // public_key_offset (u16 LE) - starts at byte 80 (16 + 64)
81    data[6..8].copy_from_slice(&80u16.to_le_bytes());
82    // public_key_instruction_index (u16 LE)
83    data[8..10].copy_from_slice(&0xFFFFu16.to_le_bytes());
84
85    // message_data_offset (u16 LE) - starts at byte 112 (16 + 64 + 32)
86    data[10..12].copy_from_slice(&112u16.to_le_bytes());
87    // message_data_size (u16 LE)
88    data[12..14].copy_from_slice(&32u16.to_le_bytes());
89    // message_instruction_index (u16 LE)
90    data[14..16].copy_from_slice(&0xFFFFu16.to_le_bytes());
91
92    // Signature (64 bytes at offset 16)
93    data[16..80].copy_from_slice(&params.signature);
94
95    // Public Key (32 bytes at offset 80)
96    data[80..112].copy_from_slice(params.pubkey.as_ref());
97
98    // Message (32 bytes at offset 112)
99    data[112..144].copy_from_slice(&params.message);
100
101    Instruction {
102        program_id: ED25519_PROGRAM_ID,
103        accounts: vec![],
104        data,
105    }
106}
107
108/// Create Ed25519 verification instructions for multiple signatures.
109pub fn create_ed25519_verify_instructions(params: &[Ed25519VerifyParams]) -> Vec<Instruction> {
110    params.iter().map(create_ed25519_verify_instruction).collect()
111}
112
113/// Create an Ed25519 verification instruction for an order.
114pub fn create_order_verify_instruction(order: &FullOrder) -> Instruction {
115    let params = Ed25519VerifyParams::from_order(order);
116    create_ed25519_verify_instruction(&params)
117}
118
119// ============================================================================
120// Strategy 2: Batch Ed25519 Verification
121// ============================================================================
122
123/// Create a single Ed25519 instruction that verifies multiple signatures.
124///
125/// This is more efficient than multiple single-signature instructions.
126/// Each additional signature adds 14 bytes header + 64 sig + 32 pubkey + 32 msg = 142 bytes.
127pub fn create_batch_ed25519_verify_instruction(params: &[Ed25519VerifyParams]) -> Instruction {
128    assert!(!params.is_empty(), "At least one signature is required");
129
130    let num_signatures = params.len();
131
132    // Calculate header size: 2 bytes base + 14 bytes per signature
133    let header_size = 2 + num_signatures * 14;
134
135    // Data layout after header: [sig0, pubkey0, msg0, sig1, pubkey1, msg1, ...]
136    // Each entry: 64 + 32 + 32 = 128 bytes
137    let entry_size = 64 + 32 + 32;
138    let total_size = header_size + num_signatures * entry_size;
139
140    let mut data = vec![0u8; total_size];
141
142    // num_signatures (u8)
143    data[0] = num_signatures as u8;
144    // padding (u8)
145    data[1] = 0;
146
147    // Per-signature header entries
148    let mut header_offset = 2;
149    for i in 0..num_signatures {
150        let data_start = header_size + i * entry_size;
151
152        // signature_offset (u16 LE)
153        data[header_offset..header_offset + 2].copy_from_slice(&(data_start as u16).to_le_bytes());
154        header_offset += 2;
155
156        // signature_instruction_index (u16 LE)
157        data[header_offset..header_offset + 2].copy_from_slice(&0xFFFFu16.to_le_bytes());
158        header_offset += 2;
159
160        // public_key_offset (u16 LE)
161        data[header_offset..header_offset + 2]
162            .copy_from_slice(&((data_start + 64) as u16).to_le_bytes());
163        header_offset += 2;
164
165        // public_key_instruction_index (u16 LE)
166        data[header_offset..header_offset + 2].copy_from_slice(&0xFFFFu16.to_le_bytes());
167        header_offset += 2;
168
169        // message_data_offset (u16 LE)
170        data[header_offset..header_offset + 2]
171            .copy_from_slice(&((data_start + 64 + 32) as u16).to_le_bytes());
172        header_offset += 2;
173
174        // message_data_size (u16 LE)
175        data[header_offset..header_offset + 2].copy_from_slice(&32u16.to_le_bytes());
176        header_offset += 2;
177
178        // message_instruction_index (u16 LE)
179        data[header_offset..header_offset + 2].copy_from_slice(&0xFFFFu16.to_le_bytes());
180        header_offset += 2;
181    }
182
183    // Data section
184    for (i, p) in params.iter().enumerate() {
185        let data_start = header_size + i * entry_size;
186        data[data_start..data_start + 64].copy_from_slice(&p.signature);
187        data[data_start + 64..data_start + 96].copy_from_slice(p.pubkey.as_ref());
188        data[data_start + 96..data_start + 128].copy_from_slice(&p.message);
189    }
190
191    Instruction {
192        program_id: ED25519_PROGRAM_ID,
193        accounts: vec![],
194        data,
195    }
196}
197
198// ============================================================================
199// Strategy 3: Cross-Instruction Reference Ed25519 Verification
200// ============================================================================
201
202/// Match instruction data layout offsets (for cross-instruction references).
203///
204/// Layout for single maker (332 bytes):
205/// - `[0]`:        discriminator (1 byte)
206/// - `[1..33]`:    taker_hash (32 bytes)        <- taker message
207/// - `[33..98]`:   taker_compact (65 bytes)     <- taker pubkey at offset 33+8=41
208/// - `[98..162]`:  taker_signature (64 bytes)   <- taker signature
209/// - `[162]`:      num_makers (1 byte)
210/// - `[163..195]`: maker_hash (32 bytes)        <- maker message
211/// - `[195..260]`: maker_compact (65 bytes)     <- maker pubkey at offset 195+8=203
212/// - `[260..324]`: maker_signature (64 bytes)   <- maker signature
213/// - `[324..332]`: maker_fill_amount (8 bytes)
214#[derive(Debug)]
215pub struct MatchIxOffsets;
216
217impl MatchIxOffsets {
218    /// Taker hash (message) starts at offset 1
219    pub const TAKER_MESSAGE: u16 = 1;
220    /// Taker compact.maker (pubkey) at 33+8
221    pub const TAKER_PUBKEY: u16 = 41;
222    /// Taker signature at offset 98
223    pub const TAKER_SIGNATURE: u16 = 98;
224    /// num_makers at offset 162
225    pub const NUM_MAKERS: u16 = 162;
226
227    /// Calculate offsets for a maker order in the match instruction data.
228    /// Each maker entry is: hash(32) + compact(65) + sig(64) + fill(8) = 169 bytes
229    pub fn maker_offsets(maker_index: usize) -> MakerOffsets {
230        let base = 163 + maker_index * 169;
231        MakerOffsets {
232            message: base as u16,
233            pubkey: (base + 32 + 8) as u16,
234            signature: (base + 32 + 65) as u16,
235        }
236    }
237}
238
239/// Offsets for a maker order within the match instruction data.
240#[derive(Debug)]
241pub struct MakerOffsets {
242    /// Offset of maker hash (message)
243    pub message: u16,
244    /// Offset of maker pubkey
245    pub pubkey: u16,
246    /// Offset of maker signature
247    pub signature: u16,
248}
249
250/// Parameters for cross-instruction Ed25519 verification.
251#[derive(Debug, Clone)]
252pub struct CrossRefEd25519Params {
253    pub signature_offset: u16,
254    pub signature_ix_index: u16,
255    pub pubkey_offset: u16,
256    pub pubkey_ix_index: u16,
257    pub message_offset: u16,
258    pub message_size: u16,
259    pub message_ix_index: u16,
260}
261
262/// Create a single Ed25519 instruction that references data in another instruction.
263///
264/// This is only 16 bytes (just header with offsets) - no embedded signature/pubkey/message data.
265pub fn create_cross_ref_ed25519_instruction(params: &CrossRefEd25519Params) -> Instruction {
266    let mut data = vec![0u8; 16];
267
268    // num_signatures (u8)
269    data[0] = 1;
270    // padding (u8)
271    data[1] = 0;
272
273    // signature_offset (u16 LE)
274    data[2..4].copy_from_slice(&params.signature_offset.to_le_bytes());
275    // signature_instruction_index (u16 LE)
276    data[4..6].copy_from_slice(&params.signature_ix_index.to_le_bytes());
277
278    // public_key_offset (u16 LE)
279    data[6..8].copy_from_slice(&params.pubkey_offset.to_le_bytes());
280    // public_key_instruction_index (u16 LE)
281    data[8..10].copy_from_slice(&params.pubkey_ix_index.to_le_bytes());
282
283    // message_data_offset (u16 LE)
284    data[10..12].copy_from_slice(&params.message_offset.to_le_bytes());
285    // message_data_size (u16 LE)
286    data[12..14].copy_from_slice(&params.message_size.to_le_bytes());
287    // message_instruction_index (u16 LE)
288    data[14..16].copy_from_slice(&params.message_ix_index.to_le_bytes());
289
290    Instruction {
291        program_id: ED25519_PROGRAM_ID,
292        accounts: vec![],
293        data,
294    }
295}
296
297/// Create Ed25519 verification instructions using cross-instruction references.
298///
299/// Instead of embedding signature/pubkey/message data in the Ed25519 instruction,
300/// this points to offsets within the matchOrdersMulti instruction data.
301///
302/// This saves ~128 bytes per order (64 sig + 32 pubkey + 32 msg).
303///
304/// # Arguments
305/// * `num_makers` - Number of maker orders
306/// * `match_ix_index` - Index of the matchOrdersMulti instruction in the transaction
307///
308/// # Returns
309/// Array of Ed25519 verification instructions (taker first, then makers)
310pub fn create_cross_ref_ed25519_instructions(
311    num_makers: usize,
312    match_ix_index: u16,
313) -> Vec<Instruction> {
314    let mut instructions = Vec::with_capacity(1 + num_makers);
315
316    // Taker verification instruction
317    let taker_ix = create_cross_ref_ed25519_instruction(&CrossRefEd25519Params {
318        signature_offset: MatchIxOffsets::TAKER_SIGNATURE,
319        signature_ix_index: match_ix_index,
320        pubkey_offset: MatchIxOffsets::TAKER_PUBKEY,
321        pubkey_ix_index: match_ix_index,
322        message_offset: MatchIxOffsets::TAKER_MESSAGE,
323        message_size: 32,
324        message_ix_index: match_ix_index,
325    });
326    instructions.push(taker_ix);
327
328    // Maker verification instructions
329    for i in 0..num_makers {
330        let offsets = MatchIxOffsets::maker_offsets(i);
331        let maker_ix = create_cross_ref_ed25519_instruction(&CrossRefEd25519Params {
332            signature_offset: offsets.signature,
333            signature_ix_index: match_ix_index,
334            pubkey_offset: offsets.pubkey,
335            pubkey_ix_index: match_ix_index,
336            message_offset: offsets.message,
337            message_size: 32,
338            message_ix_index: match_ix_index,
339        });
340        instructions.push(maker_ix);
341    }
342
343    instructions
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    #[test]
351    fn test_ed25519_verify_instruction_size() {
352        let params = Ed25519VerifyParams {
353            pubkey: Pubkey::new_unique(),
354            message: [42u8; 32],
355            signature: [0u8; 64],
356        };
357
358        let ix = create_ed25519_verify_instruction(&params);
359        assert_eq!(ix.data.len(), 144);
360        assert_eq!(ix.program_id, ED25519_PROGRAM_ID);
361        assert!(ix.accounts.is_empty());
362    }
363
364    #[test]
365    fn test_batch_ed25519_verify_instruction_size() {
366        let params = vec![
367            Ed25519VerifyParams {
368                pubkey: Pubkey::new_unique(),
369                message: [1u8; 32],
370                signature: [0u8; 64],
371            },
372            Ed25519VerifyParams {
373                pubkey: Pubkey::new_unique(),
374                message: [2u8; 32],
375                signature: [0u8; 64],
376            },
377        ];
378
379        let ix = create_batch_ed25519_verify_instruction(&params);
380
381        // Header: 2 + 2*14 = 30 bytes
382        // Data: 2 * 128 = 256 bytes
383        // Total: 286 bytes
384        assert_eq!(ix.data.len(), 30 + 256);
385    }
386
387    #[test]
388    fn test_cross_ref_ed25519_instruction_size() {
389        let params = CrossRefEd25519Params {
390            signature_offset: 98,
391            signature_ix_index: 2,
392            pubkey_offset: 41,
393            pubkey_ix_index: 2,
394            message_offset: 1,
395            message_size: 32,
396            message_ix_index: 2,
397        };
398
399        let ix = create_cross_ref_ed25519_instruction(&params);
400        assert_eq!(ix.data.len(), 16);
401    }
402
403    #[test]
404    fn test_cross_ref_instructions_count() {
405        let instructions = create_cross_ref_ed25519_instructions(2, 3);
406        // 1 taker + 2 makers = 3 instructions
407        assert_eq!(instructions.len(), 3);
408    }
409
410    #[test]
411    fn test_maker_offsets() {
412        // Maker 0
413        let offsets0 = MatchIxOffsets::maker_offsets(0);
414        assert_eq!(offsets0.message, 163);
415        assert_eq!(offsets0.pubkey, 163 + 32 + 8);
416        assert_eq!(offsets0.signature, 163 + 32 + 65);
417
418        // Maker 1 (169 bytes later)
419        let offsets1 = MatchIxOffsets::maker_offsets(1);
420        assert_eq!(offsets1.message, 163 + 169);
421    }
422}