Skip to main content

molpha_verifier/
message.rs

1//! EVM-compatible attestation message hash.
2
3use solana_keccak_hasher::hashv;
4
5use crate::payload::AttestationPayload;
6
7/// `bytes32(keccak256("MOLPHA_MESSAGE_V1"))` — EVM `Validator._constructMessage` prefix.
8///
9/// Value: `keccak256(bytes("MOLPHA_MESSAGE_V1"))`, verified by the unit test below.
10pub 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
15/// Compute the EVM-compatible attestation message hash.
16///
17/// Matches `Validator._constructMessage` in the EVM reference implementation:
18/// ```text
19/// keccak256(abi.encodePacked(
20///     MESSAGE_PREFIX, value, sourceId, registryVersion, signaturesRequired,
21///     canonicalTimestamp, signersBitmap
22/// ))
23/// ```
24pub fn compute_message_hash(payload: &AttestationPayload, signers_bitmap: [u8; 32]) -> [u8; 32] {
25    let registry_version_bytes = payload.registry_version.to_be_bytes();
26    let signatures_required_bytes = payload.signatures_required.to_be_bytes();
27    let canonical_timestamp_bytes = payload.canonical_timestamp.to_be_bytes();
28
29    hashv(&[
30        MESSAGE_PREFIX.as_slice(),
31        payload.value.as_slice(),
32        payload.source_id.as_slice(),
33        registry_version_bytes.as_slice(),
34        signatures_required_bytes.as_slice(),
35        canonical_timestamp_bytes.as_slice(),
36        signers_bitmap.as_slice(),
37    ])
38    .to_bytes()
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44    use crate::fixtures::{
45        CANONICAL_TIMESTAMP, MESSAGE_HASH, REGISTRY_VERSION, SIGNATURES_REQUIRED, SIGNERS_BITMAP,
46        SOURCE_ID, VALUE,
47    };
48
49    fn fixture_payload() -> AttestationPayload {
50        AttestationPayload {
51            value: VALUE,
52            source_id: SOURCE_ID,
53            registry_version: REGISTRY_VERSION,
54            signatures_required: SIGNATURES_REQUIRED,
55            canonical_timestamp: CANONICAL_TIMESTAMP,
56        }
57    }
58
59    fn fixture_signers_bitmap() -> [u8; 32] {
60        SIGNERS_BITMAP
61    }
62
63    #[test]
64    fn message_prefix_is_keccak_of_domain() {
65        let expected = hashv(&[b"MOLPHA_MESSAGE_V1"]).to_bytes();
66        assert_eq!(MESSAGE_PREFIX, expected);
67    }
68
69    #[test]
70    fn compute_message_hash_is_deterministic() {
71        let p = fixture_payload();
72        assert_eq!(
73            compute_message_hash(&p, fixture_signers_bitmap()),
74            compute_message_hash(&p, fixture_signers_bitmap())
75        );
76    }
77
78    #[test]
79    fn compute_message_hash_matches_evm_fixture() {
80        let p = fixture_payload();
81        assert_eq!(
82            compute_message_hash(&p, fixture_signers_bitmap()),
83            MESSAGE_HASH,
84        );
85    }
86
87    #[test]
88    fn compute_message_hash_is_sensitive_to_each_field() {
89        let base = fixture_payload();
90        let base_hash = compute_message_hash(&base, fixture_signers_bitmap());
91
92        let mut a = fixture_payload();
93        a.registry_version += 1;
94        assert_ne!(
95            compute_message_hash(&a, fixture_signers_bitmap()),
96            base_hash
97        );
98
99        let mut b = fixture_payload();
100        b.signatures_required += 1;
101        assert_ne!(
102            compute_message_hash(&b, fixture_signers_bitmap()),
103            base_hash
104        );
105
106        let c = fixture_payload();
107        let mut signers_bitmap = fixture_signers_bitmap();
108        signers_bitmap[31] ^= 0x01;
109        assert_ne!(compute_message_hash(&c, signers_bitmap), base_hash);
110
111        let mut d = fixture_payload();
112        d.value[0] ^= 0xff;
113        assert_ne!(
114            compute_message_hash(&d, fixture_signers_bitmap()),
115            base_hash
116        );
117
118        let mut e = fixture_payload();
119        e.canonical_timestamp += 1;
120        assert_ne!(
121            compute_message_hash(&e, fixture_signers_bitmap()),
122            base_hash
123        );
124
125        let mut f = fixture_payload();
126        f.source_id[0] ^= 0xff;
127        assert_ne!(
128            compute_message_hash(&f, fixture_signers_bitmap()),
129            base_hash
130        );
131    }
132}