Skip to main content

omena_cascade/
refinement.rs

1//! Refinement entry points layered above the byte-stable cascade proof module.
2
3use omena_refinement_trait::{
4    RefinementVerdictV0, RefinementWitnessV0, refinement_provenance_v0, refinement_witness_v0,
5};
6
7use crate::{
8    CascadeDeclaration, CascadeLevel, LayerFlattenInputV0, ScopeFlattenInputV0,
9    StaticSupportsAssumptionV0, StaticSupportsEvalVerdictV0, evaluate_static_supports_condition,
10    prove_layer_flatten_candidate, prove_scope_flatten_candidate,
11};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct CascadeRefinementContextV0 {
15    pub supports_condition: Option<String>,
16    pub scope_root_selector: Option<String>,
17    pub layer_name: Option<String>,
18    pub closed_bundle: bool,
19}
20
21impl Default for CascadeRefinementContextV0 {
22    fn default() -> Self {
23        Self {
24            supports_condition: None,
25            scope_root_selector: None,
26            layer_name: None,
27            closed_bundle: true,
28        }
29    }
30}
31
32pub fn refine_declaration_in_context(
33    declaration: &CascadeDeclaration,
34    context: &CascadeRefinementContextV0,
35) -> RefinementWitnessV0 {
36    let mut provenances = Vec::new();
37    let mut verdicts = Vec::new();
38
39    if let Some(condition) = context.supports_condition.as_deref() {
40        let supports = evaluate_static_supports_condition(
41            condition,
42            StaticSupportsAssumptionV0::ModernBrowser,
43        );
44        provenances.push(refinement_provenance_v0(
45            "supports-predicate",
46            Some("evaluate_static_supports_condition"),
47        ));
48        verdicts.push(match supports.verdict {
49            StaticSupportsEvalVerdictV0::AlwaysTrue => RefinementVerdictV0::SatisfiedAll,
50            StaticSupportsEvalVerdictV0::AlwaysFalse => RefinementVerdictV0::Unsatisfiable,
51            StaticSupportsEvalVerdictV0::Unknown => RefinementVerdictV0::Unknown,
52        });
53    }
54
55    if let Some(root_selector) = context.scope_root_selector.as_deref() {
56        let scope = prove_scope_flatten_candidate(ScopeFlattenInputV0 {
57            root_selector: root_selector.to_string(),
58            limit_selector: None,
59            scoped_rule_count: 1,
60            peer_scope_count: 0,
61            competing_unscoped_rule_count: 0,
62            inside_layer: context.layer_name.is_some(),
63        });
64        provenances.push(refinement_provenance_v0(
65            "scope-predicate",
66            Some("prove_scope_flatten_candidate"),
67        ));
68        verdicts.push(if scope.accepted {
69            RefinementVerdictV0::SatisfiedAll
70        } else {
71            RefinementVerdictV0::Unknown
72        });
73    }
74
75    if context.layer_name.is_some() {
76        let layer = prove_layer_flatten_candidate(LayerFlattenInputV0 {
77            layer_name: context.layer_name.clone(),
78            layer_rule_count: 1,
79            peer_layer_count: 0,
80            unlayered_rule_count: 0,
81            important_declaration_count: usize::from(matches!(
82                declaration.key.level,
83                CascadeLevel::AuthorImportant
84                    | CascadeLevel::UserImportant
85                    | CascadeLevel::UserAgentImportant
86            )),
87            closed_bundle: context.closed_bundle,
88        });
89        provenances.push(refinement_provenance_v0(
90            "layer-predicate",
91            Some("prove_layer_flatten_candidate"),
92        ));
93        verdicts.push(if layer.accepted {
94            RefinementVerdictV0::SatisfiedAll
95        } else {
96            RefinementVerdictV0::Unknown
97        });
98    }
99
100    let verdict = combine_refinement_verdicts(&verdicts);
101    refinement_witness_v0("cascade-refinement-conjunction", verdict, provenances)
102}
103
104fn combine_refinement_verdicts(verdicts: &[RefinementVerdictV0]) -> RefinementVerdictV0 {
105    if verdicts.is_empty() {
106        return RefinementVerdictV0::SatisfiedAll;
107    }
108    if verdicts.contains(&RefinementVerdictV0::Unsatisfiable) {
109        return RefinementVerdictV0::Unsatisfiable;
110    }
111    if verdicts
112        .iter()
113        .all(|verdict| *verdict == RefinementVerdictV0::SatisfiedAll)
114    {
115        return RefinementVerdictV0::SatisfiedAll;
116    }
117    if verdicts.contains(&RefinementVerdictV0::SatisfiedAll) {
118        RefinementVerdictV0::SatisfiedSome
119    } else {
120        RefinementVerdictV0::Unknown
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    const EXPECTED_LEGACY_PROOFS_RS_SHA256: [u8; 32] = [
127        0x24, 0xa4, 0x02, 0x86, 0x46, 0x88, 0xe9, 0xcf, 0x2e, 0x1a, 0x38, 0xe6, 0xc9, 0x20, 0x31,
128        0xfa, 0xb0, 0xa4, 0x2a, 0x20, 0x16, 0x23, 0x55, 0x24, 0x59, 0x0a, 0x89, 0xc7, 0x70, 0x3c,
129        0x65, 0x17,
130    ];
131
132    #[test]
133    fn legacy_proofs_rs_byte_untouched() {
134        let digest = sha256(include_bytes!("proofs.rs"));
135        assert_eq!(digest, EXPECTED_LEGACY_PROOFS_RS_SHA256);
136    }
137
138    fn sha256(input: &[u8]) -> [u8; 32] {
139        const H0: [u32; 8] = [
140            0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
141            0x5be0cd19,
142        ];
143        const K: [u32; 64] = [
144            0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
145            0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
146            0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
147            0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
148            0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
149            0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
150            0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
151            0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
152            0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
153            0xc67178f2,
154        ];
155
156        let mut bytes = input.to_vec();
157        let bit_len = (bytes.len() as u64) * 8;
158        bytes.push(0x80);
159        while bytes.len() % 64 != 56 {
160            bytes.push(0);
161        }
162        bytes.extend_from_slice(&bit_len.to_be_bytes());
163
164        let mut state = H0;
165        for chunk in bytes.chunks_exact(64) {
166            let mut w = [0u32; 64];
167            for (index, word) in chunk.chunks_exact(4).enumerate() {
168                w[index] = u32::from_be_bytes([word[0], word[1], word[2], word[3]]);
169            }
170            for index in 16..64 {
171                let s0 = w[index - 15].rotate_right(7)
172                    ^ w[index - 15].rotate_right(18)
173                    ^ (w[index - 15] >> 3);
174                let s1 = w[index - 2].rotate_right(17)
175                    ^ w[index - 2].rotate_right(19)
176                    ^ (w[index - 2] >> 10);
177                w[index] = w[index - 16]
178                    .wrapping_add(s0)
179                    .wrapping_add(w[index - 7])
180                    .wrapping_add(s1);
181            }
182
183            let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = state;
184            for index in 0..64 {
185                let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
186                let ch = (e & f) ^ ((!e) & g);
187                let temp1 = h
188                    .wrapping_add(s1)
189                    .wrapping_add(ch)
190                    .wrapping_add(K[index])
191                    .wrapping_add(w[index]);
192                let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
193                let maj = (a & b) ^ (a & c) ^ (b & c);
194                let temp2 = s0.wrapping_add(maj);
195                h = g;
196                g = f;
197                f = e;
198                e = d.wrapping_add(temp1);
199                d = c;
200                c = b;
201                b = a;
202                a = temp1.wrapping_add(temp2);
203            }
204
205            for (slot, value) in state.iter_mut().zip([a, b, c, d, e, f, g, h]) {
206                *slot = slot.wrapping_add(value);
207            }
208        }
209
210        let mut digest = [0u8; 32];
211        for (chunk, word) in digest.chunks_exact_mut(4).zip(state) {
212            chunk.copy_from_slice(&word.to_be_bytes());
213        }
214        digest
215    }
216}