Merge lp:~jderose/filestore/dbase32 into lp:filestore
- dbase32
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 304 | ||||
Proposed branch: | lp:~jderose/filestore/dbase32 | ||||
Merge into: | lp:filestore | ||||
Diff against target: |
3659 lines (+1377/-1583) 12 files modified
debian/control (+2/-2) filestore/__init__.py (+41/-58) filestore/data/MD5SUMS.json (+23/-0) filestore/data/V0.json (+24/-0) filestore/data/V1.json (+24/-0) filestore/data/test-vectors.json (+0/-50) filestore/misc.py (+52/-32) filestore/protocols.py (+68/-164) filestore/tests/__init__.py (+67/-180) filestore/tests/test_misc.py (+132/-54) filestore/tests/test_protocols.py (+943/-1042) setup.py (+1/-1) |
||||
To merge this branch: | bzr merge lp:~jderose/filestore/dbase32 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
David Jordan | Approve | ||
Review via email: mp+149313@code.launchpad.net |
Commit message
Description of the change
For details, see this bug:
https:/
Changes include:
* Use compatibility functions from `dbase32.rfc3548` so that it will be very easy to switch to Dbase32, in particular use `isb32()` instead of set operations with B32ALPHABET; we can just replace with `isdb32()` when needed
* Removed protocol selection-
* `Protocol.
* `Protocol.
* Likewise, `Protocol.
* `Protocol` is now a base class without any hashing implementation itself; real implementations are now in the `SkeinProtocol` and `OldSkeinProtocol` subclasses
* Changed V1 to use a 240-bit digest and DBase32 encoding, updated test vectors accordingly
* Split old test-vectors.json file (which contained both V0 and V1) into separate V0.json and V1.json files
* Added MD5SUMS.json with md5sums of various values used by the test vectors; these are helpful for debugging an implementation, as mention in the specification
* Huge refactor of the unit tests for `filestore.
* misc.build_
Jason Gerard DeRose (jderose) wrote : | # |
Preview Diff
1 | === modified file 'debian/control' | |||
2 | --- debian/control 2013-02-18 11:42:49 +0000 | |||
3 | +++ debian/control 2013-02-19 15:39:24 +0000 | |||
4 | @@ -6,7 +6,7 @@ | |||
5 | 6 | python3-all-dev (>= 3.3), | 6 | python3-all-dev (>= 3.3), |
6 | 7 | python3-sphinx, | 7 | python3-sphinx, |
7 | 8 | python3-skein (>= 0.7.1), | 8 | python3-skein (>= 0.7.1), |
9 | 9 | python3-dbase32 (>= 0.3), | 9 | python3-dbase32 (>= 0.4), |
10 | 10 | Standards-Version: 3.9.3 | 10 | Standards-Version: 3.9.3 |
11 | 11 | X-Python3-Version: >= 3.2 | 11 | X-Python3-Version: >= 3.2 |
12 | 12 | Homepage: https://launchpad.net/filestore | 12 | Homepage: https://launchpad.net/filestore |
13 | @@ -15,7 +15,7 @@ | |||
14 | 15 | Architecture: any | 15 | Architecture: any |
15 | 16 | Depends: ${python3:Depends}, ${shlibs:Depends}, ${misc:Depends}, | 16 | Depends: ${python3:Depends}, ${shlibs:Depends}, ${misc:Depends}, |
16 | 17 | python3-skein (>= 0.7.1), | 17 | python3-skein (>= 0.7.1), |
18 | 18 | python3-dbase32 (>= 0.3), | 18 | python3-dbase32 (>= 0.4), |
19 | 19 | Description: Dmedia hashing protocol and file layout | 19 | Description: Dmedia hashing protocol and file layout |
20 | 20 | This is the heart of Dmedia. It has been split out of Dmedia to make it easier | 20 | This is the heart of Dmedia. It has been split out of Dmedia to make it easier |
21 | 21 | to review, and in hopes that other apps might use the hashing protocol and | 21 | to review, and in hopes that other apps might use the hashing protocol and |
22 | 22 | 22 | ||
23 | === modified file 'filestore/__init__.py' | |||
24 | --- filestore/__init__.py 2013-02-18 12:41:32 +0000 | |||
25 | +++ filestore/__init__.py 2013-02-19 15:39:24 +0000 | |||
26 | @@ -76,15 +76,17 @@ | |||
27 | 76 | from os import path | 76 | from os import path |
28 | 77 | import io | 77 | import io |
29 | 78 | import stat | 78 | import stat |
31 | 79 | from base64 import b32encode, b64encode | 79 | from base64 import b64encode |
32 | 80 | from threading import Thread | 80 | from threading import Thread |
33 | 81 | from queue import Queue | 81 | from queue import Queue |
34 | 82 | from collections import namedtuple | 82 | from collections import namedtuple |
35 | 83 | import logging | 83 | import logging |
36 | 84 | 84 | ||
37 | 85 | from dbase32 import random_id | 85 | from dbase32 import random_id |
38 | 86 | from dbase32.rfc3548 import b32enc, isb32 | ||
39 | 86 | 87 | ||
41 | 87 | from .protocols import VERSION0, VERSION1 | 88 | from .protocols import TYPE_ERROR |
42 | 89 | from .protocols import VERSION0 as PROTOCOL | ||
43 | 88 | 90 | ||
44 | 89 | try: | 91 | try: |
45 | 90 | from _filestore import fallocate, fastread | 92 | from _filestore import fallocate, fastread |
46 | @@ -100,13 +102,10 @@ | |||
47 | 100 | 102 | ||
48 | 101 | log = logging.getLogger('filestore') | 103 | log = logging.getLogger('filestore') |
49 | 102 | 104 | ||
51 | 103 | LEAF_SIZE = 8 * 1024 * 1024 # 8 MiB leaf size | 105 | LEAF_SIZE = 8388608 # 8 MiB leaf size |
52 | 104 | DIGEST_BITS = 240 | 106 | DIGEST_BITS = 240 |
58 | 105 | DIGEST_BYTES = DIGEST_BITS // 8 | 107 | DIGEST_BYTES = 30 |
59 | 106 | DIGEST_B32LEN = DIGEST_BITS // 5 | 108 | DIGEST_B32LEN = 48 |
55 | 107 | |||
56 | 108 | ALLOWED_B32LEN = (48, 56) | ||
57 | 109 | PROTOCOL = VERSION0 | ||
60 | 110 | 109 | ||
61 | 111 | # Handy constants for file layout: | 110 | # Handy constants for file layout: |
62 | 112 | DOTNAME = '.dmedia' | 111 | DOTNAME = '.dmedia' |
63 | @@ -130,9 +129,6 @@ | |||
64 | 130 | 129 | ||
65 | 131 | IO_READABLE = (io.BufferedReader, io.BufferedRandom) | 130 | IO_READABLE = (io.BufferedReader, io.BufferedRandom) |
66 | 132 | 131 | ||
67 | 133 | # Provide very clear TypeError messages: | ||
68 | 134 | TYPE_ERROR = '{}: need a {!r}; got a {!r}: {!r}' | ||
69 | 135 | |||
70 | 136 | # For the README.txt in .dmedia directories: | 132 | # For the README.txt in .dmedia directories: |
71 | 137 | README = """This directory is automatically managed by Dmedia. | 133 | README = """This directory is automatically managed by Dmedia. |
72 | 138 | 134 | ||
73 | @@ -155,13 +151,13 @@ | |||
74 | 155 | 151 | ||
75 | 156 | For example: | 152 | For example: |
76 | 157 | 153 | ||
79 | 158 | >>> b32encode(hash_leaf(2, b'XYZ')) | 154 | >>> b32enc(hash_leaf(2, b'XYZ')) |
80 | 159 | b'D7GIW5I5NB6SLJC5ALAX4WU7S7CNYUB3ULMECPY67FFQG4F7' | 155 | 'D7GIW5I5NB6SLJC5ALAX4WU7S7CNYUB3ULMECPY67FFQG4F7' |
81 | 160 | 156 | ||
82 | 161 | :param leaf_index: an ``int`` >= 0 | 157 | :param leaf_index: an ``int`` >= 0 |
83 | 162 | :param leaf_data: optional ``bytes`` instance with contents of this leaf | 158 | :param leaf_data: optional ``bytes`` instance with contents of this leaf |
84 | 163 | """ | 159 | """ |
86 | 164 | return VERSION0.hash_leaf(leaf_index, leaf_data) | 160 | return PROTOCOL.hash_leaf(leaf_index, leaf_data) |
87 | 165 | 161 | ||
88 | 166 | 162 | ||
89 | 167 | def hash_root(file_size, leaf_hashes): | 163 | def hash_root(file_size, leaf_hashes): |
90 | @@ -177,15 +173,7 @@ | |||
91 | 177 | :param leaf_hashes: a ``bytes`` instance that is the concatenated leaf | 173 | :param leaf_hashes: a ``bytes`` instance that is the concatenated leaf |
92 | 178 | hashes produced by `hash_leaf()` | 174 | hashes produced by `hash_leaf()` |
93 | 179 | """ | 175 | """ |
103 | 180 | return VERSION0.hash_root(file_size, leaf_hashes) | 176 | return b32enc(PROTOCOL.hash_root(file_size, leaf_hashes)) |
95 | 181 | |||
96 | 182 | |||
97 | 183 | def get_protocol(_id): | ||
98 | 184 | check_id(_id) | ||
99 | 185 | if len(_id) == 48: | ||
100 | 186 | return VERSION0 | ||
101 | 187 | assert len(_id) == 56 | ||
102 | 188 | return VERSION1 | ||
104 | 189 | 177 | ||
105 | 190 | 178 | ||
106 | 191 | class Hasher: | 179 | class Hasher: |
107 | @@ -193,10 +181,9 @@ | |||
108 | 193 | A helper to keep track of state as you hash leaf after leaf. | 181 | A helper to keep track of state as you hash leaf after leaf. |
109 | 194 | """ | 182 | """ |
110 | 195 | 183 | ||
112 | 196 | __slots__ = ('protocol', 'file_size', 'leaf_index', 'array', 'closed') | 184 | __slots__ = ('file_size', 'leaf_index', 'array', 'closed') |
113 | 197 | 185 | ||
116 | 198 | def __init__(self, protocol=PROTOCOL): | 186 | def __init__(self): |
115 | 199 | self.protocol = protocol | ||
117 | 200 | self.file_size = 0 | 187 | self.file_size = 0 |
118 | 201 | self.leaf_index = 0 | 188 | self.leaf_index = 0 |
119 | 202 | self.array = bytearray() | 189 | self.array = bytearray() |
120 | @@ -211,9 +198,9 @@ | |||
121 | 211 | raise Exception('Expected leaf.index {}, got {}'.format( | 198 | raise Exception('Expected leaf.index {}, got {}'.format( |
122 | 212 | self.leaf_index, leaf.index) | 199 | self.leaf_index, leaf.index) |
123 | 213 | ) | 200 | ) |
125 | 214 | if len(leaf.data) < self.protocol.leaf_size: | 201 | if len(leaf.data) < LEAF_SIZE: |
126 | 215 | self.closed = True | 202 | self.closed = True |
128 | 216 | leaf_hash = self.protocol.hash_leaf(leaf.index, leaf.data) | 203 | leaf_hash = PROTOCOL.hash_leaf(leaf.index, leaf.data) |
129 | 217 | self.array.extend(leaf_hash) | 204 | self.array.extend(leaf_hash) |
130 | 218 | self.file_size += len(leaf.data) | 205 | self.file_size += len(leaf.data) |
131 | 219 | self.leaf_index += 1 | 206 | self.leaf_index += 1 |
132 | @@ -223,7 +210,7 @@ | |||
133 | 223 | self.closed = True | 210 | self.closed = True |
134 | 224 | leaf_hashes = bytes(self.array) | 211 | leaf_hashes = bytes(self.array) |
135 | 225 | return ContentHash( | 212 | return ContentHash( |
137 | 226 | self.protocol.hash_root(self.file_size, leaf_hashes), | 213 | b32enc(PROTOCOL.hash_root(self.file_size, leaf_hashes)), |
138 | 227 | self.file_size, | 214 | self.file_size, |
139 | 228 | leaf_hashes | 215 | leaf_hashes |
140 | 229 | ) | 216 | ) |
141 | @@ -345,12 +332,12 @@ | |||
142 | 345 | """ | 332 | """ |
143 | 346 | if not isinstance(_id, str): | 333 | if not isinstance(_id, str): |
144 | 347 | raise TypeError(TYPE_ERROR.format('_id', str, type(_id), _id)) | 334 | raise TypeError(TYPE_ERROR.format('_id', str, type(_id), _id)) |
146 | 348 | if not (len(_id) in ALLOWED_B32LEN and set(_id).issubset(B32ALPHABET)): | 335 | if not (len(_id) == DIGEST_B32LEN and isb32(_id)): |
147 | 349 | raise IDError(_id) | 336 | raise IDError(_id) |
148 | 350 | return _id | 337 | return _id |
149 | 351 | 338 | ||
150 | 352 | 339 | ||
152 | 353 | def check_leaf_hashes(leaf_hashes, protocol=PROTOCOL): | 340 | def check_leaf_hashes(leaf_hashes): |
153 | 354 | """ | 341 | """ |
154 | 355 | Verify that *leaf_hashes* is a ``bytes`` instance of correct length. | 342 | Verify that *leaf_hashes* is a ``bytes`` instance of correct length. |
155 | 356 | 343 | ||
156 | @@ -372,7 +359,7 @@ | |||
157 | 372 | 'leaf_hashes', bytes, type(leaf_hashes), leaf_hashes | 359 | 'leaf_hashes', bytes, type(leaf_hashes), leaf_hashes |
158 | 373 | ) | 360 | ) |
159 | 374 | ) | 361 | ) |
161 | 375 | if len(leaf_hashes) == 0 or len(leaf_hashes) % protocol.digest_bytes != 0: | 362 | if len(leaf_hashes) == 0 or len(leaf_hashes) % DIGEST_BYTES != 0: |
162 | 376 | raise LeafHashesError(leaf_hashes) | 363 | raise LeafHashesError(leaf_hashes) |
163 | 377 | return leaf_hashes | 364 | return leaf_hashes |
164 | 378 | 365 | ||
165 | @@ -400,12 +387,11 @@ | |||
166 | 400 | 22 | 387 | 22 |
167 | 401 | 388 | ||
168 | 402 | """ | 389 | """ |
171 | 403 | protocol = get_protocol(_id) | 390 | computed = hash_root(file_size, leaf_hashes) |
170 | 404 | computed = protocol.hash_root(file_size, leaf_hashes) | ||
172 | 405 | if _id != computed: | 391 | if _id != computed: |
173 | 406 | raise RootHashError(_id, file_size, leaf_hashes, computed) | 392 | raise RootHashError(_id, file_size, leaf_hashes, computed) |
174 | 407 | if unpack: | 393 | if unpack: |
176 | 408 | leaf_hashes = tuple(iter_leaf_hashes(leaf_hashes, protocol)) | 394 | leaf_hashes = tuple(iter_leaf_hashes(leaf_hashes)) |
177 | 409 | return ContentHash(_id, file_size, leaf_hashes) | 395 | return ContentHash(_id, file_size, leaf_hashes) |
178 | 410 | 396 | ||
179 | 411 | 397 | ||
180 | @@ -439,7 +425,7 @@ | |||
181 | 439 | ################################################# | 425 | ################################################# |
182 | 440 | # Utility functions for working with leaf_hashes: | 426 | # Utility functions for working with leaf_hashes: |
183 | 441 | 427 | ||
185 | 442 | def enumerate_leaf_hashes(leaf_hashes, protocol=PROTOCOL): | 428 | def enumerate_leaf_hashes(leaf_hashes): |
186 | 443 | """ | 429 | """ |
187 | 444 | Enumerate *leaf_hashes*. | 430 | Enumerate *leaf_hashes*. |
188 | 445 | 431 | ||
189 | @@ -451,13 +437,12 @@ | |||
190 | 451 | [(0, b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'), (1, b'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB')] | 437 | [(0, b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'), (1, b'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB')] |
191 | 452 | 438 | ||
192 | 453 | """ | 439 | """ |
200 | 454 | check_leaf_hashes(leaf_hashes, protocol) | 440 | check_leaf_hashes(leaf_hashes) |
201 | 455 | digest_bytes = protocol.digest_bytes | 441 | for i in range(len(leaf_hashes) // DIGEST_BYTES): |
202 | 456 | for i in range(len(leaf_hashes) // digest_bytes): | 442 | yield (i, leaf_hashes[i*DIGEST_BYTES : (i+1)*DIGEST_BYTES]) |
203 | 457 | yield (i, leaf_hashes[i*digest_bytes : (i+1)*digest_bytes]) | 443 | |
204 | 458 | 444 | ||
205 | 459 | 445 | def iter_leaf_hashes(leaf_hashes): | |
199 | 460 | def iter_leaf_hashes(leaf_hashes, protocol=PROTOCOL): | ||
206 | 461 | """ | 446 | """ |
207 | 462 | Iterate through *leaf_hashes*. | 447 | Iterate through *leaf_hashes*. |
208 | 463 | 448 | ||
209 | @@ -469,13 +454,12 @@ | |||
210 | 469 | [b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', b'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB'] | 454 | [b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', b'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB'] |
211 | 470 | 455 | ||
212 | 471 | """ | 456 | """ |
220 | 472 | check_leaf_hashes(leaf_hashes, protocol) | 457 | check_leaf_hashes(leaf_hashes) |
221 | 473 | digest_bytes = protocol.digest_bytes | 458 | for i in range(len(leaf_hashes) // DIGEST_BYTES): |
222 | 474 | for i in range(len(leaf_hashes) // digest_bytes): | 459 | yield leaf_hashes[i*DIGEST_BYTES : (i+1)*DIGEST_BYTES] |
223 | 475 | yield leaf_hashes[i*digest_bytes : (i+1)*digest_bytes] | 460 | |
224 | 476 | 461 | ||
225 | 477 | 462 | def get_leaf_hash(leaf_hashes, i): | |
219 | 478 | def get_leaf_hash(leaf_hashes, i, protocol=PROTOCOL): | ||
226 | 479 | """ | 463 | """ |
227 | 480 | Return a slice containing the i-th leaf_hash from *leaf_hashes*. | 464 | Return a slice containing the i-th leaf_hash from *leaf_hashes*. |
228 | 481 | 465 | ||
229 | @@ -490,8 +474,9 @@ | |||
230 | 490 | b'' | 474 | b'' |
231 | 491 | 475 | ||
232 | 492 | """ | 476 | """ |
235 | 493 | digest_bytes = protocol.digest_bytes | 477 | start = i * DIGEST_BYTES |
236 | 494 | return leaf_hashes[i*digest_bytes : (i+1)*digest_bytes] | 478 | stop = start + DIGEST_BYTES |
237 | 479 | return leaf_hashes[start:stop] | ||
238 | 495 | 480 | ||
239 | 496 | 481 | ||
240 | 497 | def missing_leaves(fp, leaf_hashes): | 482 | def missing_leaves(fp, leaf_hashes): |
241 | @@ -764,7 +749,7 @@ | |||
242 | 764 | thread.join() # Make sure batch_reader() terminates | 749 | thread.join() # Make sure batch_reader() terminates |
243 | 765 | 750 | ||
244 | 766 | 751 | ||
246 | 767 | def hash_fp(src_fp, dst_fp=None, protocol=PROTOCOL): | 752 | def hash_fp(src_fp, dst_fp=None): |
247 | 768 | """ | 753 | """ |
248 | 769 | Compute content hash of open file *src_fp*, optionally writing *dst_fp*. | 754 | Compute content hash of open file *src_fp*, optionally writing *dst_fp*. |
249 | 770 | 755 | ||
250 | @@ -777,7 +762,7 @@ | |||
251 | 777 | :param src_fp: A file opened in mode "rb" or "r+b" | 762 | :param src_fp: A file opened in mode "rb" or "r+b" |
252 | 778 | :param dst_fp: An optional file opened in mode "wd" | 763 | :param dst_fp: An optional file opened in mode "wd" |
253 | 779 | """ | 764 | """ |
255 | 780 | hasher = Hasher(protocol) | 765 | hasher = Hasher() |
256 | 781 | for leaf in reader_iter(src_fp): | 766 | for leaf in reader_iter(src_fp): |
257 | 782 | hasher.hash_leaf(leaf) | 767 | hasher.hash_leaf(leaf) |
258 | 783 | if dst_fp: | 768 | if dst_fp: |
259 | @@ -907,9 +892,7 @@ | |||
260 | 907 | subdir = path.join(self.basedir, 'files', prefix) | 892 | subdir = path.join(self.basedir, 'files', prefix) |
261 | 908 | for name in sorted(os.listdir(subdir)): | 893 | for name in sorted(os.listdir(subdir)): |
262 | 909 | _id = prefix + name | 894 | _id = prefix + name |
266 | 910 | if len(_id) not in ALLOWED_B32LEN: | 895 | if len(_id) != DIGEST_B32LEN or not isb32(_id): |
264 | 911 | continue | ||
265 | 912 | if not set(_id).issubset(B32ALPHABET): | ||
267 | 913 | continue | 896 | continue |
268 | 914 | fullname = path.join(subdir, name) | 897 | fullname = path.join(subdir, name) |
269 | 915 | st = os.lstat(fullname) | 898 | st = os.lstat(fullname) |
270 | @@ -1109,7 +1092,7 @@ | |||
271 | 1109 | a ContentHash namedtuple | 1092 | a ContentHash namedtuple |
272 | 1110 | """ | 1093 | """ |
273 | 1111 | src_fp = self.open(_id) | 1094 | src_fp = self.open(_id) |
275 | 1112 | c = hash_fp(src_fp, protocol=get_protocol(_id)) | 1095 | c = hash_fp(src_fp) |
276 | 1113 | if c.id != _id: | 1096 | if c.id != _id: |
277 | 1114 | raise self.move_to_corrupt(src_fp, _id, bad_id=c.id) | 1097 | raise self.move_to_corrupt(src_fp, _id, bad_id=c.id) |
278 | 1115 | if return_fp: | 1098 | if return_fp: |
279 | 1116 | 1099 | ||
280 | === added file 'filestore/data/MD5SUMS.json' | |||
281 | --- filestore/data/MD5SUMS.json 1970-01-01 00:00:00 +0000 | |||
282 | +++ filestore/data/MD5SUMS.json 2013-02-19 15:39:24 +0000 | |||
283 | @@ -0,0 +1,23 @@ | |||
284 | 1 | { | ||
285 | 2 | "files": { | ||
286 | 3 | "A": "7fc56270e7a70fa81a5935b72eacbe29", | ||
287 | 4 | "B": "d2bad3eedb424dd352d65eafbf6c79ba", | ||
288 | 5 | "C": "5dd3531303dd6764acb93e5f171a4ab8", | ||
289 | 6 | "CA": "0722f8dc36d75acb602dcee8d0427ce0", | ||
290 | 7 | "CB": "77264eb6eed7777a1ee03e2601fc9f64", | ||
291 | 8 | "CC": "1fbfabdaafff31967f9a95f3a3d3c642" | ||
292 | 9 | }, | ||
293 | 10 | "integers": { | ||
294 | 11 | "0": "cfcd208495d565ef66e7dff9f98764da", | ||
295 | 12 | "1": "c4ca4238a0b923820dcc509a6f75849b", | ||
296 | 13 | "16777215": "48ac8929ffdc78a66090d179ff1237d5", | ||
297 | 14 | "16777216": "e3e330499348f791337e9da6b534a386", | ||
298 | 15 | "8388607": "32433904a755e2b9eb82cf167723b34f", | ||
299 | 16 | "8388608": "03926fda4e223707d290ac06bb996653", | ||
300 | 17 | "8388609": "e9b74719ce6b80c5337148d12725db03" | ||
301 | 18 | }, | ||
302 | 19 | "personalization": { | ||
303 | 20 | "leaf": "8aee35e23a5a74147b230f12123ca82e", | ||
304 | 21 | "root": "0445d91d37383f5384023d49e71cc629" | ||
305 | 22 | } | ||
306 | 23 | } | ||
307 | 0 | 24 | ||
308 | === added file 'filestore/data/V0.json' | |||
309 | --- filestore/data/V0.json 1970-01-01 00:00:00 +0000 | |||
310 | +++ filestore/data/V0.json 2013-02-19 15:39:24 +0000 | |||
311 | @@ -0,0 +1,24 @@ | |||
312 | 1 | { | ||
313 | 2 | "leaf_hashes": { | ||
314 | 3 | "A": [ | ||
315 | 4 | "C23GME2TRCBFM2PA3VIZPUOD5DFYAQLQY2VR46ZKBMH4S4WL", | ||
316 | 5 | "BWRAN4NJSXYJS6V3YQNNV2EKLEEGNAK5VPSIYZKOBXJ3PPJF" | ||
317 | 6 | ], | ||
318 | 7 | "B": [ | ||
319 | 8 | "NLJ2OHB45MJELINPXJCTBCI7VT3424GRTN36BDP5Z3NE2W5Z", | ||
320 | 9 | "ALAA376NIW54WR5N6JY2EPR6VFJI7AC2V7PJCRETDIGRQJDN" | ||
321 | 10 | ], | ||
322 | 11 | "C": [ | ||
323 | 12 | "ZQ6RUCU75DHFODZEGNL66QJUM34A3UA53TG2YK7LHRU4SUTF", | ||
324 | 13 | "MCZHG4NBL5VWKCQIARDXR6IHN34ALIDH7OVYAZ5TNDLXIKA4" | ||
325 | 14 | ] | ||
326 | 15 | }, | ||
327 | 16 | "root_hashes": { | ||
328 | 17 | "A": "O4NQ4LFNI2UCMKOSWWGPRZXSNEPW2M6OOTBFM3AHTBK35D67", | ||
329 | 18 | "B": "NW6IMGBAU4NTZDQRHGIZA7VYV5CBZ5TUXADHZE43N4GDZGKK", | ||
330 | 19 | "C": "TRXCE6PVSJTHQX7H4KV7A7F5DKCMSETLMNANNCJIVT624WLU", | ||
331 | 20 | "CA": "QSUCTH4LJU3B7QG7IJ3FC7HNE4TXDPZVQEJ36B4A45ATTXDM", | ||
332 | 21 | "CB": "3FWPZ6RBPXGBSTFBNPK5IOCO7DUMSKR5YTWJ3HM2KI4AEF2O", | ||
333 | 22 | "CC": "CX2K52LIUTT6SSMFHR7NUI6YIJLO3SGKLSRIL6IDY4BP7HUZ" | ||
334 | 23 | } | ||
335 | 24 | } | ||
336 | 0 | 25 | ||
337 | === added file 'filestore/data/V1.json' | |||
338 | --- filestore/data/V1.json 1970-01-01 00:00:00 +0000 | |||
339 | +++ filestore/data/V1.json 2013-02-19 15:39:24 +0000 | |||
340 | @@ -0,0 +1,24 @@ | |||
341 | 1 | { | ||
342 | 2 | "leaf_hashes": { | ||
343 | 3 | "A": [ | ||
344 | 4 | "3M3IHYG9TV3UWTIFI37LN5OVYFEB3PDIAUAQ7OKOQF84MIQT", | ||
345 | 5 | "VACOMMVCMJP9QBIUIEMMYAPXOWRYDFW3JQUJB4NGNMF48IIW" | ||
346 | 6 | ], | ||
347 | 7 | "B": [ | ||
348 | 8 | "YSRG5KWP5IKWCXTHNN58PY7KSEUJ6JYKDA4RT3F7T8UATQC7", | ||
349 | 9 | "V6QX3QUDUNE9PVJVF8HBUP4RBKY7AXRJ7MBPWASIKTVWYVUD" | ||
350 | 10 | ], | ||
351 | 11 | "C": [ | ||
352 | 12 | "C8BNKNHHPCSONGE785HTVTIXCHFBCT5SY9GYRDELHQ3BVEHV", | ||
353 | 13 | "EEOJR9LHS46469EYDUWYWOK7BXW5Y486SNEG9KTSHSSPNIMQ" | ||
354 | 14 | ] | ||
355 | 15 | }, | ||
356 | 16 | "root_hashes": { | ||
357 | 17 | "A": "6VKN463LEKJMD3OP87UYVU5V65XXOSG3HCT3Q6NDJD58TQXK", | ||
358 | 18 | "B": "GJDVCPR3JPBFABMGP6PDUBOVQIXX5YLF9PG49SSDJVQEKLOR", | ||
359 | 19 | "C": "NXIA8BHKVQTD9ACIPT3J7QUYM96FYWJBBEL34YKTA6CKE9IC", | ||
360 | 20 | "CA": "L66Y5M37PX93NBYYWCSSKW6QQBEYH76OOJFUQJ7FQEA7IB9S", | ||
361 | 21 | "CB": "SFKVIEOWCIKENPMKDEAGATIA9DGH54LF9BW5LCOW7F9YN9BL", | ||
362 | 22 | "CC": "VCPN5J8EI4G5PH5A4CUWSPCRTEWGD65PB4WMTGQ7CEVJ6XLO" | ||
363 | 23 | } | ||
364 | 24 | } | ||
365 | 0 | 25 | ||
366 | === removed file 'filestore/data/test-vectors.json' | |||
367 | --- filestore/data/test-vectors.json 2013-01-15 04:18:38 +0000 | |||
368 | +++ filestore/data/test-vectors.json 1970-01-01 00:00:00 +0000 | |||
369 | @@ -1,50 +0,0 @@ | |||
370 | 1 | { | ||
371 | 2 | "48": { | ||
372 | 3 | "leaf_hashes": { | ||
373 | 4 | "A": [ | ||
374 | 5 | "C23GME2TRCBFM2PA3VIZPUOD5DFYAQLQY2VR46ZKBMH4S4WL", | ||
375 | 6 | "BWRAN4NJSXYJS6V3YQNNV2EKLEEGNAK5VPSIYZKOBXJ3PPJF" | ||
376 | 7 | ], | ||
377 | 8 | "B": [ | ||
378 | 9 | "NLJ2OHB45MJELINPXJCTBCI7VT3424GRTN36BDP5Z3NE2W5Z", | ||
379 | 10 | "ALAA376NIW54WR5N6JY2EPR6VFJI7AC2V7PJCRETDIGRQJDN" | ||
380 | 11 | ], | ||
381 | 12 | "C": [ | ||
382 | 13 | "ZQ6RUCU75DHFODZEGNL66QJUM34A3UA53TG2YK7LHRU4SUTF", | ||
383 | 14 | "MCZHG4NBL5VWKCQIARDXR6IHN34ALIDH7OVYAZ5TNDLXIKA4" | ||
384 | 15 | ] | ||
385 | 16 | }, | ||
386 | 17 | "root_hashes": { | ||
387 | 18 | "A": "O4NQ4LFNI2UCMKOSWWGPRZXSNEPW2M6OOTBFM3AHTBK35D67", | ||
388 | 19 | "B": "NW6IMGBAU4NTZDQRHGIZA7VYV5CBZ5TUXADHZE43N4GDZGKK", | ||
389 | 20 | "C": "TRXCE6PVSJTHQX7H4KV7A7F5DKCMSETLMNANNCJIVT624WLU", | ||
390 | 21 | "CA": "QSUCTH4LJU3B7QG7IJ3FC7HNE4TXDPZVQEJ36B4A45ATTXDM", | ||
391 | 22 | "CB": "3FWPZ6RBPXGBSTFBNPK5IOCO7DUMSKR5YTWJ3HM2KI4AEF2O", | ||
392 | 23 | "CC": "CX2K52LIUTT6SSMFHR7NUI6YIJLO3SGKLSRIL6IDY4BP7HUZ" | ||
393 | 24 | } | ||
394 | 25 | }, | ||
395 | 26 | "56": { | ||
396 | 27 | "leaf_hashes": { | ||
397 | 28 | "A": [ | ||
398 | 29 | "XZ5I6KJTUSOIWVCEBOKUELTADZUXNHOAYO77NKKHWCIW3HYGYOPMX5JN", | ||
399 | 30 | "TEC7754ZNM26MTM6YQFI6TMVTTK4RKQEMPAGT2ROQZUBPUIHSJU2DDR3" | ||
400 | 31 | ], | ||
401 | 32 | "B": [ | ||
402 | 33 | "P67PVKU3SCCQHNIRMR2Z5NICEMIP36WCFJG4AW6YBAE6UI4K6BVLY3EI", | ||
403 | 34 | "ZIFO5S2OYYPZAUN6XQWTWZGCDATXCGR2JYN7UIAX54WMVWETMIUFG7WM" | ||
404 | 35 | ], | ||
405 | 36 | "C": [ | ||
406 | 37 | "RW2GJFIGPQF5WLR53UAK77TPHNRFKMUBYRB23JFS4G2RFRRNHW6OX4CR", | ||
407 | 38 | "XBVLPYBUX6QD2DKPJTYVUXT23K3AAUAW5J4RMQ543NQNDAHORQJ7GBDE" | ||
408 | 39 | ] | ||
409 | 40 | }, | ||
410 | 41 | "root_hashes": { | ||
411 | 42 | "A": "FWV6OJYI36C5NN5DC4GS2IGWZXFCZCGJGHK35YV62LKAG7D2Z4LO4Z2S", | ||
412 | 43 | "B": "OB756PX5V32JMKJAFKIAJ4AFSFPA2WLNIK32ELNO4FJLJPEEEN6DCAAJ", | ||
413 | 44 | "C": "QSOHXCDH64IQBOG2NM67XEC6MLZKKPGBTISWWRPMCFCJ2EKMA2SMLY46", | ||
414 | 45 | "CA": "BQ5UTB33ML2VDTCTLVXK6N4VSMGGKKKDYKG24B6DOAFJB6NRSGMB5BNO", | ||
415 | 46 | "CB": "ER3LDDZ2LHMTDLOPE5XA5GEEZ6OE45VFIFLY42GEMV4TSZ2B7GJJXAIX", | ||
416 | 47 | "CC": "R6RN5KL7UBNJWR5SK5YPUKIGAOWWFMYYOVESU5DPT34X5MEK75PXXYIX" | ||
417 | 48 | } | ||
418 | 49 | } | ||
419 | 50 | } | ||
420 | 51 | \ No newline at end of file | 0 | \ No newline at end of file |
421 | 52 | 1 | ||
422 | === modified file 'filestore/misc.py' | |||
423 | --- filestore/misc.py 2013-02-14 20:08:06 +0000 | |||
424 | +++ filestore/misc.py 2013-02-19 15:39:24 +0000 | |||
425 | @@ -28,23 +28,31 @@ | |||
426 | 28 | import tempfile | 28 | import tempfile |
427 | 29 | import shutil | 29 | import shutil |
428 | 30 | from hashlib import md5 | 30 | from hashlib import md5 |
431 | 31 | from collections import OrderedDict | 31 | |
432 | 32 | 32 | from dbase32 import db32enc | |
433 | 33 | |||
434 | 34 | from .protocols import PERS_LEAF, PERS_ROOT, VERSION1 | ||
435 | 33 | from . import FileStore | 35 | from . import FileStore |
450 | 34 | from .protocols import b32dumps | 36 | |
451 | 35 | 37 | ||
452 | 36 | 38 | DATADIR = path.join(path.dirname(path.abspath(__file__)), 'data') | |
453 | 37 | TEST_VECTORS = path.join( | 39 | |
454 | 38 | path.dirname(path.abspath(__file__)), 'data', 'test-vectors.json' | 40 | |
455 | 39 | ) | 41 | def dumps(obj): |
456 | 40 | assert path.isfile(TEST_VECTORS) | 42 | return json.dumps(obj, |
457 | 41 | 43 | ensure_ascii=False, | |
458 | 42 | 44 | sort_keys=True, | |
459 | 43 | def load_test_vectors(): | 45 | separators=(',',': '), |
460 | 44 | return json.load(open(TEST_VECTORS, 'r')) | 46 | indent=4, |
461 | 45 | 47 | ) | |
462 | 46 | 48 | ||
463 | 47 | def build_test_leaves(leaf_size): | 49 | |
464 | 50 | def load_data(name): | ||
465 | 51 | filename = path.join(DATADIR, name + '.json') | ||
466 | 52 | return json.load(open(filename, 'r')) | ||
467 | 53 | |||
468 | 54 | |||
469 | 55 | def build_leaves(leaf_size): | ||
470 | 48 | return { | 56 | return { |
471 | 49 | 'A': b'A', | 57 | 'A': b'A', |
472 | 50 | 'B': b'B' * (leaf_size - 1), | 58 | 'B': b'B' * (leaf_size - 1), |
473 | @@ -52,36 +60,37 @@ | |||
474 | 52 | } | 60 | } |
475 | 53 | 61 | ||
476 | 54 | 62 | ||
481 | 55 | def build_test_vectors(protocol): | 63 | def build_vectors(protocol, encoder=db32enc): |
482 | 56 | leaves = build_test_leaves(protocol.leaf_size) | 64 | leaves = build_leaves(protocol.leaf_size) |
483 | 57 | 65 | ||
484 | 58 | def hash_root(letters): | 66 | def hash_leaf(i, L): |
485 | 67 | return encoder(protocol.hash_leaf(i, leaves[L])) | ||
486 | 68 | |||
487 | 69 | def hash_root(name): | ||
488 | 59 | leaf_hashes = b'' | 70 | leaf_hashes = b'' |
489 | 60 | file_size = 0 | 71 | file_size = 0 |
491 | 61 | for (i, L) in enumerate(letters): | 72 | for (i, L) in enumerate(name): |
492 | 62 | data = leaves[L] | 73 | data = leaves[L] |
493 | 63 | leaf_hashes += protocol.hash_leaf(i, data) | 74 | leaf_hashes += protocol.hash_leaf(i, data) |
494 | 64 | file_size += len(data) | 75 | file_size += len(data) |
496 | 65 | return protocol.hash_root(file_size, leaf_hashes) | 76 | return encoder(protocol.hash_root(file_size, leaf_hashes)) |
497 | 66 | 77 | ||
498 | 67 | vectors = { | 78 | vectors = { |
499 | 68 | 'leaf_hashes': {}, | 79 | 'leaf_hashes': {}, |
500 | 69 | 'root_hashes': {}, | 80 | 'root_hashes': {}, |
501 | 70 | } | 81 | } |
502 | 71 | 82 | ||
507 | 72 | for (key, data) in leaves.items(): | 83 | for L in leaves: |
508 | 73 | vectors['leaf_hashes'][key] = [ | 84 | vectors['leaf_hashes'][L] = [hash_leaf(i, L) for i in range(2)] |
505 | 74 | b32dumps(protocol.hash_leaf(i, data)) for i in range(2) | ||
506 | 75 | ] | ||
509 | 76 | 85 | ||
510 | 77 | for L in leaves: | 86 | for L in leaves: |
513 | 78 | for key in (L, 'C' + L): | 87 | for name in (L, 'C' + L): |
514 | 79 | vectors['root_hashes'][key] = hash_root(key) | 88 | vectors['root_hashes'][name] = hash_root(name) |
515 | 80 | 89 | ||
516 | 81 | return vectors | 90 | return vectors |
517 | 82 | 91 | ||
518 | 83 | 92 | ||
520 | 84 | def get_test_integers(leaf_size): | 93 | def get_integers(leaf_size): |
521 | 85 | return [ | 94 | return [ |
522 | 86 | 0, | 95 | 0, |
523 | 87 | 1, | 96 | 1, |
524 | @@ -93,20 +102,26 @@ | |||
525 | 93 | ] | 102 | ] |
526 | 94 | 103 | ||
527 | 95 | 104 | ||
529 | 96 | def build_test_md5(leaf_size): | 105 | def build_md5sums(leaf_size): |
530 | 97 | integers = dict( | 106 | integers = dict( |
531 | 98 | (str(i), md5(str(i).encode('utf-8')).hexdigest()) | 107 | (str(i), md5(str(i).encode('utf-8')).hexdigest()) |
533 | 99 | for i in get_test_integers(leaf_size) | 108 | for i in get_integers(leaf_size) |
534 | 100 | ) | 109 | ) |
536 | 101 | leaves = build_test_leaves(leaf_size) | 110 | |
537 | 111 | leaves = build_leaves(leaf_size) | ||
538 | 102 | C = leaves['C'] | 112 | C = leaves['C'] |
539 | 103 | files = {} | 113 | files = {} |
540 | 104 | for (key, data) in leaves.items(): | 114 | for (key, data) in leaves.items(): |
541 | 105 | files[key] = md5(data).hexdigest() | 115 | files[key] = md5(data).hexdigest() |
542 | 106 | files['C' + key] = md5(C + data).hexdigest() | 116 | files['C' + key] = md5(C + data).hexdigest() |
543 | 117 | |||
544 | 107 | return { | 118 | return { |
545 | 108 | 'integers': integers, | 119 | 'integers': integers, |
546 | 109 | 'files': files, | 120 | 'files': files, |
547 | 121 | 'personalization': { | ||
548 | 122 | 'leaf': md5(PERS_LEAF).hexdigest(), | ||
549 | 123 | 'root': md5(PERS_ROOT).hexdigest(), | ||
550 | 124 | } | ||
551 | 110 | } | 125 | } |
552 | 111 | 126 | ||
553 | 112 | 127 | ||
554 | @@ -145,3 +160,8 @@ | |||
555 | 145 | if path.isdir(self.parentdir): | 160 | if path.isdir(self.parentdir): |
556 | 146 | shutil.rmtree(self.parentdir) | 161 | shutil.rmtree(self.parentdir) |
557 | 147 | 162 | ||
558 | 163 | |||
559 | 164 | if __name__ == '__main__': | ||
560 | 165 | vectors = build_vectors(VERSION1) | ||
561 | 166 | print(dumps(vectors)) | ||
562 | 167 | |||
563 | 148 | 168 | ||
564 | === modified file 'filestore/protocols.py' | |||
565 | --- filestore/protocols.py 2013-02-16 01:28:23 +0000 | |||
566 | +++ filestore/protocols.py 2013-02-19 15:39:24 +0000 | |||
567 | @@ -94,15 +94,16 @@ | |||
568 | 94 | we still feel this is a very import architecture to embrace now. | 94 | we still feel this is a very import architecture to embrace now. |
569 | 95 | """ | 95 | """ |
570 | 96 | 96 | ||
571 | 97 | from base64 import b32encode, b32decode | ||
572 | 98 | from _skein import skein512 | 97 | from _skein import skein512 |
573 | 99 | 98 | ||
574 | 100 | 99 | ||
575 | 100 | __all__ = ('VERSION0', 'VERSION1') | ||
576 | 101 | |||
577 | 101 | # Skein personalization strings used in the Dmedia Hashing Protocol: | 102 | # Skein personalization strings used in the Dmedia Hashing Protocol: |
578 | 102 | PERS_LEAF = b'20110430 jderose@novacut.com dmedia/leaf' | 103 | PERS_LEAF = b'20110430 jderose@novacut.com dmedia/leaf' |
579 | 103 | PERS_ROOT = b'20110430 jderose@novacut.com dmedia/root' | 104 | PERS_ROOT = b'20110430 jderose@novacut.com dmedia/root' |
580 | 104 | 105 | ||
582 | 105 | # Additional Skein personalization strings used only in the old protocol: | 106 | # Additional Skein personalization strings used only in the old V0 protocol: |
583 | 106 | PERS_LEAF_INDEX = b'20110430 jderose@novacut.com dmedia/leaf-index' | 107 | PERS_LEAF_INDEX = b'20110430 jderose@novacut.com dmedia/leaf-index' |
584 | 107 | PERS_FILE_SIZE = b'20110430 jderose@novacut.com dmedia/file-size' | 108 | PERS_FILE_SIZE = b'20110430 jderose@novacut.com dmedia/file-size' |
585 | 108 | 109 | ||
586 | @@ -110,48 +111,45 @@ | |||
587 | 110 | TYPE_ERROR = '{}: need a {!r}; got a {!r}: {!r}' | 111 | TYPE_ERROR = '{}: need a {!r}; got a {!r}: {!r}' |
588 | 111 | 112 | ||
589 | 112 | # Handy: | 113 | # Handy: |
591 | 113 | MiB = 1024**2 | 114 | MiB = 1048576 |
592 | 114 | 115 | ||
593 | 115 | # For now so we don't have any issues round-tripping values through JavaScript, | 116 | # For now so we don't have any issues round-tripping values through JavaScript, |
594 | 116 | # we're setting a "soft" file-size limit at 2**53 bytes (the max integer that | 117 | # we're setting a "soft" file-size limit at 2**53 bytes (the max integer that |
595 | 117 | # fully works in JavaScript). But this is quite big, approximately 9.01 PB for | 118 | # fully works in JavaScript). But this is quite big, approximately 9.01 PB for |
596 | 118 | # a single file: | 119 | # a single file: |
598 | 119 | MAX_FILE_SIZE = 2**53 | 120 | MAX_FILE_SIZE = 9007199254740992 |
599 | 120 | 121 | ||
600 | 121 | # A real protocol would unlikely use a leaf-size this small, but a 2 MiB | 122 | # A real protocol would unlikely use a leaf-size this small, but a 2 MiB |
601 | 122 | # minimum means we can use a uint32_t for the leaf_index in C | 123 | # minimum means we can use a uint32_t for the leaf_index in C |
602 | 123 | # implementations and still reach the above MAX_FILE_SIZE: | 124 | # implementations and still reach the above MAX_FILE_SIZE: |
626 | 124 | MIN_LEAF_SIZE = 2 * MiB | 125 | MIN_LEAF_SIZE = 2097152 |
627 | 125 | 126 | ||
628 | 126 | 127 | ||
629 | 127 | def b32dumps(data): | 128 | def make_key(integer): |
630 | 128 | """ | 129 | """ |
631 | 129 | Base32-encode *data*, return as a string. | 130 | Return *integer* as it's UTF-8 encoded decimal representation. |
632 | 130 | 131 | ||
633 | 131 | >>> b32dumps(b'bbbbb') | 132 | For example: |
634 | 132 | 'MJRGEYTC' | 133 | |
635 | 133 | 134 | >>> make_key(1776) | |
636 | 134 | """ | 135 | b'1776' |
637 | 135 | return b32encode(data).decode('utf-8') | 136 | |
638 | 136 | 137 | """ | |
639 | 137 | 138 | if not isinstance(integer, int): | |
640 | 138 | def b32loads(string): | 139 | raise TypeError( |
641 | 139 | """ | 140 | TYPE_ERROR.format('integer', int, type(integer), integer) |
642 | 140 | Decode base32-encoded data in *string*. | 141 | ) |
643 | 141 | 142 | if integer < 0: | |
644 | 142 | >>> b32loads('MJRGEYTC') | 143 | raise ValueError('integer: must be >= 0; got {}'.format(integer)) |
645 | 143 | b'bbbbb' | 144 | return str(integer).encode('utf-8') |
623 | 144 | |||
624 | 145 | """ | ||
625 | 146 | return b32decode(string.encode('utf-8')) | ||
646 | 147 | 145 | ||
647 | 148 | 146 | ||
648 | 149 | class Protocol: | 147 | class Protocol: |
649 | 150 | """ | 148 | """ |
651 | 151 | Standard API for current and future versions of the Dmedia Hashing Protocol. | 149 | API for current and future versions of the Dmedia Hashing Protocol. |
652 | 152 | 150 | ||
655 | 153 | This class provides a standard API that abstracts away the details of a | 151 | This base class provides a standard API that abstracts away the details of |
656 | 154 | particular hashing protocol and allows multiple protocol versions to be | 152 | a particular hashing protocol and allows multiple protocol versions to be |
657 | 155 | supported simultaneously. | 153 | supported simultaneously. |
658 | 156 | 154 | ||
659 | 157 | The API consists of two methods: | 155 | The API consists of two methods: |
660 | @@ -174,7 +172,7 @@ | |||
661 | 174 | (while still using the standard hashing functions), you can simply create | 172 | (while still using the standard hashing functions), you can simply create |
662 | 175 | a `Protocol` instance like this: | 173 | a `Protocol` instance like this: |
663 | 176 | 174 | ||
665 | 177 | >>> experimental = Protocol(16 * MiB, 360) | 175 | >>> experimental = SkeinProtocol(16 * MiB, 360) |
666 | 178 | 176 | ||
667 | 179 | The `digest_bytes` and `digest_b32len` will be derived from the provided | 177 | The `digest_bytes` and `digest_b32len` will be derived from the provided |
668 | 180 | *digest_bits*: | 178 | *digest_bits*: |
669 | @@ -184,23 +182,15 @@ | |||
670 | 184 | >>> experimental.digest_b32len | 182 | >>> experimental.digest_b32len |
671 | 185 | 72 | 183 | 72 |
672 | 186 | 184 | ||
680 | 187 | In order to implement a protocol that uses different underlying hash | 185 | This base class doesn't provide a working protocol, to do that you must |
681 | 188 | functions, you'll need to subclass `Protocol` and override the two | 186 | create a subclass and implement these two methods: |
682 | 189 | low-level hashing methods: | 187 | |
683 | 190 | 188 | `Protocol._hash_leaf(key_leaf_index, leaf_data, challange)` | |
684 | 191 | `Protocol._hash_leaf(leaf_index, leaf_data, challange)` | 189 | |
685 | 192 | 190 | `Protocol._hash_root(key_file_size, leaf_hashes)` | |
679 | 193 | `Protocol._hash_root(file_size, leaf_hashes)` | ||
686 | 194 | """ | 191 | """ |
687 | 195 | 192 | ||
688 | 196 | def __init__(self, leaf_size, digest_bits): | 193 | def __init__(self, leaf_size, digest_bits): |
689 | 197 | """ | ||
690 | 198 | Initialize a new `Protocol` instance. | ||
691 | 199 | |||
692 | 200 | :param leaf_size: an ``int`` which is some multiple of MIN_LEAF_SIZE | ||
693 | 201 | and >= MIN_LEAF_SIZE | ||
694 | 202 | :param digest_bits: an ``int`` which is some multiple of 40 and >= 200 | ||
695 | 203 | """ | ||
696 | 204 | if not isinstance(leaf_size, int): | 194 | if not isinstance(leaf_size, int): |
697 | 205 | raise TypeError( | 195 | raise TypeError( |
698 | 206 | TYPE_ERROR.format('leaf_size', int, type(leaf_size), leaf_size) | 196 | TYPE_ERROR.format('leaf_size', int, type(leaf_size), leaf_size) |
699 | @@ -238,56 +228,7 @@ | |||
700 | 238 | self.digest_bytes = digest_bits // 8 | 228 | self.digest_bytes = digest_bits // 8 |
701 | 239 | self.digest_b32len = digest_bits // 5 | 229 | self.digest_b32len = digest_bits // 5 |
702 | 240 | 230 | ||
703 | 241 | def encode(self, digest): | ||
704 | 242 | if not isinstance(digest, bytes): | ||
705 | 243 | raise TypeError( | ||
706 | 244 | TYPE_ERROR.format('digest', bytes, type(digest), digest) | ||
707 | 245 | ) | ||
708 | 246 | if len(digest) != self.digest_bytes: | ||
709 | 247 | raise ValueError('len(digest) must be {}; got {}'.format( | ||
710 | 248 | self.digest_bytes, len(digest)) | ||
711 | 249 | ) | ||
712 | 250 | return self._encode(digest) | ||
713 | 251 | |||
714 | 252 | def _encode(self, digest): | ||
715 | 253 | return b32dumps(digest) | ||
716 | 254 | |||
717 | 255 | def decode(self, _id): | ||
718 | 256 | if not isinstance(_id, str): | ||
719 | 257 | raise TypeError( | ||
720 | 258 | TYPE_ERROR.format('_id', str, type(_id), _id) | ||
721 | 259 | ) | ||
722 | 260 | if len(_id) != self.digest_b32len: | ||
723 | 261 | raise ValueError('len(_id) must be {}; got {}'.format( | ||
724 | 262 | self.digest_b32len, len(_id)) | ||
725 | 263 | ) | ||
726 | 264 | return self._decode(_id) | ||
727 | 265 | |||
728 | 266 | def _decode(self, _id): | ||
729 | 267 | return b32loads(_id) | ||
730 | 268 | |||
731 | 269 | def hash_leaf(self, leaf_index, leaf_data, challenge=b''): | 231 | def hash_leaf(self, leaf_index, leaf_data, challenge=b''): |
732 | 270 | """ | ||
733 | 271 | Hash the leaf at (zero) index *leaf_index* containing *leaf_data*. | ||
734 | 272 | |||
735 | 273 | For example: | ||
736 | 274 | |||
737 | 275 | >>> p = Protocol(MIN_LEAF_SIZE, 200) | ||
738 | 276 | >>> b32encode(p.hash_leaf(0, b'The Leaf Data')) | ||
739 | 277 | b'EFBXKNNDAMRFIIPTIL2DYZ36YYQDAX54AAOYA7WG' | ||
740 | 278 | |||
741 | 279 | The optional *challenge* keyword argument should be a random nonce used | ||
742 | 280 | to make a remote cloud storage service prove that they actually have | ||
743 | 281 | the complete, exact file stored. For example: | ||
744 | 282 | |||
745 | 283 | >>> b32encode(p.hash_leaf(0, b'The Leaf Data', b'Random Nonce')) | ||
746 | 284 | b'LBAECE4GW3UFXREWAFBMBGV2O4GISUTJ3ALZXGJW' | ||
747 | 285 | |||
748 | 286 | :param leaf_index: an ``int`` >= 0 | ||
749 | 287 | :param leaf_data: a ``bytes`` instance with leaf content | ||
750 | 288 | :param challenge: a ``bytes`` instance containing a random nonce to be | ||
751 | 289 | used in a proof-of-storage challenge; default is ``b''`` | ||
752 | 290 | """ | ||
753 | 291 | if not isinstance(leaf_index, int): | 232 | if not isinstance(leaf_index, int): |
754 | 292 | raise TypeError( | 233 | raise TypeError( |
755 | 293 | TYPE_ERROR.format( | 234 | TYPE_ERROR.format( |
756 | @@ -315,24 +256,15 @@ | |||
757 | 315 | self.leaf_size, len(leaf_data) | 256 | self.leaf_size, len(leaf_data) |
758 | 316 | ) | 257 | ) |
759 | 317 | ) | 258 | ) |
763 | 318 | digest = self._hash_leaf(leaf_index, leaf_data, challenge) | 259 | return self._hash_leaf(make_key(leaf_index), leaf_data, challenge) |
764 | 319 | assert len(digest) == self.digest_bytes | 260 | |
765 | 320 | return digest | 261 | def _hash_leaf(self, key_leaf_index, leaf_data, challenge): |
766 | 262 | name = self.__class__.__name__ | ||
767 | 263 | raise NotImplementedError( | ||
768 | 264 | '{}._hash_leaf(key_leaf_index, leaf_data, challenge)'.format(name) | ||
769 | 265 | ) | ||
770 | 321 | 266 | ||
771 | 322 | def hash_root(self, file_size, leaf_hashes): | 267 | def hash_root(self, file_size, leaf_hashes): |
772 | 323 | """ | ||
773 | 324 | Return the root-hash for a file *file_size* bytes with *leaf_hashes*. | ||
774 | 325 | |||
775 | 326 | For example: | ||
776 | 327 | |||
777 | 328 | >>> p = Protocol(MIN_LEAF_SIZE, 200) | ||
778 | 329 | >>> p.hash_root(1776, b'DDDDDDDDDDDDDDDDDDDDDDDDD') | ||
779 | 330 | 'SJGXSO43OF5424G6HAEG55X2SFCZRW7L7LG42UKN' | ||
780 | 331 | |||
781 | 332 | :param file_size: an ``int`` >= 1 | ||
782 | 333 | :param leaf_hashes: a ``bytes`` instance containing the concatenated | ||
783 | 334 | leaf-hashes produced by `Protocol.hash_leaf()`. | ||
784 | 335 | """ | ||
785 | 336 | if not isinstance(file_size, int): | 268 | if not isinstance(file_size, int): |
786 | 337 | raise TypeError( | 269 | raise TypeError( |
787 | 338 | TYPE_ERROR.format('file_size', int, type(file_size), file_size) | 270 | TYPE_ERROR.format('file_size', int, type(file_size), file_size) |
788 | @@ -369,66 +301,41 @@ | |||
789 | 369 | low, high, file_size | 301 | low, high, file_size |
790 | 370 | ) | 302 | ) |
791 | 371 | ) | 303 | ) |
804 | 372 | digest = self._hash_root(file_size, leaf_hashes) | 304 | return self._hash_root(make_key(file_size), leaf_hashes) |
805 | 373 | return self.encode(digest) | 305 | |
806 | 374 | 306 | def _hash_root(self, key_file_size, leaf_hashes): | |
807 | 375 | def _hash_leaf(self, leaf_index, leaf_data, challenge): | 307 | name = self.__class__.__name__ |
808 | 376 | """ | 308 | raise NotImplementedError( |
809 | 377 | Protocol version 1 leaf-hashing implementation. | 309 | '{}._hash_root(key_file_size, leaf_hashes)'.format(name) |
810 | 378 | 310 | ) | |
811 | 379 | Subclasses can override this to use different hash functions, | 311 | |
812 | 380 | configurations, etc. | 312 | |
813 | 381 | """ | 313 | class SkeinProtocol(Protocol): |
814 | 382 | assert leaf_index >= 0 | 314 | def _hash_leaf(self, key_leaf_index, leaf_data, challenge): |
803 | 383 | assert 1 <= len(leaf_data) <= self.leaf_size | ||
815 | 384 | return skein512(leaf_data, | 315 | return skein512(leaf_data, |
816 | 385 | digest_bits=self.digest_bits, | 316 | digest_bits=self.digest_bits, |
817 | 386 | pers=PERS_LEAF, | 317 | pers=PERS_LEAF, |
819 | 387 | key=str(leaf_index).encode('utf-8'), | 318 | key=key_leaf_index, |
820 | 388 | nonce=challenge, | 319 | nonce=challenge, |
821 | 389 | ).digest() | 320 | ).digest() |
822 | 390 | 321 | ||
833 | 391 | def _hash_root(self, file_size, leaf_hashes): | 322 | def _hash_root(self, key_file_size, leaf_hashes): |
824 | 392 | """ | ||
825 | 393 | Protocol version 1 root-hashing implementation. | ||
826 | 394 | |||
827 | 395 | Subclasses can override this to use different hash functions, | ||
828 | 396 | configurations, etc. | ||
829 | 397 | """ | ||
830 | 398 | assert file_size >= 1 | ||
831 | 399 | assert len(leaf_hashes) > 0 | ||
832 | 400 | assert len(leaf_hashes) % self.digest_bytes == 0 | ||
834 | 401 | return skein512(leaf_hashes, | 323 | return skein512(leaf_hashes, |
835 | 402 | digest_bits=self.digest_bits, | 324 | digest_bits=self.digest_bits, |
836 | 403 | pers=PERS_ROOT, | 325 | pers=PERS_ROOT, |
838 | 404 | key=str(file_size).encode('utf-8'), | 326 | key=key_file_size, |
839 | 405 | ).digest() | 327 | ).digest() |
840 | 406 | 328 | ||
841 | 407 | 329 | ||
858 | 408 | VERSION1 = Protocol(8 * MiB, 280) | 330 | class OldSkeinProtocol(Protocol): |
859 | 409 | 331 | def _hash_leaf_index(self, key_leaf_index): | |
860 | 410 | 332 | return skein512(key_leaf_index, | |
845 | 411 | class OldProtocol(Protocol): | ||
846 | 412 | """ | ||
847 | 413 | The unofficial version zero protocol. | ||
848 | 414 | |||
849 | 415 | Although no formal support commitments were ever made for this version zero | ||
850 | 416 | protocol, it has been used enough in the wild that we should really | ||
851 | 417 | continue to support it and provide a nice migration to the version one | ||
852 | 418 | protocol. | ||
853 | 419 | """ | ||
854 | 420 | |||
855 | 421 | def _hash_leaf_index(self, leaf_index): | ||
856 | 422 | assert leaf_index >= 0 | ||
857 | 423 | return skein512(str(leaf_index).encode('utf-8'), | ||
861 | 424 | digest_bits=self.digest_bits, | 333 | digest_bits=self.digest_bits, |
862 | 425 | pers=PERS_LEAF_INDEX, | 334 | pers=PERS_LEAF_INDEX, |
863 | 426 | ).digest() | 335 | ).digest() |
864 | 427 | 336 | ||
869 | 428 | def _hash_leaf(self, leaf_index, leaf_data, challenge): | 337 | def _hash_leaf(self, key_leaf_index, leaf_data, challenge): |
870 | 429 | assert leaf_index >= 0 | 338 | skein = skein512(self._hash_leaf_index(key_leaf_index), |
867 | 430 | assert 1 <= len(leaf_data) <= self.leaf_size | ||
868 | 431 | skein = skein512(self._hash_leaf_index(leaf_index), | ||
871 | 432 | digest_bits=self.digest_bits, | 339 | digest_bits=self.digest_bits, |
872 | 433 | pers=PERS_LEAF, | 340 | pers=PERS_LEAF, |
873 | 434 | nonce=challenge, | 341 | nonce=challenge, |
874 | @@ -436,18 +343,14 @@ | |||
875 | 436 | skein.update(leaf_data) | 343 | skein.update(leaf_data) |
876 | 437 | return skein.digest() | 344 | return skein.digest() |
877 | 438 | 345 | ||
881 | 439 | def _hash_file_size(self, file_size): | 346 | def _hash_file_size(self, key_file_size): |
882 | 440 | assert file_size >= 1 | 347 | return skein512(key_file_size, |
880 | 441 | return skein512(str(file_size).encode('utf-8'), | ||
883 | 442 | digest_bits=self.digest_bits, | 348 | digest_bits=self.digest_bits, |
884 | 443 | pers=PERS_FILE_SIZE, | 349 | pers=PERS_FILE_SIZE, |
885 | 444 | ).digest() | 350 | ).digest() |
886 | 445 | 351 | ||
892 | 446 | def _hash_root(self, file_size, leaf_hashes): | 352 | def _hash_root(self, key_file_size, leaf_hashes): |
893 | 447 | assert file_size >= 1 | 353 | skein = skein512(self._hash_file_size(key_file_size), |
889 | 448 | assert len(leaf_hashes) > 0 | ||
890 | 449 | assert len(leaf_hashes) % self.digest_bytes == 0 | ||
891 | 450 | skein = skein512(self._hash_file_size(file_size), | ||
894 | 451 | digest_bits=self.digest_bits, | 354 | digest_bits=self.digest_bits, |
895 | 452 | pers=PERS_ROOT, | 355 | pers=PERS_ROOT, |
896 | 453 | ) | 356 | ) |
897 | @@ -455,6 +358,7 @@ | |||
898 | 455 | return skein.digest() | 358 | return skein.digest() |
899 | 456 | 359 | ||
900 | 457 | 360 | ||
902 | 458 | VERSION0 = OldProtocol(8 * MiB, 240) | 361 | # Create the Protocol instances: |
903 | 362 | VERSION0 = OldSkeinProtocol(8 * MiB, 240) | ||
904 | 363 | VERSION1 = SkeinProtocol(8 * MiB, 240) | ||
905 | 459 | 364 | ||
906 | 460 | PROTOCOLS = (VERSION0, VERSION1) | ||
907 | 461 | 365 | ||
908 | === modified file 'filestore/tests/__init__.py' | |||
909 | --- filestore/tests/__init__.py 2013-02-18 12:40:06 +0000 | |||
910 | +++ filestore/tests/__init__.py 2013-02-19 15:39:24 +0000 | |||
911 | @@ -35,12 +35,10 @@ | |||
912 | 35 | from random import SystemRandom | 35 | from random import SystemRandom |
913 | 36 | 36 | ||
914 | 37 | from skein import skein512 | 37 | from skein import skein512 |
915 | 38 | from dbase32.rfc3548 import b32enc, b32dec | ||
916 | 38 | from dbase32 import isdb32 | 39 | from dbase32 import isdb32 |
917 | 39 | 40 | ||
922 | 40 | from filestore import protocols | 41 | from filestore import protocols, misc |
919 | 41 | from filestore.protocols import PROTOCOLS, VERSION0, VERSION1 | ||
920 | 42 | from filestore.protocols import b32dumps, b32loads | ||
921 | 43 | from filestore import misc | ||
923 | 44 | import filestore | 42 | import filestore |
924 | 45 | 43 | ||
925 | 46 | 44 | ||
926 | @@ -69,7 +67,7 @@ | |||
927 | 69 | ]) | 67 | ]) |
928 | 70 | ID = filestore.hash_root(FILE_SIZE, LEAF_HASHES) | 68 | ID = filestore.hash_root(FILE_SIZE, LEAF_HASHES) |
929 | 71 | CH = filestore.ContentHash(ID, FILE_SIZE, LEAF_HASHES) | 69 | CH = filestore.ContentHash(ID, FILE_SIZE, LEAF_HASHES) |
931 | 72 | assert CH.id == 'DMJGE4OTWZVKSX426GHE46GZCGICMQOVWNGRVB7E665Y2RAM', CH.id | 70 | #assert CH.id == 'DMJGE4OTWZVKSX426GHE46GZCGICMQOVWNGRVB7E665Y2RAM', CH.id |
932 | 73 | 71 | ||
933 | 74 | 72 | ||
934 | 75 | def write_sample_file(fp): | 73 | def write_sample_file(fp): |
935 | @@ -110,17 +108,13 @@ | |||
936 | 110 | def touch(self, *parts): | 108 | def touch(self, *parts): |
937 | 111 | d = self.makedirs(*parts[:-1]) | 109 | d = self.makedirs(*parts[:-1]) |
938 | 112 | f = self.join(*parts) | 110 | f = self.join(*parts) |
942 | 113 | assert not path.exists(f) | 111 | open(f, 'xb').close() |
940 | 114 | open(f, 'wb').close() | ||
941 | 115 | assert path.isfile(f) and not path.islink(f) | ||
943 | 116 | return f | 112 | return f |
944 | 117 | 113 | ||
945 | 118 | def write(self, content, *parts): | 114 | def write(self, content, *parts): |
946 | 119 | d = self.makedirs(*parts[:-1]) | 115 | d = self.makedirs(*parts[:-1]) |
947 | 120 | f = self.join(*parts) | 116 | f = self.join(*parts) |
951 | 121 | assert not path.exists(f) | 117 | open(f, 'xb').write(content) |
949 | 122 | open(f, 'wb').write(content) | ||
950 | 123 | assert path.isfile(f) and not path.islink(f) | ||
952 | 124 | return f | 118 | return f |
953 | 125 | 119 | ||
954 | 126 | def copy(self, src, *parts): | 120 | def copy(self, src, *parts): |
955 | @@ -270,6 +264,33 @@ | |||
956 | 270 | self.assertEqual(str(e), "'the id' not in 'the FileStore'") | 264 | self.assertEqual(str(e), "'the id' not in 'the FileStore'") |
957 | 271 | 265 | ||
958 | 272 | 266 | ||
959 | 267 | class TestConstants(TestCase): | ||
960 | 268 | def test_leaf_size(self): | ||
961 | 269 | self.assertIsInstance(filestore.LEAF_SIZE, int) | ||
962 | 270 | self.assertEqual(filestore.LEAF_SIZE, 8 * 1024 * 1024) | ||
963 | 271 | self.assertEqual(filestore.LEAF_SIZE, 2**23) | ||
964 | 272 | self.assertEqual(filestore.LEAF_SIZE, filestore.PROTOCOL.leaf_size) | ||
965 | 273 | self.assertEqual(filestore.LEAF_SIZE, protocols.VERSION0.leaf_size) | ||
966 | 274 | self.assertEqual(filestore.LEAF_SIZE, protocols.VERSION1.leaf_size) | ||
967 | 275 | |||
968 | 276 | def test_digest_bits(self): | ||
969 | 277 | self.assertIsInstance(filestore.DIGEST_BITS, int) | ||
970 | 278 | self.assertEqual(filestore.DIGEST_BITS % 40, 0) | ||
971 | 279 | self.assertEqual(filestore.DIGEST_BITS, filestore.PROTOCOL.digest_bits) | ||
972 | 280 | |||
973 | 281 | def test_digest_bytes(self): | ||
974 | 282 | self.assertIsInstance(filestore.DIGEST_BYTES, int) | ||
975 | 283 | self.assertEqual(filestore.DIGEST_BYTES % 5, 0) | ||
976 | 284 | self.assertEqual(filestore.DIGEST_BYTES, filestore.DIGEST_BITS // 8) | ||
977 | 285 | self.assertEqual(filestore.DIGEST_BYTES, filestore.PROTOCOL.digest_bytes) | ||
978 | 286 | |||
979 | 287 | def test_digest_b32len(self): | ||
980 | 288 | self.assertIsInstance(filestore.DIGEST_B32LEN, int) | ||
981 | 289 | self.assertEqual(filestore.DIGEST_B32LEN % 8, 0) | ||
982 | 290 | self.assertEqual(filestore.DIGEST_B32LEN, filestore.DIGEST_BITS // 5) | ||
983 | 291 | self.assertEqual(filestore.DIGEST_B32LEN, filestore.PROTOCOL.digest_b32len) | ||
984 | 292 | |||
985 | 293 | |||
986 | 273 | class TestFunctions(TestCase): | 294 | class TestFunctions(TestCase): |
987 | 274 | def test_constants(self): | 295 | def test_constants(self): |
988 | 275 | """ | 296 | """ |
989 | @@ -361,7 +382,7 @@ | |||
990 | 361 | digest = f(2, content) | 382 | digest = f(2, content) |
991 | 362 | self.assertEqual( | 383 | self.assertEqual( |
992 | 363 | digest, | 384 | digest, |
994 | 364 | skein512(VERSION0._hash_leaf_index(2) + content, | 385 | skein512(protocols.VERSION0._hash_leaf_index(b'2') + content, |
995 | 365 | digest_bits=240, | 386 | digest_bits=240, |
996 | 366 | pers=protocols.PERS_LEAF, | 387 | pers=protocols.PERS_LEAF, |
997 | 367 | ).digest() | 388 | ).digest() |
998 | @@ -371,7 +392,7 @@ | |||
999 | 371 | digest = f(2, content) | 392 | digest = f(2, content) |
1000 | 372 | self.assertEqual( | 393 | self.assertEqual( |
1001 | 373 | digest, | 394 | digest, |
1003 | 374 | skein512(VERSION0._hash_leaf_index(2) + content, | 395 | skein512(protocols.VERSION0._hash_leaf_index(b'2') + content, |
1004 | 375 | digest_bits=240, | 396 | digest_bits=240, |
1005 | 376 | pers=protocols.PERS_LEAF, | 397 | pers=protocols.PERS_LEAF, |
1006 | 377 | ).digest() | 398 | ).digest() |
1007 | @@ -499,7 +520,7 @@ | |||
1008 | 499 | 520 | ||
1009 | 500 | def test_check_leaf_hashes(self): | 521 | def test_check_leaf_hashes(self): |
1010 | 501 | # Test with wrong type: | 522 | # Test with wrong type: |
1012 | 502 | leaf_hashes = 'a' * 60 | 523 | leaf_hashes = 'a' * 30 |
1013 | 503 | with self.assertRaises(TypeError) as cm: | 524 | with self.assertRaises(TypeError) as cm: |
1014 | 504 | filestore.check_leaf_hashes(leaf_hashes) | 525 | filestore.check_leaf_hashes(leaf_hashes) |
1015 | 505 | self.assertEqual( | 526 | self.assertEqual( |
1016 | @@ -514,49 +535,30 @@ | |||
1017 | 514 | self.assertIs(cm.exception.leaf_hashes, leaf_hashes) | 535 | self.assertIs(cm.exception.leaf_hashes, leaf_hashes) |
1018 | 515 | 536 | ||
1019 | 516 | # Test with wrong length: | 537 | # Test with wrong length: |
1021 | 517 | leaf_hashes = b'a' * 61 | 538 | leaf_hashes = b'a' * 29 |
1022 | 539 | with self.assertRaises(filestore.LeafHashesError) as cm: | ||
1023 | 540 | filestore.check_leaf_hashes(leaf_hashes) | ||
1024 | 541 | self.assertIs(cm.exception.leaf_hashes, leaf_hashes) | ||
1025 | 542 | |||
1026 | 543 | leaf_hashes = b'a' * 31 | ||
1027 | 544 | with self.assertRaises(filestore.LeafHashesError) as cm: | ||
1028 | 545 | filestore.check_leaf_hashes(leaf_hashes) | ||
1029 | 546 | self.assertIs(cm.exception.leaf_hashes, leaf_hashes) | ||
1030 | 547 | |||
1031 | 548 | leaf_hashes = b'a' * (18 * 30 - 1) | ||
1032 | 518 | with self.assertRaises(filestore.LeafHashesError) as cm: | 549 | with self.assertRaises(filestore.LeafHashesError) as cm: |
1033 | 519 | filestore.check_leaf_hashes(leaf_hashes) | 550 | filestore.check_leaf_hashes(leaf_hashes) |
1034 | 520 | self.assertIs(cm.exception.leaf_hashes, leaf_hashes) | 551 | self.assertIs(cm.exception.leaf_hashes, leaf_hashes) |
1035 | 521 | 552 | ||
1036 | 522 | # Test with good values: | 553 | # Test with good values: |
1074 | 523 | good30 = os.urandom(30) | 554 | good = os.urandom(30) |
1075 | 524 | self.assertIs(filestore.check_leaf_hashes(good30), good30) | 555 | self.assertIs(filestore.check_leaf_hashes(good), good) |
1076 | 525 | self.assertIs( | 556 | |
1077 | 526 | filestore.check_leaf_hashes(good30, protocol=VERSION0), | 557 | good = os.urandom(60) |
1078 | 527 | good30 | 558 | self.assertIs(filestore.check_leaf_hashes(good), good) |
1079 | 528 | ) | 559 | |
1080 | 529 | good35 = os.urandom(35) | 560 | good = os.urandom(18 * 30) |
1081 | 530 | self.assertIs( | 561 | self.assertIs(filestore.check_leaf_hashes(good), good) |
1045 | 531 | filestore.check_leaf_hashes(good35, protocol=VERSION1), | ||
1046 | 532 | good35 | ||
1047 | 533 | ) | ||
1048 | 534 | |||
1049 | 535 | # Test with wrong protocol version: | ||
1050 | 536 | with self.assertRaises(filestore.LeafHashesError) as cm: | ||
1051 | 537 | filestore.check_leaf_hashes(good30, protocol=VERSION1) | ||
1052 | 538 | self.assertIs(cm.exception.leaf_hashes, good30) | ||
1053 | 539 | with self.assertRaises(filestore.LeafHashesError) as cm: | ||
1054 | 540 | filestore.check_leaf_hashes(good35, protocol=VERSION0) | ||
1055 | 541 | self.assertIs(cm.exception.leaf_hashes, good35) | ||
1056 | 542 | |||
1057 | 543 | # Future proofing: | ||
1058 | 544 | for protocol in PROTOCOLS: | ||
1059 | 545 | good = os.urandom(protocol.digest_bytes) | ||
1060 | 546 | self.assertIs(filestore.check_leaf_hashes(good, protocol), good) | ||
1061 | 547 | |||
1062 | 548 | good = os.urandom(18 * protocol.digest_bytes) | ||
1063 | 549 | self.assertIs(filestore.check_leaf_hashes(good, protocol), good) | ||
1064 | 550 | |||
1065 | 551 | bad = os.urandom(protocol.digest_bytes + 1) | ||
1066 | 552 | with self.assertRaises(filestore.LeafHashesError) as cm: | ||
1067 | 553 | filestore.check_leaf_hashes(bad, protocol) | ||
1068 | 554 | self.assertIs(cm.exception.leaf_hashes, bad) | ||
1069 | 555 | |||
1070 | 556 | bad = os.urandom(18 * protocol.digest_bytes - 1) | ||
1071 | 557 | with self.assertRaises(filestore.LeafHashesError) as cm: | ||
1072 | 558 | filestore.check_leaf_hashes(bad, protocol) | ||
1073 | 559 | self.assertIs(cm.exception.leaf_hashes, bad) | ||
1082 | 560 | 562 | ||
1083 | 561 | def test_check_root_hash(self): | 563 | def test_check_root_hash(self): |
1084 | 562 | f = filestore.check_root_hash | 564 | f = filestore.check_root_hash |
1085 | @@ -587,62 +589,6 @@ | |||
1086 | 587 | 'OYESBWEZ4Y2AGSLMNZB4ZF75A2VG7NXVB4R25SSMRGXLN4CR' | 589 | 'OYESBWEZ4Y2AGSLMNZB4ZF75A2VG7NXVB4R25SSMRGXLN4CR' |
1087 | 588 | ) | 590 | ) |
1088 | 589 | 591 | ||
1089 | 590 | def test_check_root_hash2(self): | ||
1090 | 591 | for protocol in PROTOCOLS: | ||
1091 | 592 | file_size = protocol.leaf_size + 1 | ||
1092 | 593 | leaf0_hash = os.urandom(protocol.digest_bytes) | ||
1093 | 594 | leaf1_hash = os.urandom(protocol.digest_bytes) | ||
1094 | 595 | leaf_hashes = leaf0_hash + leaf1_hash | ||
1095 | 596 | _id = protocol.hash_root(file_size, leaf_hashes) | ||
1096 | 597 | |||
1097 | 598 | # Everything is correct: | ||
1098 | 599 | ch = filestore.check_root_hash(_id, file_size, leaf_hashes) | ||
1099 | 600 | self.assertIsInstance(ch, filestore.ContentHash) | ||
1100 | 601 | self.assertEqual(ch.id, _id) | ||
1101 | 602 | self.assertEqual(ch.file_size, file_size) | ||
1102 | 603 | self.assertEqual(ch.leaf_hashes, leaf_hashes) | ||
1103 | 604 | self.assertEqual(ch, (_id, file_size, leaf_hashes)) | ||
1104 | 605 | |||
1105 | 606 | # Everything is correct, unpack=True | ||
1106 | 607 | ch = filestore.check_root_hash(_id, file_size, leaf_hashes, | ||
1107 | 608 | unpack=True | ||
1108 | 609 | ) | ||
1109 | 610 | self.assertIsInstance(ch, filestore.ContentHash) | ||
1110 | 611 | self.assertEqual(ch.id, _id) | ||
1111 | 612 | self.assertEqual(ch.file_size, file_size) | ||
1112 | 613 | self.assertEqual(ch.leaf_hashes, (leaf0_hash, leaf1_hash)) | ||
1113 | 614 | self.assertEqual(ch, (_id, file_size, (leaf0_hash, leaf1_hash))) | ||
1114 | 615 | |||
1115 | 616 | # Wrong ID: | ||
1116 | 617 | bad_id = random_id(protocol.digest_bytes) | ||
1117 | 618 | with self.assertRaises(filestore.RootHashError) as cm: | ||
1118 | 619 | filestore.check_root_hash(bad_id, file_size, leaf_hashes) | ||
1119 | 620 | self.assertEqual(cm.exception.id, bad_id) | ||
1120 | 621 | self.assertEqual(cm.exception.file_size, file_size) | ||
1121 | 622 | self.assertEqual(cm.exception.leaf_hashes, leaf_hashes) | ||
1122 | 623 | self.assertEqual(cm.exception.bad_id, _id) | ||
1123 | 624 | |||
1124 | 625 | # Wrong file_size: | ||
1125 | 626 | with self.assertRaises(filestore.RootHashError) as cm: | ||
1126 | 627 | filestore.check_root_hash(_id, file_size + 1, leaf_hashes) | ||
1127 | 628 | self.assertEqual(cm.exception.id, _id) | ||
1128 | 629 | self.assertEqual(cm.exception.file_size, file_size + 1) | ||
1129 | 630 | self.assertEqual(cm.exception.leaf_hashes, leaf_hashes) | ||
1130 | 631 | self.assertEqual(cm.exception.bad_id, | ||
1131 | 632 | protocol.hash_root(file_size + 1, leaf_hashes) | ||
1132 | 633 | ) | ||
1133 | 634 | |||
1134 | 635 | # Wrong leaf_hashes | ||
1135 | 636 | bad_leaf_hashes = leaf0_hash + os.urandom(protocol.digest_bytes) | ||
1136 | 637 | with self.assertRaises(filestore.RootHashError) as cm: | ||
1137 | 638 | filestore.check_root_hash(_id, file_size, bad_leaf_hashes) | ||
1138 | 639 | self.assertEqual(cm.exception.id, _id) | ||
1139 | 640 | self.assertEqual(cm.exception.file_size, file_size) | ||
1140 | 641 | self.assertEqual(cm.exception.leaf_hashes, bad_leaf_hashes) | ||
1141 | 642 | self.assertEqual(cm.exception.bad_id, | ||
1142 | 643 | protocol.hash_root(file_size, bad_leaf_hashes) | ||
1143 | 644 | ) | ||
1144 | 645 | |||
1145 | 646 | def test_enumerate_leaf_hashes(self): | 592 | def test_enumerate_leaf_hashes(self): |
1146 | 647 | f = filestore.enumerate_leaf_hashes | 593 | f = filestore.enumerate_leaf_hashes |
1147 | 648 | self.assertEqual( | 594 | self.assertEqual( |
1148 | @@ -772,7 +718,7 @@ | |||
1149 | 772 | # Test with wrong length: | 718 | # Test with wrong length: |
1150 | 773 | for i in range(321): | 719 | for i in range(321): |
1151 | 774 | value = 'N' * i | 720 | value = 'N' * i |
1153 | 775 | if len(value) in (48, 56): | 721 | if len(value) == 48: |
1154 | 776 | self.assertIs(filestore.check_id(value), value) | 722 | self.assertIs(filestore.check_id(value), value) |
1155 | 777 | continue | 723 | continue |
1156 | 778 | with self.assertRaises(filestore.IDError) as cm: | 724 | with self.assertRaises(filestore.IDError) as cm: |
1157 | @@ -782,18 +728,12 @@ | |||
1158 | 782 | # Test with 48 and 56 character: | 728 | # Test with 48 and 56 character: |
1159 | 783 | id48 = random_id(30) | 729 | id48 = random_id(30) |
1160 | 784 | self.assertIs(filestore.check_id(id48), id48) | 730 | self.assertIs(filestore.check_id(id48), id48) |
1161 | 785 | id56 = random_id(35) | ||
1162 | 786 | self.assertIs(filestore.check_id(id56), id56) | ||
1163 | 787 | 731 | ||
1164 | 788 | # Test case sensitivity: | 732 | # Test case sensitivity: |
1165 | 789 | bad48 = id48.lower() | 733 | bad48 = id48.lower() |
1166 | 790 | with self.assertRaises(filestore.IDError) as cm: | 734 | with self.assertRaises(filestore.IDError) as cm: |
1167 | 791 | filestore.check_id(bad48) | 735 | filestore.check_id(bad48) |
1168 | 792 | self.assertIs(cm.exception.id, bad48) | 736 | self.assertIs(cm.exception.id, bad48) |
1169 | 793 | bad56 = id56.lower() | ||
1170 | 794 | with self.assertRaises(filestore.IDError) as cm: | ||
1171 | 795 | filestore.check_id(bad56) | ||
1172 | 796 | self.assertIs(cm.exception.id, bad56) | ||
1173 | 797 | 737 | ||
1174 | 798 | def test_iter_files(self): | 738 | def test_iter_files(self): |
1175 | 799 | """ | 739 | """ |
1176 | @@ -1247,30 +1187,18 @@ | |||
1177 | 1247 | src_fp = open(dst, 'rb') | 1187 | src_fp = open(dst, 'rb') |
1178 | 1248 | self.assertEqual(filestore.hash_fp(src_fp), ch) | 1188 | self.assertEqual(filestore.hash_fp(src_fp), ch) |
1179 | 1249 | 1189 | ||
1204 | 1250 | def test_hash_fp2(self): | 1190 | leaves = misc.build_leaves(filestore.LEAF_SIZE) |
1205 | 1251 | obj = misc.load_test_vectors() | 1191 | C = leaves['C'] |
1206 | 1252 | for proto in PROTOCOLS: | 1192 | for (L, data) in leaves.items(): |
1207 | 1253 | tmp = TempDir() | 1193 | tmp.write(data, L) |
1208 | 1254 | leaves = misc.build_test_leaves(proto.leaf_size) | 1194 | tmp.write(C + data, 'C' + L) |
1209 | 1255 | vectors = obj[str(proto.digest_b32len)] | 1195 | |
1210 | 1256 | for (key, value) in vectors['root_hashes'].items(): | 1196 | vectors = misc.load_data('V0') |
1211 | 1257 | name = tmp.join(key) | 1197 | for name in ['A', 'B', 'C', 'CA', 'CB', 'CC']: |
1212 | 1258 | assert not path.exists(name) | 1198 | src_fp = open(tmp.join(name), 'rb') |
1213 | 1259 | fp = open(name, 'wb') | 1199 | ch = filestore.hash_fp(src_fp) |
1214 | 1260 | for L in key: | 1200 | self.assertIsInstance(ch, filestore.ContentHash) |
1215 | 1261 | fp.write(leaves[L]) | 1201 | self.assertEqual(ch.id, vectors['root_hashes'][name]) |
1192 | 1262 | fp.close() | ||
1193 | 1263 | fp = open(name, 'rb') | ||
1194 | 1264 | ch = filestore.hash_fp(fp, protocol=proto) | ||
1195 | 1265 | self.assertIsInstance(ch, filestore.ContentHash) | ||
1196 | 1266 | self.assertEqual(ch.id, value) | ||
1197 | 1267 | self.assertEqual(ch.file_size, path.getsize(name)) | ||
1198 | 1268 | self.assertEqual(ch.leaf_hashes, | ||
1199 | 1269 | b''.join( | ||
1200 | 1270 | b32loads(vectors['leaf_hashes'][L][i]) | ||
1201 | 1271 | for (i, L) in enumerate(key) | ||
1202 | 1272 | ) | ||
1203 | 1273 | ) | ||
1216 | 1274 | 1202 | ||
1217 | 1275 | def test_ensuredir(self): | 1203 | def test_ensuredir(self): |
1218 | 1276 | f = filestore.ensuredir | 1204 | f = filestore.ensuredir |
1219 | @@ -1594,22 +1522,6 @@ | |||
1220 | 1594 | stats.sort(key=lambda s: s.id) | 1522 | stats.sort(key=lambda s: s.id) |
1221 | 1595 | self.assertEqual(list(fs), stats) | 1523 | self.assertEqual(list(fs), stats) |
1222 | 1596 | 1524 | ||
1223 | 1597 | # Add more valid files in (56 character IDs) | ||
1224 | 1598 | for i in range(1000): | ||
1225 | 1599 | _id = random_id(35) | ||
1226 | 1600 | size = i + 1 | ||
1227 | 1601 | f = fs.path(_id) | ||
1228 | 1602 | assert not path.exists(f) | ||
1229 | 1603 | open(f, 'wb').write(b'N' * size) | ||
1230 | 1604 | os.chmod(f, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) | ||
1231 | 1605 | self.assertEqual(path.getsize(f), size) | ||
1232 | 1606 | st = filestore.Stat(_id, f, size, path.getmtime(f)) | ||
1233 | 1607 | self.assertEqual(fs.stat(_id), st) | ||
1234 | 1608 | stats.append(st) | ||
1235 | 1609 | self.assertNotEqual(list(fs), stats) # Sorting! | ||
1236 | 1610 | stats.sort(key=lambda s: s.id) | ||
1237 | 1611 | self.assertEqual(list(fs), stats) | ||
1238 | 1612 | |||
1239 | 1613 | # Should ignore symlinks, even if to valid files | 1525 | # Should ignore symlinks, even if to valid files |
1240 | 1614 | # This makes sure os.lstat() is being used rather than os.stat() | 1526 | # This makes sure os.lstat() is being used rather than os.stat() |
1241 | 1615 | for i in range(50): | 1527 | for i in range(50): |
1242 | @@ -1989,31 +1901,6 @@ | |||
1243 | 1989 | self.assertFalse(path.exists(canonical)) | 1901 | self.assertFalse(path.exists(canonical)) |
1244 | 1990 | self.assertTrue(path.isfile(corrupt)) | 1902 | self.assertTrue(path.isfile(corrupt)) |
1245 | 1991 | 1903 | ||
1246 | 1992 | def test_verify2(self): | ||
1247 | 1993 | tmp = TempDir() | ||
1248 | 1994 | fs = filestore.FileStore(tmp.dir) | ||
1249 | 1995 | obj = misc.load_test_vectors() | ||
1250 | 1996 | for proto in PROTOCOLS: | ||
1251 | 1997 | leaves = misc.build_test_leaves(proto.leaf_size) | ||
1252 | 1998 | vectors = obj[str(proto.digest_b32len)] | ||
1253 | 1999 | for (key, value) in vectors['root_hashes'].items(): | ||
1254 | 2000 | name = fs.path(value) | ||
1255 | 2001 | assert not path.exists(name) | ||
1256 | 2002 | fp = open(name, 'wb') | ||
1257 | 2003 | for L in key: | ||
1258 | 2004 | fp.write(leaves[L]) | ||
1259 | 2005 | fp.close() | ||
1260 | 2006 | ch = fs.verify(value) | ||
1261 | 2007 | self.assertIsInstance(ch, filestore.ContentHash) | ||
1262 | 2008 | self.assertEqual(ch.id, value) | ||
1263 | 2009 | self.assertEqual(ch.file_size, path.getsize(name)) | ||
1264 | 2010 | self.assertEqual(ch.leaf_hashes, | ||
1265 | 2011 | b''.join( | ||
1266 | 2012 | b32loads(vectors['leaf_hashes'][L][i]) | ||
1267 | 2013 | for (i, L) in enumerate(key) | ||
1268 | 2014 | ) | ||
1269 | 2015 | ) | ||
1270 | 2016 | |||
1271 | 2017 | def test_verify_iter(self): | 1904 | def test_verify_iter(self): |
1272 | 2018 | tmp = TempDir() | 1905 | tmp = TempDir() |
1273 | 2019 | fs = filestore.FileStore(tmp.dir) | 1906 | fs = filestore.FileStore(tmp.dir) |
1274 | 2020 | 1907 | ||
1275 | === modified file 'filestore/tests/test_misc.py' | |||
1276 | --- filestore/tests/test_misc.py 2013-02-14 20:08:06 +0000 | |||
1277 | +++ filestore/tests/test_misc.py 2013-02-19 15:39:24 +0000 | |||
1278 | @@ -29,28 +29,44 @@ | |||
1279 | 29 | import json | 29 | import json |
1280 | 30 | from hashlib import md5 | 30 | from hashlib import md5 |
1281 | 31 | 31 | ||
1282 | 32 | from dbase32 import db32enc, db32dec | ||
1283 | 33 | from dbase32.rfc3548 import b32enc, b32dec | ||
1284 | 34 | |||
1285 | 32 | import filestore | 35 | import filestore |
1289 | 33 | from filestore.protocols import MIN_LEAF_SIZE, Protocol, VERSION0, VERSION1 | 36 | from filestore.protocols import MIN_LEAF_SIZE |
1290 | 34 | from filestore.protocols import b32dumps, b32loads | 37 | from filestore import misc, protocols |
1288 | 35 | from filestore import misc | ||
1291 | 36 | 38 | ||
1292 | 37 | 39 | ||
1293 | 38 | class TestFunctions(TestCase): | 40 | class TestFunctions(TestCase): |
1295 | 39 | def test_load_test_vectors(self): | 41 | def test_load_data(self): |
1296 | 40 | pkg = path.dirname(path.abspath(filestore.__file__)) | 42 | pkg = path.dirname(path.abspath(filestore.__file__)) |
1297 | 41 | self.assertTrue(path.isdir(pkg)) | 43 | self.assertTrue(path.isdir(pkg)) |
1310 | 42 | self.assertEqual( | 44 | self.assertEqual(misc.DATADIR, path.join(pkg, 'data')) |
1311 | 43 | misc.TEST_VECTORS, | 45 | self.assertTrue(path.isdir(misc.DATADIR)) |
1312 | 44 | path.join(pkg, 'data', 'test-vectors.json') | 46 | |
1313 | 45 | ) | 47 | V0 = path.join(misc.DATADIR, 'V0.json') |
1314 | 46 | self.assertTrue(path.isfile(misc.TEST_VECTORS)) | 48 | self.assertTrue(path.isfile(V0)) |
1315 | 47 | self.assertEqual( | 49 | self.assertEqual( |
1316 | 48 | misc.load_test_vectors(), | 50 | misc.load_data('V0'), |
1317 | 49 | json.loads(open(misc.TEST_VECTORS, 'r').read()) | 51 | json.loads(open(V0, 'r').read()) |
1318 | 50 | ) | 52 | ) |
1319 | 51 | 53 | ||
1320 | 52 | def test_build_test_leaves(self): | 54 | V1 = path.join(misc.DATADIR, 'V1.json') |
1321 | 53 | obj = misc.build_test_leaves(MIN_LEAF_SIZE) | 55 | self.assertTrue(path.isfile(V0)) |
1322 | 56 | self.assertEqual( | ||
1323 | 57 | misc.load_data('V1'), | ||
1324 | 58 | json.loads(open(V1, 'r').read()) | ||
1325 | 59 | ) | ||
1326 | 60 | |||
1327 | 61 | MD5SUMS = path.join(misc.DATADIR, 'MD5SUMS.json') | ||
1328 | 62 | self.assertTrue(path.isfile(MD5SUMS)) | ||
1329 | 63 | self.assertEqual( | ||
1330 | 64 | misc.load_data('MD5SUMS'), | ||
1331 | 65 | json.loads(open(MD5SUMS, 'r').read()) | ||
1332 | 66 | ) | ||
1333 | 67 | |||
1334 | 68 | def test_build_leaves(self): | ||
1335 | 69 | obj = misc.build_leaves(MIN_LEAF_SIZE) | ||
1336 | 54 | self.assertEqual(obj, | 70 | self.assertEqual(obj, |
1337 | 55 | { | 71 | { |
1338 | 56 | 'A': b'A', | 72 | 'A': b'A', |
1339 | @@ -62,7 +78,7 @@ | |||
1340 | 62 | self.assertEqual(len(obj['B']), MIN_LEAF_SIZE - 1) | 78 | self.assertEqual(len(obj['B']), MIN_LEAF_SIZE - 1) |
1341 | 63 | self.assertEqual(len(obj['C']), MIN_LEAF_SIZE) | 79 | self.assertEqual(len(obj['C']), MIN_LEAF_SIZE) |
1342 | 64 | 80 | ||
1344 | 65 | obj = misc.build_test_leaves(2 * MIN_LEAF_SIZE) | 81 | obj = misc.build_leaves(2 * MIN_LEAF_SIZE) |
1345 | 66 | self.assertEqual(obj, | 82 | self.assertEqual(obj, |
1346 | 67 | { | 83 | { |
1347 | 68 | 'A': b'A', | 84 | 'A': b'A', |
1348 | @@ -74,59 +90,90 @@ | |||
1349 | 74 | self.assertEqual(len(obj['B']), 2 * MIN_LEAF_SIZE - 1) | 90 | self.assertEqual(len(obj['B']), 2 * MIN_LEAF_SIZE - 1) |
1350 | 75 | self.assertEqual(len(obj['C']), 2 * MIN_LEAF_SIZE) | 91 | self.assertEqual(len(obj['C']), 2 * MIN_LEAF_SIZE) |
1351 | 76 | 92 | ||
1355 | 77 | def test_build_test_vectors(self): | 93 | def test_build_vectors(self): |
1356 | 78 | proto = Protocol(MIN_LEAF_SIZE, 200) | 94 | leaf_size = 2 * 1024 * 1024 |
1357 | 79 | leaves = misc.build_test_leaves(proto.leaf_size) | 95 | proto = protocols.SkeinProtocol(leaf_size, 200) |
1358 | 96 | leaves = misc.build_leaves(leaf_size) | ||
1359 | 97 | |||
1360 | 80 | A0 = proto.hash_leaf(0, leaves['A']) | 98 | A0 = proto.hash_leaf(0, leaves['A']) |
1361 | 81 | A1 = proto.hash_leaf(1, leaves['A']) | 99 | A1 = proto.hash_leaf(1, leaves['A']) |
1362 | 82 | B0 = proto.hash_leaf(0, leaves['B']) | 100 | B0 = proto.hash_leaf(0, leaves['B']) |
1363 | 83 | B1 = proto.hash_leaf(1, leaves['B']) | 101 | B1 = proto.hash_leaf(1, leaves['B']) |
1364 | 84 | C0 = proto.hash_leaf(0, leaves['C']) | 102 | C0 = proto.hash_leaf(0, leaves['C']) |
1365 | 85 | C1 = proto.hash_leaf(1, leaves['C']) | 103 | C1 = proto.hash_leaf(1, leaves['C']) |
1396 | 86 | self.assertEqual( | 104 | |
1397 | 87 | misc.build_test_vectors(proto), | 105 | del leaves |
1398 | 88 | { | 106 | |
1399 | 89 | 'leaf_hashes': { | 107 | A = proto.hash_root(1, A0) |
1400 | 90 | 'A': [b32dumps(A0), b32dumps(A1)], | 108 | B = proto.hash_root(leaf_size - 1, B0) |
1401 | 91 | 'B': [b32dumps(B0), b32dumps(B1)], | 109 | C = proto.hash_root(leaf_size, C0) |
1402 | 92 | 'C': [b32dumps(C0), b32dumps(C1)], | 110 | CA = proto.hash_root(leaf_size + 1, C0 + A1) |
1403 | 93 | }, | 111 | CB = proto.hash_root(2 * leaf_size - 1, C0 + B1) |
1404 | 94 | 'root_hashes': { | 112 | CC = proto.hash_root(2 * leaf_size, C0 + C1) |
1405 | 95 | 'A': proto.hash_root(1, A0), | 113 | |
1406 | 96 | 'B': proto.hash_root(MIN_LEAF_SIZE - 1, B0), | 114 | self.assertEqual( |
1407 | 97 | 'C': proto.hash_root(MIN_LEAF_SIZE, C0), | 115 | misc.build_vectors(proto), |
1408 | 98 | 'CA': proto.hash_root(MIN_LEAF_SIZE + 1, C0 + A1), | 116 | { |
1409 | 99 | 'CB': proto.hash_root(2 * MIN_LEAF_SIZE - 1, C0 + B1), | 117 | 'leaf_hashes': { |
1410 | 100 | 'CC': proto.hash_root(2 * MIN_LEAF_SIZE, C0 + C1), | 118 | 'A': [db32enc(A0), db32enc(A1)], |
1411 | 101 | } | 119 | 'B': [db32enc(B0), db32enc(B1)], |
1412 | 102 | } | 120 | 'C': [db32enc(C0), db32enc(C1)], |
1413 | 103 | ) | 121 | }, |
1414 | 104 | 122 | 'root_hashes': { | |
1415 | 105 | self.assertEqual( | 123 | 'A': db32enc(A), |
1416 | 106 | misc.load_test_vectors(), | 124 | 'B': db32enc(B), |
1417 | 107 | { | 125 | 'C': db32enc(C), |
1418 | 108 | '48': misc.build_test_vectors(VERSION0), | 126 | 'CA': db32enc(CA), |
1419 | 109 | '56': misc.build_test_vectors(VERSION1), | 127 | 'CB': db32enc(CB), |
1420 | 110 | } | 128 | 'CC': db32enc(CC), |
1421 | 111 | ) | 129 | } |
1422 | 112 | 130 | } | |
1423 | 113 | def test_get_test_integers(self): | 131 | ) |
1424 | 114 | self.assertEqual( | 132 | |
1425 | 115 | misc.get_test_integers(100), | 133 | self.assertEqual( |
1426 | 134 | misc.build_vectors(proto, encoder=b32enc), | ||
1427 | 135 | { | ||
1428 | 136 | 'leaf_hashes': { | ||
1429 | 137 | 'A': [b32enc(A0), b32enc(A1)], | ||
1430 | 138 | 'B': [b32enc(B0), b32enc(B1)], | ||
1431 | 139 | 'C': [b32enc(C0), b32enc(C1)], | ||
1432 | 140 | }, | ||
1433 | 141 | 'root_hashes': { | ||
1434 | 142 | 'A': b32enc(A), | ||
1435 | 143 | 'B': b32enc(B), | ||
1436 | 144 | 'C': b32enc(C), | ||
1437 | 145 | 'CA': b32enc(CA), | ||
1438 | 146 | 'CB': b32enc(CB), | ||
1439 | 147 | 'CC': b32enc(CC), | ||
1440 | 148 | } | ||
1441 | 149 | } | ||
1442 | 150 | ) | ||
1443 | 151 | |||
1444 | 152 | def test_get_integers(self): | ||
1445 | 153 | self.assertEqual( | ||
1446 | 154 | misc.get_integers(100), | ||
1447 | 116 | [0, 1, 99, 100, 101, 199, 200] | 155 | [0, 1, 99, 100, 101, 199, 200] |
1448 | 117 | ) | 156 | ) |
1449 | 118 | self.assertEqual( | 157 | self.assertEqual( |
1451 | 119 | misc.get_test_integers(500), | 158 | misc.get_integers(500), |
1452 | 120 | [0, 1, 499, 500, 501, 999, 1000] | 159 | [0, 1, 499, 500, 501, 999, 1000] |
1453 | 121 | ) | 160 | ) |
1454 | 122 | 161 | ||
1456 | 123 | def test_build_test_md5(self): | 162 | def test_build_md5sums(self): |
1457 | 124 | A = b'A' | 163 | A = b'A' |
1458 | 125 | B = b'B' * 99 | 164 | B = b'B' * 99 |
1459 | 126 | C = b'C' * 100 | 165 | C = b'C' * 100 |
1460 | 127 | self.assertEqual( | 166 | self.assertEqual( |
1462 | 128 | misc.build_test_md5(100), | 167 | misc.build_md5sums(100), |
1463 | 129 | { | 168 | { |
1464 | 169 | 'files': { | ||
1465 | 170 | 'A': md5(A).hexdigest(), | ||
1466 | 171 | 'B': md5(B).hexdigest(), | ||
1467 | 172 | 'C': md5(C).hexdigest(), | ||
1468 | 173 | 'CA': md5(C + A).hexdigest(), | ||
1469 | 174 | 'CB': md5(C + B).hexdigest(), | ||
1470 | 175 | 'CC': md5(C + C).hexdigest(), | ||
1471 | 176 | }, | ||
1472 | 130 | 'integers': { | 177 | 'integers': { |
1473 | 131 | '0': md5(b'0').hexdigest(), | 178 | '0': md5(b'0').hexdigest(), |
1474 | 132 | '1': md5(b'1').hexdigest(), | 179 | '1': md5(b'1').hexdigest(), |
1475 | @@ -136,6 +183,19 @@ | |||
1476 | 136 | '199': md5(b'199').hexdigest(), | 183 | '199': md5(b'199').hexdigest(), |
1477 | 137 | '200': md5(b'200').hexdigest(), | 184 | '200': md5(b'200').hexdigest(), |
1478 | 138 | }, | 185 | }, |
1479 | 186 | 'personalization': { | ||
1480 | 187 | 'leaf': md5(protocols.PERS_LEAF).hexdigest(), | ||
1481 | 188 | 'root': md5(protocols.PERS_ROOT).hexdigest(), | ||
1482 | 189 | }, | ||
1483 | 190 | } | ||
1484 | 191 | ) | ||
1485 | 192 | |||
1486 | 193 | A = b'A' | ||
1487 | 194 | B = b'B' * 499 | ||
1488 | 195 | C = b'C' * 500 | ||
1489 | 196 | self.assertEqual( | ||
1490 | 197 | misc.build_md5sums(500), | ||
1491 | 198 | { | ||
1492 | 139 | 'files': { | 199 | 'files': { |
1493 | 140 | 'A': md5(A).hexdigest(), | 200 | 'A': md5(A).hexdigest(), |
1494 | 141 | 'B': md5(B).hexdigest(), | 201 | 'B': md5(B).hexdigest(), |
1495 | @@ -143,10 +203,28 @@ | |||
1496 | 143 | 'CA': md5(C + A).hexdigest(), | 203 | 'CA': md5(C + A).hexdigest(), |
1497 | 144 | 'CB': md5(C + B).hexdigest(), | 204 | 'CB': md5(C + B).hexdigest(), |
1498 | 145 | 'CC': md5(C + C).hexdigest(), | 205 | 'CC': md5(C + C).hexdigest(), |
1500 | 146 | } | 206 | }, |
1501 | 207 | 'integers': { | ||
1502 | 208 | '0': md5(b'0').hexdigest(), | ||
1503 | 209 | '1': md5(b'1').hexdigest(), | ||
1504 | 210 | '499': md5(b'499').hexdigest(), | ||
1505 | 211 | '500': md5(b'500').hexdigest(), | ||
1506 | 212 | '501': md5(b'501').hexdigest(), | ||
1507 | 213 | '999': md5(b'999').hexdigest(), | ||
1508 | 214 | '1000': md5(b'1000').hexdigest(), | ||
1509 | 215 | }, | ||
1510 | 216 | 'personalization': { | ||
1511 | 217 | 'leaf': md5(protocols.PERS_LEAF).hexdigest(), | ||
1512 | 218 | 'root': md5(protocols.PERS_ROOT).hexdigest(), | ||
1513 | 219 | }, | ||
1514 | 147 | } | 220 | } |
1515 | 148 | ) | 221 | ) |
1516 | 149 | 222 | ||
1517 | 223 | self.assertEqual( | ||
1518 | 224 | misc.build_md5sums(8 * 1024 * 1024), | ||
1519 | 225 | misc.load_data('MD5SUMS') | ||
1520 | 226 | ) | ||
1521 | 227 | |||
1522 | 150 | 228 | ||
1523 | 151 | class TestTempFileStore(TestCase): | 229 | class TestTempFileStore(TestCase): |
1524 | 152 | def test_init(self): | 230 | def test_init(self): |
1525 | 153 | 231 | ||
1526 | === modified file 'filestore/tests/test_protocols.py' | |||
1527 | --- filestore/tests/test_protocols.py 2013-02-14 20:08:06 +0000 | |||
1528 | +++ filestore/tests/test_protocols.py 2013-02-19 15:39:24 +0000 | |||
1529 | @@ -25,73 +25,154 @@ | |||
1530 | 25 | 25 | ||
1531 | 26 | from unittest import TestCase | 26 | from unittest import TestCase |
1532 | 27 | import os | 27 | import os |
1535 | 28 | from base64 import b32encode, b32decode | 28 | from random import SystemRandom |
1534 | 29 | from hashlib import md5 | ||
1536 | 30 | 29 | ||
1537 | 31 | from skein import skein512 | 30 | from skein import skein512 |
1543 | 32 | 31 | from dbase32 import db32enc, db32dec | |
1544 | 33 | from filestore.protocols import b32dumps, b32loads | 32 | from dbase32.rfc3548 import b32enc, b32dec |
1545 | 34 | from filestore import protocols | 33 | |
1546 | 35 | 34 | from filestore import protocols, misc | |
1547 | 36 | 35 | ||
1548 | 36 | |||
1549 | 37 | random = SystemRandom() | ||
1550 | 37 | MiB = 1024 * 1024 | 38 | MiB = 1024 * 1024 |
1551 | 38 | TwoMiB = 2 * MiB | 39 | TwoMiB = 2 * MiB |
1552 | 39 | EightMiB = 8 * MiB | 40 | EightMiB = 8 * MiB |
1553 | 41 | GiB = 1024 * MiB | ||
1554 | 40 | COUNT = 50 * 1000 | 42 | COUNT = 50 * 1000 |
1555 | 41 | TYPE_ERROR = '{}: need a {!r}; got a {!r}: {!r}' | 43 | TYPE_ERROR = '{}: need a {!r}; got a {!r}: {!r}' |
1556 | 42 | 44 | ||
1567 | 43 | A = b'A' | 45 | |
1568 | 44 | B = b'B' * (EightMiB - 1) | 46 | class DigestAccum(set): |
1569 | 45 | C = b'C' * EightMiB | 47 | def add(self, digest): |
1570 | 46 | 48 | if not isinstance(digest, bytes): | |
1571 | 47 | A0_B32 = 'XZ5I6KJTUSOIWVCEBOKUELTADZUXNHOAYO77NKKHWCIW3HYGYOPMX5JN' | 49 | raise TypeError('digest must be bytes') |
1572 | 48 | A1_B32 = 'TEC7754ZNM26MTM6YQFI6TMVTTK4RKQEMPAGT2ROQZUBPUIHSJU2DDR3' | 50 | if not (25 <= len(digest) <= 35 and len(digest) % 5 == 0): |
1573 | 49 | B0_B32 = 'P67PVKU3SCCQHNIRMR2Z5NICEMIP36WCFJG4AW6YBAE6UI4K6BVLY3EI' | 51 | raise ValueError('digest has bad length {}'.format(len(digest))) |
1574 | 50 | B1_B32 = 'ZIFO5S2OYYPZAUN6XQWTWZGCDATXCGR2JYN7UIAX54WMVWETMIUFG7WM' | 52 | if digest in self: |
1575 | 51 | C0_B32 = 'RW2GJFIGPQF5WLR53UAK77TPHNRFKMUBYRB23JFS4G2RFRRNHW6OX4CR' | 53 | raise ValueError('digest already in set') |
1576 | 52 | C1_B32 = 'XBVLPYBUX6QD2DKPJTYVUXT23K3AAUAW5J4RMQ543NQNDAHORQJ7GBDE' | 54 | super().add(digest) |
1577 | 55 | return digest | ||
1578 | 56 | |||
1579 | 57 | |||
1580 | 58 | class TestAccum(TestCase): | ||
1581 | 59 | def test_add(self): | ||
1582 | 60 | accum = DigestAccum() | ||
1583 | 61 | self.assertEqual(accum, set()) | ||
1584 | 62 | self.assertEqual(len(accum), 0) | ||
1585 | 63 | |||
1586 | 64 | with self.assertRaises(TypeError) as cm: | ||
1587 | 65 | accum.add('A' * 30) | ||
1588 | 66 | self.assertEqual(str(cm.exception), 'digest must be bytes') | ||
1589 | 67 | |||
1590 | 68 | for size in [24, 26, 29, 31, 34, 36]: | ||
1591 | 69 | bad = os.urandom(size) | ||
1592 | 70 | with self.assertRaises(ValueError) as cm: | ||
1593 | 71 | accum.add(bad) | ||
1594 | 72 | self.assertEqual( | ||
1595 | 73 | str(cm.exception), | ||
1596 | 74 | 'digest has bad length {}'.format(size) | ||
1597 | 75 | ) | ||
1598 | 76 | |||
1599 | 77 | d1 = os.urandom(30) | ||
1600 | 78 | d2 = os.urandom(30) | ||
1601 | 79 | |||
1602 | 80 | self.assertIs(accum.add(d1), d1) | ||
1603 | 81 | self.assertEqual(accum, set([d1])) | ||
1604 | 82 | self.assertEqual(len(accum), 1) | ||
1605 | 83 | |||
1606 | 84 | self.assertIs(accum.add(d2), d2) | ||
1607 | 85 | self.assertEqual(accum, set([d1, d2])) | ||
1608 | 86 | self.assertEqual(len(accum), 2) | ||
1609 | 87 | |||
1610 | 88 | with self.assertRaises(ValueError) as cm: | ||
1611 | 89 | accum.add(d1) | ||
1612 | 90 | self.assertEqual(str(cm.exception), 'digest already in set') | ||
1613 | 91 | |||
1614 | 92 | self.assertEqual(accum, set([d1, d2])) | ||
1615 | 93 | self.assertEqual(len(accum), 2) | ||
1616 | 53 | 94 | ||
1617 | 54 | 95 | ||
1618 | 55 | class TestConstants(TestCase): | 96 | class TestConstants(TestCase): |
1619 | 97 | def test_pers(self): | ||
1620 | 98 | all_pers = ( | ||
1621 | 99 | protocols.PERS_LEAF, | ||
1622 | 100 | protocols.PERS_ROOT, | ||
1623 | 101 | protocols.PERS_LEAF_INDEX, | ||
1624 | 102 | protocols.PERS_FILE_SIZE, | ||
1625 | 103 | ) | ||
1626 | 104 | for pers in all_pers: | ||
1627 | 105 | self.assertIsInstance(pers, bytes) | ||
1628 | 106 | self.assertEqual(pers[:36], | ||
1629 | 107 | b'20110430 jderose@novacut.com dmedia/' | ||
1630 | 108 | ) | ||
1631 | 109 | self.assertEqual(len(set(all_pers)), len(all_pers)) | ||
1632 | 110 | |||
1633 | 111 | # Be extra safe the what's actually used in V1: | ||
1634 | 112 | self.assertIsInstance(protocols.PERS_LEAF, bytes) | ||
1635 | 113 | self.assertIsInstance(protocols.PERS_ROOT, bytes) | ||
1636 | 114 | self.assertNotEqual(protocols.PERS_LEAF, protocols.PERS_ROOT) | ||
1637 | 115 | |||
1638 | 116 | def test_mib(self): | ||
1639 | 117 | self.assertIsInstance(protocols.MiB, int) | ||
1640 | 118 | self.assertEqual(protocols.MiB, 1024 * 1024) | ||
1641 | 119 | self.assertEqual(protocols.MiB, 2**20) | ||
1642 | 120 | |||
1643 | 56 | def test_max_file_size(self): | 121 | def test_max_file_size(self): |
1644 | 57 | # For now, constrain to JavaScript integer limit (2**53): | 122 | # For now, constrain to JavaScript integer limit (2**53): |
1646 | 58 | self.assertEqual(protocols.MAX_FILE_SIZE, 9007199254740992) | 123 | self.assertIsInstance(protocols.MAX_FILE_SIZE, int) |
1647 | 59 | self.assertEqual(protocols.MAX_FILE_SIZE, 2**53) | 124 | self.assertEqual(protocols.MAX_FILE_SIZE, 2**53) |
1648 | 60 | 125 | ||
1649 | 61 | def test_min_leaf_size(self): | 126 | def test_min_leaf_size(self): |
1651 | 62 | self.assertEqual(protocols.MIN_LEAF_SIZE, 2 * MiB) | 127 | self.assertIsInstance(protocols.MIN_LEAF_SIZE, int) |
1652 | 128 | self.assertEqual(protocols.MIN_LEAF_SIZE, 2 * 1024 * 1024) | ||
1653 | 129 | self.assertEqual(protocols.MIN_LEAF_SIZE, 2**21) | ||
1654 | 63 | self.assertEqual(protocols.MIN_LEAF_SIZE, | 130 | self.assertEqual(protocols.MIN_LEAF_SIZE, |
1655 | 64 | protocols.MAX_FILE_SIZE // 2**32 | 131 | protocols.MAX_FILE_SIZE // 2**32 |
1656 | 65 | ) | 132 | ) |
1657 | 66 | 133 | ||
1658 | 67 | 134 | ||
1659 | 68 | class TestFunctions(TestCase): | 135 | class TestFunctions(TestCase): |
1686 | 69 | def test_b32dumps(self): | 136 | def test_make_key(self): |
1687 | 70 | self.assertEqual(b32dumps(b'\x00\x00\x00\x00\x00'), 'AAAAAAAA') | 137 | with self.assertRaises(TypeError) as cm: |
1688 | 71 | self.assertEqual(b32dumps(b'\xff\xff\xff\xff\xff'), '77777777') | 138 | protocols.make_key(1.0) |
1689 | 72 | self.assertEqual(b32dumps(b'\x00' * 35), 'A' * 56) | 139 | self.assertEqual( |
1690 | 73 | self.assertEqual(b32dumps(b'\xff' * 35), '7' * 56) | 140 | str(cm.exception), |
1691 | 74 | for i in range(1000): | 141 | TYPE_ERROR.format('integer', int, float, 1.0) |
1692 | 75 | data = os.urandom(15) | 142 | ) |
1693 | 76 | self.assertEqual( | 143 | with self.assertRaises(TypeError) as cm: |
1694 | 77 | b32dumps(data), | 144 | protocols.make_key('1') |
1695 | 78 | b32encode(data).decode('utf-8') | 145 | self.assertEqual( |
1696 | 79 | ) | 146 | str(cm.exception), |
1697 | 80 | 147 | TYPE_ERROR.format('integer', int, str, '1') | |
1698 | 81 | def test_b32loads(self): | 148 | ) |
1699 | 82 | self.assertEqual(b32loads('AAAAAAAA'), b'\x00\x00\x00\x00\x00') | 149 | |
1700 | 83 | self.assertEqual(b32loads('77777777'), b'\xff\xff\xff\xff\xff') | 150 | with self.assertRaises(ValueError) as cm: |
1701 | 84 | self.assertEqual(b32loads('A' * 56), b'\x00' * 35) | 151 | protocols.make_key(-1) |
1702 | 85 | self.assertEqual(b32loads('7' * 56), b'\xff' * 35) | 152 | self.assertEqual( |
1703 | 86 | for i in range(1000): | 153 | str(cm.exception), |
1704 | 87 | data = os.urandom(15) | 154 | 'integer: must be >= 0; got -1' |
1705 | 88 | b32 = b32encode(data) | 155 | ) |
1706 | 89 | self.assertEqual(b32loads(b32.decode('utf-8')), data) | 156 | with self.assertRaises(ValueError) as cm: |
1707 | 90 | self.assertEqual(b32decode(b32), data) | 157 | protocols.make_key(-17) |
1708 | 91 | 158 | self.assertEqual( | |
1709 | 92 | class ProtocolTestCase(TestCase): | 159 | str(cm.exception), |
1710 | 93 | def check_hash_leaf(self, proto): | 160 | 'integer: must be >= 0; got -17' |
1711 | 94 | 161 | ) | |
1712 | 162 | |||
1713 | 163 | self.assertEqual(protocols.make_key(0), b'0') | ||
1714 | 164 | self.assertEqual(protocols.make_key(1), b'1') | ||
1715 | 165 | self.assertEqual(protocols.make_key(1776), b'1776') | ||
1716 | 166 | for i in range(1000): | ||
1717 | 167 | n = random.randint(0, 16 * GiB) | ||
1718 | 168 | self.assertEqual(protocols.make_key(n), repr(n).encode('ascii')) | ||
1719 | 169 | |||
1720 | 170 | |||
1721 | 171 | class BaseTestCase1(TestCase): | ||
1722 | 172 | def setUp(self): | ||
1723 | 173 | self._checks = [] | ||
1724 | 174 | |||
1725 | 175 | def check_hash_leaf_validation(self, proto): | ||
1726 | 95 | # Test with wrong leaf_index type: | 176 | # Test with wrong leaf_index type: |
1727 | 96 | with self.assertRaises(TypeError) as cm: | 177 | with self.assertRaises(TypeError) as cm: |
1728 | 97 | proto.hash_leaf(0.0, None) | 178 | proto.hash_leaf(0.0, None) |
1729 | @@ -165,63 +246,9 @@ | |||
1730 | 165 | ) | 246 | ) |
1731 | 166 | ) | 247 | ) |
1732 | 167 | 248 | ||
1790 | 168 | # Smallest possible leaf, index=0 | 249 | self._checks.append('hash_leaf_validation') |
1791 | 169 | accum = set() | 250 | |
1792 | 170 | leaf_data = b'D' | 251 | def check_hash_root_validation(self, proto): |
1736 | 171 | digest = proto.hash_leaf(0, leaf_data) | ||
1737 | 172 | accum.add(digest) | ||
1738 | 173 | self.assertEqual(proto._hash_leaf(0, leaf_data, b''), digest) | ||
1739 | 174 | |||
1740 | 175 | # Smallest possible leaf, index=1 | ||
1741 | 176 | digest = proto.hash_leaf(1, leaf_data) | ||
1742 | 177 | self.assertNotIn(digest, accum) | ||
1743 | 178 | accum.add(digest) | ||
1744 | 179 | self.assertEqual(proto._hash_leaf(1, leaf_data, b''), digest) | ||
1745 | 180 | |||
1746 | 181 | # Largest possible leaf, index=0 | ||
1747 | 182 | leaf_data = b'D' * proto.leaf_size | ||
1748 | 183 | digest = proto.hash_leaf(0, leaf_data) | ||
1749 | 184 | self.assertNotIn(digest, accum) | ||
1750 | 185 | accum.add(digest) | ||
1751 | 186 | self.assertEqual(proto._hash_leaf(0, leaf_data, b''), digest) | ||
1752 | 187 | |||
1753 | 188 | # Largest possible leaf, index=1 | ||
1754 | 189 | digest = proto.hash_leaf(1, leaf_data) | ||
1755 | 190 | self.assertNotIn(digest, accum) | ||
1756 | 191 | accum.add(digest) | ||
1757 | 192 | self.assertEqual(proto._hash_leaf(1, leaf_data, b''), digest) | ||
1758 | 193 | |||
1759 | 194 | # Test with challenge, smallest leaf, index=0: | ||
1760 | 195 | challenge = os.urandom(16) | ||
1761 | 196 | leaf_data = b'D' | ||
1762 | 197 | digest = proto.hash_leaf(0, leaf_data, challenge) | ||
1763 | 198 | self.assertNotIn(digest, accum) | ||
1764 | 199 | accum.add(digest) | ||
1765 | 200 | self.assertEqual(proto._hash_leaf(0, leaf_data, challenge), digest) | ||
1766 | 201 | |||
1767 | 202 | # Test with challenge, smallest leaf, index=1: | ||
1768 | 203 | digest = proto.hash_leaf(1, leaf_data, challenge) | ||
1769 | 204 | self.assertNotIn(digest, accum) | ||
1770 | 205 | accum.add(digest) | ||
1771 | 206 | self.assertEqual(proto._hash_leaf(1, leaf_data, challenge), digest) | ||
1772 | 207 | |||
1773 | 208 | # Test with challenge, largest leaf, index=0: | ||
1774 | 209 | leaf_data = b'D' * proto.leaf_size | ||
1775 | 210 | digest = proto.hash_leaf(0, leaf_data, challenge) | ||
1776 | 211 | self.assertNotIn(digest, accum) | ||
1777 | 212 | accum.add(digest) | ||
1778 | 213 | self.assertEqual(proto._hash_leaf(0, leaf_data, challenge), digest) | ||
1779 | 214 | |||
1780 | 215 | # Test with challenge, largest leaf, index=1: | ||
1781 | 216 | digest = proto.hash_leaf(1, leaf_data, challenge) | ||
1782 | 217 | self.assertNotIn(digest, accum) | ||
1783 | 218 | accum.add(digest) | ||
1784 | 219 | self.assertEqual(proto._hash_leaf(1, leaf_data, challenge), digest) | ||
1785 | 220 | |||
1786 | 221 | # Make sure we didn't goof | ||
1787 | 222 | self.assertEqual(len(accum), 8) | ||
1788 | 223 | |||
1789 | 224 | def check_hash_root(self, proto): | ||
1793 | 225 | # Test with wrong file_size type: | 252 | # Test with wrong file_size type: |
1794 | 226 | with self.assertRaises(TypeError) as cm: | 253 | with self.assertRaises(TypeError) as cm: |
1795 | 227 | proto.hash_root(1.0, None) | 254 | proto.hash_root(1.0, None) |
1796 | @@ -321,99 +348,10 @@ | |||
1797 | 321 | ) | 348 | ) |
1798 | 322 | ) | 349 | ) |
1799 | 323 | 350 | ||
1893 | 324 | # Test with good file_size and leaf_hashes | 351 | self._checks.append('hash_root_validation') |
1894 | 325 | accum = set() | 352 | |
1895 | 326 | leaf_hashes = b'D' * proto.digest_bytes | 353 | |
1896 | 327 | _id = proto.hash_root(1, leaf_hashes) | 354 | class TestProtocol(BaseTestCase1): |
1804 | 328 | self.assertNotIn(_id, accum) | ||
1805 | 329 | accum.add(_id) | ||
1806 | 330 | self.assertEqual( | ||
1807 | 331 | proto._hash_root(1, leaf_hashes), | ||
1808 | 332 | b32decode(_id.encode('utf-8')) | ||
1809 | 333 | ) | ||
1810 | 334 | |||
1811 | 335 | _id = proto.hash_root(2, leaf_hashes) | ||
1812 | 336 | self.assertNotIn(_id, accum) | ||
1813 | 337 | accum.add(_id) | ||
1814 | 338 | self.assertEqual( | ||
1815 | 339 | proto._hash_root(2, leaf_hashes), | ||
1816 | 340 | b32decode(_id.encode('utf-8')) | ||
1817 | 341 | ) | ||
1818 | 342 | |||
1819 | 343 | leaf_hashes = b'AB' * proto.digest_bytes | ||
1820 | 344 | _id = proto.hash_root(proto.leaf_size + 1, leaf_hashes) | ||
1821 | 345 | self.assertNotIn(_id, accum) | ||
1822 | 346 | accum.add(_id) | ||
1823 | 347 | self.assertEqual( | ||
1824 | 348 | proto._hash_root(proto.leaf_size + 1, leaf_hashes), | ||
1825 | 349 | b32decode(_id.encode('utf-8')) | ||
1826 | 350 | ) | ||
1827 | 351 | |||
1828 | 352 | _id = proto.hash_root(proto.leaf_size + 2, leaf_hashes) | ||
1829 | 353 | self.assertNotIn(_id, accum) | ||
1830 | 354 | accum.add(_id) | ||
1831 | 355 | self.assertEqual( | ||
1832 | 356 | proto._hash_root(proto.leaf_size + 2, leaf_hashes), | ||
1833 | 357 | b32decode(_id.encode('utf-8')) | ||
1834 | 358 | ) | ||
1835 | 359 | |||
1836 | 360 | # Make sure we didn't goof | ||
1837 | 361 | self.assertEqual(len(accum), 4) | ||
1838 | 362 | |||
1839 | 363 | def sanity_check_hash_leaf(self, proto): | ||
1840 | 364 | """ | ||
1841 | 365 | Random value sanity check on Protocol._hash_leaf(). | ||
1842 | 366 | """ | ||
1843 | 367 | |||
1844 | 368 | # Sanity check on our crytographic claim that the | ||
1845 | 369 | # leaf-hash is tied to the leaf-index: | ||
1846 | 370 | leaf_data = os.urandom(16) | ||
1847 | 371 | accum = set( | ||
1848 | 372 | proto._hash_leaf(i, leaf_data, b'') | ||
1849 | 373 | for i in range(COUNT) | ||
1850 | 374 | ) | ||
1851 | 375 | self.assertEqual(len(accum), COUNT) | ||
1852 | 376 | |||
1853 | 377 | # Sanity check on our crytographic claim that the | ||
1854 | 378 | # leaf-hash is tied to the leaf-data: | ||
1855 | 379 | accum = set( | ||
1856 | 380 | proto._hash_leaf(21, os.urandom(16), b'') | ||
1857 | 381 | for i in range(COUNT) | ||
1858 | 382 | ) | ||
1859 | 383 | self.assertEqual(len(accum), COUNT) | ||
1860 | 384 | |||
1861 | 385 | # Sanity check on our crytographic claim that the | ||
1862 | 386 | # leaf-hash is tied to the challenge: | ||
1863 | 387 | accum = set( | ||
1864 | 388 | proto._hash_leaf(21, leaf_data, os.urandom(16)) | ||
1865 | 389 | for i in range(COUNT) | ||
1866 | 390 | ) | ||
1867 | 391 | self.assertEqual(len(accum), COUNT) | ||
1868 | 392 | |||
1869 | 393 | def sanity_check_hash_root(self, proto): | ||
1870 | 394 | """ | ||
1871 | 395 | Random value sanity check on Protocol._hash_root(). | ||
1872 | 396 | """ | ||
1873 | 397 | |||
1874 | 398 | # Sanity check on our crytographic claim that the | ||
1875 | 399 | # root-hash is tied to the file-size: | ||
1876 | 400 | leaf_hashes = os.urandom(proto.digest_bytes) | ||
1877 | 401 | accum = set( | ||
1878 | 402 | proto._hash_root(size, leaf_hashes) | ||
1879 | 403 | for size in range(1, COUNT + 1) | ||
1880 | 404 | ) | ||
1881 | 405 | self.assertEqual(len(accum), COUNT) | ||
1882 | 406 | |||
1883 | 407 | # Sanity check on our crytographic claim that the | ||
1884 | 408 | # root-hash is tied to the leaf-hashes: | ||
1885 | 409 | accum = set( | ||
1886 | 410 | proto._hash_root(314159, os.urandom(proto.digest_bytes)) | ||
1887 | 411 | for i in range(COUNT) | ||
1888 | 412 | ) | ||
1889 | 413 | self.assertEqual(len(accum), COUNT) | ||
1890 | 414 | |||
1891 | 415 | |||
1892 | 416 | class TestProtocol(ProtocolTestCase): | ||
1897 | 417 | def test_init(self): | 355 | def test_init(self): |
1898 | 418 | # Wrong `leaf_size` type: | 356 | # Wrong `leaf_size` type: |
1899 | 419 | with self.assertRaises(TypeError) as cm: | 357 | with self.assertRaises(TypeError) as cm: |
1900 | @@ -474,7 +412,7 @@ | |||
1901 | 474 | self.assertEqual(proto.digest_bytes, 25) | 412 | self.assertEqual(proto.digest_bytes, 25) |
1902 | 475 | self.assertEqual(proto.digest_b32len, 40) | 413 | self.assertEqual(proto.digest_b32len, 40) |
1903 | 476 | 414 | ||
1905 | 477 | # Test with the actual v0 protocol values: | 415 | # Test with the actual v1 protocol values: |
1906 | 478 | proto = protocols.Protocol(8 * MiB, 240) | 416 | proto = protocols.Protocol(8 * MiB, 240) |
1907 | 479 | self.assertEqual(proto.leaf_size, 8 * MiB) | 417 | self.assertEqual(proto.leaf_size, 8 * MiB) |
1908 | 480 | self.assertEqual(proto.max_leaf_count, 1073741824) | 418 | self.assertEqual(proto.max_leaf_count, 1073741824) |
1909 | @@ -483,928 +421,891 @@ | |||
1910 | 483 | self.assertEqual(proto.digest_bytes, 30) | 421 | self.assertEqual(proto.digest_bytes, 30) |
1911 | 484 | self.assertEqual(proto.digest_b32len, 48) | 422 | self.assertEqual(proto.digest_b32len, 48) |
1912 | 485 | 423 | ||
1918 | 486 | # Test with the actual v1 protocol values: | 424 | # Test with possible future values: |
1919 | 487 | proto = protocols.Protocol(8 * MiB, 280) | 425 | proto = protocols.Protocol(16 * MiB, 280) |
1920 | 488 | self.assertEqual(proto.leaf_size, 8 * MiB) | 426 | self.assertEqual(proto.leaf_size, 16 * MiB) |
1921 | 489 | self.assertEqual(proto.max_leaf_count, 1073741824) | 427 | self.assertEqual(proto.max_leaf_count, 536870912) |
1922 | 490 | self.assertEqual(proto.max_leaf_count, 2**30) | 428 | self.assertEqual(proto.max_leaf_count, 2**29) |
1923 | 491 | self.assertEqual(proto.digest_bits, 280) | 429 | self.assertEqual(proto.digest_bits, 280) |
1924 | 492 | self.assertEqual(proto.digest_bytes, 35) | 430 | self.assertEqual(proto.digest_bytes, 35) |
1925 | 493 | self.assertEqual(proto.digest_b32len, 56) | 431 | self.assertEqual(proto.digest_b32len, 56) |
1926 | 494 | 432 | ||
1977 | 495 | def test_encode(self): | 433 | def test_hash_leaf(self): |
1978 | 496 | proto = protocols.Protocol(TwoMiB, 200) | 434 | proto = protocols.Protocol(TwoMiB, 200) |
1979 | 497 | 435 | self.check_hash_leaf_validation(proto) | |
1980 | 498 | # Wrong type: | 436 | with self.assertRaises(NotImplementedError) as cm: |
1981 | 499 | digest = 'A' * 25 | 437 | proto.hash_leaf(0, b'data') |
1982 | 500 | with self.assertRaises(TypeError) as cm: | 438 | self.assertEqual( |
1983 | 501 | proto.encode(digest) | 439 | str(cm.exception), |
1984 | 502 | self.assertEqual( | 440 | 'Protocol._hash_leaf(key_leaf_index, leaf_data, challenge)' |
1985 | 503 | str(cm.exception), | 441 | ) |
1986 | 504 | TYPE_ERROR.format('digest', bytes, str, digest) | 442 | with self.assertRaises(NotImplementedError) as cm: |
1987 | 505 | ) | 443 | proto.hash_leaf(0, b'data', b'secret') |
1988 | 506 | 444 | self.assertEqual( | |
1989 | 507 | # Wrong length: | 445 | str(cm.exception), |
1990 | 508 | digest = b'A' * 30 | 446 | 'Protocol._hash_leaf(key_leaf_index, leaf_data, challenge)' |
1991 | 509 | with self.assertRaises(ValueError) as cm: | 447 | ) |
1992 | 510 | proto.encode(digest) | 448 | with self.assertRaises(NotImplementedError) as cm: |
1993 | 511 | self.assertEqual(str(cm.exception), 'len(digest) must be 25; got 30') | 449 | proto._hash_leaf(0, b'data', b'secret') |
1994 | 512 | 450 | self.assertEqual( | |
1995 | 513 | digest = os.urandom(25) | 451 | str(cm.exception), |
1996 | 514 | self.assertEqual(proto.encode(digest), b32dumps(digest)) | 452 | 'Protocol._hash_leaf(key_leaf_index, leaf_data, challenge)' |
1997 | 515 | 453 | ) | |
1998 | 516 | def test_decode(self): | 454 | |
1999 | 517 | proto = protocols.Protocol(TwoMiB, 200) | 455 | class Example(protocols.Protocol): |
2000 | 518 | 456 | pass | |
2001 | 519 | # Wrong type: | 457 | |
2002 | 520 | _id = b'A' * 40 | 458 | proto = Example(TwoMiB, 200) |
2003 | 521 | with self.assertRaises(TypeError) as cm: | 459 | self.check_hash_leaf_validation(proto) |
2004 | 522 | proto.decode(_id) | 460 | with self.assertRaises(NotImplementedError) as cm: |
2005 | 523 | self.assertEqual( | 461 | proto.hash_leaf(0, b'data') |
2006 | 524 | str(cm.exception), | 462 | self.assertEqual( |
2007 | 525 | TYPE_ERROR.format('_id', str, bytes, _id) | 463 | str(cm.exception), |
2008 | 526 | ) | 464 | 'Example._hash_leaf(key_leaf_index, leaf_data, challenge)' |
2009 | 527 | 465 | ) | |
2010 | 528 | # Wrong length: | 466 | with self.assertRaises(NotImplementedError) as cm: |
2011 | 529 | _id = 'A' * 48 | 467 | proto.hash_leaf(0, b'data', b'secret') |
2012 | 530 | with self.assertRaises(ValueError) as cm: | 468 | self.assertEqual( |
2013 | 531 | proto.decode(_id) | 469 | str(cm.exception), |
2014 | 532 | self.assertEqual(str(cm.exception), 'len(_id) must be 40; got 48') | 470 | 'Example._hash_leaf(key_leaf_index, leaf_data, challenge)' |
2015 | 533 | 471 | ) | |
2016 | 534 | digest = os.urandom(25) | 472 | with self.assertRaises(NotImplementedError) as cm: |
2017 | 535 | _id = b32dumps(digest) | 473 | proto._hash_leaf(0, b'data', b'secret') |
2018 | 536 | self.assertEqual(proto.decode(_id), digest) | 474 | self.assertEqual( |
2019 | 537 | 475 | str(cm.exception), | |
2020 | 538 | # Test round-trip: | 476 | 'Example._hash_leaf(key_leaf_index, leaf_data, challenge)' |
2021 | 539 | for i in range(100): | 477 | ) |
2022 | 540 | digest = os.urandom(25) | 478 | |
2023 | 541 | self.assertEqual(proto.decode(proto.encode(digest)), digest) | 479 | class FooProtocol(protocols.Protocol): |
2024 | 542 | 480 | def _hash_leaf(self, *args): | |
2025 | 543 | def test_hash_leaf(self): | 481 | assert not hasattr(self, '_call') |
2026 | 544 | proto = protocols.Protocol(TwoMiB, 200) | 482 | assert not hasattr(self, '_ret') |
2027 | 483 | self._call = args | ||
2028 | 484 | self._ret = os.urandom(self.digest_bytes) | ||
2029 | 485 | return self._ret | ||
2030 | 486 | |||
2031 | 487 | foo = FooProtocol(TwoMiB, 200) | ||
2032 | 488 | self.check_hash_leaf_validation(foo) | ||
2033 | 489 | digest = foo.hash_leaf(0, b'data') | ||
2034 | 490 | self.assertEqual(foo._call, (b'0', b'data', b'')) | ||
2035 | 491 | self.assertIs(digest, foo._ret) | ||
2036 | 492 | |||
2037 | 493 | foo = FooProtocol(TwoMiB, 200) | ||
2038 | 494 | digest = foo.hash_leaf(17, b'more data', b'secret') | ||
2039 | 495 | self.assertEqual(foo._call, (b'17', b'more data', b'secret')) | ||
2040 | 496 | self.assertIs(digest, foo._ret) | ||
2041 | 497 | |||
2042 | 498 | for i in range(100): | ||
2043 | 499 | index = random.randint(0, MiB) | ||
2044 | 500 | length = random.randint(1, TwoMiB) | ||
2045 | 501 | data = os.urandom(1) * length | ||
2046 | 502 | |||
2047 | 503 | foo = FooProtocol(TwoMiB, 200) | ||
2048 | 504 | digest = foo.hash_leaf(index, data) | ||
2049 | 505 | self.assertEqual(foo._call, | ||
2050 | 506 | (str(index).encode('utf-8'), data, b'') | ||
2051 | 507 | ) | ||
2052 | 508 | self.assertIs(digest, foo._ret) | ||
2053 | 509 | |||
2054 | 510 | nonce = os.urandom(16) | ||
2055 | 511 | foo = FooProtocol(TwoMiB, 200) | ||
2056 | 512 | digest = foo.hash_leaf(index, data, nonce) | ||
2057 | 513 | self.assertEqual(foo._call, | ||
2058 | 514 | (str(index).encode('utf-8'), data, nonce) | ||
2059 | 515 | ) | ||
2060 | 516 | self.assertIs(digest, foo._ret) | ||
2061 | 517 | |||
2062 | 518 | def test_hash_root(self): | ||
2063 | 519 | proto = protocols.Protocol(TwoMiB, 200) | ||
2064 | 520 | self.check_hash_root_validation(proto) | ||
2065 | 521 | with self.assertRaises(NotImplementedError) as cm: | ||
2066 | 522 | proto.hash_root(1, b'A' * 25) | ||
2067 | 523 | self.assertEqual( | ||
2068 | 524 | str(cm.exception), | ||
2069 | 525 | 'Protocol._hash_root(key_file_size, leaf_hashes)' | ||
2070 | 526 | ) | ||
2071 | 527 | with self.assertRaises(NotImplementedError) as cm: | ||
2072 | 528 | proto._hash_root(1, b'A' * 25) | ||
2073 | 529 | self.assertEqual( | ||
2074 | 530 | str(cm.exception), | ||
2075 | 531 | 'Protocol._hash_root(key_file_size, leaf_hashes)' | ||
2076 | 532 | ) | ||
2077 | 533 | |||
2078 | 534 | class Example(protocols.Protocol): | ||
2079 | 535 | pass | ||
2080 | 536 | |||
2081 | 537 | proto = Example(TwoMiB, 200) | ||
2082 | 538 | self.check_hash_root_validation(proto) | ||
2083 | 539 | with self.assertRaises(NotImplementedError) as cm: | ||
2084 | 540 | proto.hash_root(1, b'A' * 25) | ||
2085 | 541 | self.assertEqual( | ||
2086 | 542 | str(cm.exception), | ||
2087 | 543 | 'Example._hash_root(key_file_size, leaf_hashes)' | ||
2088 | 544 | ) | ||
2089 | 545 | with self.assertRaises(NotImplementedError) as cm: | ||
2090 | 546 | proto._hash_root(1, b'A' * 25) | ||
2091 | 547 | self.assertEqual( | ||
2092 | 548 | str(cm.exception), | ||
2093 | 549 | 'Example._hash_root(key_file_size, leaf_hashes)' | ||
2094 | 550 | ) | ||
2095 | 551 | |||
2096 | 552 | class FooProtocol(protocols.Protocol): | ||
2097 | 553 | def _hash_root(self, *args): | ||
2098 | 554 | assert not hasattr(self, '_call') | ||
2099 | 555 | assert not hasattr(self, '_ret') | ||
2100 | 556 | self._call = args | ||
2101 | 557 | self._ret = os.urandom(self.digest_bytes) | ||
2102 | 558 | return self._ret | ||
2103 | 559 | |||
2104 | 560 | foo = FooProtocol(TwoMiB, 200) | ||
2105 | 561 | self.check_hash_root_validation(foo) | ||
2106 | 562 | digest = foo.hash_root(1, b'A' * 25) | ||
2107 | 563 | self.assertEqual(foo._call, (b'1', b'A' * 25)) | ||
2108 | 564 | self.assertIs(digest, foo._ret) | ||
2109 | 565 | |||
2110 | 566 | for i in range(100): | ||
2111 | 567 | size = random.randint(1, GiB) | ||
2112 | 568 | count = size // TwoMiB | ||
2113 | 569 | if size % TwoMiB: | ||
2114 | 570 | count += 1 | ||
2115 | 571 | data = os.urandom(25) * count | ||
2116 | 572 | |||
2117 | 573 | foo = FooProtocol(TwoMiB, 200) | ||
2118 | 574 | digest = foo.hash_root(size, data) | ||
2119 | 575 | self.assertEqual(foo._call, | ||
2120 | 576 | (str(size).encode('utf-8'), data) | ||
2121 | 577 | ) | ||
2122 | 578 | self.assertIs(digest, foo._ret) | ||
2123 | 579 | |||
2124 | 580 | |||
2125 | 581 | class BaseTestCase2(BaseTestCase1): | ||
2126 | 582 | def check_hash_leaf_simple(self, proto): | ||
2127 | 583 | accum = DigestAccum() | ||
2128 | 584 | small = b'D' # Smallest possible leaf | ||
2129 | 585 | large = b'D' * proto.leaf_size # Largest possible leaf | ||
2130 | 586 | |||
2131 | 587 | # Smallest leaf, index=0 | ||
2132 | 588 | digest = accum.add(proto.hash_leaf(0, small)) | ||
2133 | 589 | self.assertEqual(proto._hash_leaf(b'0', small, b''), digest) | ||
2134 | 590 | |||
2135 | 591 | # Smallest leaf, index=1 | ||
2136 | 592 | digest = accum.add(proto.hash_leaf(1, small)) | ||
2137 | 593 | self.assertEqual(proto._hash_leaf(b'1', small, b''), digest) | ||
2138 | 594 | |||
2139 | 595 | # Largest leaf, index=0 | ||
2140 | 596 | digest = accum.add(proto.hash_leaf(0, large)) | ||
2141 | 597 | self.assertEqual(proto._hash_leaf(b'0', large, b''), digest) | ||
2142 | 598 | |||
2143 | 599 | # Largest leaf, index=1 | ||
2144 | 600 | digest = accum.add(proto.hash_leaf(1, large)) | ||
2145 | 601 | self.assertEqual(proto._hash_leaf(b'1', large, b''), digest) | ||
2146 | 602 | |||
2147 | 603 | # Test with challenge, smallest leaf, index=0: | ||
2148 | 604 | digest = accum.add(proto.hash_leaf(0, small, b'secret')) | ||
2149 | 605 | self.assertEqual(proto._hash_leaf(b'0', small, b'secret'), digest) | ||
2150 | 606 | |||
2151 | 607 | # Test with challenge, smallest leaf, index=1: | ||
2152 | 608 | digest = accum.add(proto.hash_leaf(1, small, b'secret')) | ||
2153 | 609 | self.assertEqual(proto._hash_leaf(b'1', small, b'secret'), digest) | ||
2154 | 610 | |||
2155 | 611 | # Test with challenge, largest leaf, index=0: | ||
2156 | 612 | digest = accum.add(proto.hash_leaf(0, large, b'secret')) | ||
2157 | 613 | self.assertEqual(proto._hash_leaf(b'0', large, b'secret'), digest) | ||
2158 | 614 | |||
2159 | 615 | # Test with challenge, largest leaf, index=1: | ||
2160 | 616 | digest = accum.add(proto.hash_leaf(1, large, b'secret')) | ||
2161 | 617 | self.assertEqual(proto._hash_leaf(b'1', large, b'secret'), digest) | ||
2162 | 618 | |||
2163 | 619 | # Make sure we didn't goof | ||
2164 | 620 | self.assertEqual(len(accum), 8) | ||
2165 | 621 | for digest in accum: | ||
2166 | 622 | self.assertEqual(len(digest), proto.digest_bytes) | ||
2167 | 623 | safety = set() | ||
2168 | 624 | for index in [0, 1]: | ||
2169 | 625 | for data in [small, large]: | ||
2170 | 626 | for challenge in [b'', b'secret']: | ||
2171 | 627 | safety.add(proto.hash_leaf(index, data, challenge)) | ||
2172 | 628 | self.assertEqual(safety, accum) | ||
2173 | 629 | |||
2174 | 630 | self._checks.append('hash_leaf_simple') | ||
2175 | 631 | |||
2176 | 632 | def check_hash_root_simple(self, proto): | ||
2177 | 633 | accum = DigestAccum() | ||
2178 | 634 | one = b'D' * proto.digest_bytes | ||
2179 | 635 | two = b'AB' * proto.digest_bytes | ||
2180 | 636 | |||
2181 | 637 | digest = accum.add(proto.hash_root(1, one)) | ||
2182 | 638 | self.assertEqual(proto._hash_root(b'1', one), digest) | ||
2183 | 639 | |||
2184 | 640 | digest = accum.add(proto.hash_root(2, one)) | ||
2185 | 641 | self.assertEqual(proto._hash_root(b'2', one), digest) | ||
2186 | 642 | |||
2187 | 643 | size = proto.leaf_size + 1 | ||
2188 | 644 | digest = accum.add(proto.hash_root(size, two)) | ||
2189 | 645 | self.assertEqual( | ||
2190 | 646 | proto._hash_root(str(size).encode('utf-8'), two), | ||
2191 | 647 | digest | ||
2192 | 648 | ) | ||
2193 | 649 | |||
2194 | 650 | size = proto.leaf_size + 2 | ||
2195 | 651 | digest = accum.add(proto.hash_root(size, two)) | ||
2196 | 652 | self.assertEqual( | ||
2197 | 653 | proto._hash_root(str(size).encode('utf-8'), two), | ||
2198 | 654 | digest | ||
2199 | 655 | ) | ||
2200 | 656 | |||
2201 | 657 | size = proto.leaf_size * 2 | ||
2202 | 658 | digest = accum.add(proto.hash_root(size, two)) | ||
2203 | 659 | self.assertEqual( | ||
2204 | 660 | proto._hash_root(str(size).encode('utf-8'), two), | ||
2205 | 661 | digest | ||
2206 | 662 | ) | ||
2207 | 663 | |||
2208 | 664 | # Make sure we didn't goof | ||
2209 | 665 | self.assertEqual(len(accum), 5) | ||
2210 | 666 | for digest in accum: | ||
2211 | 667 | self.assertEqual(len(digest), proto.digest_bytes) | ||
2212 | 668 | safety = set([ | ||
2213 | 669 | proto.hash_root(1, one), | ||
2214 | 670 | proto.hash_root(2, one), | ||
2215 | 671 | proto.hash_root(proto.leaf_size + 1, two), | ||
2216 | 672 | proto.hash_root(proto.leaf_size + 2, two), | ||
2217 | 673 | proto.hash_root(proto.leaf_size * 2, two), | ||
2218 | 674 | ]) | ||
2219 | 675 | self.assertEqual(safety, accum) | ||
2220 | 676 | |||
2221 | 677 | self._checks.append('hash_root_simple') | ||
2222 | 678 | |||
2223 | 679 | def check_hash_leaf_crypto(self, proto): | ||
2224 | 680 | """ | ||
2225 | 681 | Random value sanity check on Protocol.hash_leaf(). | ||
2226 | 682 | """ | ||
2227 | 683 | |||
2228 | 684 | # Sanity check on our cryptographic claim that the | ||
2229 | 685 | # leaf-hash is tied to the leaf-index: | ||
2230 | 686 | leaf_data = os.urandom(16) | ||
2231 | 687 | accum = set( | ||
2232 | 688 | proto.hash_leaf(i, leaf_data, b'') | ||
2233 | 689 | for i in range(COUNT) | ||
2234 | 690 | ) | ||
2235 | 691 | self.assertEqual(len(accum), COUNT) | ||
2236 | 692 | |||
2237 | 693 | # Sanity check on our cryptographic claim that the | ||
2238 | 694 | # leaf-hash is tied to the leaf-data: | ||
2239 | 695 | accum = set( | ||
2240 | 696 | proto.hash_leaf(21, os.urandom(16), b'') | ||
2241 | 697 | for i in range(COUNT) | ||
2242 | 698 | ) | ||
2243 | 699 | self.assertEqual(len(accum), COUNT) | ||
2244 | 700 | |||
2245 | 701 | # Sanity check on our cryptographic claim that the | ||
2246 | 702 | # leaf-hash is tied to the challenge: | ||
2247 | 703 | accum = set( | ||
2248 | 704 | proto.hash_leaf(21, leaf_data, os.urandom(16)) | ||
2249 | 705 | for i in range(COUNT) | ||
2250 | 706 | ) | ||
2251 | 707 | self.assertEqual(len(accum), COUNT) | ||
2252 | 708 | |||
2253 | 709 | self._checks.append('hash_leaf_crypto') | ||
2254 | 710 | |||
2255 | 711 | def check_hash_root_crypto(self, proto): | ||
2256 | 712 | """ | ||
2257 | 713 | Random value sanity check on Protocol.hash_root(). | ||
2258 | 714 | """ | ||
2259 | 715 | |||
2260 | 716 | # Sanity check on our cryptographic claim that the | ||
2261 | 717 | # root-hash is tied to the file-size: | ||
2262 | 718 | leaf_hashes = os.urandom(proto.digest_bytes) | ||
2263 | 719 | accum = set( | ||
2264 | 720 | proto.hash_root(size, leaf_hashes) | ||
2265 | 721 | for size in range(1, COUNT + 1) | ||
2266 | 722 | ) | ||
2267 | 723 | self.assertEqual(len(accum), COUNT) | ||
2268 | 724 | |||
2269 | 725 | # Sanity check on our cryptographic claim that the | ||
2270 | 726 | # root-hash is tied to the leaf-hashes: | ||
2271 | 727 | accum = set( | ||
2272 | 728 | proto.hash_root(314159, os.urandom(proto.digest_bytes)) | ||
2273 | 729 | for i in range(COUNT) | ||
2274 | 730 | ) | ||
2275 | 731 | self.assertEqual(len(accum), COUNT) | ||
2276 | 732 | |||
2277 | 733 | self._checks.append('hash_root_crypto') | ||
2278 | 734 | |||
2279 | 735 | def check_hash_leaf(self, proto): | ||
2280 | 736 | self.check_hash_leaf_validation(proto) | ||
2281 | 737 | self.check_hash_leaf_simple(proto) | ||
2282 | 738 | self.check_hash_leaf_crypto(proto) | ||
2283 | 739 | |||
2284 | 740 | def check_hash_root(self, proto): | ||
2285 | 741 | self.check_hash_root_validation(proto) | ||
2286 | 742 | self.check_hash_root_simple(proto) | ||
2287 | 743 | self.check_hash_root_crypto(proto) | ||
2288 | 744 | |||
2289 | 745 | |||
2290 | 746 | class TestSkeinProtocol(BaseTestCase2): | ||
2291 | 747 | def test_hash_leaf(self): | ||
2292 | 748 | proto = protocols.SkeinProtocol(TwoMiB, 200) | ||
2293 | 545 | self.check_hash_leaf(proto) | 749 | self.check_hash_leaf(proto) |
2353 | 546 | self.sanity_check_hash_leaf(proto) | 750 | self.assertEqual(self._checks, [ |
2354 | 547 | 751 | 'hash_leaf_validation', | |
2355 | 548 | # Smallest possible leaf, index=0 | 752 | 'hash_leaf_simple', |
2356 | 549 | accum = set() | 753 | 'hash_leaf_crypto', |
2357 | 550 | leaf_data = b'D' | 754 | ]) |
2358 | 551 | digest = proto.hash_leaf(0, leaf_data) | 755 | |
2359 | 552 | accum.add(digest) | 756 | accum = DigestAccum() |
2360 | 553 | self.assertEqual( | 757 | small = b'A' # Smallest possible leaf |
2361 | 554 | digest, | 758 | large = b'B' * proto.leaf_size # Largest possible leaf |
2362 | 555 | skein512(leaf_data, | 759 | |
2363 | 556 | digest_bits=200, | 760 | # Smallest leaf, index=0 |
2364 | 557 | pers=protocols.PERS_LEAF, | 761 | digest = accum.add(proto.hash_leaf(0, small)) |
2365 | 558 | key=b'0', | 762 | self.assertEqual(digest, |
2366 | 559 | ).digest() | 763 | skein512(small, |
2367 | 560 | ) | 764 | digest_bits=200, |
2368 | 561 | self.assertEqual(proto._hash_leaf(0, leaf_data, b''), digest) | 765 | pers=protocols.PERS_LEAF, |
2369 | 562 | 766 | key=b'0', | |
2370 | 563 | # Smallest possible leaf, index=1 | 767 | ).digest() |
2371 | 564 | digest = proto.hash_leaf(1, leaf_data) | 768 | ) |
2372 | 565 | self.assertNotIn(digest, accum) | 769 | self.assertEqual(proto._hash_leaf(b'0', small, b''), digest) |
2373 | 566 | accum.add(digest) | 770 | |
2374 | 567 | self.assertEqual( | 771 | # Smallest leaf, index=1 |
2375 | 568 | digest, | 772 | digest = accum.add(proto.hash_leaf(1, small)) |
2376 | 569 | skein512(leaf_data, | 773 | self.assertEqual(digest, |
2377 | 570 | digest_bits=200, | 774 | skein512(small, |
2378 | 571 | pers=protocols.PERS_LEAF, | 775 | digest_bits=200, |
2379 | 572 | key=b'1', | 776 | pers=protocols.PERS_LEAF, |
2380 | 573 | ).digest() | 777 | key=b'1', |
2381 | 574 | ) | 778 | ).digest() |
2382 | 575 | self.assertEqual(proto._hash_leaf(1, leaf_data, b''), digest) | 779 | ) |
2383 | 576 | 780 | self.assertEqual(proto._hash_leaf(b'1', small, b''), digest) | |
2384 | 577 | # Largest possible leaf, index=0 | 781 | |
2385 | 578 | leaf_data = b'D' * TwoMiB | 782 | # Largest leaf, index=0 |
2386 | 579 | digest = proto.hash_leaf(0, leaf_data) | 783 | digest = accum.add(proto.hash_leaf(0, large)) |
2387 | 580 | self.assertNotIn(digest, accum) | 784 | self.assertEqual(digest, |
2388 | 581 | accum.add(digest) | 785 | skein512(large, |
2389 | 582 | self.assertEqual( | 786 | digest_bits=200, |
2390 | 583 | digest, | 787 | pers=protocols.PERS_LEAF, |
2391 | 584 | skein512(leaf_data, | 788 | key=b'0', |
2392 | 585 | digest_bits=200, | 789 | ).digest() |
2393 | 586 | pers=protocols.PERS_LEAF, | 790 | ) |
2394 | 587 | key=b'0', | 791 | self.assertEqual(proto._hash_leaf(b'0', large, b''), digest) |
2395 | 588 | ).digest() | 792 | |
2396 | 589 | ) | 793 | # Largest leaf, index=1 |
2397 | 590 | self.assertEqual(proto._hash_leaf(0, leaf_data, b''), digest) | 794 | digest = accum.add(proto.hash_leaf(1, large)) |
2398 | 591 | 795 | self.assertEqual(digest, | |
2399 | 592 | # Largest possible leaf, index=1 | 796 | skein512(large, |
2400 | 593 | digest = proto.hash_leaf(1, leaf_data) | 797 | digest_bits=200, |
2401 | 594 | self.assertNotIn(digest, accum) | 798 | pers=protocols.PERS_LEAF, |
2402 | 595 | accum.add(digest) | 799 | key=b'1', |
2403 | 596 | self.assertEqual( | 800 | ).digest() |
2404 | 597 | digest, | 801 | ) |
2405 | 598 | skein512(leaf_data, | 802 | self.assertEqual(proto._hash_leaf(b'1', large, b''), digest) |
2347 | 599 | digest_bits=200, | ||
2348 | 600 | pers=protocols.PERS_LEAF, | ||
2349 | 601 | key=b'1', | ||
2350 | 602 | ).digest() | ||
2351 | 603 | ) | ||
2352 | 604 | self.assertEqual(proto._hash_leaf(1, leaf_data, b''), digest) | ||
2406 | 605 | 803 | ||
2407 | 606 | # Test with challenge, smallest leaf, index=0: | 804 | # Test with challenge, smallest leaf, index=0: |
2416 | 607 | challenge = os.urandom(16) | 805 | digest = accum.add(proto.hash_leaf(0, small, b'secret')) |
2417 | 608 | leaf_data = b'D' | 806 | self.assertEqual(digest, |
2418 | 609 | digest = proto.hash_leaf(0, leaf_data, challenge) | 807 | skein512(small, |
2411 | 610 | self.assertNotIn(digest, accum) | ||
2412 | 611 | accum.add(digest) | ||
2413 | 612 | self.assertEqual( | ||
2414 | 613 | digest, | ||
2415 | 614 | skein512(leaf_data, | ||
2419 | 615 | digest_bits=200, | 808 | digest_bits=200, |
2420 | 616 | pers=protocols.PERS_LEAF, | 809 | pers=protocols.PERS_LEAF, |
2421 | 617 | key=b'0', | 810 | key=b'0', |
2423 | 618 | nonce=challenge, | 811 | nonce=b'secret', |
2424 | 619 | ).digest() | 812 | ).digest() |
2425 | 620 | ) | 813 | ) |
2427 | 621 | self.assertEqual(proto._hash_leaf(0, leaf_data, challenge), digest) | 814 | self.assertEqual(proto._hash_leaf(b'0', small, b'secret'), digest) |
2428 | 622 | 815 | ||
2429 | 623 | # Test with challenge, smallest leaf, index=1: | 816 | # Test with challenge, smallest leaf, index=1: |
2436 | 624 | digest = proto.hash_leaf(1, leaf_data, challenge) | 817 | digest = accum.add(proto.hash_leaf(1, small, b'secret')) |
2437 | 625 | self.assertNotIn(digest, accum) | 818 | self.assertEqual(digest, |
2438 | 626 | accum.add(digest) | 819 | skein512(small, |
2433 | 627 | self.assertEqual( | ||
2434 | 628 | digest, | ||
2435 | 629 | skein512(leaf_data, | ||
2439 | 630 | digest_bits=200, | 820 | digest_bits=200, |
2440 | 631 | pers=protocols.PERS_LEAF, | 821 | pers=protocols.PERS_LEAF, |
2441 | 632 | key=b'1', | 822 | key=b'1', |
2443 | 633 | nonce=challenge, | 823 | nonce=b'secret', |
2444 | 634 | ).digest() | 824 | ).digest() |
2445 | 635 | ) | 825 | ) |
2447 | 636 | self.assertEqual(proto._hash_leaf(1, leaf_data, challenge), digest) | 826 | self.assertEqual(proto._hash_leaf(b'1', small, b'secret'), digest) |
2448 | 637 | 827 | ||
2449 | 638 | # Test with challenge, largest leaf, index=0: | 828 | # Test with challenge, largest leaf, index=0: |
2457 | 639 | leaf_data = b'D' * TwoMiB | 829 | digest = accum.add(proto.hash_leaf(0, large, b'secret')) |
2458 | 640 | digest = proto.hash_leaf(0, leaf_data, challenge) | 830 | self.assertEqual(digest, |
2459 | 641 | self.assertNotIn(digest, accum) | 831 | skein512(large, |
2453 | 642 | accum.add(digest) | ||
2454 | 643 | self.assertEqual( | ||
2455 | 644 | digest, | ||
2456 | 645 | skein512(leaf_data, | ||
2460 | 646 | digest_bits=200, | 832 | digest_bits=200, |
2461 | 647 | pers=protocols.PERS_LEAF, | 833 | pers=protocols.PERS_LEAF, |
2462 | 648 | key=b'0', | 834 | key=b'0', |
2464 | 649 | nonce=challenge, | 835 | nonce=b'secret', |
2465 | 650 | ).digest() | 836 | ).digest() |
2466 | 651 | ) | 837 | ) |
2468 | 652 | self.assertEqual(proto._hash_leaf(0, leaf_data, challenge), digest) | 838 | self.assertEqual(proto._hash_leaf(b'0', large, b'secret'), digest) |
2469 | 653 | 839 | ||
2470 | 654 | # Test with challenge, largest leaf, index=1: | 840 | # Test with challenge, largest leaf, index=1: |
2477 | 655 | digest = proto.hash_leaf(1, leaf_data, challenge) | 841 | digest = accum.add(proto.hash_leaf(1, large, b'secret')) |
2478 | 656 | self.assertNotIn(digest, accum) | 842 | self.assertEqual(digest, |
2479 | 657 | accum.add(digest) | 843 | skein512(large, |
2474 | 658 | self.assertEqual( | ||
2475 | 659 | digest, | ||
2476 | 660 | skein512(leaf_data, | ||
2480 | 661 | digest_bits=200, | 844 | digest_bits=200, |
2481 | 662 | pers=protocols.PERS_LEAF, | 845 | pers=protocols.PERS_LEAF, |
2482 | 663 | key=b'1', | 846 | key=b'1', |
2484 | 664 | nonce=challenge, | 847 | nonce=b'secret', |
2485 | 665 | ).digest() | 848 | ).digest() |
2486 | 666 | ) | 849 | ) |
2488 | 667 | self.assertEqual(proto._hash_leaf(1, leaf_data, challenge), digest) | 850 | self.assertEqual(proto._hash_leaf(b'1', large, b'secret'), digest) |
2489 | 668 | 851 | ||
2490 | 669 | # Make sure we didn't goof | 852 | # Make sure we didn't goof |
2491 | 853 | safety = set() | ||
2492 | 854 | for index in [0, 1]: | ||
2493 | 855 | for data in [small, large]: | ||
2494 | 856 | for challenge in [b'', b'secret']: | ||
2495 | 857 | safety.add(proto.hash_leaf(index, data, challenge)) | ||
2496 | 858 | self.assertEqual(safety, accum) | ||
2497 | 670 | self.assertEqual(len(accum), 8) | 859 | self.assertEqual(len(accum), 8) |
2498 | 860 | for digest in accum: | ||
2499 | 861 | self.assertEqual(len(digest), 25) | ||
2500 | 671 | 862 | ||
2501 | 672 | def test_hash_root(self): | 863 | def test_hash_root(self): |
2503 | 673 | proto = protocols.Protocol(TwoMiB, 200) | 864 | proto = protocols.SkeinProtocol(TwoMiB, 200) |
2504 | 674 | self.check_hash_root(proto) | 865 | self.check_hash_root(proto) |
2506 | 675 | self.sanity_check_hash_root(proto) | 866 | self.assertEqual(self._checks, [ |
2507 | 867 | 'hash_root_validation', | ||
2508 | 868 | 'hash_root_simple', | ||
2509 | 869 | 'hash_root_crypto', | ||
2510 | 870 | ]) | ||
2511 | 676 | 871 | ||
2513 | 677 | accum = set() | 872 | accum = DigestAccum() |
2514 | 873 | one = b'D' * 25 | ||
2515 | 874 | two = b'D' * 50 | ||
2516 | 678 | 875 | ||
2517 | 679 | # Smallest file-size possible for one leaf: | 876 | # Smallest file-size possible for one leaf: |
2525 | 680 | leaf_hashes = b'D' * 25 | 877 | digest = accum.add(proto.hash_root(1, one)) |
2526 | 681 | digest = proto._hash_root(1, leaf_hashes) | 878 | self.assertEqual(digest, |
2527 | 682 | self.assertNotIn(digest, accum) | 879 | skein512(one, |
2521 | 683 | accum.add(digest) | ||
2522 | 684 | self.assertEqual( | ||
2523 | 685 | digest, | ||
2524 | 686 | skein512(leaf_hashes, | ||
2528 | 687 | digest_bits=200, | 880 | digest_bits=200, |
2529 | 688 | pers=protocols.PERS_ROOT, | 881 | pers=protocols.PERS_ROOT, |
2530 | 689 | key=b'1', | 882 | key=b'1', |
2531 | 690 | ).digest() | 883 | ).digest() |
2532 | 691 | ) | 884 | ) |
2537 | 692 | self.assertEqual( | 885 | self.assertEqual(proto._hash_root(b'1', one), digest) |
2534 | 693 | proto.hash_root(1, leaf_hashes), | ||
2535 | 694 | b32encode(digest).decode('utf-8') | ||
2536 | 695 | ) | ||
2538 | 696 | 886 | ||
2539 | 697 | # Largest file-size possible for one leaf: | 887 | # Largest file-size possible for one leaf: |
2546 | 698 | digest = proto._hash_root(TwoMiB, leaf_hashes) | 888 | digest = accum.add(proto.hash_root(TwoMiB, one)) |
2547 | 699 | self.assertNotIn(digest, accum) | 889 | self.assertEqual(digest, |
2548 | 700 | accum.add(digest) | 890 | skein512(one, |
2543 | 701 | self.assertEqual( | ||
2544 | 702 | digest, | ||
2545 | 703 | skein512(leaf_hashes, | ||
2549 | 704 | digest_bits=200, | 891 | digest_bits=200, |
2550 | 705 | pers=protocols.PERS_ROOT, | 892 | pers=protocols.PERS_ROOT, |
2551 | 706 | key=b'2097152', | 893 | key=b'2097152', |
2552 | 707 | ).digest() | 894 | ).digest() |
2553 | 708 | ) | 895 | ) |
2558 | 709 | self.assertEqual( | 896 | self.assertEqual(proto._hash_root(b'2097152', one), digest) |
2555 | 710 | proto.hash_root(TwoMiB, leaf_hashes), | ||
2556 | 711 | b32encode(digest).decode('utf-8') | ||
2557 | 712 | ) | ||
2559 | 713 | 897 | ||
2560 | 714 | # Smallest file-size possible for two leaves: | 898 | # Smallest file-size possible for two leaves: |
2568 | 715 | leaf_hashes = b'D' * 50 | 899 | digest = accum.add(proto.hash_root(TwoMiB + 1, two)) |
2569 | 716 | digest = proto._hash_root(TwoMiB + 1, leaf_hashes) | 900 | self.assertEqual(digest, |
2570 | 717 | self.assertNotIn(digest, accum) | 901 | skein512(two, |
2564 | 718 | accum.add(digest) | ||
2565 | 719 | self.assertEqual( | ||
2566 | 720 | digest, | ||
2567 | 721 | skein512(leaf_hashes, | ||
2571 | 722 | digest_bits=200, | 902 | digest_bits=200, |
2572 | 723 | pers=protocols.PERS_ROOT, | 903 | pers=protocols.PERS_ROOT, |
2573 | 724 | key=b'2097153', | 904 | key=b'2097153', |
2574 | 725 | ).digest() | 905 | ).digest() |
2575 | 726 | ) | 906 | ) |
2580 | 727 | self.assertEqual( | 907 | self.assertEqual(proto._hash_root(b'2097153', two), digest) |
2577 | 728 | proto.hash_root(TwoMiB + 1, leaf_hashes), | ||
2578 | 729 | b32encode(digest).decode('utf-8') | ||
2579 | 730 | ) | ||
2581 | 731 | 908 | ||
2582 | 732 | # Largest file-size possible for two leaves: | 909 | # Largest file-size possible for two leaves: |
2589 | 733 | digest = proto._hash_root(2 * TwoMiB, leaf_hashes) | 910 | digest = accum.add(proto.hash_root(2 * TwoMiB, two)) |
2590 | 734 | self.assertNotIn(digest, accum) | 911 | self.assertEqual(digest, |
2591 | 735 | accum.add(digest) | 912 | skein512(two, |
2586 | 736 | self.assertEqual( | ||
2587 | 737 | digest, | ||
2588 | 738 | skein512(leaf_hashes, | ||
2592 | 739 | digest_bits=200, | 913 | digest_bits=200, |
2593 | 740 | pers=protocols.PERS_ROOT, | 914 | pers=protocols.PERS_ROOT, |
2594 | 741 | key=b'4194304', | 915 | key=b'4194304', |
2595 | 742 | ).digest() | 916 | ).digest() |
2596 | 743 | ) | 917 | ) |
2601 | 744 | self.assertEqual( | 918 | self.assertEqual(proto._hash_root(b'4194304', two), digest) |
2598 | 745 | proto.hash_root(2 * TwoMiB, leaf_hashes), | ||
2599 | 746 | b32encode(digest).decode('utf-8') | ||
2600 | 747 | ) | ||
2602 | 748 | 919 | ||
2603 | 749 | # Make sure we didn't goof | 920 | # Make sure we didn't goof |
2604 | 750 | self.assertEqual(len(accum), 4) | 921 | self.assertEqual(len(accum), 4) |
2674 | 751 | 922 | safety = set([ | |
2675 | 752 | 923 | proto.hash_root(1, one), | |
2676 | 753 | class TestVersionOne(ProtocolTestCase): | 924 | proto.hash_root(TwoMiB, one), |
2677 | 754 | 925 | proto.hash_root(TwoMiB + 1, two), | |
2678 | 755 | def test_md5(self): | 926 | proto.hash_root(TwoMiB * 2, two), |
2679 | 756 | # To aid debugging, md5sums of UTF-8 strings needed by test vectors | 927 | ]) |
2680 | 757 | self.assertEqual( | 928 | self.assertEqual(safety, accum) |
2681 | 758 | md5(protocols.PERS_LEAF).hexdigest(), | 929 | for digest in accum: |
2682 | 759 | '8aee35e23a5a74147b230f12123ca82e' | 930 | self.assertEqual(len(digest), 25) |
2683 | 760 | ) | 931 | |
2684 | 761 | self.assertEqual( | 932 | |
2685 | 762 | md5(protocols.PERS_ROOT).hexdigest(), | 933 | class TestOldSkeinProtocol(BaseTestCase2): |
2617 | 763 | '0445d91d37383f5384023d49e71cc629' | ||
2618 | 764 | ) | ||
2619 | 765 | self.assertEqual( | ||
2620 | 766 | md5(str(0).encode('utf-8')).hexdigest(), | ||
2621 | 767 | 'cfcd208495d565ef66e7dff9f98764da' | ||
2622 | 768 | ) | ||
2623 | 769 | self.assertEqual( | ||
2624 | 770 | md5(str(1).encode('utf-8')).hexdigest(), | ||
2625 | 771 | 'c4ca4238a0b923820dcc509a6f75849b' | ||
2626 | 772 | ) | ||
2627 | 773 | self.assertEqual( | ||
2628 | 774 | md5(str(8388607).encode('utf-8')).hexdigest(), | ||
2629 | 775 | '32433904a755e2b9eb82cf167723b34f' | ||
2630 | 776 | ) | ||
2631 | 777 | self.assertEqual( | ||
2632 | 778 | md5(str(8388608).encode('utf-8')).hexdigest(), | ||
2633 | 779 | '03926fda4e223707d290ac06bb996653' | ||
2634 | 780 | ) | ||
2635 | 781 | self.assertEqual( | ||
2636 | 782 | md5(str(8388609).encode('utf-8')).hexdigest(), | ||
2637 | 783 | 'e9b74719ce6b80c5337148d12725db03' | ||
2638 | 784 | ) | ||
2639 | 785 | self.assertEqual( | ||
2640 | 786 | md5(str(16777215).encode('utf-8')).hexdigest(), | ||
2641 | 787 | '48ac8929ffdc78a66090d179ff1237d5' | ||
2642 | 788 | ) | ||
2643 | 789 | self.assertEqual( | ||
2644 | 790 | md5(str(16777216).encode('utf-8')).hexdigest(), | ||
2645 | 791 | 'e3e330499348f791337e9da6b534a386' | ||
2646 | 792 | ) | ||
2647 | 793 | |||
2648 | 794 | # To aid debugging, md5sums of the test vector files: | ||
2649 | 795 | self.assertEqual( | ||
2650 | 796 | md5(A).hexdigest(), | ||
2651 | 797 | '7fc56270e7a70fa81a5935b72eacbe29' | ||
2652 | 798 | ) | ||
2653 | 799 | self.assertEqual( | ||
2654 | 800 | md5(B).hexdigest(), | ||
2655 | 801 | 'd2bad3eedb424dd352d65eafbf6c79ba' | ||
2656 | 802 | ) | ||
2657 | 803 | self.assertEqual( | ||
2658 | 804 | md5(C).hexdigest(), | ||
2659 | 805 | '5dd3531303dd6764acb93e5f171a4ab8' | ||
2660 | 806 | ) | ||
2661 | 807 | self.assertEqual( | ||
2662 | 808 | md5(C + A).hexdigest(), | ||
2663 | 809 | '0722f8dc36d75acb602dcee8d0427ce0' | ||
2664 | 810 | ) | ||
2665 | 811 | self.assertEqual( | ||
2666 | 812 | md5(C + B).hexdigest(), | ||
2667 | 813 | '77264eb6eed7777a1ee03e2601fc9f64' | ||
2668 | 814 | ) | ||
2669 | 815 | self.assertEqual( | ||
2670 | 816 | md5(C + C).hexdigest(), | ||
2671 | 817 | '1fbfabdaafff31967f9a95f3a3d3c642' | ||
2672 | 818 | ) | ||
2673 | 819 | |||
2686 | 820 | def test_hash_leaf(self): | 934 | def test_hash_leaf(self): |
2689 | 821 | proto = protocols.VERSION1 | 935 | proto = protocols.OldSkeinProtocol(TwoMiB, 200) |
2688 | 822 | |||
2690 | 823 | self.check_hash_leaf(proto) | 936 | self.check_hash_leaf(proto) |
2770 | 824 | self.sanity_check_hash_leaf(proto) | 937 | self.assertEqual(self._checks, [ |
2771 | 825 | 938 | 'hash_leaf_validation', | |
2772 | 826 | # A0 | 939 | 'hash_leaf_simple', |
2773 | 827 | digest = proto._hash_leaf(0, A, b'') | 940 | 'hash_leaf_crypto', |
2774 | 828 | self.assertEqual(digest, b32loads(A0_B32)) | 941 | ]) |
2775 | 829 | self.assertEqual( | 942 | |
2776 | 830 | digest, | 943 | accum = DigestAccum() |
2777 | 831 | skein512(A, | 944 | small = b'A' # Smallest possible leaf |
2778 | 832 | digest_bits=280, | 945 | large = b'B' * proto.leaf_size # Largest possible leaf |
2779 | 833 | pers=protocols.PERS_LEAF, | 946 | key0 = proto._hash_leaf_index(b'0') |
2780 | 834 | key=b'0', | 947 | key1 = proto._hash_leaf_index(b'1') |
2781 | 835 | ).digest() | 948 | |
2782 | 836 | ) | 949 | # Smallest leaf, index=0 |
2783 | 837 | self.assertEqual(proto.hash_leaf(0, A), digest) | 950 | digest = accum.add(proto.hash_leaf(0, small)) |
2784 | 838 | 951 | self.assertEqual(digest, | |
2785 | 839 | # A1 | 952 | skein512(key0 + small, |
2786 | 840 | digest = proto._hash_leaf(1, A, b'') | 953 | digest_bits=200, |
2787 | 841 | self.assertEqual(digest, b32loads(A1_B32)) | 954 | pers=protocols.PERS_LEAF, |
2788 | 842 | self.assertEqual( | 955 | ).digest() |
2789 | 843 | digest, | 956 | ) |
2790 | 844 | skein512(A, | 957 | self.assertEqual(proto._hash_leaf(b'0', small, b''), digest) |
2791 | 845 | digest_bits=280, | 958 | |
2792 | 846 | pers=protocols.PERS_LEAF, | 959 | # Smallest leaf, index=1 |
2793 | 847 | key=b'1', | 960 | digest = accum.add(proto.hash_leaf(1, small)) |
2794 | 848 | ).digest() | 961 | self.assertEqual(digest, |
2795 | 849 | ) | 962 | skein512(key1 + small, |
2796 | 850 | self.assertEqual(proto.hash_leaf(1, A), digest) | 963 | digest_bits=200, |
2797 | 851 | 964 | pers=protocols.PERS_LEAF, | |
2798 | 852 | # B0 | 965 | ).digest() |
2799 | 853 | digest = proto._hash_leaf(0, B, b'') | 966 | ) |
2800 | 854 | self.assertEqual(digest, b32loads(B0_B32)) | 967 | self.assertEqual(proto._hash_leaf(b'1', small, b''), digest) |
2801 | 855 | self.assertEqual( | 968 | |
2802 | 856 | digest, | 969 | # Largest leaf, index=0 |
2803 | 857 | skein512(B, | 970 | digest = accum.add(proto.hash_leaf(0, large)) |
2804 | 858 | digest_bits=280, | 971 | self.assertEqual(digest, |
2805 | 859 | pers=protocols.PERS_LEAF, | 972 | skein512(key0 + large, |
2806 | 860 | key=b'0', | 973 | digest_bits=200, |
2807 | 861 | ).digest() | 974 | pers=protocols.PERS_LEAF, |
2808 | 862 | ) | 975 | ).digest() |
2809 | 863 | self.assertEqual(proto.hash_leaf(0, B), digest) | 976 | ) |
2810 | 864 | 977 | self.assertEqual(proto._hash_leaf(b'0', large, b''), digest) | |
2811 | 865 | # B1 | 978 | |
2812 | 866 | digest = proto._hash_leaf(1, B, b'') | 979 | # Largest leaf, index=1 |
2813 | 867 | self.assertEqual(digest, b32loads(B1_B32)) | 980 | digest = accum.add(proto.hash_leaf(1, large)) |
2814 | 868 | self.assertEqual( | 981 | self.assertEqual(digest, |
2815 | 869 | digest, | 982 | skein512(key1 + large, |
2816 | 870 | skein512(B, | 983 | digest_bits=200, |
2817 | 871 | digest_bits=280, | 984 | pers=protocols.PERS_LEAF, |
2818 | 872 | pers=protocols.PERS_LEAF, | 985 | ).digest() |
2819 | 873 | key=b'1', | 986 | ) |
2820 | 874 | ).digest() | 987 | self.assertEqual(proto._hash_leaf(b'1', large, b''), digest) |
2821 | 875 | ) | 988 | |
2822 | 876 | self.assertEqual(proto.hash_leaf(1, B), digest) | 989 | # Test with challenge, smallest leaf, index=0: |
2823 | 877 | 990 | digest = accum.add(proto.hash_leaf(0, small, b'secret')) | |
2824 | 878 | # C0 | 991 | self.assertEqual(digest, |
2825 | 879 | digest = proto._hash_leaf(0, C, b'') | 992 | skein512(key0 + small, |
2826 | 880 | self.assertEqual(digest, b32loads(C0_B32)) | 993 | digest_bits=200, |
2827 | 881 | self.assertEqual( | 994 | pers=protocols.PERS_LEAF, |
2828 | 882 | digest, | 995 | nonce=b'secret', |
2829 | 883 | skein512(C, | 996 | ).digest() |
2830 | 884 | digest_bits=280, | 997 | ) |
2831 | 885 | pers=protocols.PERS_LEAF, | 998 | self.assertEqual(proto._hash_leaf(b'0', small, b'secret'), digest) |
2832 | 886 | key=b'0', | 999 | |
2833 | 887 | ).digest() | 1000 | # Test with challenge, smallest leaf, index=1: |
2834 | 888 | ) | 1001 | digest = accum.add(proto.hash_leaf(1, small, b'secret')) |
2835 | 889 | self.assertEqual(proto.hash_leaf(0, C), digest) | 1002 | self.assertEqual(digest, |
2836 | 890 | 1003 | skein512(key1 + small, | |
2837 | 891 | # C1 | 1004 | digest_bits=200, |
2838 | 892 | digest = proto._hash_leaf(1, C, b'') | 1005 | pers=protocols.PERS_LEAF, |
2839 | 893 | self.assertEqual(digest, b32loads(C1_B32)) | 1006 | nonce=b'secret', |
2840 | 894 | self.assertEqual( | 1007 | ).digest() |
2841 | 895 | digest, | 1008 | ) |
2842 | 896 | skein512(C, | 1009 | self.assertEqual(proto._hash_leaf(b'1', small, b'secret'), digest) |
2843 | 897 | digest_bits=280, | 1010 | |
2844 | 898 | pers=protocols.PERS_LEAF, | 1011 | # Test with challenge, largest leaf, index=0: |
2845 | 899 | key=b'1', | 1012 | digest = accum.add(proto.hash_leaf(0, large, b'secret')) |
2846 | 900 | ).digest() | 1013 | self.assertEqual(digest, |
2847 | 901 | ) | 1014 | skein512(key0 + large, |
2848 | 902 | self.assertEqual(proto.hash_leaf(1, C), digest) | 1015 | digest_bits=200, |
2849 | 1016 | pers=protocols.PERS_LEAF, | ||
2850 | 1017 | nonce=b'secret', | ||
2851 | 1018 | ).digest() | ||
2852 | 1019 | ) | ||
2853 | 1020 | self.assertEqual(proto._hash_leaf(b'0', large, b'secret'), digest) | ||
2854 | 1021 | |||
2855 | 1022 | # Test with challenge, largest leaf, index=1: | ||
2856 | 1023 | digest = accum.add(proto.hash_leaf(1, large, b'secret')) | ||
2857 | 1024 | self.assertEqual(digest, | ||
2858 | 1025 | skein512(key1 + large, | ||
2859 | 1026 | digest_bits=200, | ||
2860 | 1027 | pers=protocols.PERS_LEAF, | ||
2861 | 1028 | nonce=b'secret', | ||
2862 | 1029 | ).digest() | ||
2863 | 1030 | ) | ||
2864 | 1031 | self.assertEqual(proto._hash_leaf(b'1', large, b'secret'), digest) | ||
2865 | 1032 | |||
2866 | 1033 | # Make sure we didn't goof | ||
2867 | 1034 | safety = set() | ||
2868 | 1035 | for index in [0, 1]: | ||
2869 | 1036 | for data in [small, large]: | ||
2870 | 1037 | for challenge in [b'', b'secret']: | ||
2871 | 1038 | safety.add(proto.hash_leaf(index, data, challenge)) | ||
2872 | 1039 | self.assertEqual(safety, accum) | ||
2873 | 1040 | self.assertEqual(len(accum), 8) | ||
2874 | 1041 | for digest in accum: | ||
2875 | 1042 | self.assertEqual(len(digest), 25) | ||
2876 | 903 | 1043 | ||
2877 | 904 | def test_hash_root(self): | 1044 | def test_hash_root(self): |
2880 | 905 | proto = protocols.VERSION1 | 1045 | proto = protocols.OldSkeinProtocol(TwoMiB, 200) |
2879 | 906 | |||
2881 | 907 | self.check_hash_root(proto) | 1046 | self.check_hash_root(proto) |
3007 | 908 | self.sanity_check_hash_root(proto) | 1047 | self.assertEqual(self._checks, [ |
3008 | 909 | 1048 | 'hash_root_validation', | |
3009 | 910 | A0 = b32loads(A0_B32) | 1049 | 'hash_root_simple', |
3010 | 911 | A1 = b32loads(A1_B32) | 1050 | 'hash_root_crypto', |
3011 | 912 | B0 = b32loads(B0_B32) | 1051 | ]) |
3012 | 913 | B1 = b32loads(B1_B32) | 1052 | |
3013 | 914 | C0 = b32loads(C0_B32) | 1053 | accum = DigestAccum() |
3014 | 915 | C1 = b32loads(C1_B32) | 1054 | one = b'D' * 25 |
3015 | 916 | 1055 | two = b'D' * 50 | |
3016 | 917 | # A | 1056 | key1 = proto._hash_file_size(b'1') |
3017 | 918 | digest = proto._hash_root(1, A0) | 1057 | key2 = proto._hash_file_size(b'2097152') |
3018 | 919 | self.assertEqual( | 1058 | key3 = proto._hash_file_size(b'2097153') |
3019 | 920 | b32encode(digest).decode('utf-8'), | 1059 | key4 = proto._hash_file_size(b'4194304') |
3020 | 921 | 'FWV6OJYI36C5NN5DC4GS2IGWZXFCZCGJGHK35YV62LKAG7D2Z4LO4Z2S' | 1060 | |
3021 | 922 | ) | 1061 | # Smallest file-size possible for one leaf: |
3022 | 923 | self.assertEqual( | 1062 | digest = accum.add(proto.hash_root(1, one)) |
3023 | 924 | digest, | 1063 | self.assertEqual(digest, |
3024 | 925 | skein512(A0, | 1064 | skein512(key1 + one, |
3025 | 926 | digest_bits=280, | 1065 | digest_bits=200, |
3026 | 927 | pers=protocols.PERS_ROOT, | 1066 | pers=protocols.PERS_ROOT, |
3027 | 928 | key=b'1', | 1067 | ).digest() |
3028 | 929 | ).digest() | 1068 | ) |
3029 | 930 | ) | 1069 | self.assertEqual(proto._hash_root(b'1', one), digest) |
3030 | 931 | self.assertEqual( | 1070 | |
3031 | 932 | proto.hash_root(1, A0), | 1071 | # Largest file-size possible for one leaf: |
3032 | 933 | b32encode(digest).decode('utf-8') | 1072 | digest = accum.add(proto.hash_root(TwoMiB, one)) |
3033 | 934 | ) | 1073 | self.assertEqual(digest, |
3034 | 935 | 1074 | skein512(key2 + one, | |
3035 | 936 | # B | 1075 | digest_bits=200, |
3036 | 937 | digest = proto._hash_root(8388607, B0) | 1076 | pers=protocols.PERS_ROOT, |
3037 | 938 | self.assertEqual( | 1077 | ).digest() |
3038 | 939 | b32encode(digest).decode('utf-8'), | 1078 | ) |
3039 | 940 | 'OB756PX5V32JMKJAFKIAJ4AFSFPA2WLNIK32ELNO4FJLJPEEEN6DCAAJ' | 1079 | self.assertEqual(proto._hash_root(b'2097152', one), digest) |
3040 | 941 | ) | 1080 | |
3041 | 942 | self.assertEqual( | 1081 | # Smallest file-size possible for two leaves: |
3042 | 943 | digest, | 1082 | digest = accum.add(proto.hash_root(TwoMiB + 1, two)) |
3043 | 944 | skein512(B0, | 1083 | self.assertEqual(digest, |
3044 | 945 | digest_bits=280, | 1084 | skein512(key3 + two, |
3045 | 946 | pers=protocols.PERS_ROOT, | 1085 | digest_bits=200, |
3046 | 947 | key=b'8388607', | 1086 | pers=protocols.PERS_ROOT, |
3047 | 948 | ).digest() | 1087 | ).digest() |
3048 | 949 | ) | 1088 | ) |
3049 | 950 | self.assertEqual( | 1089 | self.assertEqual(proto._hash_root(b'2097153', two), digest) |
3050 | 951 | proto.hash_root(8388607, B0), | 1090 | |
3051 | 952 | b32encode(digest).decode('utf-8') | 1091 | # Largest file-size possible for two leaves: |
3052 | 953 | ) | 1092 | digest = accum.add(proto.hash_root(2 * TwoMiB, two)) |
3053 | 954 | 1093 | self.assertEqual(digest, | |
3054 | 955 | # C | 1094 | skein512(key4 + two, |
3055 | 956 | digest = proto._hash_root(8388608, C0) | 1095 | digest_bits=200, |
3056 | 957 | self.assertEqual( | 1096 | pers=protocols.PERS_ROOT, |
3057 | 958 | b32encode(digest).decode('utf-8'), | 1097 | ).digest() |
3058 | 959 | 'QSOHXCDH64IQBOG2NM67XEC6MLZKKPGBTISWWRPMCFCJ2EKMA2SMLY46' | 1098 | ) |
3059 | 960 | ) | 1099 | self.assertEqual(proto._hash_root(b'4194304', two), digest) |
3060 | 961 | self.assertEqual( | 1100 | |
3061 | 962 | digest, | 1101 | # Make sure we didn't goof |
3062 | 963 | skein512(C0, | 1102 | self.assertEqual(len(accum), 4) |
3063 | 964 | digest_bits=280, | 1103 | safety = set([ |
3064 | 965 | pers=protocols.PERS_ROOT, | 1104 | proto.hash_root(1, one), |
3065 | 966 | key=b'8388608', | 1105 | proto.hash_root(TwoMiB, one), |
3066 | 967 | ).digest() | 1106 | proto.hash_root(TwoMiB + 1, two), |
3067 | 968 | ) | 1107 | proto.hash_root(TwoMiB * 2, two), |
3068 | 969 | self.assertEqual( | 1108 | ]) |
3069 | 970 | proto.hash_root(8388608, C0), | 1109 | self.assertEqual(safety, accum) |
3070 | 971 | b32encode(digest).decode('utf-8') | 1110 | for digest in accum: |
3071 | 972 | ) | 1111 | self.assertEqual(len(digest), 25) |
3072 | 973 | 1112 | ||
2948 | 974 | # CA | ||
2949 | 975 | digest = proto._hash_root(8388609, C0 + A1) | ||
2950 | 976 | self.assertEqual( | ||
2951 | 977 | b32encode(digest).decode('utf-8'), | ||
2952 | 978 | 'BQ5UTB33ML2VDTCTLVXK6N4VSMGGKKKDYKG24B6DOAFJB6NRSGMB5BNO' | ||
2953 | 979 | ) | ||
2954 | 980 | self.assertEqual( | ||
2955 | 981 | digest, | ||
2956 | 982 | skein512(C0 + A1, | ||
2957 | 983 | digest_bits=280, | ||
2958 | 984 | pers=protocols.PERS_ROOT, | ||
2959 | 985 | key=b'8388609', | ||
2960 | 986 | ).digest() | ||
2961 | 987 | ) | ||
2962 | 988 | self.assertEqual( | ||
2963 | 989 | proto.hash_root(8388609, C0 + A1), | ||
2964 | 990 | b32encode(digest).decode('utf-8') | ||
2965 | 991 | ) | ||
2966 | 992 | |||
2967 | 993 | # CB | ||
2968 | 994 | digest = proto._hash_root(16777215, C0 + B1) | ||
2969 | 995 | self.assertEqual( | ||
2970 | 996 | b32encode(digest).decode('utf-8'), | ||
2971 | 997 | 'ER3LDDZ2LHMTDLOPE5XA5GEEZ6OE45VFIFLY42GEMV4TSZ2B7GJJXAIX' | ||
2972 | 998 | ) | ||
2973 | 999 | self.assertEqual( | ||
2974 | 1000 | digest, | ||
2975 | 1001 | skein512(C0 + B1, | ||
2976 | 1002 | digest_bits=280, | ||
2977 | 1003 | pers=protocols.PERS_ROOT, | ||
2978 | 1004 | key=b'16777215', | ||
2979 | 1005 | ).digest() | ||
2980 | 1006 | ) | ||
2981 | 1007 | self.assertEqual( | ||
2982 | 1008 | proto.hash_root(16777215, C0 + B1), | ||
2983 | 1009 | b32encode(digest).decode('utf-8') | ||
2984 | 1010 | ) | ||
2985 | 1011 | |||
2986 | 1012 | # CC | ||
2987 | 1013 | digest = proto._hash_root(16777216, C0 + C1) | ||
2988 | 1014 | self.assertEqual( | ||
2989 | 1015 | b32encode(digest).decode('utf-8'), | ||
2990 | 1016 | 'R6RN5KL7UBNJWR5SK5YPUKIGAOWWFMYYOVESU5DPT34X5MEK75PXXYIX' | ||
2991 | 1017 | ) | ||
2992 | 1018 | self.assertEqual( | ||
2993 | 1019 | digest, | ||
2994 | 1020 | skein512(C0 + C1, | ||
2995 | 1021 | digest_bits=280, | ||
2996 | 1022 | pers=protocols.PERS_ROOT, | ||
2997 | 1023 | key=b'16777216', | ||
2998 | 1024 | ).digest() | ||
2999 | 1025 | ) | ||
3000 | 1026 | self.assertEqual( | ||
3001 | 1027 | proto.hash_root(16777216, C0 + C1), | ||
3002 | 1028 | b32encode(digest).decode('utf-8') | ||
3003 | 1029 | ) | ||
3004 | 1030 | |||
3005 | 1031 | |||
3006 | 1032 | class TestOldProtocol(TestCase): | ||
3073 | 1033 | def test_hash_leaf_index(self): | 1113 | def test_hash_leaf_index(self): |
3262 | 1034 | proto = protocols.OldProtocol(TwoMiB, 200) | 1114 | proto = protocols.OldSkeinProtocol(TwoMiB, 200) |
3263 | 1035 | accum = set() | 1115 | accum = DigestAccum() |
3264 | 1036 | key = proto._hash_leaf_index(0) | 1116 | for key in [b'0', b'1', b'17', b'18', b'21']: |
3265 | 1037 | accum.add(key) | 1117 | digest = accum.add(proto._hash_leaf_index(key)) |
3266 | 1038 | self.assertEqual(key, | 1118 | self.assertEqual(digest, |
3267 | 1039 | skein512(b'0', | 1119 | skein512(key, |
3268 | 1040 | digest_bits=200, | 1120 | digest_bits=200, |
3269 | 1041 | pers=protocols.PERS_LEAF_INDEX, | 1121 | pers=protocols.PERS_LEAF_INDEX, |
3270 | 1042 | ).digest() | 1122 | ).digest() |
3271 | 1043 | ) | 1123 | ) |
3272 | 1044 | 1124 | self.assertEqual(len(accum), 5) | |
3085 | 1045 | key = proto._hash_leaf_index(17) | ||
3086 | 1046 | self.assertNotIn(key, accum) | ||
3087 | 1047 | accum.add(key) | ||
3088 | 1048 | self.assertEqual(key, | ||
3089 | 1049 | skein512(b'17', | ||
3090 | 1050 | digest_bits=200, | ||
3091 | 1051 | pers=protocols.PERS_LEAF_INDEX, | ||
3092 | 1052 | ).digest() | ||
3093 | 1053 | ) | ||
3094 | 1054 | self.assertEqual(len(accum), 2) | ||
3095 | 1055 | |||
3096 | 1056 | count = 25 * 1000 | ||
3097 | 1057 | accum = set( | ||
3098 | 1058 | proto._hash_leaf_index(i) for i in range(count) | ||
3099 | 1059 | ) | ||
3100 | 1060 | self.assertEqual(len(accum), count) | ||
3101 | 1061 | |||
3102 | 1062 | ############################################# | ||
3103 | 1063 | # Again, this time with different digest_bits | ||
3104 | 1064 | proto = protocols.OldProtocol(TwoMiB, 240) | ||
3105 | 1065 | accum = set() | ||
3106 | 1066 | key = proto._hash_leaf_index(0) | ||
3107 | 1067 | accum.add(key) | ||
3108 | 1068 | self.assertEqual(key, | ||
3109 | 1069 | skein512(b'0', | ||
3110 | 1070 | digest_bits=240, | ||
3111 | 1071 | pers=protocols.PERS_LEAF_INDEX, | ||
3112 | 1072 | ).digest() | ||
3113 | 1073 | ) | ||
3114 | 1074 | |||
3115 | 1075 | key = proto._hash_leaf_index(17) | ||
3116 | 1076 | self.assertNotIn(key, accum) | ||
3117 | 1077 | accum.add(key) | ||
3118 | 1078 | self.assertEqual(key, | ||
3119 | 1079 | skein512(b'17', | ||
3120 | 1080 | digest_bits=240, | ||
3121 | 1081 | pers=protocols.PERS_LEAF_INDEX, | ||
3122 | 1082 | ).digest() | ||
3123 | 1083 | ) | ||
3124 | 1084 | self.assertEqual(len(accum), 2) | ||
3125 | 1085 | |||
3126 | 1086 | count = 25 * 1000 | ||
3127 | 1087 | accum = set( | ||
3128 | 1088 | proto._hash_leaf_index(i) for i in range(count) | ||
3129 | 1089 | ) | ||
3130 | 1090 | self.assertEqual(len(accum), count) | ||
3131 | 1091 | |||
3132 | 1092 | def test_hash_leaf(self): | ||
3133 | 1093 | challenge = b'secret foo bar' | ||
3134 | 1094 | proto = protocols.OldProtocol(TwoMiB, 200) | ||
3135 | 1095 | key0 = proto._hash_leaf_index(0) | ||
3136 | 1096 | key17 = proto._hash_leaf_index(17) | ||
3137 | 1097 | |||
3138 | 1098 | accum = set() | ||
3139 | 1099 | |||
3140 | 1100 | # Min leaf size, index=0 | ||
3141 | 1101 | leaf_data = b'D' | ||
3142 | 1102 | digest = proto._hash_leaf(0, leaf_data, b'') | ||
3143 | 1103 | self.assertNotIn(digest, accum) | ||
3144 | 1104 | accum.add(digest) | ||
3145 | 1105 | self.assertEqual(digest, | ||
3146 | 1106 | skein512(key0 + leaf_data, | ||
3147 | 1107 | digest_bits=200, | ||
3148 | 1108 | pers=protocols.PERS_LEAF, | ||
3149 | 1109 | ).digest() | ||
3150 | 1110 | ) | ||
3151 | 1111 | |||
3152 | 1112 | # Min leaf size, index=17 | ||
3153 | 1113 | digest = proto._hash_leaf(17, leaf_data, b'') | ||
3154 | 1114 | self.assertNotIn(digest, accum) | ||
3155 | 1115 | accum.add(digest) | ||
3156 | 1116 | self.assertEqual(digest, | ||
3157 | 1117 | skein512(key17 + leaf_data, | ||
3158 | 1118 | digest_bits=200, | ||
3159 | 1119 | pers=protocols.PERS_LEAF, | ||
3160 | 1120 | ).digest() | ||
3161 | 1121 | ) | ||
3162 | 1122 | |||
3163 | 1123 | # With challenge, min leaf size, index=0 | ||
3164 | 1124 | digest = proto._hash_leaf(0, leaf_data, challenge) | ||
3165 | 1125 | self.assertNotIn(digest, accum) | ||
3166 | 1126 | accum.add(digest) | ||
3167 | 1127 | self.assertEqual(digest, | ||
3168 | 1128 | skein512(key0 + leaf_data, | ||
3169 | 1129 | digest_bits=200, | ||
3170 | 1130 | pers=protocols.PERS_LEAF, | ||
3171 | 1131 | nonce=challenge, | ||
3172 | 1132 | ).digest() | ||
3173 | 1133 | ) | ||
3174 | 1134 | |||
3175 | 1135 | # With challenge, min leaf size, index=17 | ||
3176 | 1136 | digest = proto._hash_leaf(17, leaf_data, challenge) | ||
3177 | 1137 | self.assertNotIn(digest, accum) | ||
3178 | 1138 | accum.add(digest) | ||
3179 | 1139 | self.assertEqual(digest, | ||
3180 | 1140 | skein512(key17 + leaf_data, | ||
3181 | 1141 | digest_bits=200, | ||
3182 | 1142 | pers=protocols.PERS_LEAF, | ||
3183 | 1143 | nonce=challenge, | ||
3184 | 1144 | ).digest() | ||
3185 | 1145 | ) | ||
3186 | 1146 | |||
3187 | 1147 | # Max leaf size, index=0 | ||
3188 | 1148 | leaf_data = b'D' * TwoMiB | ||
3189 | 1149 | digest = proto._hash_leaf(0, leaf_data, b'') | ||
3190 | 1150 | self.assertNotIn(digest, accum) | ||
3191 | 1151 | accum.add(digest) | ||
3192 | 1152 | self.assertEqual(digest, | ||
3193 | 1153 | skein512(key0 + leaf_data, | ||
3194 | 1154 | digest_bits=200, | ||
3195 | 1155 | pers=protocols.PERS_LEAF, | ||
3196 | 1156 | ).digest() | ||
3197 | 1157 | ) | ||
3198 | 1158 | |||
3199 | 1159 | # Max leaf size, index=17 | ||
3200 | 1160 | digest = proto._hash_leaf(17, leaf_data, b'') | ||
3201 | 1161 | self.assertNotIn(digest, accum) | ||
3202 | 1162 | accum.add(digest) | ||
3203 | 1163 | self.assertEqual(digest, | ||
3204 | 1164 | skein512(key17 + leaf_data, | ||
3205 | 1165 | digest_bits=200, | ||
3206 | 1166 | pers=protocols.PERS_LEAF, | ||
3207 | 1167 | ).digest() | ||
3208 | 1168 | ) | ||
3209 | 1169 | |||
3210 | 1170 | # With challenge, max leaf size, index=0 | ||
3211 | 1171 | digest = proto._hash_leaf(0, leaf_data, challenge) | ||
3212 | 1172 | self.assertNotIn(digest, accum) | ||
3213 | 1173 | accum.add(digest) | ||
3214 | 1174 | self.assertEqual(digest, | ||
3215 | 1175 | skein512(key0 + leaf_data, | ||
3216 | 1176 | digest_bits=200, | ||
3217 | 1177 | pers=protocols.PERS_LEAF, | ||
3218 | 1178 | nonce=challenge, | ||
3219 | 1179 | ).digest() | ||
3220 | 1180 | ) | ||
3221 | 1181 | |||
3222 | 1182 | # With challenge, max leaf size, index=17 | ||
3223 | 1183 | digest = proto._hash_leaf(17, leaf_data, challenge) | ||
3224 | 1184 | self.assertNotIn(digest, accum) | ||
3225 | 1185 | accum.add(digest) | ||
3226 | 1186 | self.assertEqual(digest, | ||
3227 | 1187 | skein512(key17 + leaf_data, | ||
3228 | 1188 | digest_bits=200, | ||
3229 | 1189 | pers=protocols.PERS_LEAF, | ||
3230 | 1190 | nonce=challenge, | ||
3231 | 1191 | ).digest() | ||
3232 | 1192 | ) | ||
3233 | 1193 | |||
3234 | 1194 | # Make sure we didn't goof: | ||
3235 | 1195 | self.assertEqual(len(accum), 8) | ||
3236 | 1196 | |||
3237 | 1197 | # A 25k value sanity check on our crytographic claim that the | ||
3238 | 1198 | # leaf-hash is tied to the leaf-index: | ||
3239 | 1199 | count = 25 * 1000 | ||
3240 | 1200 | leaf_data = os.urandom(32) | ||
3241 | 1201 | accum = set( | ||
3242 | 1202 | proto._hash_leaf(i, leaf_data, b'') | ||
3243 | 1203 | for i in range(count) | ||
3244 | 1204 | ) | ||
3245 | 1205 | self.assertEqual(len(accum), count) | ||
3246 | 1206 | |||
3247 | 1207 | # A 25k random value sanity check on our crytographic claim that the | ||
3248 | 1208 | # leaf-hash is tied to the leaf-data: | ||
3249 | 1209 | accum = set( | ||
3250 | 1210 | proto._hash_leaf(21, os.urandom(32), b'') | ||
3251 | 1211 | for i in range(count) | ||
3252 | 1212 | ) | ||
3253 | 1213 | self.assertEqual(len(accum), count) | ||
3254 | 1214 | |||
3255 | 1215 | # A 25k random value sanity check on our crytographic claim that the | ||
3256 | 1216 | # leaf-hash is tied to the challenge: | ||
3257 | 1217 | accum = set( | ||
3258 | 1218 | proto._hash_leaf(21, leaf_data, os.urandom(16)) | ||
3259 | 1219 | for i in range(count) | ||
3260 | 1220 | ) | ||
3261 | 1221 | self.assertEqual(len(accum), count) | ||
3273 | 1222 | 1125 | ||
3274 | 1223 | def test_hash_file_size(self): | 1126 | def test_hash_file_size(self): |
3462 | 1224 | proto = protocols.OldProtocol(TwoMiB, 200) | 1127 | proto = protocols.OldSkeinProtocol(TwoMiB, 200) |
3463 | 1225 | accum = set() | 1128 | accum = DigestAccum() |
3464 | 1226 | 1129 | for key in [b'1', b'2', b'2097152', b'2097153', b'4194304']: | |
3465 | 1227 | key = proto._hash_file_size(1) | 1130 | digest = accum.add(proto._hash_file_size(key)) |
3466 | 1228 | self.assertNotIn(key, accum) | 1131 | self.assertEqual(digest, |
3467 | 1229 | accum.add(key) | 1132 | skein512(key, |
3468 | 1230 | self.assertEqual(key, | 1133 | digest_bits=200, |
3469 | 1231 | skein512(b'1', | 1134 | pers=protocols.PERS_FILE_SIZE, |
3470 | 1232 | digest_bits=200, | 1135 | ).digest() |
3471 | 1233 | pers=protocols.PERS_FILE_SIZE, | 1136 | ) |
3472 | 1234 | ).digest() | 1137 | self.assertEqual(len(accum), 5) |
3473 | 1235 | ) | 1138 | |
3474 | 1236 | 1139 | ||
3475 | 1237 | key = proto._hash_file_size(18) | 1140 | class Test_VERSION0(BaseTestCase2): |
3476 | 1238 | self.assertNotIn(key, accum) | 1141 | proto = protocols.VERSION0 |
3477 | 1239 | accum.add(key) | 1142 | |
3478 | 1240 | self.assertEqual(key, | 1143 | def test_hash_leaf(self): |
3479 | 1241 | skein512(b'18', | 1144 | self.check_hash_leaf(self.proto) |
3480 | 1242 | digest_bits=200, | 1145 | self.assertEqual(self._checks, [ |
3481 | 1243 | pers=protocols.PERS_FILE_SIZE, | 1146 | 'hash_leaf_validation', |
3482 | 1244 | ).digest() | 1147 | 'hash_leaf_simple', |
3483 | 1245 | ) | 1148 | 'hash_leaf_crypto', |
3484 | 1246 | self.assertEqual(len(accum), 2) | 1149 | ]) |
3485 | 1247 | 1150 | ||
3486 | 1248 | count = 25 * 1000 | 1151 | leaves = misc.build_leaves(EightMiB) |
3487 | 1249 | accum = set( | 1152 | vectors = misc.load_data('V0') |
3488 | 1250 | proto._hash_file_size(size) for | 1153 | self.assertEqual( |
3489 | 1251 | size in range(1, count + 1) | 1154 | b32enc(self.proto.hash_leaf(0, leaves['A'])), |
3490 | 1252 | ) | 1155 | vectors['leaf_hashes']['A'][0] |
3491 | 1253 | self.assertEqual(len(accum), count) | 1156 | ) |
3492 | 1254 | 1157 | self.assertEqual( | |
3493 | 1255 | ############################################# | 1158 | b32enc(self.proto.hash_leaf(1, leaves['A'])), |
3494 | 1256 | # Again, this time with different digest_bits | 1159 | vectors['leaf_hashes']['A'][1] |
3495 | 1257 | proto = protocols.OldProtocol(TwoMiB, 240) | 1160 | ) |
3496 | 1258 | accum = set() | 1161 | self.assertEqual( |
3497 | 1259 | 1162 | b32enc(self.proto.hash_leaf(0, leaves['B'])), | |
3498 | 1260 | key = proto._hash_file_size(1) | 1163 | vectors['leaf_hashes']['B'][0] |
3499 | 1261 | self.assertNotIn(key, accum) | 1164 | ) |
3500 | 1262 | accum.add(key) | 1165 | self.assertEqual( |
3501 | 1263 | self.assertEqual(key, | 1166 | b32enc(self.proto.hash_leaf(1, leaves['B'])), |
3502 | 1264 | skein512(b'1', | 1167 | vectors['leaf_hashes']['B'][1] |
3503 | 1265 | digest_bits=240, | 1168 | ) |
3504 | 1266 | pers=protocols.PERS_FILE_SIZE, | 1169 | self.assertEqual( |
3505 | 1267 | ).digest() | 1170 | b32enc(self.proto.hash_leaf(0, leaves['C'])), |
3506 | 1268 | ) | 1171 | vectors['leaf_hashes']['C'][0] |
3507 | 1269 | 1172 | ) | |
3508 | 1270 | key = proto._hash_file_size(18) | 1173 | self.assertEqual( |
3509 | 1271 | self.assertNotIn(key, accum) | 1174 | b32enc(self.proto.hash_leaf(1, leaves['C'])), |
3510 | 1272 | accum.add(key) | 1175 | vectors['leaf_hashes']['C'][1] |
3511 | 1273 | self.assertEqual(key, | 1176 | ) |
3512 | 1274 | skein512(b'18', | 1177 | |
3513 | 1275 | digest_bits=240, | 1178 | def test_hash_root(self): |
3514 | 1276 | pers=protocols.PERS_FILE_SIZE, | 1179 | self.check_hash_root(self.proto) |
3515 | 1277 | ).digest() | 1180 | self.assertEqual(self._checks, [ |
3516 | 1278 | ) | 1181 | 'hash_root_validation', |
3517 | 1279 | self.assertEqual(len(accum), 2) | 1182 | 'hash_root_simple', |
3518 | 1280 | 1183 | 'hash_root_crypto', | |
3519 | 1281 | count = 25 * 1000 | 1184 | ]) |
3520 | 1282 | accum = set( | 1185 | |
3521 | 1283 | proto._hash_file_size(size) for | 1186 | vectors = misc.load_data('V0') |
3522 | 1284 | size in range(1, count + 1) | 1187 | A0 = b32dec(vectors['leaf_hashes']['A'][0]) |
3523 | 1285 | ) | 1188 | A1 = b32dec(vectors['leaf_hashes']['A'][1]) |
3524 | 1286 | self.assertEqual(len(accum), count) | 1189 | B0 = b32dec(vectors['leaf_hashes']['B'][0]) |
3525 | 1287 | 1190 | B1 = b32dec(vectors['leaf_hashes']['B'][1]) | |
3526 | 1288 | def test_hash_root(self): | 1191 | C0 = b32dec(vectors['leaf_hashes']['C'][0]) |
3527 | 1289 | proto = protocols.OldProtocol(TwoMiB, 200) | 1192 | C1 = b32dec(vectors['leaf_hashes']['C'][1]) |
3528 | 1290 | key1 = proto._hash_file_size(1) | 1193 | self.assertEqual( |
3529 | 1291 | key18 = proto._hash_file_size(18) | 1194 | b32enc(self.proto.hash_root(1, A0)), |
3530 | 1292 | keyM = proto._hash_file_size(TwoMiB) | 1195 | vectors['root_hashes']['A'] |
3531 | 1293 | keyM1 = proto._hash_file_size(TwoMiB + 1) | 1196 | ) |
3532 | 1294 | keyM18 = proto._hash_file_size(TwoMiB + 18) | 1197 | self.assertEqual( |
3533 | 1295 | key2M = proto._hash_file_size(2 * TwoMiB) | 1198 | b32enc(self.proto.hash_root(EightMiB - 1, B0)), |
3534 | 1296 | 1199 | vectors['root_hashes']['B'] | |
3535 | 1297 | accum = set() | 1200 | ) |
3536 | 1298 | 1201 | self.assertEqual( | |
3537 | 1299 | # One leaf, file_size=1 | 1202 | b32enc(self.proto.hash_root(EightMiB , C0)), |
3538 | 1300 | leaf_hashes = b'D' * 25 | 1203 | vectors['root_hashes']['C'] |
3539 | 1301 | digest = proto._hash_root(1, leaf_hashes) | 1204 | ) |
3540 | 1302 | self.assertNotIn(digest, accum) | 1205 | self.assertEqual( |
3541 | 1303 | accum.add(digest) | 1206 | b32enc(self.proto.hash_root(EightMiB + 1, C0 + A1)), |
3542 | 1304 | self.assertEqual(digest, | 1207 | vectors['root_hashes']['CA'] |
3543 | 1305 | skein512(key1 + leaf_hashes, | 1208 | ) |
3544 | 1306 | digest_bits=200, | 1209 | self.assertEqual( |
3545 | 1307 | pers=protocols.PERS_ROOT, | 1210 | b32enc(self.proto.hash_root(2 * EightMiB - 1, C0 + B1)), |
3546 | 1308 | ).digest() | 1211 | vectors['root_hashes']['CB'] |
3547 | 1309 | ) | 1212 | ) |
3548 | 1310 | self.assertEqual( | 1213 | self.assertEqual( |
3549 | 1311 | proto.hash_root(1, leaf_hashes), | 1214 | b32enc(self.proto.hash_root(2 * EightMiB , C0 + C1)), |
3550 | 1312 | b32encode(digest).decode('utf-8') | 1215 | vectors['root_hashes']['CC'] |
3551 | 1313 | ) | 1216 | ) |
3552 | 1314 | 1217 | ||
3553 | 1315 | # One leaf, file_size=18 | 1218 | def test_vectors(self): |
3554 | 1316 | digest = proto._hash_root(18, leaf_hashes) | 1219 | V0 = misc.load_data('V0') |
3555 | 1317 | self.assertNotIn(digest, accum) | 1220 | self.assertEqual(misc.build_vectors(self.proto, b32enc), V0) |
3556 | 1318 | accum.add(digest) | 1221 | |
3557 | 1319 | self.assertEqual(digest, | 1222 | |
3558 | 1320 | skein512(key18 + leaf_hashes, | 1223 | class Test_VERSION1(BaseTestCase2): |
3559 | 1321 | digest_bits=200, | 1224 | proto = protocols.VERSION1 |
3560 | 1322 | pers=protocols.PERS_ROOT, | 1225 | |
3561 | 1323 | ).digest() | 1226 | def test_hash_leaf(self): |
3562 | 1324 | ) | 1227 | self.check_hash_leaf(self.proto) |
3563 | 1325 | self.assertEqual( | 1228 | self.assertEqual(self._checks, [ |
3564 | 1326 | proto.hash_root(18, leaf_hashes), | 1229 | 'hash_leaf_validation', |
3565 | 1327 | b32encode(digest).decode('utf-8') | 1230 | 'hash_leaf_simple', |
3566 | 1328 | ) | 1231 | 'hash_leaf_crypto', |
3567 | 1329 | 1232 | ]) | |
3568 | 1330 | # One leaf, file_size=TwoMiB | 1233 | |
3569 | 1331 | digest = proto._hash_root(TwoMiB, leaf_hashes) | 1234 | leaves = misc.build_leaves(EightMiB) |
3570 | 1332 | self.assertNotIn(digest, accum) | 1235 | vectors = misc.load_data('V1') |
3571 | 1333 | accum.add(digest) | 1236 | self.assertEqual( |
3572 | 1334 | self.assertEqual(digest, | 1237 | db32enc(self.proto.hash_leaf(0, leaves['A'])), |
3573 | 1335 | skein512(keyM + leaf_hashes, | 1238 | vectors['leaf_hashes']['A'][0] |
3574 | 1336 | digest_bits=200, | 1239 | ) |
3575 | 1337 | pers=protocols.PERS_ROOT, | 1240 | self.assertEqual( |
3576 | 1338 | ).digest() | 1241 | db32enc(self.proto.hash_leaf(1, leaves['A'])), |
3577 | 1339 | ) | 1242 | vectors['leaf_hashes']['A'][1] |
3578 | 1340 | self.assertEqual( | 1243 | ) |
3579 | 1341 | proto.hash_root(TwoMiB, leaf_hashes), | 1244 | self.assertEqual( |
3580 | 1342 | b32encode(digest).decode('utf-8') | 1245 | db32enc(self.proto.hash_leaf(0, leaves['B'])), |
3581 | 1343 | ) | 1246 | vectors['leaf_hashes']['B'][0] |
3582 | 1344 | 1247 | ) | |
3583 | 1345 | # Two leaves, file_size=(TwoMiB + 1) | 1248 | self.assertEqual( |
3584 | 1346 | leaf_hashes = b'D' * 50 | 1249 | db32enc(self.proto.hash_leaf(1, leaves['B'])), |
3585 | 1347 | digest = proto._hash_root(TwoMiB + 1, leaf_hashes) | 1250 | vectors['leaf_hashes']['B'][1] |
3586 | 1348 | self.assertNotIn(digest, accum) | 1251 | ) |
3587 | 1349 | accum.add(digest) | 1252 | self.assertEqual( |
3588 | 1350 | self.assertEqual(digest, | 1253 | db32enc(self.proto.hash_leaf(0, leaves['C'])), |
3589 | 1351 | skein512(keyM1 + leaf_hashes, | 1254 | vectors['leaf_hashes']['C'][0] |
3590 | 1352 | digest_bits=200, | 1255 | ) |
3591 | 1353 | pers=protocols.PERS_ROOT, | 1256 | self.assertEqual( |
3592 | 1354 | ).digest() | 1257 | db32enc(self.proto.hash_leaf(1, leaves['C'])), |
3593 | 1355 | ) | 1258 | vectors['leaf_hashes']['C'][1] |
3594 | 1356 | self.assertEqual( | 1259 | ) |
3595 | 1357 | proto.hash_root(TwoMiB + 1, leaf_hashes), | 1260 | |
3596 | 1358 | b32encode(digest).decode('utf-8') | 1261 | def test_hash_root(self): |
3597 | 1359 | ) | 1262 | self.check_hash_root(self.proto) |
3598 | 1360 | 1263 | self.assertEqual(self._checks, [ | |
3599 | 1361 | # Two leaves, file_size=(TwoMiB + 18) | 1264 | 'hash_root_validation', |
3600 | 1362 | digest = proto._hash_root(TwoMiB + 18, leaf_hashes) | 1265 | 'hash_root_simple', |
3601 | 1363 | self.assertNotIn(digest, accum) | 1266 | 'hash_root_crypto', |
3602 | 1364 | accum.add(digest) | 1267 | ]) |
3603 | 1365 | self.assertEqual(digest, | 1268 | |
3604 | 1366 | skein512(keyM18 + leaf_hashes, | 1269 | vectors = misc.load_data('V1') |
3605 | 1367 | digest_bits=200, | 1270 | A0 = db32dec(vectors['leaf_hashes']['A'][0]) |
3606 | 1368 | pers=protocols.PERS_ROOT, | 1271 | A1 = db32dec(vectors['leaf_hashes']['A'][1]) |
3607 | 1369 | ).digest() | 1272 | B0 = db32dec(vectors['leaf_hashes']['B'][0]) |
3608 | 1370 | ) | 1273 | B1 = db32dec(vectors['leaf_hashes']['B'][1]) |
3609 | 1371 | self.assertEqual( | 1274 | C0 = db32dec(vectors['leaf_hashes']['C'][0]) |
3610 | 1372 | proto.hash_root(TwoMiB + 18, leaf_hashes), | 1275 | C1 = db32dec(vectors['leaf_hashes']['C'][1]) |
3611 | 1373 | b32encode(digest).decode('utf-8') | 1276 | self.assertEqual( |
3612 | 1374 | ) | 1277 | db32enc(self.proto.hash_root(1, A0)), |
3613 | 1375 | 1278 | vectors['root_hashes']['A'] | |
3614 | 1376 | # Two leaves, file_size=(2 * TwoMiB) | 1279 | ) |
3615 | 1377 | digest = proto._hash_root(2 * TwoMiB, leaf_hashes) | 1280 | self.assertEqual( |
3616 | 1378 | self.assertNotIn(digest, accum) | 1281 | db32enc(self.proto.hash_root(EightMiB - 1, B0)), |
3617 | 1379 | accum.add(digest) | 1282 | vectors['root_hashes']['B'] |
3618 | 1380 | self.assertEqual(digest, | 1283 | ) |
3619 | 1381 | skein512(key2M + leaf_hashes, | 1284 | self.assertEqual( |
3620 | 1382 | digest_bits=200, | 1285 | db32enc(self.proto.hash_root(EightMiB , C0)), |
3621 | 1383 | pers=protocols.PERS_ROOT, | 1286 | vectors['root_hashes']['C'] |
3622 | 1384 | ).digest() | 1287 | ) |
3623 | 1385 | ) | 1288 | self.assertEqual( |
3624 | 1386 | self.assertEqual( | 1289 | db32enc(self.proto.hash_root(EightMiB + 1, C0 + A1)), |
3625 | 1387 | proto.hash_root(2 * TwoMiB, leaf_hashes), | 1290 | vectors['root_hashes']['CA'] |
3626 | 1388 | b32encode(digest).decode('utf-8') | 1291 | ) |
3627 | 1389 | ) | 1292 | self.assertEqual( |
3628 | 1390 | 1293 | db32enc(self.proto.hash_root(2 * EightMiB - 1, C0 + B1)), | |
3629 | 1391 | # Make sure we didn't goof: | 1294 | vectors['root_hashes']['CB'] |
3630 | 1392 | self.assertEqual(len(accum), 6) | 1295 | ) |
3631 | 1393 | 1296 | self.assertEqual( | |
3632 | 1394 | # A 25k value sanity check on our crytographic claim that the | 1297 | db32enc(self.proto.hash_root(2 * EightMiB , C0 + C1)), |
3633 | 1395 | # root-hash is tied to the file-size: | 1298 | vectors['root_hashes']['CC'] |
3634 | 1396 | count = 25 * 1000 | 1299 | ) |
3635 | 1397 | leaf_hashes = b'D' * 25 | 1300 | |
3636 | 1398 | accum = set( | 1301 | def test_vectors(self): |
3637 | 1399 | proto._hash_root(size, leaf_hashes) | 1302 | V1 = misc.load_data('V1') |
3638 | 1400 | for size in range(1, count + 1) | 1303 | self.assertEqual(misc.build_vectors(self.proto, db32enc), V1) |
3639 | 1401 | ) | 1304 | self.assertNotEqual(misc.build_vectors(self.proto, b32enc), V1) |
3640 | 1402 | self.assertEqual(len(accum), count) | 1305 | self.assertNotEqual(misc.build_vectors(protocols.VERSION0, db32enc), V1) |
3641 | 1403 | 1306 | self.assertNotEqual(misc.build_vectors(protocols.VERSION0, b32enc), V1) | |
3642 | 1404 | # A 25k random value sanity check on our crytographic claim that the | 1307 | |
3643 | 1405 | # root-hash is tied to the leaf-hashes: | 1308 | def test_md5sums(self): |
3644 | 1406 | accum = set( | 1309 | MD5SUMS = misc.load_data('MD5SUMS') |
3645 | 1407 | proto._hash_root(21, os.urandom(25)) | 1310 | self.assertEqual(misc.build_md5sums(self.proto.leaf_size), MD5SUMS) |
3646 | 1408 | for i in range(count) | 1311 | |
3460 | 1409 | ) | ||
3461 | 1410 | self.assertEqual(len(accum), count) | ||
3647 | 1411 | 1312 | ||
3648 | === modified file 'setup.py' | |||
3649 | --- setup.py 2013-02-18 12:58:01 +0000 | |||
3650 | +++ setup.py 2013-02-19 15:39:24 +0000 | |||
3651 | @@ -67,7 +67,7 @@ | |||
3652 | 67 | 'filestore', | 67 | 'filestore', |
3653 | 68 | 'filestore.tests' | 68 | 'filestore.tests' |
3654 | 69 | ], | 69 | ], |
3656 | 70 | package_data={'filestore': ['data/test-vectors.json']}, | 70 | package_data={'filestore': ['data/*.json']}, |
3657 | 71 | scripts=['dmediasum'], | 71 | scripts=['dmediasum'], |
3658 | 72 | cmdclass={'test': Test}, | 72 | cmdclass={'test': Test}, |
3659 | 73 | ext_modules=[_filestore], | 73 | ext_modules=[_filestore], |
As this diff is pretty sprawling and noisy, you can probably understand the key changes more easily by just looking at filestore/ protocols. py:
http:// bazaar. launchpad. net/~jderose/ filestore/ dbase32/ view/head: /filestore/ protocols. py