Skip to main content

molpha_verifier/
payload.rs

1//! Plain `DataUpdate` payload struct.
2//!
3//! Field order and types match the on-chain `SubmitDataUpdateArgs` instruction argument so a
4//! mechanical field copy converts between the two. With the `borsh` feature enabled, `value`
5//! serializes as standard Borsh `Vec<u8>` (u32 little-endian length prefix + bytes), matching
6//! Anchor `Vec<u8>` on-chain. The crate does not rely on that wire layout for verification.
7
8/// A signed Molpha data update.
9#[derive(Clone, Debug, PartialEq, Eq)]
10#[cfg_attr(
11    feature = "borsh",
12    derive(borsh::BorshSerialize, borsh::BorshDeserialize)
13)]
14pub struct DataUpdate {
15    pub source_id: [u8; 32],
16    /// Arbitrary-length payload bytes (hashed as-is into the EVM message).
17    pub value: Vec<u8>,
18    pub canonical_timestamp: i64,
19    pub registry_version: u32,
20    pub signatures_required: u8,
21}
22
23#[derive(Clone, Debug, PartialEq, Eq)]
24#[cfg_attr(
25    feature = "borsh",
26    derive(borsh::BorshSerialize, borsh::BorshDeserialize)
27)]
28pub struct SchnorrSignature {
29    pub agg_sig_s: [u8; 32],
30    pub commitment_addr: [u8; 20],
31    pub signers_bitmap: [u8; 32],
32}
33
34#[cfg(all(test, feature = "borsh"))]
35mod tests {
36    use super::*;
37    use borsh::{BorshDeserialize, BorshSerialize};
38
39    /// Same 32-byte padded "solana-compat-val" as the EVM-compat fixture (hashed as-is).
40    const FIXTURE_VALUE: [u8; 32] = [
41        0x73, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x2d, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x2d, 0x76,
42        0x61, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43        0x00, 0x00,
44    ];
45
46    /// 81-byte borsh encoding used by `examples/verify_data_update.rs`.
47    const FIXTURE_PAYLOAD_BORSH: [u8; 81] = [
48        0x73, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x2d, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x2d, 0x6a,
49        0x6f, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
50        0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x73, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x2d, 0x63, 0x6f,
51        0x6d, 0x70, 0x61, 0x74, 0x2d, 0x76, 0x61, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
52        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0xf1, 0x53, 0x65, 0x00, 0x00, 0x00,
53        0x00, 0x01, 0x00, 0x00, 0x00, 0x08,
54    ];
55
56    /// 84-byte borsh encoding used by `examples/verify_data_update.rs`.
57    const FIXTURE_SIGNATURE_BORSH: [u8; 84] = [
58        0xc7, 0xe0, 0x99, 0x60, 0x3c, 0xee, 0xd2, 0xa1, 0x13, 0xd7, 0x5a, 0x9d, 0x95, 0xe2, 0x0f,
59        0x92, 0x00, 0x6b, 0x06, 0xc5, 0x49, 0x7a, 0xdd, 0x09, 0x81, 0x7d, 0xa8, 0x90, 0x8d, 0x39,
60        0x0d, 0xa5, 0xc6, 0xb9, 0x4f, 0xea, 0x5d, 0xd5, 0xf9, 0x65, 0xd8, 0x67, 0x14, 0xb1, 0xd9,
61        0x9d, 0xcf, 0xaf, 0x1e, 0x72, 0xee, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
62        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
63        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
64    ];
65
66    #[test]
67    fn fixture_payload_borsh_roundtrip() {
68        let decoded = DataUpdate::try_from_slice(&FIXTURE_PAYLOAD_BORSH).expect("decode payload");
69        assert_eq!(decoded.value.as_slice(), FIXTURE_VALUE.as_slice());
70        assert_eq!(decoded.registry_version, 1);
71        assert_eq!(decoded.signatures_required, 8);
72        assert_eq!(decoded.canonical_timestamp, 1_700_000_123);
73
74        let encoded = decoded.try_to_vec().expect("encode payload");
75        assert_eq!(encoded.as_slice(), FIXTURE_PAYLOAD_BORSH.as_slice());
76    }
77
78    #[test]
79    fn fixture_signature_borsh_roundtrip() {
80        let decoded =
81            SchnorrSignature::try_from_slice(&FIXTURE_SIGNATURE_BORSH).expect("decode signature");
82        assert_eq!(decoded.signers_bitmap[31], 0xff);
83
84        let encoded = decoded.try_to_vec().expect("encode signature");
85        assert_eq!(encoded.as_slice(), FIXTURE_SIGNATURE_BORSH.as_slice());
86    }
87}