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(
28 payload: &DataUpdate,
29 signers_bitmap: [u8; 32],
30 signatures_required: u8,
31) -> [u8; 32] {
32 let registry_version_bytes = payload.registry_version.to_be_bytes();
33 let signatures_required_bytes = u32::from(signatures_required).to_be_bytes();
34 let canonical_timestamp_bytes = (payload.canonical_timestamp as u64).to_be_bytes();
35
36 hashv(&[
37 MESSAGE_PREFIX.as_slice(),
38 payload.source_id.as_slice(),
39 registry_version_bytes.as_slice(),
40 signatures_required_bytes.as_slice(),
41 signers_bitmap.as_slice(),
42 payload.value.as_slice(),
43 canonical_timestamp_bytes.as_slice(),
44 ])
45 .to_bytes()
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 fn fixture_payload() -> DataUpdate {
53 DataUpdate {
54 source_id: [
56 0x73, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x2d, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x2d,
57 0x6a, 0x6f, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
58 0x00, 0x00, 0x00, 0x00,
59 ],
60 registry_version: 1,
61 value: vec![
63 0x73, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x2d, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x2d,
64 0x76, 0x61, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
65 0x00, 0x00, 0x00, 0x00,
66 ],
67 canonical_timestamp: 1_700_000_123,
68 signatures_required: 8,
69 }
70 }
71
72 fn fixture_signers_bitmap() -> [u8; 32] {
73 [
75 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
76 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
77 0x00, 0x00, 0x00, 0xff,
78 ]
79 }
80
81 #[test]
82 fn message_prefix_is_keccak_of_domain() {
83 let expected = hashv(&[b"MOLPHA_MESSAGE_V1"]).to_bytes();
84 assert_eq!(MESSAGE_PREFIX, expected);
85 }
86
87 #[test]
88 fn compute_message_hash_is_deterministic() {
89 let p = fixture_payload();
90 assert_eq!(
91 compute_message_hash(&p, fixture_signers_bitmap(), p.signatures_required),
92 compute_message_hash(&p, fixture_signers_bitmap(), p.signatures_required)
93 );
94 }
95
96 #[test]
97 fn compute_message_hash_is_sensitive_to_each_field() {
98 let base = fixture_payload();
99 let base_hash =
100 compute_message_hash(&base, fixture_signers_bitmap(), base.signatures_required);
101
102 let mut a = fixture_payload();
103 a.registry_version += 1;
104 assert_ne!(
105 compute_message_hash(&a, fixture_signers_bitmap(), a.signatures_required),
106 base_hash
107 );
108
109 let b = fixture_payload();
110 assert_ne!(
111 compute_message_hash(
112 &b,
113 fixture_signers_bitmap(),
114 b.signatures_required.saturating_sub(1)
115 ),
116 base_hash
117 );
118
119 let c = fixture_payload();
120 let mut signers_bitmap = fixture_signers_bitmap();
121 signers_bitmap[31] ^= 0x01;
122 assert_ne!(
123 compute_message_hash(&c, signers_bitmap, c.signatures_required),
124 base_hash
125 );
126
127 let mut d = fixture_payload();
128 d.value[0] ^= 0xff;
129 assert_ne!(
130 compute_message_hash(&d, fixture_signers_bitmap(), d.signatures_required),
131 base_hash
132 );
133
134 let mut e = fixture_payload();
135 e.canonical_timestamp += 1;
136 assert_ne!(
137 compute_message_hash(&e, fixture_signers_bitmap(), e.signatures_required),
138 base_hash
139 );
140
141 let mut f = fixture_payload();
142 f.source_id[0] ^= 0xff;
143 assert_ne!(
144 compute_message_hash(&f, fixture_signers_bitmap(), f.signatures_required),
145 base_hash
146 );
147 }
148}