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, REGISTRY_VERSION, SIGNATURES_REQUIRED, SIGNERS_BITMAP, SOURCE_ID,
46        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_is_sensitive_to_each_field() {
80        let base = fixture_payload();
81        let base_hash = compute_message_hash(&base, fixture_signers_bitmap());
82
83        let mut a = fixture_payload();
84        a.registry_version += 1;
85        assert_ne!(
86            compute_message_hash(&a, fixture_signers_bitmap()),
87            base_hash
88        );
89
90        let mut b = fixture_payload();
91        b.signatures_required += 1;
92        assert_ne!(
93            compute_message_hash(&b, fixture_signers_bitmap()),
94            base_hash
95        );
96
97        let c = fixture_payload();
98        let mut signers_bitmap = fixture_signers_bitmap();
99        signers_bitmap[31] ^= 0x01;
100        assert_ne!(compute_message_hash(&c, signers_bitmap), base_hash);
101
102        let mut d = fixture_payload();
103        d.value[0] ^= 0xff;
104        assert_ne!(
105            compute_message_hash(&d, fixture_signers_bitmap()),
106            base_hash
107        );
108
109        let mut e = fixture_payload();
110        e.canonical_timestamp += 1;
111        assert_ne!(
112            compute_message_hash(&e, fixture_signers_bitmap()),
113            base_hash
114        );
115
116        let mut f = fixture_payload();
117        f.source_id[0] ^= 0xff;
118        assert_ne!(
119            compute_message_hash(&f, fixture_signers_bitmap()),
120            base_hash
121        );
122    }
123}