1use solana_keccak_hasher::hashv;
4
5use crate::payload::DataUpdate;
6
7pub const MESSAGE_PREFIX: [u8; 32] = [
11 0xa7, 0x55, 0x23, 0xa2, 0xab, 0x7b, 0x71, 0x8d, 0x9c, 0xff, 0xd2, 0xfa, 0x97, 0xed, 0x06, 0x9f,
12 0xc1, 0x21, 0x84, 0xea, 0xbe, 0xe7, 0xd5, 0x07, 0x85, 0x4d, 0x09, 0x22, 0xf7, 0x0e, 0x7f, 0xe7,
13];
14
15pub fn compute_message_hash(payload: &DataUpdate, signatures_required: u8) -> [u8; 32] {
28 let registry_version_bytes = payload.registry_version.to_be_bytes();
29 let signatures_required_bytes = u32::from(signatures_required).to_be_bytes();
30 let canonical_timestamp_bytes = (payload.canonical_timestamp as u64).to_be_bytes();
31
32 hashv(&[
33 MESSAGE_PREFIX.as_slice(),
34 payload.feed_id.as_slice(),
35 registry_version_bytes.as_slice(),
36 signatures_required_bytes.as_slice(),
37 payload.signers_bitmap.as_slice(),
38 payload.value.as_slice(),
39 canonical_timestamp_bytes.as_slice(),
40 ])
41 .to_bytes()
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 fn fixture_payload() -> DataUpdate {
49 DataUpdate {
50 feed_id: [
52 0x73, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x2d, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x2d,
53 0x6a, 0x6f, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
54 0x00, 0x00, 0x00, 0x00,
55 ],
56 registry_version: 1,
57 value: [
59 0x73, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x2d, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x2d,
60 0x76, 0x61, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
61 0x00, 0x00, 0x00, 0x00,
62 ],
63 canonical_timestamp: 1_700_000_123,
64 signatures_required: 8,
65 agg_sig_s: [0u8; 32],
66 commitment_addr: [0u8; 20],
67 signers_bitmap: [
69 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
70 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
71 0x00, 0x00, 0x00, 0xff,
72 ],
73 }
74 }
75
76 #[test]
77 fn message_prefix_is_keccak_of_domain() {
78 let expected = hashv(&[b"MOLPHA_MESSAGE_V1"]).to_bytes();
79 assert_eq!(MESSAGE_PREFIX, expected);
80 }
81
82 #[test]
83 fn compute_message_hash_is_deterministic() {
84 let p = fixture_payload();
85 assert_eq!(
86 compute_message_hash(&p, p.signatures_required),
87 compute_message_hash(&p, p.signatures_required)
88 );
89 }
90
91 #[test]
92 fn compute_message_hash_is_sensitive_to_each_field() {
93 let base = fixture_payload();
94 let base_hash = compute_message_hash(&base, base.signatures_required);
95
96 let mut a = fixture_payload();
97 a.registry_version += 1;
98 assert_ne!(compute_message_hash(&a, a.signatures_required), base_hash);
99
100 let b = fixture_payload();
101 assert_ne!(
102 compute_message_hash(&b, b.signatures_required.saturating_sub(1)),
103 base_hash
104 );
105
106 let mut c = fixture_payload();
107 c.signers_bitmap[31] ^= 0x01;
108 assert_ne!(compute_message_hash(&c, c.signatures_required), base_hash);
109
110 let mut d = fixture_payload();
111 d.value[0] ^= 0xff;
112 assert_ne!(compute_message_hash(&d, d.signatures_required), base_hash);
113
114 let mut e = fixture_payload();
115 e.canonical_timestamp += 1;
116 assert_ne!(compute_message_hash(&e, e.signatures_required), base_hash);
117
118 let mut f = fixture_payload();
119 f.feed_id[0] ^= 0xff;
120 assert_ne!(compute_message_hash(&f, f.signatures_required), base_hash);
121 }
122}