use anyhow::{bail, Result};
use borsh::{BorshDeserialize, BorshSerialize};
use crate::on_demand::oracle_quote::feed_info::{PackedFeedInfo, PackedQuoteHeader};
use crate::sysvar::ed25519_sysvar::{
Ed25519SignatureOffsets, ED25519_PUBKEY_SERIALIZED_SIZE, ED25519_SIGNATURE_OFFSETS_SERIALIZED_SIZE,
ED25519_SIGNATURE_SERIALIZED_SIZE,
};
#[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)]
pub struct ParsedEd25519Instruction {
pub num_signatures: u8,
pub padding: u8,
pub signatures: Vec<OracleSignatureData>,
pub quote_header: PackedQuoteHeader,
pub feeds: Vec<PackedFeedInfo>,
pub oracle_idxs: Vec<u8>,
pub slot: u64,
pub version: u8,
pub discriminator: [u8; 4],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct OracleSignatureData {
pub offsets: Ed25519SignatureOffsets,
pub pubkey: [u8; 32],
pub signature: [u8; 64],
}
impl ParsedEd25519Instruction {
pub fn parse(data: &[u8]) -> Result<Self> {
let data_len = data.len();
if data_len < 2 {
bail!("Data too short for Ed25519SignatureHeader");
}
let num_signatures = data[0];
let padding = data[1];
if num_signatures == 0 {
bail!("No signatures found in instruction data");
}
if num_signatures > 8 {
bail!("Too many signatures - maximum 8 supported");
}
let suffix_len = num_signatures as usize + 13; if data_len < suffix_len {
bail!(
"Data too short for suffix: need {} bytes, got {}",
suffix_len,
data_len
);
}
let end_of_message = data_len - suffix_len;
let suffix = &data[end_of_message..];
let oracle_idxs = suffix[..num_signatures as usize].to_vec();
let slot_bytes = &suffix[num_signatures as usize..num_signatures as usize + 8];
let slot = u64::from_le_bytes(slot_bytes.try_into().unwrap());
let version = suffix[num_signatures as usize + 8];
let discriminator: [u8; 4] = suffix[num_signatures as usize + 9..num_signatures as usize + 13]
.try_into()
.unwrap();
let message_data = &data[..end_of_message];
let mut signatures = Vec::with_capacity(num_signatures as usize);
let mut offset = 2usize;
if offset + ED25519_SIGNATURE_OFFSETS_SERIALIZED_SIZE > message_data.len() {
bail!("Data too short for first signature offsets");
}
let first_offsets = Self::read_offsets(&message_data[offset..offset + ED25519_SIGNATURE_OFFSETS_SERIALIZED_SIZE]);
let message_offset = first_offsets.message_data_offset as usize;
let message_size = first_offsets.message_data_size as usize;
for i in 0..num_signatures {
let offsets = Self::read_offsets(&message_data[offset..offset + ED25519_SIGNATURE_OFFSETS_SERIALIZED_SIZE]);
let pubkey_offset = offsets.public_key_offset as usize;
if pubkey_offset + ED25519_PUBKEY_SERIALIZED_SIZE > message_data.len() {
bail!("Pubkey offset out of bounds");
}
let pubkey: [u8; 32] = message_data[pubkey_offset..pubkey_offset + ED25519_PUBKEY_SERIALIZED_SIZE]
.try_into()
.unwrap();
let sig_offset = offsets.signature_offset as usize;
if sig_offset + ED25519_SIGNATURE_SERIALIZED_SIZE > message_data.len() {
bail!("Signature offset out of bounds");
}
let signature: [u8; 64] = message_data[sig_offset..sig_offset + ED25519_SIGNATURE_SERIALIZED_SIZE]
.try_into()
.unwrap();
signatures.push(OracleSignatureData {
offsets,
pubkey,
signature,
});
offset += ED25519_SIGNATURE_OFFSETS_SERIALIZED_SIZE;
if i > 0 {
if offsets.message_data_offset != first_offsets.message_data_offset {
bail!("Message offset mismatch");
}
if offsets.message_data_size != first_offsets.message_data_size {
bail!("Message size mismatch");
}
}
}
if message_offset + message_size > message_data.len() {
bail!("Message offset/size out of bounds");
}
let message = &message_data[message_offset..message_offset + message_size];
if message.len() < 32 {
bail!("Message too short for PackedQuoteHeader");
}
let quote_header = PackedQuoteHeader::try_from_slice(&message[0..32])
.map_err(|e| anyhow::anyhow!("Failed to deserialize PackedQuoteHeader: {}", e))?;
let mut feeds = Vec::new();
let mut feed_offset = 32; while feed_offset + 49 <= message.len() {
let feed_bytes = &message[feed_offset..feed_offset + 49];
let feed = PackedFeedInfo::try_from_slice(feed_bytes)
.map_err(|e| anyhow::anyhow!("Failed to deserialize PackedFeedInfo: {}", e))?;
feeds.push(feed);
feed_offset += 49;
}
Ok(Self {
num_signatures,
padding,
signatures,
quote_header,
feeds,
oracle_idxs,
slot,
version,
discriminator,
})
}
fn read_offsets(data: &[u8]) -> Ed25519SignatureOffsets {
assert!(data.len() >= ED25519_SIGNATURE_OFFSETS_SERIALIZED_SIZE);
Ed25519SignatureOffsets {
signature_offset: u16::from_le_bytes([data[0], data[1]]),
signature_instruction_index: u16::from_le_bytes([data[2], data[3]]),
public_key_offset: u16::from_le_bytes([data[4], data[5]]),
public_key_instruction_index: u16::from_le_bytes([data[6], data[7]]),
message_data_offset: u16::from_le_bytes([data[8], data[9]]),
message_data_size: u16::from_le_bytes([data[10], data[11]]),
message_instruction_index: u16::from_le_bytes([data[12], data[13]]),
}
}
pub fn to_instruction_bytes(&self) -> Vec<u8> {
unimplemented!("Reconstructing offset-based format not yet implemented")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_owned_structure() {
let parsed = ParsedEd25519Instruction {
num_signatures: 1,
padding: 0,
signatures: vec![OracleSignatureData {
offsets: Ed25519SignatureOffsets {
signature_offset: 0,
signature_instruction_index: 0,
public_key_offset: 0,
public_key_instruction_index: 0,
message_data_offset: 0,
message_data_size: 0,
message_instruction_index: 0,
},
pubkey: [0u8; 32],
signature: [0u8; 64],
}],
quote_header: PackedQuoteHeader {
signed_slothash: [0u8; 32],
},
feeds: vec![],
oracle_idxs: vec![0],
slot: 0,
version: 0,
discriminator: *b"SBOD",
};
let serialized = borsh::to_vec(&parsed).expect("Failed to serialize");
let deserialized: ParsedEd25519Instruction =
borsh::from_slice(&serialized).expect("Failed to deserialize");
assert_eq!(parsed, deserialized);
}
}