├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── Rust.fst ├── bloom-filter ├── .gitignore ├── Cargo.toml ├── Makefile ├── src-fstar │ ├── BloomFilter.fst │ ├── Proof.BloomFilter.fst │ ├── Spec.BloomFilter.fst │ └── Test.BloomFilter.fst └── src │ └── lib.rs ├── chacha20 ├── .gitignore ├── Cargo.toml └── src │ └── lib.rs ├── dalek-field-mul ├── .gitignore ├── Cargo.toml ├── src-fstar │ └── DalekFieldMul.fst └── src │ └── lib.rs ├── rox-star-lib ├── .gitignore ├── Cargo.toml └── src │ ├── bytes.rs │ ├── lib.rs │ └── nat_int.rs └── textinput ├── .gitignore ├── Cargo.toml ├── Makefile ├── src-fstar ├── Test.TextInput.fst └── TextInput.fst └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *.hints 2 | *~ 3 | *# 4 | .#* 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Needs FSTAR_HOME to point to FStar's root directory 2 | 3 | FSTAR=$(FSTAR_HOME)/bin/fstar.exe 4 | 5 | verify-bloom-filter: 6 | make -C bloom-filter/ verify 7 | 8 | clean-bloom-filter: 9 | make -C bloom-filter/ clean 10 | 11 | test-bloom-filter: 12 | make -C bloom-filter/ test 13 | 14 | verify-textinput: 15 | make -C textinput/ verify 16 | 17 | clean-textinput: 18 | make -C textinput/ clean 19 | 20 | test-textinput: 21 | make -C textinput/ test 22 | 23 | verify: verify-bloom-filter verify-textinput 24 | 25 | clean: clean-bloom-filter clean-textinput 26 | 27 | test: test-bloom-filter test-textinput 28 | 29 | .PHONY: verify-bloom-filter verify-textinput 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rox-star 2 | 3 | The full context for this work is presented [on the author's blog](https://blog.merigoux.ovh/en/2019/04/16/verifying-rust.html). 4 | 5 | ## Examples 6 | 7 | This repo contains motivating examples for a Rust -> Oxide -> F\* verification toolchain. The two main examples are in the folders `textinput` and `bloom-filter`. Each one is a Rust crate that you can `cargo build` or `cargo test`, along with an F\* implementation that you can `make verify` or `make test`. 8 | 9 | To install F*, please follow the instructions [here](https://github.com/FStarLang/FStar). 10 | -------------------------------------------------------------------------------- /Rust.fst: -------------------------------------------------------------------------------- 1 | module Rust 2 | 3 | module U128 = FStar.UInt128 4 | module U64 = FStar.UInt64 5 | module Usize = FStar.UInt32 6 | module U32 = FStar.UInt32 7 | module U8 = FStar.UInt8 8 | module I64 = FStar.Int64 9 | module Isize = FStar.Int32 10 | module I32 = FStar.Int32 11 | 12 | #reset-options "--max_fuel 1" 13 | 14 | (*** Integers *) 15 | 16 | (**** Types and max values *) 17 | 18 | type u128 = U128.t 19 | let _MAX_U128 = U128.uint_to_t (FStar.UInt.max_int 128) 20 | 21 | type u64 = U64.t 22 | let _MAX_U64 = U64.uint_to_t (FStar.UInt.max_int 64) 23 | 24 | type usize = Usize.t 25 | let _MAX_USIZE = Usize.uint_to_t (FStar.UInt.max_int 32) 26 | 27 | type u32 = U32.t 28 | let _MAX_U32 = U32.uint_to_t (FStar.UInt.max_int 32) 29 | 30 | type u8 = U8.t 31 | let _MAX_U8 = U8.uint_to_t (FStar.UInt.max_int 8) 32 | 33 | type i64 = I64.t 34 | let _MAX_I64 = I64.int_to_t (FStar.Int.max_int 64) 35 | let _MIN_I64 = I64.int_to_t (FStar.Int.min_int 64) 36 | 37 | type isize = Isize.t 38 | let _MAX_ISIZE = Isize.int_to_t (FStar.Int.max_int 32) 39 | let _MIN_ISIZE = Isize.int_to_t (FStar.Int.min_int 32) 40 | 41 | 42 | type i32 = I32.t 43 | let _MAX_I32 = I32.int_to_t (FStar.Int.max_int 32) 44 | let _MIN_I32 = I32.int_to_t (FStar.Int.min_int 32) 45 | 46 | (**** Casts and basic functions *) 47 | 48 | let isize_to_usize_safe (x:isize{Isize.(x >=^ 0l)}) : Tot usize = Usize.uint_to_t (Isize.v x) 49 | let usize_to_isize_safe (x:usize{Usize.v x <= Isize.v _MAX_ISIZE}) : Tot isize = 50 | Isize.int_to_t (Usize.v x) 51 | 52 | let u32_to_u64 (x:u32) : u64 = U64.uint_to_t (U32.v x) 53 | let u64_to_u128 (x:u64) : u128 = U128.uint_to_t (U64.v x) 54 | 55 | let u128_to_u64 (x:u128) : u64 = U64.uint_to_t (U128.v x % U64.v _MAX_U64) 56 | 57 | let max_isize (x y:isize) = Isize.(if x >=^ y then x else y) 58 | let min_isize (x y:isize) = Isize.(if x >=^ y then y else x) 59 | 60 | let max_usize (x y:usize) = Usize.(if x >=^ y then x else y) 61 | let min_usize (x y:usize) = Usize.(if x >=^ y then y else x) 62 | 63 | (*** Strings *) 64 | 65 | type rust_string = s:string{FStar.String.strlen s <= Usize.v _MAX_USIZE} 66 | 67 | let string_length (s:rust_string) : Tot usize = Usize.uint_to_t (FStar.String.strlen s) 68 | 69 | open FStar.Seq 70 | 71 | (*** Vectors *) 72 | 73 | let vec (a : Type0) = l:(seq a){length l <= Usize.v _MAX_USIZE} 74 | 75 | val vec_length : #a:Type0 -> vec a -> Tot usize 76 | let vec_length #a v = Usize.uint_to_t (length v) 77 | 78 | val vec_empty : #a:Type0 -> unit -> Tot (r:(vec a){vec_length r = 0ul}) 79 | let vec_empty #a () = empty 80 | 81 | let vec_idx (#a: Type0) (s:vec a) = idx:usize{Usize.(idx <^ vec_length s)} 82 | 83 | val vec_index : #a:Type0 -> s:vec a -> i:vec_idx s -> Tot a 84 | let vec_index #a v i = index #a v (Usize.v i) 85 | 86 | val vec_push : #a:Type0 -> s:vec a{length s + 1 <= Usize.v _MAX_USIZE} -> new_val:a -> vec a 87 | let vec_push #a s new_val = s @| (create 1 new_val) 88 | 89 | let vec_push_lemma1 (#a:eqtype) (s:vec a{length s + 1 <= Usize.v _MAX_USIZE}) (new_val:a) 90 | (idx:vec_idx s) 91 | : Lemma (ensures (vec_index (vec_push s new_val) idx = vec_index s idx)) 92 | [SMTPat (vec_index (vec_push s new_val) idx)] = () 93 | 94 | let vec_push_lemma2 (#a:eqtype) (s:vec a{length s + 1 <= Usize.v _MAX_USIZE}) (new_val:a) 95 | : Lemma (ensures (vec_index (vec_push s new_val) (vec_length s)) = new_val) 96 | [SMTPat (vec_index (vec_push s new_val) (vec_length s))] = () 97 | 98 | let vec_push_lemma3 (#a:eqtype) (s:vec a{length s + 1 <= Usize.v _MAX_USIZE}) (new_val:a) 99 | : Lemma (ensures (vec_length (vec_push s new_val) = Usize.(vec_length s +^ 1ul))) 100 | [SMTPat (vec_length (vec_push s new_val))] = () 101 | 102 | val vec_update : #a:Type0 -> s:vec a -> idx:vec_idx s -> new_val:a -> Tot (vec a) 103 | let vec_update #a s idx new_val = upd #a s (Usize.v idx) new_val 104 | 105 | let vec_update_lemma1 (#a:eqtype) (s:vec a) (idx idx':vec_idx s) (new_val:a) 106 | : Lemma (requires (idx <> idx')) 107 | (ensures (vec_index (vec_update s idx new_val) idx' = vec_index s idx')) 108 | [SMTPat (vec_index (vec_update s idx new_val) idx')] 109 | = () 110 | 111 | let vec_update_lemma2 (#a:eqtype) (s:vec a) (idx:vec_idx s) (new_val:a) 112 | : Lemma (ensures (vec_index (vec_update s idx new_val) idx = new_val)) 113 | [SMTPat (vec_index (vec_update s idx new_val) idx)] 114 | = () 115 | 116 | (*** Arrays *) 117 | 118 | let array (a : Type0) (len: usize) = l:(seq a){length l = Usize.v len} 119 | 120 | val array_new: #a:Type0 -> len:usize -> v:a -> Tot (arr:array a len) 121 | let array_new #a len v = create #a (Usize.v len) v 122 | 123 | val array_length : #a:Type0 -> #len:usize -> array a len -> Tot usize 124 | let array_length #a #len array = len 125 | 126 | let arr_idx (#a:Type0) (#len:usize) (arr:array a len) = idx:usize{Usize.(idx <^ len)} 127 | 128 | val array_index : #a:Type0 -> #len:usize -> arr:array a len -> i:arr_idx arr -> Tot a 129 | let array_index #a #len arr i = index #a arr (Usize.v i) 130 | 131 | val op_Array_Access: #a:Type0 -> #len:usize -> arr:array a len -> i:arr_idx arr -> Tot a 132 | let op_Array_Access = array_index 133 | 134 | val array_update : #a:eqtype -> #len:usize -> arr:array a len -> i:arr_idx arr -> new_value:a -> 135 | Tot (new_arr:array a len) 136 | let array_update #a #len arr i new_value = vec_update #a arr i new_value 137 | 138 | let array_update_lemma1 (#a:eqtype) (#len:usize) (s:array a len) (idx idx':arr_idx s) (new_val:a) 139 | : Lemma (requires (idx <> idx')) 140 | (ensures (array_index (array_update s idx new_val) idx' = array_index s idx')) 141 | [SMTPat (array_index (array_update s idx new_val) idx')] 142 | = () 143 | 144 | let array_update_lemma2 (#a:eqtype) (#len:usize) (s:array a len) (idx:arr_idx s) (new_val:a) 145 | : Lemma (ensures (array_index (array_update s idx new_val) idx = new_val)) 146 | [SMTPat (array_index (array_update s idx new_val) idx)] 147 | = () 148 | 149 | (*** Iterators *) 150 | 151 | let iter (a: Type0) = vec a 152 | val array_to_iter : #a:eqtype -> #len:usize -> array a len -> iter a 153 | let array_to_iter #a #len arr = arr 154 | 155 | val vec_to_iter : #a:eqtype -> vec a -> iter a 156 | let vec_to_iter #a s = s 157 | 158 | val iter_collect : #a:eqtype -> iter a -> vec a 159 | let iter_collect #a it = it 160 | 161 | private val repeat_left: 162 | lo:nat 163 | -> hi:nat{lo <= hi} 164 | -> a:(i:nat{lo <= i /\ i <= hi} -> Type) 165 | -> f:(i:nat{lo <= i /\ i < hi} -> a i -> a (i + 1)) 166 | -> acc:a lo 167 | -> Tot (a hi) (decreases (hi - lo)) 168 | private let rec repeat_left lo hi a f acc = 169 | if lo = hi then acc 170 | else repeat_left (lo + 1) hi a f (f lo acc) 171 | 172 | val iter_foldl : #b:Type -> #a:Type -> 173 | s:vec b -> 174 | init:a -> 175 | f:(a -> b -> Tot a) -> 176 | Tot a 177 | let iter_foldl #b #a s x f = 178 | let len = vec_length s in 179 | if len = 0ul then x else 180 | repeat_left 0 (Usize.v len) 181 | (fun _ -> a ) 182 | (fun i acc -> f acc (vec_index s (Usize.uint_to_t i))) 183 | x 184 | 185 | val vec_all : #b:Type -> vector:vec b -> f:(b -> bool) -> Tot bool (decreases vector) 186 | let rec vec_all #b vector f = let len = vec_length vector in 187 | repeat_left 0 (Usize.v len) 188 | (fun _ -> bool) 189 | (fun i acc -> acc && f (vec_index vector (Usize.uint_to_t i))) 190 | true 191 | 192 | 193 | private val enumerate_aux : 194 | #a:eqtype -> 195 | tot:nat{tot <= Usize.v _MAX_USIZE} -> 196 | s:(iter a){length s <= tot} -> 197 | Tot (r:(iter (a & usize)){length r = length s}) 198 | (decreases (length s)) 199 | private let rec enumerate_aux #a tot s = 200 | let len = length s in 201 | if len = 0 then empty else 202 | let hd = index s 0 in let tl = slice s 1 len in 203 | (create 1 (hd, Usize.uint_to_t (tot - len))) @| (enumerate_aux tot tl) 204 | 205 | private let rec enumerate_aux_lemma 206 | (#a:eqtype) 207 | (tot:nat{tot <= Usize.v _MAX_USIZE}) 208 | (s:(vec a){length s <= tot}) 209 | (i:usize{Usize.v i < tot}) 210 | : Lemma (ensures ( 211 | let r = enumerate_aux tot s in 212 | if Usize.v i < tot - length s then true else 213 | let rel_i = Usize.uint_to_t (Usize.v i - tot + length s) in 214 | vec_index r rel_i = (vec_index s rel_i, i) 215 | )) (decreases (length s)) = 216 | let len = length s in if len = 0 then () else 217 | let tl = slice s 1 len in enumerate_aux_lemma #a tot tl i 218 | 219 | val enumerate: #a:eqtype -> vec a -> Tot (vec (a & usize)) 220 | let enumerate #a s = enumerate_aux (length s) s 221 | 222 | let enumerate_lemma1 (#a:eqtype) (s:iter a) (idx:vec_idx s) 223 | : Lemma (ensures (vec_index (iter_collect (enumerate s)) idx = (vec_index s idx, idx))) 224 | [SMTPat (vec_index (iter_collect (enumerate s)) idx)] = enumerate_aux_lemma (length s) s idx 225 | 226 | val iter_range: #a:Type -> low:usize -> high:usize{Usize.(low <=^ high)} -> 227 | f:(i:usize{Usize.(low <=^ i) /\ Usize.(i <^ high)} -> a -> a) -> init:a -> 228 | Tot a 229 | let iter_range #a low high f init = if low = high then init else repeat_left 230 | (Usize.v low) (Usize.(v (high -^ 1ul))) 231 | (fun _ -> a ) 232 | (fun i acc -> f (Usize.uint_to_t i) acc) 233 | init 234 | 235 | (*** Sugars *) 236 | 237 | (**** Option type *) 238 | 239 | let unwrap_or (#a:Type) (x:option a) (d:a) : a = match x with 240 | | Some x -> x 241 | | None -> d 242 | 243 | let is_some (#a:Type) (x:option a) : bool = match x with 244 | | Some _ -> true 245 | | None -> false 246 | 247 | let is_none (#a:Type) (x:option a) : bool = not (is_some x) 248 | 249 | (*** Testing *) 250 | 251 | let assert_eq (#a:eqtype) (id:string) (printing: (a -> All.ML unit)) (x y:a) : All.ML unit = 252 | if x = y then () else begin 253 | IO.print_string "assertion ["; 254 | IO.print_string id; 255 | IO.print_string "] failed: "; 256 | printing x; 257 | IO.print_string " != "; 258 | printing y; 259 | IO.print_string "\n" 260 | end 261 | 262 | private val repeat_left_ml: 263 | lo:nat 264 | -> hi:nat{lo <= hi} 265 | -> a:(i:nat{lo <= i /\ i <= hi} -> Type) 266 | -> f:(i:nat{lo <= i /\ i < hi} -> a i -> All.ML (a (i + 1))) 267 | -> acc:a lo 268 | -> All.ML (a hi) (decreases (hi - lo)) 269 | private let rec repeat_left_ml lo hi a f acc = 270 | if lo = hi then acc 271 | else repeat_left_ml (lo + 1) hi a f (f lo acc) 272 | 273 | 274 | val iter_range_ml: #a:Type -> low:usize -> high:usize{Usize.(low <=^ high)} -> 275 | f:(i:usize{Usize.(low <=^ i) /\ Usize.(i <^ high)} -> a -> All.ML a) -> init:a -> 276 | All.ML a 277 | let iter_range_ml #a low high f init = if low = high then init else 278 | repeat_left_ml 279 | (Usize.v low) (Usize.(v (high -^ 1ul))) 280 | (fun _ -> a ) 281 | (fun i acc -> f (Usize.uint_to_t i) acc) 282 | init 283 | -------------------------------------------------------------------------------- /bloom-filter/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | *.exe 4 | -------------------------------------------------------------------------------- /bloom-filter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "verified-bloom-filter" 3 | version = "0.1.0" 4 | authors = ["Denis Merigoux "] 5 | 6 | [dependencies] 7 | rox-star-lib = { path = "../rox-star-lib" } 8 | -------------------------------------------------------------------------------- /bloom-filter/Makefile: -------------------------------------------------------------------------------- 1 | # Needs FSTAR_HOME to point to FStar's root directory 2 | 3 | FSTAR=$(FSTAR_HOME)/bin/fstar.exe 4 | 5 | include $(FSTAR_HOME)/ulib/ml/Makefile.include 6 | 7 | %.fst.hints: %.fst 8 | $(FSTAR) $< --use_hints --record_hints --include ../ --include src-fstar/ 9 | 10 | %.exe: %.fst 11 | mkdir -p target/ocaml 12 | $(FSTAR) $(FSTAR_DEFAULT_ARGS) --codegen OCaml --lax --odir target/ocaml \ 13 | --include ../ --include src-fstar $^ 14 | $(OCAMLOPT) -I target/ocaml/ target/ocaml/Rust.ml target/ocaml/BloomFilter.ml \ 15 | target/ocaml/Spec_BloomFilter.ml target/ocaml/Test_BloomFilter.ml \ 16 | -o Test_BloomFilter.exe 17 | 18 | verify: src-fstar/Proof.BloomFilter.fst.hints 19 | 20 | clean: 21 | rm src-fstar/Proof.BloomFilter.fst.hints 22 | 23 | extract: src-fstar/Test.BloomFilter.exe 24 | 25 | test: src-fstar/Test.BloomFilter.exe 26 | ./Test_BloomFilter.exe 27 | cargo test 28 | 29 | .PHONY: %.fst 30 | -------------------------------------------------------------------------------- /bloom-filter/src-fstar/BloomFilter.fst: -------------------------------------------------------------------------------- 1 | module BloomFilter 2 | 3 | module U64 = FStar.UInt64 4 | module Usize = FStar.UInt32 5 | module U32 = FStar.UInt32 6 | module U8 = FStar.UInt8 7 | module I64 = FStar.Int64 8 | module Isize = FStar.Int32 9 | module I32 = FStar.Int32 10 | 11 | open Rust 12 | 13 | #reset-options "--max_fuel 0" 14 | 15 | let _KEY_SIZE: usize = 12ul 16 | 17 | let _ARRAY_SIZE: usize = Usize.(1ul <<^ _KEY_SIZE) 18 | 19 | let _KEY_MASK: u32 = Usize.((1ul <<^ _KEY_SIZE) -%^ 1ul) 20 | 21 | type bloom_storage_u8 = { 22 | counters: array u8 _ARRAY_SIZE 23 | } 24 | 25 | let valid_index = i:usize{Usize.(i <^ _ARRAY_SIZE)} 26 | 27 | val hash1: u32 -> valid_index 28 | let hash1 hash = 29 | let and_value = U32.(hash &^ _KEY_MASK) in 30 | (* *) FStar.UInt.logand_mask (U32.v hash) 12; 31 | and_value 32 | 33 | val hash2: u32 -> valid_index 34 | let hash2 hash = 35 | let middle_value = U32.(hash >>^ _KEY_SIZE) in 36 | let and_value = U32.(middle_value &^ _KEY_MASK) in 37 | (* *) FStar.UInt.logand_mask (U32.v middle_value) 12; 38 | and_value 39 | 40 | let first_slot_index (hash: u32) : valid_index = 41 | hash1 hash 42 | 43 | let second_slot_index (hash: u32) : valid_index = 44 | hash2 hash 45 | 46 | val slot_value: bf:bloom_storage_u8 -> valid_index -> Tot u8 47 | let slot_value bf idx = 48 | array_index bf.counters idx 49 | 50 | val slot_is_empty: bf:bloom_storage_u8 -> valid_index -> Tot bool 51 | let slot_is_empty bf idx = 52 | slot_value bf idx = 0uy 53 | 54 | val first_slot_is_empty : bf:bloom_storage_u8 -> u32 -> Tot bool 55 | let first_slot_is_empty bf hash = slot_is_empty bf (first_slot_index hash) 56 | 57 | val second_slot_is_empty : bf:bloom_storage_u8 -> u32 -> Tot bool 58 | let second_slot_is_empty bf hash = slot_is_empty bf (second_slot_index hash) 59 | 60 | val might_contain_hash: bf:bloom_storage_u8 -> u32 -> bool 61 | let might_contain_hash bf hash = 62 | not (first_slot_is_empty bf hash) && 63 | not (second_slot_is_empty bf hash) 64 | 65 | val adjust_slot: 66 | bf:bloom_storage_u8 -> 67 | idx:valid_index -> 68 | increment:bool -> 69 | Tot (new_bf:bloom_storage_u8) 70 | let adjust_slot bf idx increment = 71 | let slot = array_index bf.counters idx in 72 | if slot <> 0xffuy then 73 | if increment then begin 74 | { bf with counters = array_update bf.counters idx U8.(slot +%^ 1uy) } 75 | end else 76 | { bf with counters = array_update bf.counters idx U8.(slot -%^ 1uy) } 77 | else bf 78 | 79 | val adjust_first_slot: 80 | bf:bloom_storage_u8 -> 81 | hash:u32 -> 82 | increment:bool -> 83 | Tot (new_bf:bloom_storage_u8) 84 | let adjust_first_slot bf hash increment = 85 | adjust_slot bf (first_slot_index hash) increment 86 | 87 | val adjust_second_slot: 88 | bf:bloom_storage_u8 -> 89 | hash:u32 -> 90 | increment:bool -> 91 | Tot (new_bf:bloom_storage_u8) 92 | let adjust_second_slot bf hash increment = 93 | adjust_slot bf (second_slot_index hash) increment 94 | 95 | val insert_hash: 96 | bf:bloom_storage_u8 -> 97 | hash: u32 -> 98 | Tot (new_bf:bloom_storage_u8) 99 | let insert_hash bf hash = 100 | let bf = adjust_first_slot bf hash true in 101 | adjust_second_slot bf hash true 102 | 103 | val remove_hash: 104 | bf:bloom_storage_u8 -> 105 | hash:u32 -> 106 | Tot (new_bf:bloom_storage_u8) 107 | let remove_hash bf hash = 108 | let bf = adjust_first_slot bf hash false in 109 | adjust_second_slot bf hash false 110 | 111 | #reset-options "--max_fuel 1" 112 | 113 | val bloom_storage_u8_new : unit -> Tot bloom_storage_u8 114 | let bloom_storage_u8_new () = { 115 | counters = array_new _ARRAY_SIZE 0uy ; 116 | } 117 | -------------------------------------------------------------------------------- /bloom-filter/src-fstar/Proof.BloomFilter.fst: -------------------------------------------------------------------------------- 1 | module Proof.BloomFilter 2 | 3 | module U64 = FStar.UInt64 4 | module Usize = FStar.UInt32 5 | module U32 = FStar.UInt32 6 | module U8 = FStar.UInt8 7 | module I64 = FStar.Int64 8 | module Isize = FStar.Int32 9 | module I32 = FStar.Int32 10 | 11 | open Rust 12 | open Spec.BloomFilter 13 | open BloomFilter 14 | 15 | (**** Adjust slot properties *) 16 | 17 | #reset-options "--max_fuel 0" 18 | 19 | let is_max (bf:bloom_storage_u8) (idx:valid_index) : Tot bool = 20 | slot_value bf idx = _MAX_U8 21 | 22 | let is_almost_max (bf:bloom_storage_u8) (idx:valid_index) : Tot bool = 23 | slot_value bf idx = U8.(_MAX_U8 -^ 1uy) 24 | 25 | let adjust_slot_lemma (bf:bloom_storage_u8) (idx:valid_index) 26 | (increment:bool) (idx':valid_index): Lemma (ensures ( 27 | let new_bf = adjust_slot bf idx increment in 28 | if idx' <> idx then 29 | slot_value new_bf idx' = slot_value bf idx' 30 | else begin 31 | if is_max bf idx then 32 | is_max new_bf idx 33 | else if increment then 34 | slot_value new_bf idx = U8.(slot_value bf idx +^ 1uy) 35 | else if slot_is_empty bf idx then 36 | is_max new_bf idx 37 | else 38 | slot_value new_bf idx = U8.(slot_value bf idx -^ 1uy) 39 | end 40 | )) = () 41 | 42 | (**** Insert hash properties *) 43 | 44 | let increase_or_saturate (old_bf:bloom_storage_u8) (new_bf:bloom_storage_u8) 45 | (hash:u32) (idx:valid_index) = 46 | let idx1 = first_slot_index hash in let idx2 = second_slot_index hash in 47 | if is_max old_bf idx then 48 | is_max new_bf idx 49 | else if idx1 <> idx2 then 50 | if idx = idx1 || idx = idx2 then 51 | slot_value new_bf idx = U8.(slot_value old_bf idx +^ 1uy) 52 | else slot_value new_bf idx = slot_value old_bf idx 53 | else if idx = idx1 then 54 | if is_almost_max old_bf idx1 then is_max new_bf idx1 55 | else slot_value new_bf idx = U8.(slot_value old_bf idx +^ 2uy) 56 | else slot_value new_bf idx = slot_value old_bf idx 57 | 58 | let insert_hash_lemma (old_bf: bloom_storage_u8) (hash:u32) (idx:valid_index) 59 | : Lemma (ensures ( 60 | let idx1 = first_slot_index hash in let idx2 = second_slot_index hash in 61 | let new_bf = insert_hash old_bf hash in 62 | increase_or_saturate old_bf new_bf hash idx /\ 63 | might_contain_hash new_bf hash 64 | )) 65 | = () 66 | 67 | (**** Remove hash properties *) 68 | 69 | let is_almost_min (bf:bloom_storage_u8) (idx:valid_index) : Tot bool = 70 | slot_value bf idx = 1uy 71 | 72 | let decrease_or_saturate (old_bf:bloom_storage_u8) (new_bf:bloom_storage_u8) 73 | (hash:u32) (idx:valid_index) = 74 | let idx1 = first_slot_index hash in let idx2 = second_slot_index hash in 75 | if is_max old_bf idx then 76 | is_max new_bf idx 77 | else if idx1 <> idx2 then 78 | if idx = idx1 || idx = idx2 then 79 | if slot_is_empty old_bf idx then 80 | is_max new_bf idx 81 | else 82 | slot_value new_bf idx = U8.(slot_value old_bf idx -^ 1uy) 83 | else slot_value new_bf idx = slot_value old_bf idx 84 | else if idx = idx1 then 85 | if is_almost_min old_bf idx || slot_is_empty old_bf idx then is_max new_bf idx 86 | else slot_value new_bf idx = U8.(slot_value old_bf idx -^ 2uy) 87 | else slot_value new_bf idx = slot_value old_bf idx 88 | 89 | let remove_hash_lemma (bf: bloom_storage_u8) (hash:u32) (idx:valid_index) 90 | : Lemma (ensures ( 91 | let new_bf = remove_hash bf hash in 92 | let idx1 = first_slot_index hash in 93 | let idx2 = second_slot_index hash in 94 | decrease_or_saturate bf new_bf hash idx 95 | )) 96 | = () 97 | 98 | (**** Invariants *) 99 | 100 | let element_invalidation (bf: bloom_filter) (e:element) = 101 | not (might_contain_hash bf.storage (hash e)) ==> not (contains bf.ghost_state.elements e) 102 | 103 | #reset-options "--max_fuel 1" 104 | 105 | let rec compute_count_sum (idx:valid_index) (l:count_list element) : Tot count (decreases l) = 106 | match l with 107 | | [] -> 0 108 | | (e, count)::tl -> 109 | let sum = compute_count_sum idx tl in 110 | let sum = if first_slot_index (hash e) = idx then sum + count else sum in 111 | if second_slot_index (hash e) = idx then sum + count else sum 112 | 113 | let all_elements_counts (bf:bloom_filter) (idx:valid_index) : Tot count = 114 | compute_count_sum idx bf.ghost_state.elements 115 | 116 | #reset-options "--max_fuel 0" 117 | 118 | let count_invariant_property (bf:bloom_filter) (idx:valid_index) : Tot bool = 119 | if is_max bf.storage idx then true else 120 | all_elements_counts bf idx = U8.v (slot_value bf.storage idx) 121 | 122 | let count_invariant (bf:bloom_filter) (idx:valid_index) = 123 | count_invariant_property bf idx 124 | 125 | #reset-options "--max_fuel 1 --z3rlimit 20" 126 | 127 | let rec count_sum_component_lemma1 (l:count_list element) (e':element) (idx:valid_index) 128 | : Lemma (requires ( 129 | let idx1 = first_slot_index (hash e') in let idx2 = second_slot_index (hash e') in 130 | idx = idx1 \/ idx = idx2 131 | )) (ensures (compute_count_sum idx l >= element_count l e')) = match l with 132 | | [] -> () 133 | | (e'', count)::tl -> count_sum_component_lemma1 tl e' idx 134 | 135 | let rec count_sum_component_lemma2 (l:count_list element) (idx:valid_index) 136 | : Lemma (requires (compute_count_sum idx l > 0)) 137 | (ensures (exists (e:element). 138 | (idx = first_slot_index (hash e) \/ idx = second_slot_index (hash e)) /\ 139 | element_count l e > 0 140 | )) 141 | = match l with 142 | | [] -> () 143 | | (e, count)::tl -> 144 | if (idx = first_slot_index (hash e) || idx = second_slot_index (hash e)) && count > 0 then 145 | () 146 | else count_sum_component_lemma2 tl idx 147 | 148 | let hash_collision (bf: bloom_filter) (e:element) = 149 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 150 | might_contain_hash bf.storage (hash e) ==> (contains bf.ghost_state.elements e \/ 151 | (is_max bf.storage idx1 \/ is_max bf.storage idx2) \/ 152 | (exists (e':element{e' <> e}). 153 | let idx1' = first_slot_index (hash e') in let idx2' = second_slot_index (hash e') in 154 | (idx1 = idx1' \/ idx1 = idx2' \/ idx2 = idx1' \/ idx2 = idx2')) 155 | ) 156 | 157 | (**** New bloom filter properties *) 158 | 159 | (***** Element invalidation *) 160 | 161 | let new_bf_element_invalidation_lemma (e':element) 162 | : Lemma (ensures (element_invalidation (new_bloom_filter ()) e')) 163 | = () 164 | 165 | (***** Count invariant *) 166 | 167 | let new_bf_count_invariant_lemma (idx:valid_index) 168 | : Lemma (ensures (count_invariant_property (new_bloom_filter ()) idx)) 169 | = () 170 | 171 | (***** Hash collision *) 172 | 173 | let new_bf_hash_collision_lemma (e:element) 174 | : Lemma (ensures (hash_collision (new_bloom_filter ()) e)) 175 | = () 176 | 177 | (**** Insert element properties *) 178 | 179 | (***** Element invalidation *) 180 | 181 | let insert_element_element_invalidation_lemma (bf:bloom_filter) (e:element) (e':element) 182 | : Lemma (requires (element_invalidation bf e')) 183 | (ensures (element_invalidation (insert_element bf e) e')) 184 | = let new_bf = insert_element bf e in 185 | if contains new_bf.ghost_state.elements e' then 186 | if e <> e' then () else () 187 | else () 188 | 189 | 190 | (***** Count invariant *) 191 | 192 | let rec insert_element_compute_count_sum_lemma1 (l:count_list element) (e:element) (idx:valid_index) 193 | : Lemma (requires ( 194 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 195 | idx1 <> idx2 /\ (idx = idx1 \/ idx = idx2) 196 | )) 197 | (ensures (let new_bf = spec_insert_element ({elements = l}) e in 198 | compute_count_sum idx new_bf.elements = compute_count_sum idx l + 1 199 | )) (decreases l) = match l with 200 | | [] -> () 201 | | (e', count)::tl -> insert_element_compute_count_sum_lemma1 tl e idx 202 | 203 | let insert_element_all_elements_counts_lemma1 (bf:bloom_filter) (e:element) 204 | : Lemma (requires (first_slot_index (hash e) <> second_slot_index (hash e))) 205 | (ensures (let new_bf = insert_element bf e in 206 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 207 | all_elements_counts new_bf idx1 = all_elements_counts bf idx1 + 1 && 208 | all_elements_counts new_bf idx2 = all_elements_counts bf idx2 + 1 209 | )) 210 | = let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 211 | insert_element_compute_count_sum_lemma1 bf.ghost_state.elements e idx1; 212 | insert_element_compute_count_sum_lemma1 bf.ghost_state.elements e idx2 213 | 214 | let rec insert_element_compute_count_sum_lemma2 (l:count_list element) (e:element) (idx:valid_index) 215 | : Lemma (requires ( 216 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 217 | idx1 = idx2 /\ idx = idx1 218 | )) 219 | (ensures (let new_bf = spec_insert_element ({elements = l}) e in 220 | compute_count_sum idx new_bf.elements = compute_count_sum idx l + 2 221 | )) (decreases l) = match l with 222 | | [] -> () 223 | | (e', count)::tl -> insert_element_compute_count_sum_lemma2 tl e idx 224 | 225 | let insert_element_all_elements_counts_lemma2 (bf:bloom_filter) (e:element) 226 | : Lemma (requires (first_slot_index (hash e) = second_slot_index (hash e))) 227 | (ensures (let new_bf = insert_element bf e in 228 | let idx = first_slot_index (hash e) in 229 | all_elements_counts new_bf idx = all_elements_counts bf idx + 2 230 | )) 231 | = insert_element_compute_count_sum_lemma2 bf.ghost_state.elements e (first_slot_index (hash e)) 232 | 233 | let rec insert_element_compute_count_sum_lemma3 (l:count_list element) (e:element) (idx:valid_index) 234 | : Lemma (requires ( 235 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 236 | idx <> idx1 /\ idx <> idx2 237 | )) 238 | (ensures (let new_bf = spec_insert_element ({elements = l}) e in 239 | compute_count_sum idx new_bf.elements = compute_count_sum idx l 240 | )) (decreases l) = match l with 241 | | [] -> () 242 | | (e', count)::tl -> insert_element_compute_count_sum_lemma3 tl e idx 243 | 244 | let insert_element_all_elements_counts_lemma3 (bf:bloom_filter) (e:element) (idx:valid_index) 245 | : Lemma (requires ( 246 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 247 | idx <> idx1 /\ idx <> idx2 248 | )) (ensures (let new_bf = insert_element bf e in 249 | all_elements_counts new_bf idx = all_elements_counts bf idx 250 | )) 251 | = insert_element_compute_count_sum_lemma3 bf.ghost_state.elements e idx 252 | 253 | let insert_element_count_invariant_lemma_prelim 254 | (bf:bloom_filter) (e:element) (idx:valid_index) 255 | : Lemma (requires (not (is_max bf.storage idx) /\ 256 | all_elements_counts bf idx = U8.v (slot_value bf.storage idx))) 257 | (ensures (let new_bf = insert_element bf e in 258 | not (is_max new_bf.storage idx) ==> 259 | all_elements_counts new_bf idx = U8.v (slot_value new_bf.storage idx) 260 | )) 261 | = insert_hash_lemma bf.storage (hash e) idx; 262 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 263 | let new_bf = insert_element bf e in 264 | if idx1 <> idx2 then 265 | if idx = idx1 || idx = idx2 then 266 | if is_max new_bf.storage idx then () else insert_element_all_elements_counts_lemma1 bf e 267 | else insert_element_all_elements_counts_lemma3 bf e idx 268 | else if idx = idx1 then 269 | if is_almost_max bf.storage idx1 then () 270 | else insert_element_all_elements_counts_lemma2 bf e 271 | else insert_element_all_elements_counts_lemma3 bf e idx 272 | 273 | let insert_element_count_invariant_lemma (bf:bloom_filter) (e:element) (idx:valid_index) 274 | : Lemma (requires (count_invariant bf idx)) 275 | (ensures (count_invariant (insert_element bf e) idx)) 276 | = if is_max bf.storage idx then () else insert_element_count_invariant_lemma_prelim bf e idx 277 | 278 | (***** Hash collision *) 279 | 280 | let insert_element_hash_collision_lemma (bf:bloom_filter) (e e':element) 281 | : Lemma (requires (hash_collision bf e')) (ensures (hash_collision (insert_element bf e) e')) 282 | = () 283 | 284 | (**** Remove element properties *) 285 | 286 | (***** Count invariant *) 287 | 288 | let rec remove_element_compute_count_sum_lemma1 (l:count_list element) (e:element) (idx:valid_index) 289 | : Lemma (requires ( 290 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 291 | idx1 <> idx2 /\ (idx = idx1 \/ idx = idx2) /\ contains l e 292 | )) 293 | (ensures (let new_bf = spec_remove_element ({elements = l}) e in 294 | compute_count_sum idx new_bf.elements = compute_count_sum idx l - 1 295 | )) (decreases l) = match l with 296 | | [] -> () 297 | | (e', count)::tl -> if e = e' then () else remove_element_compute_count_sum_lemma1 tl e idx 298 | 299 | let remove_element_all_elements_counts_lemma1 (bf:bloom_filter) (e:element) 300 | : Lemma (requires ( 301 | first_slot_index (hash e) <> second_slot_index (hash e) /\ contains bf.ghost_state.elements e 302 | )) (ensures (let new_bf = remove_element bf e in 303 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 304 | all_elements_counts new_bf idx1 = all_elements_counts bf idx1 - 1 && 305 | all_elements_counts new_bf idx2 = all_elements_counts bf idx2 - 1 306 | )) 307 | = let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 308 | remove_element_compute_count_sum_lemma1 bf.ghost_state.elements e idx1; 309 | remove_element_compute_count_sum_lemma1 bf.ghost_state.elements e idx2 310 | 311 | let rec remove_element_compute_count_sum_lemma2 (l:count_list element) (e:element) (idx:valid_index) 312 | : Lemma (requires ( 313 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 314 | idx1 = idx2 /\ idx = idx1 /\ contains l e 315 | )) 316 | (ensures (let new_bf = spec_remove_element ({elements = l}) e in 317 | compute_count_sum idx new_bf.elements = compute_count_sum idx l - 2 318 | )) (decreases l) = if not (contains l e ) then () else match l with 319 | | [] -> () 320 | | (e', count)::tl -> if e' = e then () else remove_element_compute_count_sum_lemma2 tl e idx 321 | 322 | let remove_element_all_elements_counts_lemma2 (bf:bloom_filter) (e:element) 323 | : Lemma (requires ( 324 | first_slot_index (hash e) = second_slot_index (hash e) /\ contains bf.ghost_state.elements e 325 | )) (ensures (let new_bf = remove_element bf e in let idx = first_slot_index (hash e) in 326 | all_elements_counts new_bf idx = all_elements_counts bf idx - 2 327 | )) 328 | = remove_element_compute_count_sum_lemma2 bf.ghost_state.elements e (first_slot_index (hash e)) 329 | 330 | let rec remove_element_compute_count_sum_lemma3 (l:count_list element) (e:element) (idx:valid_index) 331 | : Lemma (requires ( 332 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 333 | idx <> idx1 /\ idx <> idx2 /\ contains l e 334 | )) 335 | (ensures (let new_bf = spec_remove_element ({elements = l}) e in 336 | compute_count_sum idx new_bf.elements = compute_count_sum idx l 337 | )) (decreases l) = match l with 338 | | [] -> () 339 | | (e', count)::tl -> if e = e' then () else remove_element_compute_count_sum_lemma3 tl e idx 340 | 341 | let remove_element_all_elements_counts_lemma3 (bf:bloom_filter) (e:element) (idx:valid_index) 342 | : Lemma (requires ( 343 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 344 | idx <> idx1 /\ idx <> idx2 /\ contains bf.ghost_state.elements e 345 | )) (ensures (let new_bf = remove_element bf e in 346 | all_elements_counts new_bf idx = all_elements_counts bf idx 347 | )) 348 | = remove_element_compute_count_sum_lemma3 bf.ghost_state.elements e idx 349 | 350 | #reset-options "--max_fuel 0 --z3rlimit 40" 351 | 352 | let remove_element_count_invariant_lemma_prelim 353 | (bf:bloom_filter) (e:element) (idx:valid_index) 354 | : Lemma (requires ( 355 | not (is_max bf.storage idx) /\ all_elements_counts bf idx = U8.v (slot_value bf.storage idx) /\ 356 | contains bf.ghost_state.elements e 357 | )) 358 | (ensures (let new_bf = remove_element bf e in 359 | not (is_max new_bf.storage idx) ==> 360 | all_elements_counts new_bf idx = U8.v (slot_value new_bf.storage idx) 361 | )) 362 | = insert_hash_lemma bf.storage (hash e) idx; 363 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 364 | let new_bf = remove_element bf e in 365 | if idx1 <> idx2 then 366 | if idx = idx1 || idx = idx2 then 367 | if slot_is_empty bf.storage idx then () 368 | else if is_max bf.storage idx then () 369 | else remove_element_all_elements_counts_lemma1 bf e 370 | else remove_element_all_elements_counts_lemma3 bf e idx 371 | else if idx = idx1 then 372 | if slot_is_empty bf.storage idx1 then () 373 | else if is_almost_min bf.storage idx1 then () 374 | else remove_element_all_elements_counts_lemma2 bf e 375 | else remove_element_all_elements_counts_lemma3 bf e idx 376 | 377 | #reset-options "--z3rlimit 5" 378 | 379 | let remove_element_count_invariant_lemma (bf:bloom_filter) (e:element) (idx:valid_index) 380 | : Lemma (requires (contains bf.ghost_state.elements e /\ count_invariant bf idx)) 381 | (ensures (let new_bf = remove_element bf e in count_invariant new_bf idx)) 382 | = if is_max bf.storage idx then () else remove_element_count_invariant_lemma_prelim bf e idx 383 | 384 | (***** Element invalidation *) 385 | 386 | #reset-options "--max_fuel 1" 387 | 388 | let rec remove_element_same_count_lemma (bf:bloom_filter) (e e':element) 389 | : Lemma (requires (e <> e' /\ contains bf.ghost_state.elements e)) 390 | (ensures (let new_bf = remove_element bf e in 391 | element_count bf.ghost_state.elements e' = element_count new_bf.ghost_state.elements e' 392 | )) (decreases bf.ghost_state.elements) 393 | = match bf.ghost_state.elements with 394 | | [] -> () 395 | | (e'', old_count)::tl -> if e'' = e then () else remove_element_same_count_lemma 396 | ({bf with ghost_state = { bf.ghost_state with elements = tl} }) e e' 397 | 398 | #reset-options "--max_fuel 0" 399 | 400 | let remove_element_element_invalidation_prelim_lemma1 401 | (bf:bloom_filter) (e e':element) (idx:valid_index) 402 | : Lemma (requires ( 403 | (idx = first_slot_index (hash e') \/ idx = second_slot_index (hash e')) /\ 404 | contains bf.ghost_state.elements e /\ contains bf.ghost_state.elements e' /\ 405 | e <> e' /\ count_invariant bf idx /\ U8.(slot_value bf.storage idx >^ 0uy) 406 | )) (ensures ( 407 | let new_bf = remove_element bf e in U8.(slot_value new_bf.storage idx >^ 0uy) 408 | )) = let new_bf = remove_element bf e in 409 | remove_element_count_invariant_lemma bf e idx; 410 | count_sum_component_lemma1 bf.ghost_state.elements e' idx; 411 | remove_element_same_count_lemma bf e e'; 412 | count_sum_component_lemma1 new_bf.ghost_state.elements e' idx 413 | 414 | let remove_element_element_invalidation_prelim_lemma2 (bf:bloom_filter) (e:element) 415 | : Lemma (requires ( 416 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 417 | element_count bf.ghost_state.elements e > 1 /\ count_invariant bf idx1 /\ 418 | count_invariant bf idx2 419 | )) (ensures (let new_bf = remove_element bf e in 420 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 421 | U8.(slot_value new_bf.storage idx1 >^ 0uy) /\ U8.(slot_value new_bf.storage idx2 >^ 0uy) 422 | )) = let new_bf = remove_element bf e in 423 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 424 | remove_element_count_invariant_lemma bf e idx1;remove_element_count_invariant_lemma bf e idx2; 425 | count_sum_component_lemma1 new_bf.ghost_state.elements e idx1; 426 | count_sum_component_lemma1 new_bf.ghost_state.elements e idx2 427 | 428 | let remove_element_element_invalidation_lemma (bf:bloom_filter) (e e':element) 429 | : Lemma (requires ( 430 | element_invalidation bf e' /\ count_invariant bf (first_slot_index (hash e')) /\ 431 | count_invariant bf (second_slot_index (hash e')) /\ contains bf.ghost_state.elements e 432 | )) 433 | (ensures (element_invalidation (remove_element bf e) e')) = 434 | let new_bf = remove_element bf e in 435 | if contains new_bf.ghost_state.elements e' then begin 436 | if e <> e' then begin 437 | let idx1 = first_slot_index (hash e') in let idx2 = second_slot_index (hash e') in 438 | remove_element_element_invalidation_prelim_lemma1 bf e e' idx1; 439 | remove_element_element_invalidation_prelim_lemma1 bf e e' idx2 440 | end else if element_count bf.ghost_state.elements e > 1 then begin 441 | remove_element_element_invalidation_prelim_lemma2 bf e 442 | end else () 443 | end else () 444 | 445 | (***** Hash collision *) 446 | 447 | #reset-options "--z3rlimit 20" 448 | 449 | let remove_element_hash_collision_prelim_lemma1 (bf:bloom_filter) (e:element) (idx:valid_index) 450 | : Lemma (requires ( 451 | (idx = first_slot_index (hash e) \/ idx = second_slot_index (hash e)) /\ 452 | U8.(slot_value bf.storage idx >^ 0uy) /\ not (is_max bf.storage idx) /\ 453 | count_invariant bf idx /\ 454 | not (contains bf.ghost_state.elements e) 455 | )) (ensures (exists (e':element{e' <> e}). 456 | let idx1' = first_slot_index (hash e') in let idx2' = second_slot_index (hash e') in 457 | idx = idx1' \/ idx = idx2' 458 | )) = count_sum_component_lemma2 bf.ghost_state.elements idx 459 | 460 | let remove_element_hash_collision_lemma (bf:bloom_filter) (e e':element) 461 | : Lemma (requires (count_invariant bf (first_slot_index (hash e')) /\ 462 | count_invariant bf (second_slot_index (hash e')) /\ hash_collision bf e' /\ 463 | contains bf.ghost_state.elements e)) 464 | (ensures (hash_collision (remove_element bf e) e')) 465 | = let new_bf = remove_element bf e in 466 | if not (might_contain_hash new_bf.storage (hash e')) then () else 467 | let idx1 = first_slot_index (hash e) in let idx2 = second_slot_index (hash e) in 468 | let idx1' = first_slot_index (hash e') in let idx2' = second_slot_index (hash e') in 469 | assert ( 470 | U8.(slot_value new_bf.storage idx1' >^ 0uy) \/ U8.(slot_value new_bf.storage idx2' >^ 0uy) 471 | ); 472 | if contains new_bf.ghost_state.elements e' then () else 473 | if U8.(slot_value new_bf.storage idx1' >^ 0uy) then 474 | if is_max new_bf.storage idx1' then () else begin 475 | remove_element_count_invariant_lemma bf e idx1'; 476 | remove_element_hash_collision_prelim_lemma1 new_bf e' idx1' 477 | end else if U8.(slot_value new_bf.storage idx2' >^ 0uy) then 478 | if is_max new_bf.storage idx2' then () else begin 479 | remove_element_count_invariant_lemma bf e idx2'; 480 | remove_element_hash_collision_prelim_lemma1 new_bf e' idx2' 481 | end else () 482 | 483 | #reset-options "--z3rlimit 5" 484 | 485 | (**** Final displayable properties *) 486 | 487 | (* 488 | This is the full invariant that is preserved by each function manipulating the bloom filter. 489 | It specifies what means the value returned by might_contain_hash. If it returns no, that means 490 | the element is truly not contained in the bloom filter. If it returns yes, then three cases arise: 491 | - either the bloom filter truly contains the element; 492 | - either one of the slot indexes corresponding to the element's hash have been saturated; 493 | - or there exists a hash collision with another element. 494 | The third component of the invariant is a deep specification of the link between the values of 495 | the slots of the arrays and the number of times you've inserted the corresponding elements in the 496 | bloom filter. It is not directly useful from a client's perspective but necessary for the proof. 497 | *) 498 | let valid_bf (bf:bloom_filter) = (forall (e':element). element_invalidation bf e') /\ 499 | (forall (e':element). hash_collision bf e') /\ 500 | (forall (idx:valid_index). count_invariant_property bf idx) 501 | 502 | let new_bf_correctness () : Lemma (ensures (valid_bf (new_bloom_filter ()))) = 503 | let new_bf = new_bloom_filter () in 504 | let f (idx:valid_index) : Lemma (ensures (count_invariant new_bf idx)) = 505 | new_bf_count_invariant_lemma idx 506 | in Classical.forall_intro f; 507 | let g (e':element) : Lemma (ensures (element_invalidation new_bf e')) = 508 | new_bf_element_invalidation_lemma e' 509 | in Classical.forall_intro g; 510 | let h (e':element) : Lemma (ensures (hash_collision new_bf e')) = 511 | new_bf_hash_collision_lemma e' 512 | in Classical.forall_intro h 513 | 514 | let insert_element_corectness (bf:bloom_filter) (e:element) 515 | : Lemma (requires (valid_bf bf)) (ensures (valid_bf (insert_element bf e))) = 516 | let new_bf = insert_element bf e in 517 | let f (idx:valid_index) : Lemma (ensures (count_invariant new_bf idx)) = 518 | insert_element_count_invariant_lemma bf e idx 519 | in Classical.forall_intro f; 520 | let g (e':element) : Lemma (ensures (element_invalidation new_bf e')) = 521 | insert_element_element_invalidation_lemma bf e e' 522 | in Classical.forall_intro g; 523 | let h (e':element) : Lemma (ensures (hash_collision new_bf e')) = 524 | insert_element_hash_collision_lemma bf e e' 525 | in Classical.forall_intro h 526 | 527 | let remove_element_corectness (bf:bloom_filter) (e:element) 528 | : Lemma (requires (valid_bf bf /\ contains bf.ghost_state.elements e)) 529 | (ensures (valid_bf (remove_element bf e))) = 530 | let new_bf = remove_element bf e in 531 | let f (idx:valid_index) : Lemma (ensures (count_invariant new_bf idx)) = 532 | remove_element_count_invariant_lemma bf e idx 533 | in Classical.forall_intro f; 534 | let g (e':element) : Lemma (ensures (element_invalidation new_bf e')) = 535 | remove_element_element_invalidation_lemma bf e e' 536 | in Classical.forall_intro g; 537 | let h (e':element) : Lemma (ensures (hash_collision new_bf e')) = 538 | remove_element_hash_collision_lemma bf e e' 539 | in Classical.forall_intro h 540 | -------------------------------------------------------------------------------- /bloom-filter/src-fstar/Spec.BloomFilter.fst: -------------------------------------------------------------------------------- 1 | module Spec.BloomFilter 2 | 3 | module U64 = FStar.UInt64 4 | module Usize = FStar.UInt32 5 | module U32 = FStar.UInt32 6 | module U8 = FStar.UInt8 7 | module I64 = FStar.Int64 8 | module Isize = FStar.Int32 9 | module I32 = FStar.Int32 10 | 11 | open Rust 12 | open BloomFilter 13 | open FStar.List.Tot.Base 14 | 15 | assume type element: eqtype 16 | 17 | assume val hash: element -> u32 18 | 19 | type count = n:nat 20 | 21 | #reset-options "--max_fuel 1" 22 | 23 | let contains (#a:eqtype) (l:list (a & count)) (e:a) : Tot bool = 24 | existsb (fun ((e', n) : a & count) -> e = e' && n > 0) l 25 | 26 | let rec no_duplicates (#a:eqtype) (l:list (a & count)) : Tot bool (decreases l) = 27 | match l with 28 | | [] -> true 29 | | (e, _)::tl -> not (contains tl e) && no_duplicates tl 30 | 31 | type count_list (a:eqtype) = (l:list (a & count){no_duplicates l}) 32 | 33 | let same_elements (#a: eqtype) (l1 l2: count_list a) = 34 | (forall (e:a). contains l1 e <==> contains l2 e) 35 | 36 | val element_count: #a:eqtype -> 37 | l:count_list a -> 38 | e: a -> 39 | Tot (c:count{c > 0 <==> contains #a l e}) (decreases l) 40 | let rec element_count #a l e = match l with 41 | | [] -> 0 42 | | (e', count)::tl -> 43 | if e = e' then count else element_count tl e 44 | 45 | val incr_count: 46 | #a:eqtype -> 47 | l:count_list a -> 48 | e : a{contains l e} -> 49 | Tot (l':count_list a{same_elements l l'}) (decreases l) 50 | let rec incr_count #a l e = 51 | match l with 52 | | [] -> l 53 | | (e', old_count)::tl -> 54 | if e = e' then 55 | (e', old_count + 1)::tl 56 | else 57 | (e', old_count)::(incr_count tl e) 58 | 59 | let same_elements_except (#a:eqtype) (l1 l2: count_list a) (e:a) = 60 | (forall (e':a{e' <> e}). contains l1 e' <==> contains l2 e') 61 | 62 | val decr_count: 63 | #a:eqtype -> 64 | l:count_list a -> 65 | e : a{contains l e} -> 66 | Tot (l':count_list a{ 67 | if element_count l e > 1 then 68 | same_elements l l' /\ element_count l' e = element_count l e - 1 69 | else 70 | same_elements_except l l' e /\ not (contains l' e) 71 | }) (decreases l) 72 | let rec decr_count #a l e = 73 | match l with 74 | | [] -> l 75 | | (e', old_count)::tl -> 76 | if e = e' then 77 | (e', old_count - 1)::tl 78 | else 79 | (e', old_count)::(decr_count tl e) 80 | 81 | type spec_bloom_storage_u8 = { 82 | elements: (l:(count_list element){no_duplicates l}) 83 | } 84 | 85 | val spec_insert_element: 86 | bf:spec_bloom_storage_u8 -> 87 | e:element -> 88 | Tot spec_bloom_storage_u8 89 | let spec_insert_element bf e = 90 | if contains bf.elements e then 91 | { bf with elements = incr_count bf.elements e } 92 | else 93 | { bf with elements = (e,1)::bf.elements } 94 | 95 | 96 | val spec_remove_element: 97 | bf:spec_bloom_storage_u8 -> 98 | e:element{contains bf.elements e} -> 99 | Tot spec_bloom_storage_u8 100 | let spec_remove_element bf e = 101 | { bf with elements = decr_count bf.elements e } 102 | 103 | type bloom_filter = { 104 | storage: bloom_storage_u8; 105 | ghost_state: spec_bloom_storage_u8 106 | } 107 | 108 | val new_bloom_filter: unit -> Tot bloom_filter 109 | let new_bloom_filter () = { 110 | storage = bloom_storage_u8_new (); 111 | ghost_state = { elements = [] } 112 | } 113 | 114 | val insert_element: bf:bloom_filter -> e:element -> Tot bloom_filter 115 | let insert_element bf e = 116 | let hash_val = hash e in 117 | (* *) let new_bf = { bf with ghost_state = spec_insert_element bf.ghost_state e } in 118 | { new_bf with storage = insert_hash new_bf.storage hash_val } 119 | 120 | val remove_element: 121 | bf:bloom_filter -> 122 | e:element{contains bf.ghost_state.elements e} -> 123 | Tot bloom_filter 124 | let remove_element bf e = 125 | let hash_e = hash e in 126 | (* *) let new_bf = { bf with ghost_state = spec_remove_element bf.ghost_state e } in 127 | { new_bf with storage = remove_hash new_bf.storage hash_e } 128 | -------------------------------------------------------------------------------- /bloom-filter/src-fstar/Test.BloomFilter.fst: -------------------------------------------------------------------------------- 1 | module Test.BloomFilter 2 | 3 | open Rust 4 | open BloomFilter 5 | open Spec.BloomFilter 6 | 7 | module U64 = FStar.UInt64 8 | module Usize = FStar.UInt32 9 | module U32 = FStar.UInt32 10 | module U8 = FStar.UInt8 11 | module I64 = FStar.Int64 12 | module Isize = FStar.Int32 13 | module I32 = FStar.Int32 14 | 15 | 16 | let hash (i:usize) : All.ML u32 = 17 | let hash : u32 = i in 18 | let hash = U32.(hash ^^ (hash <<^ U32.uint_to_t 12)) in 19 | let hash = U32.(hash ^^ (0xa5d8f52cul >>^ (i %^ 0xfful)) +%^ (hash &^ 0x589dcul)) in 20 | let hash = U32.(hash -%^ (hash |^ (hash <<^ U32.uint_to_t 4))) in 21 | hash 22 | 23 | let print_bool (b:bool) : All.ML unit = 24 | if b then IO.print_string "true" else IO.print_string "false" 25 | 26 | let test () : All.ML unit = 27 | IO.print_string "Beginning bloom filter test.\n"; 28 | let bf = new_bloom_filter () in 29 | let bf = { bf with storage = iter_range_ml 0ul 1000ul (fun i bf -> 30 | let bf = insert_hash bf (hash i) in 31 | bf 32 | ) bf.storage } in 33 | iter_range_ml 0ul 1000ul (fun i () -> 34 | assert_eq #bool "1" 35 | print_bool 36 | (might_contain_hash bf.storage (hash i)) 37 | true 38 | ) (); 39 | let bf = { bf with storage = iter_range_ml 0ul 100ul (fun i bf -> 40 | let bf = remove_hash bf (hash i) in 41 | bf 42 | ) bf.storage } in 43 | iter_range_ml 100ul 1000ul (fun i () -> 44 | assert_eq #bool "2" 45 | print_bool 46 | (might_contain_hash bf.storage (hash i)) 47 | true 48 | ) (); 49 | IO.print_string "End of test. If nothing appeared then it passed !\n" 50 | 51 | 52 | let _ = test () 53 | -------------------------------------------------------------------------------- /bloom-filter/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// This code is inspired by Servo's bloom filter implementation 2 | /// contained in the file 3 | /// [`servo/components/selector/bloom.rs`](https://github.com/servo/servo/blob/master/components/selectors/bloom.rs) 4 | 5 | const KEY_SIZE : usize = 12; 6 | const ARRAY_SIZE : usize = 1 << KEY_SIZE; 7 | const KEY_MASK : u32 = (1 << KEY_SIZE) - 1; 8 | 9 | pub struct BloomStorageU8 { 10 | counters: [u8; ARRAY_SIZE], 11 | } 12 | 13 | #[allow(dead_code)] 14 | fn valid_index(i: usize) -> bool { 15 | i < ARRAY_SIZE 16 | } 17 | 18 | #[inline] 19 | fn hash1(hash: u32) -> u32 { 20 | hash & KEY_MASK 21 | } 22 | 23 | #[inline] 24 | fn hash2(hash: u32) -> u32 { 25 | (hash >> KEY_SIZE) & KEY_MASK 26 | } 27 | 28 | impl Default for BloomStorageU8 { 29 | fn default() -> Self { 30 | BloomStorageU8 { 31 | counters: [0; ARRAY_SIZE], 32 | } 33 | } 34 | } 35 | 36 | 37 | impl BloomStorageU8 { 38 | 39 | #[inline] 40 | fn first_slot_index(hash: u32) -> usize { 41 | hash1(hash) as usize 42 | } 43 | 44 | #[inline] 45 | fn second_slot_index(hash: u32) -> usize { 46 | hash2(hash) as usize 47 | } 48 | 49 | //#[requires (| index | => valid_index(index))] 50 | #[inline] 51 | fn slot_value(&self, index: usize) -> u8 { 52 | self.counters[index] 53 | } 54 | 55 | //#[requires (| index | => valid_index(index))] 56 | #[inline] 57 | fn slot_is_empty(&self, index: usize) -> bool { 58 | self.slot_value(index) == 0.into() 59 | } 60 | 61 | #[inline] 62 | fn first_slot_is_empty(&self, hash: u32) -> bool { 63 | self.slot_is_empty(Self::first_slot_index(hash)) 64 | } 65 | 66 | #[inline] 67 | fn second_slot_is_empty(&self, hash: u32) -> bool { 68 | self.slot_is_empty(Self::second_slot_index(hash)) 69 | } 70 | 71 | /// Check whether the filter might contain an item with the given hash. 72 | /// This can sometimes return true even if the item is not in the filter, 73 | /// but will never return false for items that are actually in the filter. 74 | #[inline] 75 | pub fn might_contain_hash(&self, hash: u32) -> bool { 76 | !self.first_slot_is_empty(hash) && !self.second_slot_is_empty(hash) 77 | } 78 | 79 | //#[requires (| index | => valid_index(index))] 80 | #[inline] 81 | fn adjust_slot(&mut self, index: usize, increment: bool) { 82 | let slot = &mut self.counters[index]; 83 | if *slot != 0xff.into() { 84 | // full 85 | if increment { 86 | *slot += 1; 87 | } else { 88 | *slot -= 1; 89 | } 90 | } 91 | } 92 | 93 | #[inline] 94 | fn adjust_first_slot(&mut self, hash: u32, increment: bool) { 95 | self.adjust_slot(Self::first_slot_index(hash), increment) 96 | } 97 | 98 | #[inline] 99 | fn adjust_second_slot(&mut self, hash: u32, increment: bool) { 100 | self.adjust_slot(Self::second_slot_index(hash), increment) 101 | } 102 | 103 | /// Inserts an item with a particular hash into the bloom filter. 104 | #[inline] 105 | pub fn insert_hash(&mut self, hash: u32) { 106 | self.adjust_first_slot(hash, true); 107 | self.adjust_second_slot(hash, true); 108 | } 109 | 110 | /// Removes an item with a particular hash from the bloom filter. 111 | #[inline] 112 | pub fn remove_hash(&mut self, hash: u32) { 113 | self.adjust_first_slot(hash, false); 114 | self.adjust_second_slot(hash, false); 115 | } 116 | 117 | /// Creates a new bloom filter. 118 | #[inline] 119 | pub fn new() -> Self { 120 | Default::default() 121 | } 122 | 123 | } 124 | 125 | #[test] 126 | fn create_and_insert_some_stuff() { 127 | use std::collections::hash_map::DefaultHasher; 128 | use std::hash::{Hash, Hasher}; 129 | 130 | fn hash_as_str(i: usize) -> u32 { 131 | let mut hasher = DefaultHasher::new(); 132 | let s = i.to_string(); 133 | s.hash(&mut hasher); 134 | let hash: u64 = hasher.finish(); 135 | (hash >> 32) as u32 ^ (hash as u32) 136 | } 137 | 138 | let mut bf = BloomStorageU8::new(); 139 | 140 | for i in 0_usize..1000 { 141 | bf.insert_hash(hash_as_str(i)); 142 | } 143 | 144 | for i in 0_usize..1000 { 145 | assert!(bf.might_contain_hash(hash_as_str(i))); 146 | } 147 | 148 | let false_positives = (1001_usize..2000) 149 | .filter(|i| bf.might_contain_hash(hash_as_str(*i))) 150 | .count(); 151 | 152 | assert!(false_positives < 190, "{} is not < 190", false_positives); // 19%. 153 | 154 | for i in 0_usize..100 { 155 | bf.remove_hash(hash_as_str(i)); 156 | } 157 | 158 | for i in 100_usize..1000 { 159 | assert!(bf.might_contain_hash(hash_as_str(i))); 160 | } 161 | 162 | let false_positives = (0_usize..100) 163 | .filter(|i| bf.might_contain_hash(hash_as_str(*i))) 164 | .count(); 165 | 166 | assert!(false_positives < 20, "{} is not < 20", false_positives); // 20%. 167 | 168 | } 169 | -------------------------------------------------------------------------------- /chacha20/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /chacha20/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chacha20" 3 | version = "0.1.0" 4 | authors = ["Denis Merigoux "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rox-star-lib = { path = "../rox-star-lib" } 9 | -------------------------------------------------------------------------------- /chacha20/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rox_star_lib; 2 | 3 | use rox_star_lib::*; 4 | 5 | const BLOCK_SIZE : usize = 64; 6 | type State = [U32; 16]; 7 | //#[refinement (|x| x.len() == 32)] 8 | type Key = Vec; 9 | //#[refinement (|x| x.len() == 12)] 10 | type Nonce = Vec; 11 | type Block = [U8;64]; 12 | type Constants = [u32;4]; 13 | type Index = usize; 14 | type RotVal = u32; 15 | 16 | fn line(a:Index, b:Index, d:Index, s:RotVal, m: &mut State) { 17 | m[a] = m[a] + m[b]; 18 | m[d] = m[d] ^ m[a]; 19 | m[d] = m[d].rotate_left(s); 20 | } 21 | 22 | 23 | fn quarter_round(a:Index, b:Index, c:Index, d:Index, m: &mut State) { 24 | line(a,b,d,16,m); 25 | line(c,d,b,12,m); 26 | line(a,b,d,8,m); 27 | line(c,d,b,7,m); 28 | } 29 | 30 | fn double_round(m: &mut State) { 31 | quarter_round(0,4,8,12,m); 32 | quarter_round(1,5,9,13,m); 33 | quarter_round(2,6,10,14,m); 34 | quarter_round(3,7,11,15,m); 35 | 36 | quarter_round(0,5,10,15,m); 37 | quarter_round(1,6,11,12,m); 38 | quarter_round(2,7,8,13,m); 39 | quarter_round(3,4,9,14,m); 40 | } 41 | 42 | const CONSTANTS: Constants = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; 43 | 44 | fn chacha20_init(k:&Key, counter:U32, nonce:&Nonce) -> State { 45 | let mut st = [U32::classify(0u32);16]; 46 | st[0..4].copy_from_slice(&classify_u32s(&CONSTANTS)); 47 | st[4..12].copy_from_slice(U32::from_bytes_le(k).as_slice()); 48 | st[12] = counter; 49 | st[13..16].copy_from_slice(U32::from_bytes_le(nonce).as_slice()); 50 | st 51 | } 52 | 53 | fn chacha20_core(st:&mut State) { 54 | let mut working_state = st.clone(); 55 | for _ in 0..10 { 56 | double_round(&mut working_state); 57 | } 58 | for i in 0..16 { 59 | st[i] += working_state[i]; 60 | } 61 | } 62 | 63 | fn chacha20(k:&Key, counter:U32, nonce:&Nonce) -> State { 64 | let mut st = chacha20_init(k, counter, nonce); 65 | chacha20_core(&mut st); 66 | st 67 | } 68 | 69 | fn chacha20_block(k:&Key, counter:U32, nonce:&Nonce) -> Block { 70 | let st = chacha20(k, counter, nonce); 71 | let mut block = [U8::classify(0u8);BLOCK_SIZE]; 72 | block.copy_from_slice(U32::to_bytes_le(&st).as_slice()); 73 | block 74 | } 75 | 76 | fn xor_block(block: &Block, key_block: &Block) -> Block { 77 | let v_out = fill(BLOCK_SIZE, &|i| block[i] ^ key_block[i]); 78 | let mut out = [U8::classify(0u8);BLOCK_SIZE]; 79 | out.copy_from_slice(v_out.as_slice()); 80 | out 81 | } 82 | 83 | fn chacha20_counter_mode(key: &Key, counter:U32, nonce:&Nonce, msg:&Vec) -> Vec { 84 | let mut blocks : Vec<[U8;BLOCK_SIZE]> = msg.chunks(BLOCK_SIZE).map(|block| { 85 | let mut new_block = [U8::classify(0u8);BLOCK_SIZE]; 86 | new_block[0..block.len()].copy_from_slice(block); 87 | new_block 88 | }).collect(); 89 | let nb_blocks = blocks.len(); 90 | let mut key_block : [U8; BLOCK_SIZE]; 91 | let mut ctr = counter; 92 | for i in 0..blocks.len() - 1 { 93 | key_block = chacha20_block(key, ctr, nonce); 94 | blocks[i] = xor_block(&blocks[i], &key_block); 95 | ctr += U32::classify(1u32); 96 | } 97 | let last = &mut blocks[nb_blocks - 1]; 98 | key_block = chacha20_block(key, ctr, nonce); 99 | *last = xor_block(last, &key_block); 100 | blocks.iter().map(|block| block.to_vec()).flatten().take(msg.len()).collect() 101 | } 102 | 103 | pub fn chacha20_encrypt(key: &Key, counter: U32, nonce:&Nonce, msg: &Vec) -> Vec { 104 | chacha20_counter_mode(key, counter, nonce, msg) 105 | } 106 | 107 | pub fn chacha20_decrypt(key: &Key, counter: U32, nonce:&Nonce, msg: &Vec) -> Vec { 108 | chacha20_counter_mode(key, counter, nonce, msg) 109 | } 110 | 111 | #[test] 112 | fn chacha20_test() { 113 | let plaintext = classify_u8s(&vec![ 114 | 0x4c,0x61,0x64,0x69,0x65,0x73,0x20,0x61, 115 | 0x6e,0x64,0x20,0x47,0x65,0x6e,0x74,0x6c, 116 | 0x65,0x6d,0x65,0x6e,0x20,0x6f,0x66,0x20, 117 | 0x74,0x68,0x65,0x20,0x63,0x6c,0x61,0x73, 118 | 0x73,0x20,0x6f,0x66,0x20,0x27,0x39,0x39, 119 | 0x3a,0x20,0x49,0x66,0x20,0x49,0x20,0x63, 120 | 0x6f,0x75,0x6c,0x64,0x20,0x6f,0x66,0x66, 121 | 0x65,0x72,0x20,0x79,0x6f,0x75,0x20,0x6f, 122 | 0x6e,0x6c,0x79,0x20,0x6f,0x6e,0x65,0x20, 123 | 0x74,0x69,0x70,0x20,0x66,0x6f,0x72,0x20, 124 | 0x74,0x68,0x65,0x20,0x66,0x75,0x74,0x75, 125 | 0x72,0x65,0x2c,0x20,0x73,0x75,0x6e,0x73, 126 | 0x63,0x72,0x65,0x65,0x6e,0x20,0x77,0x6f, 127 | 0x75,0x6c,0x64,0x20,0x62,0x65,0x20,0x69, 128 | 0x74,0x2e]); 129 | let ciphertext = classify_u8s(&vec![ 130 | 0x6e,0x2e,0x35,0x9a,0x25,0x68,0xf9,0x80, 131 | 0x41,0xba,0x07,0x28,0xdd,0x0d,0x69,0x81, 132 | 0xe9,0x7e,0x7a,0xec,0x1d,0x43,0x60,0xc2, 133 | 0x0a,0x27,0xaf,0xcc,0xfd,0x9f,0xae,0x0b, 134 | 0xf9,0x1b,0x65,0xc5,0x52,0x47,0x33,0xab, 135 | 0x8f,0x59,0x3d,0xab,0xcd,0x62,0xb3,0x57, 136 | 0x16,0x39,0xd6,0x24,0xe6,0x51,0x52,0xab, 137 | 0x8f,0x53,0x0c,0x35,0x9f,0x08,0x61,0xd8, 138 | 0x07,0xca,0x0d,0xbf,0x50,0x0d,0x6a,0x61, 139 | 0x56,0xa3,0x8e,0x08,0x8a,0x22,0xb6,0x5e, 140 | 0x52,0xbc,0x51,0x4d,0x16,0xcc,0xf8,0x06, 141 | 0x81,0x8c,0xe9,0x1a,0xb7,0x79,0x37,0x36, 142 | 0x5a,0xf9,0x0b,0xbf,0x74,0xa3,0x5b,0xe6, 143 | 0xb4,0x0b,0x8e,0xed,0xf2,0x78,0x5e,0x42, 144 | 0x87,0x4d 145 | ]); 146 | let key = classify_u8s(&vec![ 147 | 0u8,1u8,2u8,3u8,4u8,5u8,6u8,7u8, 148 | 8u8,9u8,10u8,11u8,12u8,13u8,14u8,15u8, 149 | 16u8,17u8,18u8,19u8,20u8,21u8,22u8,23u8, 150 | 24u8,25u8,26u8,27u8,28u8,29u8,30u8,31u8 151 | ]); 152 | let nonce = classify_u8s(&vec![0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4a,0x0,0x0,0x0,0x0]); 153 | let computed_ciphertext = chacha20_encrypt(&key, U32::classify(1u32), &nonce, &plaintext); 154 | for (i, (x1,x2)) in ciphertext.iter().zip(computed_ciphertext).enumerate() { 155 | assert_eq!(x1.declassify(), x2.declassify(), "at index {:?}", i); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /dalek-field-mul/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /dalek-field-mul/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dalek-field-mul" 3 | version = "0.1.0" 4 | authors = ["Denis Merigoux "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | secret_integers = { path = "../../secret_integers" } 9 | -------------------------------------------------------------------------------- /dalek-field-mul/src-fstar/DalekFieldMul.fst: -------------------------------------------------------------------------------- 1 | module DalekFieldMul 2 | 3 | module U128 = FStar.UInt128 4 | module U64 = FStar.UInt64 5 | module Usize = FStar.UInt32 6 | module U32 = FStar.UInt32 7 | module U8 = FStar.UInt8 8 | module I64 = FStar.Int64 9 | module Isize = FStar.Int32 10 | module I32 = FStar.Int32 11 | 12 | open Rust 13 | 14 | #reset-options "--max_fuel 0" 15 | 16 | type is_54bits (x:u64) = U64.(x <^ (1uL <<^ 54ul)) 17 | 18 | type field_element_32 = array u64 5ul 19 | 20 | val mul: self:field_element_32 -> rhs: field_element_32 -> Pure field_element_32 21 | (requires ( 22 | is_54bits self.(0ul) /\ is_54bits self.(1ul) /\ 23 | is_54bits self.(2ul) /\ is_54bits self.(3ul) /\ is_54bits self.(4ul) 24 | )) 25 | (ensures (fun _ -> True)) 26 | let mul self rhs = 27 | let m (x:u64) (y:u64) : Tot u128 = U128.mul_wide x y in 28 | let a : array u64 5ul = self in 29 | let b : array u64 5ul = self in 30 | let b1_19 = U64.(b.(1ul) *%^ 19uL) in 31 | let b2_19 = U64.(b.(2ul) *%^ 19uL) in 32 | let b3_19 = U64.(b.(3ul) *%^ 19uL) in 33 | let b4_19 = U64.(b.(4ul) *%^ 19uL) in 34 | let c0 : u128 = U128.( 35 | (m a.(0ul) b.(0ul)) +%^ (m a.(4ul) b1_19) +%^ (m a.(2ul) b3_19) +%^ (m a.(1ul) b4_19) 36 | ) in 37 | let c1 : u128 = U128.( 38 | (m a.(1ul) b.(0ul)) +%^ (m a.(0ul) b.(1ul)) +%^ (m a.(2ul) b3_19) +%^ (m a.(1ul) b4_19) 39 | ) in 40 | let c2 : u128 = U128.( 41 | (m a.(2ul) b.(0ul)) +%^ (m a.(1ul) b.(1ul)) +%^ (m a.(2ul) b3_19) +%^ (m a.(1ul) b4_19) 42 | ) in 43 | let c3 : u128 = U128.( 44 | (m a.(3ul) b.(0ul)) +%^ (m a.(2ul) b.(1ul)) +%^ (m a.(2ul) b3_19) +%^ (m a.(1ul) b4_19) 45 | ) in 46 | let c4 : u128 = U128.( 47 | (m a.(4ul) b.(0ul)) +%^ (m a.(3ul) b.(1ul)) +%^ (m a.(2ul) b3_19) +%^ (m a.(1ul) b4_19) 48 | ) in 49 | let _LOW_51_BIT_MASK : u64 = U64.((1uL <<^ 51ul) -%^ 1uL) in 50 | let out = array_new 5ul 0uL in 51 | let c1 = U128.((c1 +%^ u64_to_u128 (u128_to_u64 U128.(c0 >>^ 51ul)))) in 52 | let out = array_update out 0ul U64.((u128_to_u64 c0) &^ _LOW_51_BIT_MASK) in 53 | let c2 = U128.((c2 +%^ u64_to_u128 (u128_to_u64 U128.(c1 >>^ 51ul)))) in 54 | let out = array_update out 1ul U64.((u128_to_u64 c1) &^ _LOW_51_BIT_MASK) in 55 | let c3 = U128.((c3 +%^ u64_to_u128 (u128_to_u64 U128.(c2 >>^ 51ul)))) in 56 | let out = array_update out 2ul U64.((u128_to_u64 c2) &^ _LOW_51_BIT_MASK) in 57 | let c4 = U128.((c4 +%^ u64_to_u128 (u128_to_u64 U128.(c3 >>^ 51ul)))) in 58 | let out = array_update out 3ul U64.((u128_to_u64 c3) &^ _LOW_51_BIT_MASK) in 59 | let carry : u64 = u128_to_u64 U128.(c4 >>^ 51ul) in 60 | let out = array_update out 4ul U64.((u128_to_u64 c4) &^ _LOW_51_BIT_MASK) in 61 | let out = array_update out 0ul U64.(out.(0ul) +%^ carry *%^ 19uL) in 62 | let out = array_update out 1ul U64.(out.(0ul) >>^ 51ul) in 63 | let out = array_update out 0ul U64.(out.(0ul) &^ _LOW_51_BIT_MASK) in 64 | out 65 | -------------------------------------------------------------------------------- /dalek-field-mul/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// This code is inspired from Dalek's field multiplication for 64-bits backends contained in the 2 | /// file [`src/backend/u64/field.rs`](https://github.com/dalek-cryptography/curve25519-dalek/blob/master/src/backend/u64/field.rs) 3 | 4 | extern crate secret_integers; 5 | 6 | use secret_integers::*; 7 | use core::ops::Mul; 8 | 9 | /// A `FieldElement64` represents an element of the field 10 | /// \\( \mathbb Z / (2\^{255} - 19)\\). 11 | /// 12 | /// In the 64-bit implementation, a `FieldElement` is represented in 13 | /// radix \\(2\^{51}\\) as five `u64`s; the coefficients are allowed to 14 | /// grow up to \\(2\^{54}\\) between reductions modulo \\(p\\). 15 | /// 16 | /// # Note 17 | /// 18 | /// The `curve25519_dalek::field` module provides a type alias 19 | /// `curve25519_dalek::field::FieldElement` to either `FieldElement64` 20 | /// or `FieldElement32`. 21 | /// 22 | /// The backend-specific type `FieldElement64` should not be used 23 | /// outside of the `curve25519_dalek::field` module. 24 | type Limb = U64; 25 | 26 | #[derive(Copy, Clone)] 27 | pub struct FieldElement64(pub (crate) [Limb; 5]); 28 | 29 | impl<'a, 'b> Mul<&'b FieldElement64> for &'a FieldElement64 { 30 | type Output = FieldElement64; 31 | fn mul(self, _rhs: &'b FieldElement64) -> FieldElement64 { 32 | /// Helper function to multiply two 64-bit integers with 128 33 | /// bits of output. 34 | #[inline(always)] 35 | fn m(x: U64, y: U64) -> U128 { U128::from(x) * y.into() } 36 | 37 | // Alias self, _rhs for more readable formulas 38 | let a: &[Limb; 5] = &self.0; 39 | let b: &[Limb; 5] = &_rhs.0; 40 | 41 | // Precondition: assume input limbs a[i], b[i] are bounded as 42 | // 43 | // a[i], b[i] < 2^(51 + b) 44 | // 45 | // where b is a real parameter measuring the "bit excess" of the limbs. 46 | 47 | // 64-bit precomputations to avoid 128-bit multiplications. 48 | // 49 | // This fits into a u64 whenever 51 + b + lg(19) < 64. 50 | // 51 | // Since 51 + b + lg(19) < 51 + 4.25 + b 52 | // = 55.25 + b, 53 | // this fits if b < 8.75. 54 | let nineteen = 19u64.into(); 55 | let b1_19 = b[1] * nineteen; 56 | let b2_19 = b[2] * nineteen; 57 | let b3_19 = b[3] * nineteen; 58 | let b4_19 = b[4] * nineteen; 59 | 60 | // Multiply to get 128-bit coefficients of output 61 | let c0: U128 = m(a[0],b[0]) + m(a[4],b1_19) + m(a[3],b2_19) + m(a[2],b3_19) + m(a[1],b4_19); 62 | let mut c1: U128 = m(a[1],b[0]) + m(a[0],b[1]) + m(a[4],b2_19) + m(a[3],b3_19) + m(a[2],b4_19); 63 | let mut c2: U128 = m(a[2],b[0]) + m(a[1],b[1]) + m(a[0],b[2]) + m(a[4],b3_19) + m(a[3],b4_19); 64 | let mut c3: U128 = m(a[3],b[0]) + m(a[2],b[1]) + m(a[1],b[2]) + m(a[0],b[3]) + m(a[4],b4_19); 65 | let mut c4: U128 = m(a[4],b[0]) + m(a[3],b[1]) + m(a[2],b[2]) + m(a[1],b[3]) + m(a[0],b[4]); 66 | 67 | // How big are the c[i]? We have 68 | // 69 | // c[i] < 2^(102 + 2*b) * (1+i + (4-i)*19) 70 | // < 2^(102 + lg(1 + 4*19) + 2*b) 71 | // < 2^(108.27 + 2*b) 72 | // 73 | // The carry (c[i] >> 51) fits into a u64 when 74 | // 108.27 + 2*b - 51 < 64 75 | // 2*b < 6.73 76 | // b < 3.365. 77 | // 78 | // So we require b < 3 to ensure this fits. 79 | debug_assert!(U64::declassify(a[0]) < (1 << 54)); 80 | debug_assert!(U64::declassify(b[0]) < (1 << 54)); 81 | debug_assert!(U64::declassify(a[1]) < (1 << 54)); 82 | debug_assert!(U64::declassify(b[1]) < (1 << 54)); 83 | debug_assert!(U64::declassify(a[2]) < (1 << 54)); 84 | debug_assert!(U64::declassify(b[2]) < (1 << 54)); 85 | debug_assert!(U64::declassify(a[3]) < (1 << 54)); 86 | debug_assert!(U64::declassify(b[3]) < (1 << 54)); 87 | debug_assert!(U64::declassify(a[4]) < (1 << 54)); 88 | debug_assert!(U64::declassify(b[4]) < (1 << 54)); 89 | 90 | // Casting to u64 and back tells the compiler that the carry is 91 | // bounded by 2^64, so that the addition is a u128 + u64 rather 92 | // than u128 + u128. 93 | 94 | const LOW_51_BIT_MASK: u64 = (1u64 << 51) - 1; 95 | let mut out = [U64::classify(0u64); 5]; 96 | 97 | c1 += U64::from(c0 >> 51).into(); 98 | out[0] = U64::from(c0) & LOW_51_BIT_MASK.into(); 99 | 100 | c2 += U64::from(c1 >> 51).into(); 101 | out[1] = U64::from(c1) & LOW_51_BIT_MASK.into(); 102 | 103 | c3 += U64::from(c2 >> 51).into(); 104 | out[2] = U64::from(c2) & LOW_51_BIT_MASK.into(); 105 | 106 | c4 += U64::from(c3 >> 51).into(); 107 | out[3] = U64::from(c3) & LOW_51_BIT_MASK.into(); 108 | 109 | let carry: U64 = (c4 >> 51).into(); 110 | out[4] = U64::from(c4) & LOW_51_BIT_MASK.into(); 111 | 112 | // To see that this does not overflow, we need out[0] + carry * 19 < 2^64. 113 | // 114 | // c4 < a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0 + (carry from c3) 115 | // < 5*(2^(51 + b) * 2^(51 + b)) + (carry from c3) 116 | // < 2^(102 + 2*b + lg(5)) + 2^64. 117 | // 118 | // When b < 3 we get 119 | // 120 | // c4 < 2^110.33 so that carry < 2^59.33 121 | // 122 | // so that 123 | // 124 | // out[0] + carry * 19 < 2^51 + 19 * 2^59.33 < 2^63.58 125 | // 126 | // and there is no overflow. 127 | out[0] = out[0] + carry * nineteen; 128 | 129 | // Now out[1] < 2^51 + 2^(64 -51) = 2^51 + 2^13 < 2^(51 + epsilon). 130 | out[1] += out[0] >> 51; 131 | out[0] &= LOW_51_BIT_MASK.into(); 132 | 133 | // Now out[i] < 2^(51 + epsilon) for all i. 134 | FieldElement64(out) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /rox-star-lib/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /rox-star-lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rox-star-lib" 3 | version = "0.1.0" 4 | authors = ["Denis Merigoux "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | num-bigint = "*" 9 | num-traits = "*" 10 | secret_integers = "0.1.3" 11 | -------------------------------------------------------------------------------- /rox-star-lib/src/bytes.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use std::fmt::Display; 3 | use secret_integers::*; 4 | 5 | pub fn fill(len:usize, f: F) -> Vec where F: Fn(usize) -> B { 6 | let mut a = Vec::with_capacity(len); 7 | a.resize(len, Default::default()); 8 | for i in 0..a.len() { 9 | a[i] = f(i); 10 | }; 11 | a 12 | } 13 | 14 | pub fn classify_u8s(v: &[u8]) -> Vec { 15 | v.iter().map(|x| U8::classify(*x)).collect() 16 | } 17 | 18 | pub fn classify_u32s(v: &[u32]) -> Vec { 19 | v.iter().map(|x| U32::classify(*x)).collect() 20 | } 21 | 22 | pub fn format_bytes(v: &Vec) -> String { 23 | let mut comma_separated = String::new(); 24 | 25 | for num in v { 26 | comma_separated.push_str(&format!("{}", num)); 27 | } 28 | comma_separated 29 | } 30 | -------------------------------------------------------------------------------- /rox-star-lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate num_bigint; 2 | extern crate num_traits; 3 | extern crate secret_integers; 4 | 5 | #[macro_use] 6 | mod macros { 7 | #[macro_export] 8 | macro_rules! verif_assert { 9 | ($e:expr) => { assert!($e) } 10 | } 11 | #[macro_export] 12 | macro_rules! verif_pre { 13 | ($e:expr) => { assert!($e) } 14 | } 15 | #[macro_export] 16 | macro_rules! verif_post { 17 | ($e:expr) => { assert!($e) } 18 | } 19 | } 20 | 21 | mod nat_int; 22 | mod bytes; 23 | 24 | pub use secret_integers::*; 25 | pub use self::nat_int::*; 26 | pub use self::secret_integers::*; 27 | pub use self::bytes::*; 28 | -------------------------------------------------------------------------------- /rox-star-lib/src/nat_int.rs: -------------------------------------------------------------------------------- 1 | // Natural integers type 2 | 3 | use std::ops::*; 4 | 5 | use num_bigint::{BigInt}; 6 | use num_traits::{Pow, One, identities::Zero}; 7 | 8 | #[derive(Clone, PartialEq, PartialOrd, Eq, Ord)] 9 | pub struct Int(BigInt); 10 | 11 | impl Add for Int { 12 | type Output = Int; 13 | 14 | fn add(self, rhs:Int) -> Int { 15 | Int(self.0 + rhs.0) 16 | } 17 | } 18 | 19 | impl Sub for Int { 20 | type Output = Int; 21 | 22 | fn sub(self, rhs:Int) -> Int { 23 | Int(self.0 - rhs.0) 24 | } 25 | } 26 | 27 | impl Mul for Int { 28 | type Output = Int; 29 | 30 | fn mul(self, rhs:Int) -> Int { 31 | Int(self.0 * rhs.0) 32 | } 33 | } 34 | 35 | impl Div for Int { 36 | type Output = Int; 37 | 38 | //#[requires(| self, rhs | => rhs != 0.into())] 39 | fn div(self, rhs:Int) -> Int { 40 | verif_pre!(rhs != 0.into()); 41 | Int(self.0 / rhs.0) 42 | } 43 | } 44 | 45 | impl Rem for Int { 46 | type Output = Int; 47 | 48 | //#[requires(| self, rhs | => rhs != 0.into())] 49 | fn rem(self, rhs:Int) -> Int { 50 | verif_pre!(rhs != 0.into()); 51 | Int(self.0 % rhs.0) 52 | } 53 | } 54 | 55 | 56 | impl Pow for Int { 57 | type Output = Int; 58 | //#[requires (|self, exp| => exp >= 0)] 59 | fn pow(self, exp: Int) -> Int { 60 | verif_pre!(exp >= 0.into()); 61 | let mut exp = exp.0; 62 | if exp == BigInt::zero() { 63 | return Int(BigInt::one()); 64 | } 65 | let mut base = self.0.clone(); 66 | 67 | while exp.clone() & BigInt::one() == BigInt::zero() { 68 | base = &base * &base; 69 | exp >>= 1; 70 | } 71 | 72 | if exp == BigInt::one() { 73 | return Int(base); 74 | } 75 | 76 | let mut acc = base.clone(); 77 | while exp.clone() > BigInt::one() { 78 | exp >>= 1; 79 | base = &base * &base; 80 | if exp.clone() & BigInt::one() == BigInt::one() { 81 | acc = &acc * &base; 82 | } 83 | } 84 | Int(acc) 85 | } 86 | } 87 | 88 | impl From for Int { 89 | fn from(x:u128) -> Int { 90 | Int(BigInt::from(x)) 91 | } 92 | } 93 | 94 | impl From for Int { 95 | fn from(x:u64) -> Int { 96 | Int(BigInt::from(x)) 97 | } 98 | } 99 | 100 | impl From for Int { 101 | fn from(x:u32) -> Int { 102 | Int(BigInt::from(x)) 103 | } 104 | } 105 | 106 | impl From for Int { 107 | fn from(x:u8) -> Int { 108 | Int(BigInt::from(x)) 109 | } 110 | } 111 | 112 | impl From for Int { 113 | fn from(x:usize) -> Int { 114 | Int(BigInt::from(x)) 115 | } 116 | } 117 | 118 | impl From for Int { 119 | fn from(x:i128) -> Int { 120 | Int(BigInt::from(x)) 121 | } 122 | } 123 | 124 | impl From for Int { 125 | fn from(x:i64) -> Int { 126 | Int(BigInt::from(x)) 127 | } 128 | } 129 | 130 | impl From for Int { 131 | fn from(x:i32) -> Int { 132 | Int(BigInt::from(x)) 133 | } 134 | } 135 | 136 | impl From for Int { 137 | fn from(x:i8) -> Int { 138 | Int(BigInt::from(x)) 139 | } 140 | } 141 | 142 | impl From for Int { 143 | fn from(x:isize) -> Int { 144 | Int(BigInt::from(x)) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /textinput/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | *.exe 4 | _build/ 5 | -------------------------------------------------------------------------------- /textinput/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "verified-textinput" 3 | version = "0.1.0" 4 | authors = ["Denis Merigoux "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /textinput/Makefile: -------------------------------------------------------------------------------- 1 | # Needs FSTAR_HOME to point to FStar's root directory 2 | 3 | FSTAR=$(FSTAR_HOME)/bin/fstar.exe 4 | 5 | include $(FSTAR_HOME)/ulib/ml/Makefile.include 6 | 7 | %.fst.hints: %.fst 8 | $(FSTAR) $< --record_hints --use_hints --include ../ 9 | 10 | %.exe: %.fst 11 | mkdir -p target/ocaml 12 | $(FSTAR) $(FSTAR_DEFAULT_ARGS) --codegen OCaml --lax --odir target/ocaml \ 13 | --include ../ --include src-fstar $^ 14 | $(OCAMLOPT) -I target/ocaml/ target/ocaml/Rust.ml target/ocaml/TextInput.ml \ 15 | target/ocaml/Test_TextInput.ml -o Test_TextInput.exe 16 | 17 | verify: src-fstar/TextInput.fst.hints 18 | 19 | clean: 20 | rm src-fstar/TextInput.fst.hints 21 | 22 | extract: src-fstar/Test.TextInput.exe 23 | 24 | test : src-fstar/Test.TextInput.exe 25 | ./Test_TextInput.exe 26 | cargo test 27 | -------------------------------------------------------------------------------- /textinput/src-fstar/Test.TextInput.fst: -------------------------------------------------------------------------------- 1 | module Test.TextInput 2 | 3 | open TextInput 4 | open Rust 5 | 6 | 7 | let input : text = 8 | let v = vec_empty () in 9 | let v = vec_push v "abc" in 10 | let v = vec_push v "de" in 11 | let v = vec_push v "f" in 12 | v 13 | 14 | let test () : All.ML unit = 15 | IO.print_string "Beginning test.\n"; 16 | let input = selection_new input in 17 | let input = adjust_horizontal input 3l NotSelected in 18 | assert_eq "1" (IO.print_uint32) input.edit_point.line 0ul; 19 | assert_eq "2" (IO.print_uint32) input.edit_point.index 3ul; 20 | let input = adjust_vertical input 1l Selected in 21 | assert_eq "3" (IO.print_uint32) input.edit_point.line 1ul; 22 | assert_eq "4" (IO.print_uint32) input.edit_point.index 2ul; 23 | let input = adjust_vertical input (-1l) NotSelected in 24 | assert_eq "5" (IO.print_uint32) input.edit_point.line 0ul; 25 | assert_eq "6" (IO.print_uint32) input.edit_point.index 2ul; 26 | let input = adjust_vertical input 2l NotSelected in 27 | assert_eq "7" (IO.print_uint32) input.edit_point.line 2ul; 28 | assert_eq "8" (IO.print_uint32) input.edit_point.index 1ul; 29 | let input = adjust_vertical input (-1l) NotSelected in 30 | assert_eq "9" (IO.print_uint32) input.edit_point.line 1ul; 31 | assert_eq "10" (IO.print_uint32) input.edit_point.index 1ul; 32 | let input = adjust_horizontal input 2l NotSelected in 33 | assert_eq "11" (IO.print_uint32) input.edit_point.line 2ul; 34 | assert_eq "12" (IO.print_uint32) input.edit_point.index 0ul; 35 | IO.print_string "End of test. If no failed assertion then passed!\n" 36 | 37 | let _ = test () 38 | -------------------------------------------------------------------------------- /textinput/src-fstar/TextInput.fst: -------------------------------------------------------------------------------- 1 | module TextInput 2 | 3 | open Rust 4 | module U64 = FStar.UInt64 5 | module Usize = FStar.UInt32 6 | module U32 = FStar.UInt32 7 | module U8 = FStar.UInt8 8 | module I64 = FStar.Int64 9 | module Isize = FStar.Int32 10 | module I32 = FStar.Int32 11 | 12 | #reset-options "--max_fuel 0" 13 | 14 | type selection_status = 15 | | Selected 16 | | NotSelected 17 | 18 | type selection_direction = 19 | | Forward 20 | | Backward 21 | | Unspecified 22 | 23 | type direction = 24 | | DForward 25 | | DBackward 26 | 27 | type small_usize = x:usize{Usize.v x <= Isize.v _MAX_ISIZE } 28 | 29 | type text_point = { 30 | line: small_usize; 31 | index: small_usize; 32 | } 33 | 34 | val text_point_lte : text_point -> text_point -> Tot bool 35 | let text_point_lte self other = 36 | Usize.(self.line <^ other.line) || 37 | Usize.((self.line = other.line && self.index <=^ other.index)) 38 | 39 | type selection_state = { 40 | start: text_point; 41 | final: text_point; 42 | direction: selection_direction 43 | } 44 | 45 | 46 | type dom_string = (s:rust_string{Usize.v (string_length s) < Isize.v _MAX_ISIZE}) 47 | 48 | let dom_string_len (s:dom_string) : Tot usize = string_length s 49 | 50 | let number_of_lines (t:vec dom_string) : Tot usize = vec_length t 51 | 52 | type text = text:vec dom_string{ 53 | Usize.(number_of_lines text >^ 0ul) /\ Usize.v (number_of_lines text) <= Isize.v _MAX_ISIZE 54 | } 55 | 56 | val line_len : text:text -> i:vec_idx text -> Tot usize 57 | let line_len t i = dom_string_len (vec_index t i) 58 | 59 | noeq type text_input = { 60 | lines: text; 61 | edit_point: text_point; 62 | selection_origin: option text_point; 63 | selection_direction: selection_direction; 64 | } 65 | 66 | let assert_edit_order (input: text_input) : Tot bool = 67 | begin match input.selection_origin with 68 | | Some(start) -> 69 | Usize.(start.line <^ (number_of_lines input.lines)) && 70 | Usize.(start.index <=^ (line_len input.lines start.line)) && 71 | begin match input.selection_direction with 72 | | Unspecified | Forward -> text_point_lte start input.edit_point 73 | | Backward -> text_point_lte input.edit_point start 74 | end 75 | | None -> true 76 | end 77 | 78 | val assert_ok_selection : text_input -> Tot bool 79 | let assert_ok_selection input = 80 | assert_edit_order input && 81 | Usize.(input.edit_point.line <^ (number_of_lines input.lines)) && 82 | Usize.(input.edit_point.index <=^ (line_len input.lines input.edit_point.line)) 83 | 84 | type selection = input:text_input{assert_ok_selection input} 85 | 86 | let is_valid_text_point (self:text_point) (input:selection) = 87 | Usize.(self.line <^ number_of_lines input.lines) && 88 | Usize.(self.index <=^ (line_len input.lines self.line)) 89 | 90 | type valid_text_point (input:selection) = p:text_point{is_valid_text_point p input} 91 | 92 | val current_line_length : 93 | self:text_input{Usize.(self.edit_point.line <^ number_of_lines self.lines)} -> 94 | usize 95 | let current_line_length self = line_len self.lines self.edit_point.line 96 | 97 | val selection_new: t:text -> Tot selection 98 | let selection_new t = { 99 | lines = t; 100 | edit_point = { line = 0ul; index = 0ul }; 101 | selection_origin = None; 102 | selection_direction = Unspecified 103 | } 104 | 105 | val clear_selection : selection -> Tot selection 106 | let clear_selection self = 107 | let self = { self with selection_origin = None} in 108 | let self = { self with selection_direction = Unspecified} in 109 | self 110 | 111 | val select_all : selection -> Tot selection 112 | let select_all self = 113 | let last_line = Usize.(number_of_lines self.lines -%^ 1ul) in 114 | let self = { self with selection_origin = Some({ line = 0ul; index = 0ul }) } in 115 | let self = 116 | { self with edit_point = { line = last_line; index = line_len self.lines last_line} } 117 | in let self = { self with selection_direction = Forward } in 118 | self 119 | 120 | val selection_origin_or_edit_point : self:selection -> Tot (valid_text_point self) 121 | let selection_origin_or_edit_point self = 122 | unwrap_or self.selection_origin self.edit_point 123 | 124 | val selection_start : self:selection -> Tot (valid_text_point self) 125 | let selection_start self = match self.selection_direction with 126 | | Unspecified | Forward -> selection_origin_or_edit_point self 127 | | Backward -> self.edit_point 128 | 129 | val selection_end : self:selection -> Tot (valid_text_point self) 130 | let selection_end self = match self.selection_direction with 131 | | Unspecified | Forward -> self.edit_point 132 | | Backward -> selection_origin_or_edit_point self 133 | 134 | let has_selection (self:selection) : Tot bool = 135 | is_some self.selection_origin 136 | 137 | val adjust_selection_for_horizontal_change : 138 | selection -> 139 | direction -> 140 | selection_status -> 141 | Tot (selection * bool) 142 | let adjust_selection_for_horizontal_change self adjust select = 143 | if select = Selected then 144 | let self = if is_none self.selection_origin then 145 | let self = { self with selection_origin = Some (self.edit_point) } in 146 | self 147 | else self in 148 | (self, false) 149 | else if has_selection self then 150 | let self = { self with edit_point = match adjust with 151 | | DBackward -> selection_start self 152 | | DForward -> selection_end self 153 | } in 154 | let self = clear_selection self in 155 | (self,true) 156 | else (self, false) 157 | 158 | val adjust_horizontal_to_limit : selection -> direction -> selection_status -> text_input 159 | let adjust_horizontal_to_limit self direction select = 160 | let (self, adjust) = adjust_selection_for_horizontal_change self direction select in 161 | if adjust then 162 | self 163 | else match direction with 164 | | DBackward -> let self = { self with edit_point = { self.edit_point with line = 0ul } } in 165 | let self = { self with edit_point = { self.edit_point with index = 0ul } } in 166 | self 167 | | DForward -> let self = { self with edit_point = { self.edit_point with 168 | line = Usize.((number_of_lines self.lines) -%^ 1ul) } } in 169 | let self = { self with edit_point = { self.edit_point with 170 | index = line_len self.lines Usize.((number_of_lines self.lines) -%^ 1ul) } } in 171 | self 172 | 173 | val clear_selection_to_limit : selection -> direction -> selection 174 | let clear_selection_to_limit self direction = 175 | let self = clear_selection self in 176 | adjust_horizontal_to_limit self direction NotSelected 177 | 178 | 179 | val update_selection_direction : text_input -> text_input 180 | let update_selection_direction input = 181 | { input with 182 | selection_direction = match input.selection_origin with 183 | | Some(origin) -> if text_point_lte input.edit_point origin then Backward else Forward 184 | | None -> Forward 185 | } 186 | 187 | #reset-options "--z3rlimit 20" 188 | 189 | val adjust_vertical : 190 | self:selection -> 191 | adjust:isize{(Isize.v adjust) + (Usize.v self.edit_point.line) + 1 <= Isize.v _MAX_ISIZE} -> 192 | selection_status -> 193 | selection 194 | let adjust_vertical self adjust select = 195 | let self = if select = Selected then 196 | if is_none self.selection_origin then 197 | let self = { self with selection_origin = Some (self.edit_point) } in 198 | self 199 | else self 200 | else clear_selection self in 201 | assert (Usize.(self.edit_point.line <^ number_of_lines self.lines)); 202 | let target_line : isize = Isize.((usize_to_isize_safe self.edit_point.line) +^ adjust) in 203 | if Isize.(target_line <^ 0l) then 204 | let self = { self with edit_point = { self.edit_point with line = 0ul } } in 205 | let self = { self with edit_point = { self.edit_point with index = 0ul } } in 206 | let self = 207 | if is_some self.selection_origin && 208 | (self.selection_direction = Unspecified || self.selection_direction = Forward) 209 | then 210 | let self = { self with selection_origin = Some (self.edit_point) } in 211 | self 212 | else self 213 | in self 214 | else if Usize.((isize_to_usize_safe target_line) >=^ number_of_lines self.lines) then begin 215 | let self = { self with edit_point = { self.edit_point with 216 | line = Usize.((number_of_lines self.lines) -%^ 1ul); 217 | } } in 218 | let self = { self with edit_point = { self.edit_point with 219 | index = current_line_length self; 220 | } } in 221 | let self = if is_some self.selection_origin && self.selection_direction = Backward then 222 | let self = { self with selection_origin = Some (self.edit_point) } in 223 | self 224 | else self in 225 | self 226 | end else begin 227 | let target_line_length = line_len self.lines (isize_to_usize_safe target_line) in 228 | let col = min_usize self.edit_point.index target_line_length in 229 | let self = { self with edit_point = { self.edit_point with 230 | line = isize_to_usize_safe target_line } } in 231 | let self = { self with edit_point = { self.edit_point with index = col } } in 232 | let self = match self.selection_origin with 233 | | Some origin -> if 234 | ((self.selection_direction = Unspecified || self.selection_direction = Forward) && 235 | text_point_lte self.edit_point origin) || 236 | (self.selection_direction = Backward && text_point_lte origin self.edit_point) then 237 | let self = { self with selection_origin = Some self.edit_point } in 238 | self else self 239 | | None -> self 240 | in self 241 | end 242 | 243 | #reset-options "--z3rlimit 50" 244 | 245 | val perform_horizontal_adjustment : 246 | self:selection -> 247 | adjust:isize{ 248 | Isize.(adjust >^ _MIN_ISIZE) 249 | } -> 250 | selection_status -> 251 | Tot selection 252 | (decreases (Isize.v (if Isize.(adjust <^ 0l) then Isize.(0l -^ adjust) else adjust))) 253 | let rec perform_horizontal_adjustment self adjust select = 254 | let self = if Isize.(adjust <^ 0l) then begin 255 | let neg_val = Isize.(0l -^ adjust) in 256 | let adjust_abs = isize_to_usize_safe neg_val in 257 | let remaining = self.edit_point.index in 258 | if Usize.(adjust_abs >^ remaining) && Usize.(self.edit_point.line >^ 0ul) then 259 | let self = adjust_vertical self (-1l) select in 260 | let self = 261 | { self with edit_point = { self.edit_point with index = current_line_length self }} 262 | in 263 | let adjust = Isize.(adjust +^ ((usize_to_isize_safe remaining) +^ 1l)) in 264 | let direction = if Isize.(adjust >=^ 0l) then DForward else DBackward in 265 | let (self, done) = adjust_selection_for_horizontal_change self direction select in 266 | if done then self else perform_horizontal_adjustment self adjust select 267 | else 268 | let self = { self with edit_point = { self.edit_point with index = 269 | isize_to_usize_safe 270 | (max_isize 0l Isize.((usize_to_isize_safe self.edit_point.index) +^ adjust)) 271 | } } in 272 | self 273 | end else begin 274 | let remaining = Usize.((current_line_length self) -^ self.edit_point.index) in 275 | if 276 | Usize.((isize_to_usize_safe adjust) >^ remaining) && 277 | Usize.(number_of_lines self.lines >^ self.edit_point.line +^ 1ul) 278 | then 279 | let self = adjust_vertical self 1l select in 280 | let self = 281 | { self with edit_point = { self.edit_point with index = 0ul }} 282 | in 283 | let adjust = Isize.(adjust -^ (usize_to_isize_safe remaining) -^ 1l) in 284 | let direction = if Isize.(adjust >=^ 0l) then DForward else DBackward in 285 | let (self, done) = adjust_selection_for_horizontal_change self direction select in 286 | if done then self else perform_horizontal_adjustment self adjust select 287 | else let self = { self with 288 | edit_point = { self.edit_point with 289 | index = min_usize (current_line_length self) 290 | Usize.(self.edit_point.index +^ (isize_to_usize_safe adjust)) 291 | } 292 | } in self 293 | end in 294 | update_selection_direction self 295 | 296 | val adjust_horizontal : 297 | self:selection -> 298 | adjust:isize{Isize.(adjust >^ _MIN_ISIZE)} -> 299 | selection_status -> 300 | Tot selection 301 | let adjust_horizontal self adjust select = 302 | let direction = if Isize.(adjust >=^ 0l) then DForward else DBackward in 303 | let (self, done) = adjust_selection_for_horizontal_change self direction select in 304 | if done then self else perform_horizontal_adjustment self adjust select 305 | 306 | val adjust_horizontal_to_line_end : selection -> direction -> selection_status -> Tot selection 307 | let adjust_horizontal_to_line_end self dir status = 308 | let (self, done) = adjust_selection_for_horizontal_change self dir status in 309 | if not done then 310 | let shift : isize = 311 | match dir with 312 | | DBackward -> Isize.(0l -^ (usize_to_isize_safe self.edit_point.index)) 313 | | DForward -> usize_to_isize_safe 314 | Usize.((line_len self.lines self.edit_point.line) -^ self.edit_point.index) 315 | in 316 | perform_horizontal_adjustment self shift status 317 | else self 318 | -------------------------------------------------------------------------------- /textinput/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// This code is inspired by Servo's bloom filter implementation 2 | /// contained in the file 3 | /// [`servo/script/textinput.rs`](https://github.com/servo/servo/blob/master/components/script/textinput.rs) 4 | 5 | #[derive(Clone, Copy, PartialEq)] 6 | pub enum SelectionStatus { 7 | Selected, 8 | NotSelected, 9 | } 10 | 11 | #[derive(Clone, Copy, Debug, PartialEq)] 12 | pub enum SelectionDirection { 13 | Forward, 14 | Backward, 15 | Unspecified, 16 | } 17 | 18 | #[derive(Clone, Copy, Eq, PartialEq)] 19 | pub enum Direction { 20 | Forward, 21 | Backward, 22 | } 23 | 24 | //#[refinement | x | => x.to_nat() <= std::isize::MAX.to_nat()) ] 25 | type SmallUsize = usize; 26 | 27 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] 28 | pub struct TextPoint { 29 | /// 0-based line number 30 | pub line: SmallUsize, 31 | /// 0-based column number 32 | pub index: SmallUsize, 33 | } 34 | 35 | impl TextPoint { 36 | fn lte(&self, other: &TextPoint) -> bool { 37 | self.line < other.line || (self.line == other.line && self.index <= other.index) 38 | } 39 | } 40 | 41 | //#[refinement (| s | => s.0.len().to_nat() < std::isize::MAX.to_nat() )] 42 | pub type DOMString = String; 43 | 44 | fn number_of_lines(t: &Vec) -> usize { 45 | t.len() 46 | } 47 | 48 | //#[refinement (| t | => number_of_lines(t) > 0 && 49 | // number_of_lines(t).to_nat() <= std::isize::MAX.to_nat() 50 | //)] 51 | type Text = Vec; 52 | 53 | fn line_len(t: &Text, i: usize) -> usize { 54 | t[i].len() 55 | } 56 | 57 | //#[fstar_prefix "noeq"] 58 | #[derive(Debug)] 59 | pub struct TextInput { 60 | /// Current text input content, split across lines without trailing '\n' 61 | lines: Text, 62 | 63 | /// Current cursor input point 64 | edit_point: TextPoint, 65 | 66 | /// The current selection goes from the selection_origin until the edit_point. Note that the 67 | /// selection_origin may be after the edit_point, in the case of a backward selection. 68 | selection_origin: Option, 69 | selection_direction: SelectionDirection, 70 | } 71 | 72 | impl TextInput { 73 | // Check that the selection is valid. 74 | fn assert_edit_order(&self) -> bool { 75 | if let Some(begin) = self.selection_origin { 76 | begin.line < self.lines.len() 77 | && begin.index <= self.lines[begin.line].len() 78 | && (match self.selection_direction { 79 | SelectionDirection::Unspecified | SelectionDirection::Forward => { 80 | begin.lte(&self.edit_point) 81 | } 82 | SelectionDirection::Backward => self.edit_point.lte(&begin), 83 | }) 84 | } else { 85 | true 86 | } 87 | } 88 | 89 | #[allow(dead_code)] 90 | fn assert_ok_selection(&self) -> bool { 91 | self.assert_edit_order() 92 | && self.edit_point.line < self.lines.len() 93 | && self.edit_point.index <= self.lines[self.edit_point.line].len() 94 | } 95 | } 96 | 97 | //#[refinement (| t | => t.assert_ok_selection( )] 98 | #[allow(dead_code)] 99 | type Selection = TextInput; 100 | 101 | impl TextPoint { 102 | #[allow(dead_code)] 103 | fn is_valid_text_point(&self, input: &TextInput) -> bool { 104 | self.line < number_of_lines(&input.lines) && self.index <= line_len(&input.lines, self.line) 105 | } 106 | } 107 | 108 | type ValidTextPoint = TextPoint; 109 | 110 | impl TextInput { 111 | fn current_line_length(&self) -> usize { 112 | line_len(&self.lines, self.edit_point.line) 113 | } 114 | } 115 | 116 | impl Selection { 117 | pub fn new( 118 | lines: Text, 119 | ) -> Selection { 120 | Selection { 121 | lines, 122 | edit_point: TextPoint { line: 0, index: 0 }, 123 | selection_origin: None, 124 | selection_direction : SelectionDirection::Unspecified, 125 | } 126 | } 127 | 128 | 129 | pub fn clear_selection(&mut self) { 130 | self.selection_origin = None; 131 | self.selection_direction = SelectionDirection::Unspecified; 132 | } 133 | 134 | pub fn select_all(&mut self) { 135 | let last_line = number_of_lines(&self.lines) - 1; 136 | self.selection_origin = Some(TextPoint { line: 0, index: 0 }); 137 | self.edit_point = TextPoint { 138 | line: last_line, 139 | index: line_len(&self.lines, last_line), 140 | }; 141 | self.selection_direction = SelectionDirection::Forward; 142 | } 143 | 144 | fn selection_origin_or_edit_point(&self) -> ValidTextPoint { 145 | self.selection_origin.unwrap_or(self.edit_point) 146 | } 147 | 148 | pub fn selection_start(&self) -> ValidTextPoint { 149 | match self.selection_direction { 150 | SelectionDirection::Unspecified | SelectionDirection::Forward => { 151 | self.selection_origin_or_edit_point() 152 | } 153 | SelectionDirection::Backward => self.edit_point, 154 | } 155 | } 156 | 157 | pub fn selection_end(&self) -> ValidTextPoint { 158 | match self.selection_direction { 159 | SelectionDirection::Unspecified | SelectionDirection::Forward => self.edit_point, 160 | SelectionDirection::Backward => self.selection_origin_or_edit_point(), 161 | } 162 | } 163 | 164 | /// Whether or not there is an active selection (the selection may be zero-length) 165 | #[inline] 166 | pub fn has_selection(&self) -> bool { 167 | self.selection_origin.is_some() 168 | } 169 | 170 | fn adjust_selection_for_horizontal_change( 171 | &mut self, 172 | adjust: Direction, 173 | select: SelectionStatus, 174 | ) -> bool { 175 | if select == SelectionStatus::Selected { 176 | if self.selection_origin.is_none() { 177 | self.selection_origin = Some(self.edit_point); 178 | } 179 | false 180 | } else { 181 | if self.has_selection() { 182 | self.edit_point = match adjust { 183 | Direction::Backward => self.selection_start(), 184 | Direction::Forward => self.selection_end(), 185 | }; 186 | self.clear_selection(); 187 | true 188 | } else { 189 | false 190 | } 191 | } 192 | } 193 | 194 | pub fn adjust_horizontal_to_limit(&mut self, direction: Direction, select: SelectionStatus) { 195 | if self.adjust_selection_for_horizontal_change(direction, select) { 196 | () 197 | } else { 198 | match direction { 199 | Direction::Backward => { 200 | self.edit_point.line = 0; 201 | self.edit_point.index = 0; 202 | } 203 | Direction::Forward => { 204 | self.edit_point.line = &self.lines.len() - 1; 205 | self.edit_point.index = line_len(&self.lines, number_of_lines(&self.lines) - 1); 206 | } 207 | } 208 | } 209 | } 210 | 211 | pub fn clear_selection_to_limit(&mut self, direction: Direction) { 212 | self.clear_selection(); 213 | self.adjust_horizontal_to_limit(direction, SelectionStatus::NotSelected); 214 | } 215 | 216 | fn update_selection_direction(&mut self) { 217 | self.selection_direction = if let Some(origin) = self.selection_origin { 218 | if self.edit_point.lte(&origin) { 219 | SelectionDirection::Backward 220 | } else { 221 | SelectionDirection::Forward 222 | } 223 | } else { 224 | SelectionDirection::Forward 225 | } 226 | } 227 | 228 | //#[requires (| self , adjust , select | => 229 | // adjust.to_nat() + self.edit_point.line.to_nat() + 1 <= std::isize::MAX.to_nat() 230 | //)] 231 | pub fn adjust_vertical(&mut self, adjust: isize, select: SelectionStatus) { 232 | if select == SelectionStatus::Selected { 233 | if self.selection_origin.is_none() { 234 | self.selection_origin = Some(self.edit_point); 235 | } 236 | } else { 237 | self.clear_selection(); 238 | } 239 | 240 | assert!(self.edit_point.line < number_of_lines(&self.lines)); 241 | 242 | let target_line: isize = self.edit_point.line as isize + adjust; 243 | 244 | if target_line < 0 { 245 | self.edit_point.line = 0; 246 | self.edit_point.index = 0; 247 | if self.selection_origin.is_some() 248 | && (self.selection_direction == SelectionDirection::Unspecified 249 | || self.selection_direction == SelectionDirection::Forward) 250 | { 251 | self.selection_origin = Some(TextPoint { line: 0, index: 0 }); 252 | } 253 | } else if target_line as usize >= number_of_lines(&self.lines) { 254 | self.edit_point.line = self.lines.len() - 1; 255 | self.edit_point.index = self.current_line_length(); 256 | if self.selection_origin.is_some() 257 | && (self.selection_direction == SelectionDirection::Backward) 258 | { 259 | self.selection_origin = Some(self.edit_point); 260 | } 261 | } else { 262 | let target_line_length = line_len(&self.lines, target_line as usize); 263 | let col = std::cmp::min(self.edit_point.index, target_line_length); 264 | self.edit_point.line = target_line as usize; 265 | self.edit_point.index = col; 266 | if let Some(origin) = self.selection_origin { 267 | if ((self.selection_direction == SelectionDirection::Unspecified 268 | || self.selection_direction == SelectionDirection::Forward) 269 | && self.edit_point.lte(&origin)) 270 | || (self.selection_direction == SelectionDirection::Backward 271 | && origin.lte(&self.edit_point)) 272 | { 273 | self.selection_origin = Some(self.edit_point); 274 | } 275 | } 276 | } 277 | } 278 | 279 | //#[requires (| self , adjust, select | => adjust > std::isize::MIN)] 280 | fn perform_horizontal_adjustment(&mut self, adjust: isize, select: SelectionStatus) { 281 | if adjust < 0 { 282 | let remaining = self.edit_point.index; 283 | let neg_val = -adjust; 284 | let adjust_abs = neg_val as usize; 285 | if adjust_abs as usize > remaining && self.edit_point.line > 0 { 286 | self.adjust_vertical(-1, select); 287 | self.edit_point.index = self.current_line_length(); 288 | let adjust = adjust + remaining as isize + 1; 289 | let direction = if adjust >= 0 { 290 | Direction::Forward 291 | } else { 292 | Direction::Backward 293 | }; 294 | let done = self.adjust_selection_for_horizontal_change(direction, select); 295 | if !done { 296 | self.perform_horizontal_adjustment(adjust, select); 297 | } 298 | } else { 299 | self.edit_point.index = 300 | std::cmp::max(0, self.edit_point.index as isize + adjust) as usize; 301 | } 302 | } else { 303 | let remaining = self.current_line_length() - self.edit_point.index; 304 | if adjust as usize > remaining && self.lines.len() > self.edit_point.line + 1 { 305 | self.adjust_vertical(1, select); 306 | self.edit_point.index = 0; 307 | // one shift is consumed by the change of line, hence the -1 308 | let adjust = adjust - remaining as isize - 1; 309 | let direction = if adjust >= 0 { 310 | Direction::Forward 311 | } else { 312 | Direction::Backward 313 | }; 314 | let done = self.adjust_selection_for_horizontal_change(direction, select); 315 | if !done { 316 | self.perform_horizontal_adjustment(adjust, select); 317 | } 318 | } else { 319 | self.edit_point.index = std::cmp::min( 320 | self.current_line_length(), 321 | self.edit_point.index + adjust as usize, 322 | ); 323 | } 324 | } 325 | self.update_selection_direction(); 326 | } 327 | 328 | //#[requires (| self, adjust, select | => adjust > std::isize::MIN)] 329 | pub fn adjust_horizontal(&mut self, adjust: isize, select: SelectionStatus) { 330 | let direction = if adjust >= 0 { 331 | Direction::Forward 332 | } else { 333 | Direction::Backward 334 | }; 335 | if self.adjust_selection_for_horizontal_change(direction, select) { 336 | return; 337 | } 338 | self.perform_horizontal_adjustment(adjust, select); 339 | } 340 | 341 | pub fn adjust_horizontal_to_line_end(&mut self, direction: Direction, select: SelectionStatus) { 342 | let done = self.adjust_selection_for_horizontal_change(direction, select); 343 | if !done { 344 | let shift: isize = { 345 | match direction { 346 | Direction::Backward => -(self.edit_point.index as isize), 347 | Direction::Forward => { 348 | (line_len(&self.lines, self.edit_point.line) - self.edit_point.index) 349 | as isize 350 | } 351 | } 352 | }; 353 | self.perform_horizontal_adjustment(shift, select); 354 | } 355 | } 356 | } 357 | 358 | /// Testing 359 | 360 | mod test { 361 | use crate::*; 362 | 363 | #[allow(dead_code)] 364 | fn text_input(s: Vec<&str>) -> TextInput { 365 | TextInput::new( 366 | s.iter().map(|s| String::from(*s)).collect(), 367 | ) 368 | } 369 | 370 | #[test] 371 | fn test_textinput() { 372 | let mut textinput = text_input(vec!["abc","de","f"]); 373 | textinput.adjust_horizontal(3, SelectionStatus::NotSelected); 374 | assert_eq!(textinput.edit_point.line, 0); 375 | assert_eq!(textinput.edit_point.index, 3); 376 | 377 | textinput.adjust_vertical(1, SelectionStatus::Selected); 378 | assert_eq!(textinput.edit_point.line, 1); 379 | assert_eq!(textinput.edit_point.index, 2); 380 | 381 | textinput.adjust_vertical(-1, SelectionStatus::NotSelected); 382 | assert_eq!(textinput.edit_point.line, 0); 383 | assert_eq!(textinput.edit_point.index, 2); 384 | 385 | textinput.adjust_vertical(2, SelectionStatus::NotSelected); 386 | assert_eq!(textinput.edit_point.line, 2); 387 | assert_eq!(textinput.edit_point.index, 1); 388 | 389 | textinput.adjust_vertical(-1, SelectionStatus::Selected); 390 | assert_eq!(textinput.edit_point.line, 1); 391 | assert_eq!(textinput.edit_point.index, 1); 392 | 393 | textinput.adjust_horizontal(2, SelectionStatus::Selected); 394 | assert_eq!(textinput.edit_point.line, 2); 395 | assert_eq!(textinput.edit_point.index, 0); 396 | } 397 | 398 | } 399 | --------------------------------------------------------------------------------