Merge lp:~jderose/filestore/dbase32 into lp:filestore

Proposed by Jason Gerard DeRose
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
Reviewer Review Type Date Requested Status
David Jordan Approve
Review via email: mp+149313@code.launchpad.net

Description of the change

For details, see this bug:

https://bugs.launchpad.net/filestore/+bug/1130156

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-by-ID-length functionality, for now; this was incomplete and as we have a demanding migration ahead of us, I backed this feature out so we have less complexity to deal with during the migration

* `Protocol.hash_root()` now returns a binary digest instead of a Base32 encoded ID, and `filestore.protocols` is now completely encoding agnostic, does nothing with encoding itself

* `Protocol.hash_leaf()` now transforms *leaf_index* into the key bytes rather than relying on `Protocol._hash_leaf()` to do this

* Likewise, `Protocol.hash_root()` now transforms *file_size* into the key bytes rather than relying on `Protocol._hash_root()` to do this

* `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.protocols`; in particular, no digest values are in the unit tests anymore; instead, digests come from the JSON data files only, making it much easier to tweak a protocol without having to make huge changes in the unit tests

* misc.build_vectors() now takes optional *encoder* kwarg, which defaults to `db32enc`, so the test vector building is no longer hard-coded to a specific encoding

To post a comment you must log in.
Revision history for this message
Jason Gerard DeRose (jderose) wrote :

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

Revision history for this message
David Jordan (dmj726) wrote :

Approve.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'debian/control'
--- debian/control 2013-02-18 11:42:49 +0000
+++ debian/control 2013-02-19 15:39:24 +0000
@@ -6,7 +6,7 @@
6 python3-all-dev (>= 3.3),6 python3-all-dev (>= 3.3),
7 python3-sphinx,7 python3-sphinx,
8 python3-skein (>= 0.7.1),8 python3-skein (>= 0.7.1),
9 python3-dbase32 (>= 0.3),9 python3-dbase32 (>= 0.4),
10Standards-Version: 3.9.310Standards-Version: 3.9.3
11X-Python3-Version: >= 3.211X-Python3-Version: >= 3.2
12Homepage: https://launchpad.net/filestore12Homepage: https://launchpad.net/filestore
@@ -15,7 +15,7 @@
15Architecture: any15Architecture: any
16Depends: ${python3:Depends}, ${shlibs:Depends}, ${misc:Depends},16Depends: ${python3:Depends}, ${shlibs:Depends}, ${misc:Depends},
17 python3-skein (>= 0.7.1),17 python3-skein (>= 0.7.1),
18 python3-dbase32 (>= 0.3),18 python3-dbase32 (>= 0.4),
19Description: Dmedia hashing protocol and file layout19Description: Dmedia hashing protocol and file layout
20 This is the heart of Dmedia. It has been split out of Dmedia to make it easier20 This is the heart of Dmedia. It has been split out of Dmedia to make it easier
21 to review, and in hopes that other apps might use the hashing protocol and21 to review, and in hopes that other apps might use the hashing protocol and
2222
=== modified file 'filestore/__init__.py'
--- filestore/__init__.py 2013-02-18 12:41:32 +0000
+++ filestore/__init__.py 2013-02-19 15:39:24 +0000
@@ -76,15 +76,17 @@
76from os import path76from os import path
77import io77import io
78import stat78import stat
79from base64 import b32encode, b64encode79from base64 import b64encode
80from threading import Thread80from threading import Thread
81from queue import Queue81from queue import Queue
82from collections import namedtuple82from collections import namedtuple
83import logging83import logging
8484
85from dbase32 import random_id85from dbase32 import random_id
86from dbase32.rfc3548 import b32enc, isb32
8687
87from .protocols import VERSION0, VERSION188from .protocols import TYPE_ERROR
89from .protocols import VERSION0 as PROTOCOL
8890
89try:91try:
90 from _filestore import fallocate, fastread92 from _filestore import fallocate, fastread
@@ -100,13 +102,10 @@
100102
101log = logging.getLogger('filestore')103log = logging.getLogger('filestore')
102104
103LEAF_SIZE = 8 * 1024 * 1024 # 8 MiB leaf size105LEAF_SIZE = 8388608 # 8 MiB leaf size
104DIGEST_BITS = 240106DIGEST_BITS = 240
105DIGEST_BYTES = DIGEST_BITS // 8107DIGEST_BYTES = 30
106DIGEST_B32LEN = DIGEST_BITS // 5108DIGEST_B32LEN = 48
107
108ALLOWED_B32LEN = (48, 56)
109PROTOCOL = VERSION0
110109
111# Handy constants for file layout:110# Handy constants for file layout:
112DOTNAME = '.dmedia'111DOTNAME = '.dmedia'
@@ -130,9 +129,6 @@
130129
131IO_READABLE = (io.BufferedReader, io.BufferedRandom)130IO_READABLE = (io.BufferedReader, io.BufferedRandom)
132131
133# Provide very clear TypeError messages:
134TYPE_ERROR = '{}: need a {!r}; got a {!r}: {!r}'
135
136# For the README.txt in .dmedia directories:132# For the README.txt in .dmedia directories:
137README = """This directory is automatically managed by Dmedia.133README = """This directory is automatically managed by Dmedia.
138134
@@ -155,13 +151,13 @@
155151
156 For example:152 For example:
157153
158 >>> b32encode(hash_leaf(2, b'XYZ'))154 >>> b32enc(hash_leaf(2, b'XYZ'))
159 b'D7GIW5I5NB6SLJC5ALAX4WU7S7CNYUB3ULMECPY67FFQG4F7'155 'D7GIW5I5NB6SLJC5ALAX4WU7S7CNYUB3ULMECPY67FFQG4F7'
160156
161 :param leaf_index: an ``int`` >= 0157 :param leaf_index: an ``int`` >= 0
162 :param leaf_data: optional ``bytes`` instance with contents of this leaf158 :param leaf_data: optional ``bytes`` instance with contents of this leaf
163 """159 """
164 return VERSION0.hash_leaf(leaf_index, leaf_data)160 return PROTOCOL.hash_leaf(leaf_index, leaf_data)
165161
166162
167def hash_root(file_size, leaf_hashes):163def hash_root(file_size, leaf_hashes):
@@ -177,15 +173,7 @@
177 :param leaf_hashes: a ``bytes`` instance that is the concatenated leaf173 :param leaf_hashes: a ``bytes`` instance that is the concatenated leaf
178 hashes produced by `hash_leaf()`174 hashes produced by `hash_leaf()`
179 """175 """
180 return VERSION0.hash_root(file_size, leaf_hashes)176 return b32enc(PROTOCOL.hash_root(file_size, leaf_hashes))
181
182
183def get_protocol(_id):
184 check_id(_id)
185 if len(_id) == 48:
186 return VERSION0
187 assert len(_id) == 56
188 return VERSION1
189177
190178
191class Hasher:179class Hasher:
@@ -193,10 +181,9 @@
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.
194 """182 """
195183
196 __slots__ = ('protocol', 'file_size', 'leaf_index', 'array', 'closed')184 __slots__ = ('file_size', 'leaf_index', 'array', 'closed')
197185
198 def __init__(self, protocol=PROTOCOL):186 def __init__(self):
199 self.protocol = protocol
200 self.file_size = 0187 self.file_size = 0
201 self.leaf_index = 0188 self.leaf_index = 0
202 self.array = bytearray()189 self.array = bytearray()
@@ -211,9 +198,9 @@
211 raise Exception('Expected leaf.index {}, got {}'.format(198 raise Exception('Expected leaf.index {}, got {}'.format(
212 self.leaf_index, leaf.index)199 self.leaf_index, leaf.index)
213 )200 )
214 if len(leaf.data) < self.protocol.leaf_size:201 if len(leaf.data) < LEAF_SIZE:
215 self.closed = True202 self.closed = True
216 leaf_hash = self.protocol.hash_leaf(leaf.index, leaf.data)203 leaf_hash = PROTOCOL.hash_leaf(leaf.index, leaf.data)
217 self.array.extend(leaf_hash)204 self.array.extend(leaf_hash)
218 self.file_size += len(leaf.data)205 self.file_size += len(leaf.data)
219 self.leaf_index += 1206 self.leaf_index += 1
@@ -223,7 +210,7 @@
223 self.closed = True210 self.closed = True
224 leaf_hashes = bytes(self.array)211 leaf_hashes = bytes(self.array)
225 return ContentHash(212 return ContentHash(
226 self.protocol.hash_root(self.file_size, leaf_hashes),213 b32enc(PROTOCOL.hash_root(self.file_size, leaf_hashes)),
227 self.file_size,214 self.file_size,
228 leaf_hashes215 leaf_hashes
229 )216 )
@@ -345,12 +332,12 @@
345 """332 """
346 if not isinstance(_id, str):333 if not isinstance(_id, str):
347 raise TypeError(TYPE_ERROR.format('_id', str, type(_id), _id))334 raise TypeError(TYPE_ERROR.format('_id', str, type(_id), _id))
348 if not (len(_id) in ALLOWED_B32LEN and set(_id).issubset(B32ALPHABET)):335 if not (len(_id) == DIGEST_B32LEN and isb32(_id)):
349 raise IDError(_id)336 raise IDError(_id)
350 return _id337 return _id
351338
352339
353def check_leaf_hashes(leaf_hashes, protocol=PROTOCOL):340def check_leaf_hashes(leaf_hashes):
354 """341 """
355 Verify that *leaf_hashes* is a ``bytes`` instance of correct length.342 Verify that *leaf_hashes* is a ``bytes`` instance of correct length.
356343
@@ -372,7 +359,7 @@
372 'leaf_hashes', bytes, type(leaf_hashes), leaf_hashes359 'leaf_hashes', bytes, type(leaf_hashes), leaf_hashes
373 )360 )
374 )361 )
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:
376 raise LeafHashesError(leaf_hashes)363 raise LeafHashesError(leaf_hashes)
377 return leaf_hashes364 return leaf_hashes
378365
@@ -400,12 +387,11 @@
400 22387 22
401388
402 """389 """
403 protocol = get_protocol(_id)390 computed = hash_root(file_size, leaf_hashes)
404 computed = protocol.hash_root(file_size, leaf_hashes)
405 if _id != computed:391 if _id != computed:
406 raise RootHashError(_id, file_size, leaf_hashes, computed)392 raise RootHashError(_id, file_size, leaf_hashes, computed)
407 if unpack:393 if unpack:
408 leaf_hashes = tuple(iter_leaf_hashes(leaf_hashes, protocol))394 leaf_hashes = tuple(iter_leaf_hashes(leaf_hashes))
409 return ContentHash(_id, file_size, leaf_hashes)395 return ContentHash(_id, file_size, leaf_hashes)
410396
411397
@@ -439,7 +425,7 @@
439#################################################425#################################################
440# Utility functions for working with leaf_hashes:426# Utility functions for working with leaf_hashes:
441427
442def enumerate_leaf_hashes(leaf_hashes, protocol=PROTOCOL):428def enumerate_leaf_hashes(leaf_hashes):
443 """429 """
444 Enumerate *leaf_hashes*.430 Enumerate *leaf_hashes*.
445431
@@ -451,13 +437,12 @@
451 [(0, b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'), (1, b'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB')]437 [(0, b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'), (1, b'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB')]
452438
453 """439 """
454 check_leaf_hashes(leaf_hashes, protocol)440 check_leaf_hashes(leaf_hashes)
455 digest_bytes = protocol.digest_bytes441 for i in range(len(leaf_hashes) // DIGEST_BYTES):
456 for i in range(len(leaf_hashes) // digest_bytes):442 yield (i, leaf_hashes[i*DIGEST_BYTES : (i+1)*DIGEST_BYTES])
457 yield (i, leaf_hashes[i*digest_bytes : (i+1)*digest_bytes])443
458444
459445def iter_leaf_hashes(leaf_hashes):
460def iter_leaf_hashes(leaf_hashes, protocol=PROTOCOL):
461 """446 """
462 Iterate through *leaf_hashes*.447 Iterate through *leaf_hashes*.
463448
@@ -469,13 +454,12 @@
469 [b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', b'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB']454 [b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', b'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB']
470455
471 """456 """
472 check_leaf_hashes(leaf_hashes, protocol)457 check_leaf_hashes(leaf_hashes)
473 digest_bytes = protocol.digest_bytes458 for i in range(len(leaf_hashes) // DIGEST_BYTES):
474 for i in range(len(leaf_hashes) // digest_bytes):459 yield leaf_hashes[i*DIGEST_BYTES : (i+1)*DIGEST_BYTES]
475 yield leaf_hashes[i*digest_bytes : (i+1)*digest_bytes]460
476461
477462def get_leaf_hash(leaf_hashes, i):
478def get_leaf_hash(leaf_hashes, i, protocol=PROTOCOL):
479 """463 """
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*.
481465
@@ -490,8 +474,9 @@
490 b''474 b''
491475
492 """476 """
493 digest_bytes = protocol.digest_bytes477 start = i * DIGEST_BYTES
494 return leaf_hashes[i*digest_bytes : (i+1)*digest_bytes]478 stop = start + DIGEST_BYTES
479 return leaf_hashes[start:stop]
495480
496481
497def missing_leaves(fp, leaf_hashes):482def missing_leaves(fp, leaf_hashes):
@@ -764,7 +749,7 @@
764 thread.join() # Make sure batch_reader() terminates749 thread.join() # Make sure batch_reader() terminates
765750
766751
767def hash_fp(src_fp, dst_fp=None, protocol=PROTOCOL):752def hash_fp(src_fp, dst_fp=None):
768 """753 """
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*.
770755
@@ -777,7 +762,7 @@
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"
778 :param dst_fp: An optional file opened in mode "wd"763 :param dst_fp: An optional file opened in mode "wd"
779 """764 """
780 hasher = Hasher(protocol)765 hasher = Hasher()
781 for leaf in reader_iter(src_fp):766 for leaf in reader_iter(src_fp):
782 hasher.hash_leaf(leaf)767 hasher.hash_leaf(leaf)
783 if dst_fp:768 if dst_fp:
@@ -907,9 +892,7 @@
907 subdir = path.join(self.basedir, 'files', prefix)892 subdir = path.join(self.basedir, 'files', prefix)
908 for name in sorted(os.listdir(subdir)):893 for name in sorted(os.listdir(subdir)):
909 _id = prefix + name894 _id = prefix + name
910 if len(_id) not in ALLOWED_B32LEN:895 if len(_id) != DIGEST_B32LEN or not isb32(_id):
911 continue
912 if not set(_id).issubset(B32ALPHABET):
913 continue896 continue
914 fullname = path.join(subdir, name) 897 fullname = path.join(subdir, name)
915 st = os.lstat(fullname)898 st = os.lstat(fullname)
@@ -1109,7 +1092,7 @@
1109 a ContentHash namedtuple1092 a ContentHash namedtuple
1110 """1093 """
1111 src_fp = self.open(_id)1094 src_fp = self.open(_id)
1112 c = hash_fp(src_fp, protocol=get_protocol(_id))1095 c = hash_fp(src_fp)
1113 if c.id != _id:1096 if c.id != _id:
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)
1115 if return_fp:1098 if return_fp:
11161099
=== added file 'filestore/data/MD5SUMS.json'
--- filestore/data/MD5SUMS.json 1970-01-01 00:00:00 +0000
+++ filestore/data/MD5SUMS.json 2013-02-19 15:39:24 +0000
@@ -0,0 +1,23 @@
1{
2 "files": {
3 "A": "7fc56270e7a70fa81a5935b72eacbe29",
4 "B": "d2bad3eedb424dd352d65eafbf6c79ba",
5 "C": "5dd3531303dd6764acb93e5f171a4ab8",
6 "CA": "0722f8dc36d75acb602dcee8d0427ce0",
7 "CB": "77264eb6eed7777a1ee03e2601fc9f64",
8 "CC": "1fbfabdaafff31967f9a95f3a3d3c642"
9 },
10 "integers": {
11 "0": "cfcd208495d565ef66e7dff9f98764da",
12 "1": "c4ca4238a0b923820dcc509a6f75849b",
13 "16777215": "48ac8929ffdc78a66090d179ff1237d5",
14 "16777216": "e3e330499348f791337e9da6b534a386",
15 "8388607": "32433904a755e2b9eb82cf167723b34f",
16 "8388608": "03926fda4e223707d290ac06bb996653",
17 "8388609": "e9b74719ce6b80c5337148d12725db03"
18 },
19 "personalization": {
20 "leaf": "8aee35e23a5a74147b230f12123ca82e",
21 "root": "0445d91d37383f5384023d49e71cc629"
22 }
23}
024
=== added file 'filestore/data/V0.json'
--- filestore/data/V0.json 1970-01-01 00:00:00 +0000
+++ filestore/data/V0.json 2013-02-19 15:39:24 +0000
@@ -0,0 +1,24 @@
1{
2 "leaf_hashes": {
3 "A": [
4 "C23GME2TRCBFM2PA3VIZPUOD5DFYAQLQY2VR46ZKBMH4S4WL",
5 "BWRAN4NJSXYJS6V3YQNNV2EKLEEGNAK5VPSIYZKOBXJ3PPJF"
6 ],
7 "B": [
8 "NLJ2OHB45MJELINPXJCTBCI7VT3424GRTN36BDP5Z3NE2W5Z",
9 "ALAA376NIW54WR5N6JY2EPR6VFJI7AC2V7PJCRETDIGRQJDN"
10 ],
11 "C": [
12 "ZQ6RUCU75DHFODZEGNL66QJUM34A3UA53TG2YK7LHRU4SUTF",
13 "MCZHG4NBL5VWKCQIARDXR6IHN34ALIDH7OVYAZ5TNDLXIKA4"
14 ]
15 },
16 "root_hashes": {
17 "A": "O4NQ4LFNI2UCMKOSWWGPRZXSNEPW2M6OOTBFM3AHTBK35D67",
18 "B": "NW6IMGBAU4NTZDQRHGIZA7VYV5CBZ5TUXADHZE43N4GDZGKK",
19 "C": "TRXCE6PVSJTHQX7H4KV7A7F5DKCMSETLMNANNCJIVT624WLU",
20 "CA": "QSUCTH4LJU3B7QG7IJ3FC7HNE4TXDPZVQEJ36B4A45ATTXDM",
21 "CB": "3FWPZ6RBPXGBSTFBNPK5IOCO7DUMSKR5YTWJ3HM2KI4AEF2O",
22 "CC": "CX2K52LIUTT6SSMFHR7NUI6YIJLO3SGKLSRIL6IDY4BP7HUZ"
23 }
24}
025
=== added file 'filestore/data/V1.json'
--- filestore/data/V1.json 1970-01-01 00:00:00 +0000
+++ filestore/data/V1.json 2013-02-19 15:39:24 +0000
@@ -0,0 +1,24 @@
1{
2 "leaf_hashes": {
3 "A": [
4 "3M3IHYG9TV3UWTIFI37LN5OVYFEB3PDIAUAQ7OKOQF84MIQT",
5 "VACOMMVCMJP9QBIUIEMMYAPXOWRYDFW3JQUJB4NGNMF48IIW"
6 ],
7 "B": [
8 "YSRG5KWP5IKWCXTHNN58PY7KSEUJ6JYKDA4RT3F7T8UATQC7",
9 "V6QX3QUDUNE9PVJVF8HBUP4RBKY7AXRJ7MBPWASIKTVWYVUD"
10 ],
11 "C": [
12 "C8BNKNHHPCSONGE785HTVTIXCHFBCT5SY9GYRDELHQ3BVEHV",
13 "EEOJR9LHS46469EYDUWYWOK7BXW5Y486SNEG9KTSHSSPNIMQ"
14 ]
15 },
16 "root_hashes": {
17 "A": "6VKN463LEKJMD3OP87UYVU5V65XXOSG3HCT3Q6NDJD58TQXK",
18 "B": "GJDVCPR3JPBFABMGP6PDUBOVQIXX5YLF9PG49SSDJVQEKLOR",
19 "C": "NXIA8BHKVQTD9ACIPT3J7QUYM96FYWJBBEL34YKTA6CKE9IC",
20 "CA": "L66Y5M37PX93NBYYWCSSKW6QQBEYH76OOJFUQJ7FQEA7IB9S",
21 "CB": "SFKVIEOWCIKENPMKDEAGATIA9DGH54LF9BW5LCOW7F9YN9BL",
22 "CC": "VCPN5J8EI4G5PH5A4CUWSPCRTEWGD65PB4WMTGQ7CEVJ6XLO"
23 }
24}
025
=== removed file 'filestore/data/test-vectors.json'
--- filestore/data/test-vectors.json 2013-01-15 04:18:38 +0000
+++ filestore/data/test-vectors.json 1970-01-01 00:00:00 +0000
@@ -1,50 +0,0 @@
1{
2 "48": {
3 "leaf_hashes": {
4 "A": [
5 "C23GME2TRCBFM2PA3VIZPUOD5DFYAQLQY2VR46ZKBMH4S4WL",
6 "BWRAN4NJSXYJS6V3YQNNV2EKLEEGNAK5VPSIYZKOBXJ3PPJF"
7 ],
8 "B": [
9 "NLJ2OHB45MJELINPXJCTBCI7VT3424GRTN36BDP5Z3NE2W5Z",
10 "ALAA376NIW54WR5N6JY2EPR6VFJI7AC2V7PJCRETDIGRQJDN"
11 ],
12 "C": [
13 "ZQ6RUCU75DHFODZEGNL66QJUM34A3UA53TG2YK7LHRU4SUTF",
14 "MCZHG4NBL5VWKCQIARDXR6IHN34ALIDH7OVYAZ5TNDLXIKA4"
15 ]
16 },
17 "root_hashes": {
18 "A": "O4NQ4LFNI2UCMKOSWWGPRZXSNEPW2M6OOTBFM3AHTBK35D67",
19 "B": "NW6IMGBAU4NTZDQRHGIZA7VYV5CBZ5TUXADHZE43N4GDZGKK",
20 "C": "TRXCE6PVSJTHQX7H4KV7A7F5DKCMSETLMNANNCJIVT624WLU",
21 "CA": "QSUCTH4LJU3B7QG7IJ3FC7HNE4TXDPZVQEJ36B4A45ATTXDM",
22 "CB": "3FWPZ6RBPXGBSTFBNPK5IOCO7DUMSKR5YTWJ3HM2KI4AEF2O",
23 "CC": "CX2K52LIUTT6SSMFHR7NUI6YIJLO3SGKLSRIL6IDY4BP7HUZ"
24 }
25 },
26 "56": {
27 "leaf_hashes": {
28 "A": [
29 "XZ5I6KJTUSOIWVCEBOKUELTADZUXNHOAYO77NKKHWCIW3HYGYOPMX5JN",
30 "TEC7754ZNM26MTM6YQFI6TMVTTK4RKQEMPAGT2ROQZUBPUIHSJU2DDR3"
31 ],
32 "B": [
33 "P67PVKU3SCCQHNIRMR2Z5NICEMIP36WCFJG4AW6YBAE6UI4K6BVLY3EI",
34 "ZIFO5S2OYYPZAUN6XQWTWZGCDATXCGR2JYN7UIAX54WMVWETMIUFG7WM"
35 ],
36 "C": [
37 "RW2GJFIGPQF5WLR53UAK77TPHNRFKMUBYRB23JFS4G2RFRRNHW6OX4CR",
38 "XBVLPYBUX6QD2DKPJTYVUXT23K3AAUAW5J4RMQ543NQNDAHORQJ7GBDE"
39 ]
40 },
41 "root_hashes": {
42 "A": "FWV6OJYI36C5NN5DC4GS2IGWZXFCZCGJGHK35YV62LKAG7D2Z4LO4Z2S",
43 "B": "OB756PX5V32JMKJAFKIAJ4AFSFPA2WLNIK32ELNO4FJLJPEEEN6DCAAJ",
44 "C": "QSOHXCDH64IQBOG2NM67XEC6MLZKKPGBTISWWRPMCFCJ2EKMA2SMLY46",
45 "CA": "BQ5UTB33ML2VDTCTLVXK6N4VSMGGKKKDYKG24B6DOAFJB6NRSGMB5BNO",
46 "CB": "ER3LDDZ2LHMTDLOPE5XA5GEEZ6OE45VFIFLY42GEMV4TSZ2B7GJJXAIX",
47 "CC": "R6RN5KL7UBNJWR5SK5YPUKIGAOWWFMYYOVESU5DPT34X5MEK75PXXYIX"
48 }
49 }
50}
51\ No newline at end of file0\ No newline at end of file
521
=== modified file 'filestore/misc.py'
--- filestore/misc.py 2013-02-14 20:08:06 +0000
+++ filestore/misc.py 2013-02-19 15:39:24 +0000
@@ -28,23 +28,31 @@
28import tempfile28import tempfile
29import shutil29import shutil
30from hashlib import md530from hashlib import md5
31from collections import OrderedDict31
3232from dbase32 import db32enc
33
34from .protocols import PERS_LEAF, PERS_ROOT, VERSION1
33from . import FileStore35from . import FileStore
34from .protocols import b32dumps36
3537
3638DATADIR = path.join(path.dirname(path.abspath(__file__)), 'data')
37TEST_VECTORS = path.join(39
38 path.dirname(path.abspath(__file__)), 'data', 'test-vectors.json'40
39)41def dumps(obj):
40assert path.isfile(TEST_VECTORS)42 return json.dumps(obj,
4143 ensure_ascii=False,
4244 sort_keys=True,
43def load_test_vectors():45 separators=(',',': '),
44 return json.load(open(TEST_VECTORS, 'r'))46 indent=4,
4547 )
4648
47def build_test_leaves(leaf_size):49
50def load_data(name):
51 filename = path.join(DATADIR, name + '.json')
52 return json.load(open(filename, 'r'))
53
54
55def build_leaves(leaf_size):
48 return {56 return {
49 'A': b'A',57 'A': b'A',
50 'B': b'B' * (leaf_size - 1),58 'B': b'B' * (leaf_size - 1),
@@ -52,36 +60,37 @@
52 }60 }
5361
5462
55def build_test_vectors(protocol):63def build_vectors(protocol, encoder=db32enc):
56 leaves = build_test_leaves(protocol.leaf_size)64 leaves = build_leaves(protocol.leaf_size)
5765
58 def hash_root(letters):66 def hash_leaf(i, L):
67 return encoder(protocol.hash_leaf(i, leaves[L]))
68
69 def hash_root(name):
59 leaf_hashes = b''70 leaf_hashes = b''
60 file_size = 071 file_size = 0
61 for (i, L) in enumerate(letters):72 for (i, L) in enumerate(name):
62 data = leaves[L]73 data = leaves[L]
63 leaf_hashes += protocol.hash_leaf(i, data)74 leaf_hashes += protocol.hash_leaf(i, data)
64 file_size += len(data)75 file_size += len(data)
65 return protocol.hash_root(file_size, leaf_hashes)76 return encoder(protocol.hash_root(file_size, leaf_hashes))
6677
67 vectors = {78 vectors = {
68 'leaf_hashes': {},79 'leaf_hashes': {},
69 'root_hashes': {},80 'root_hashes': {},
70 }81 }
7182
72 for (key, data) in leaves.items():83 for L in leaves:
73 vectors['leaf_hashes'][key] = [84 vectors['leaf_hashes'][L] = [hash_leaf(i, L) for i in range(2)]
74 b32dumps(protocol.hash_leaf(i, data)) for i in range(2)
75 ]
7685
77 for L in leaves:86 for L in leaves:
78 for key in (L, 'C' + L):87 for name in (L, 'C' + L):
79 vectors['root_hashes'][key] = hash_root(key)88 vectors['root_hashes'][name] = hash_root(name)
8089
81 return vectors90 return vectors
8291
8392
84def get_test_integers(leaf_size):93def get_integers(leaf_size):
85 return [94 return [
86 0,95 0,
87 1,96 1,
@@ -93,20 +102,26 @@
93 ]102 ]
94103
95104
96def build_test_md5(leaf_size):105def build_md5sums(leaf_size):
97 integers = dict(106 integers = dict(
98 (str(i), md5(str(i).encode('utf-8')).hexdigest())107 (str(i), md5(str(i).encode('utf-8')).hexdigest())
99 for i in get_test_integers(leaf_size)108 for i in get_integers(leaf_size)
100 )109 )
101 leaves = build_test_leaves(leaf_size)110
111 leaves = build_leaves(leaf_size)
102 C = leaves['C']112 C = leaves['C']
103 files = {}113 files = {}
104 for (key, data) in leaves.items():114 for (key, data) in leaves.items():
105 files[key] = md5(data).hexdigest()115 files[key] = md5(data).hexdigest()
106 files['C' + key] = md5(C + data).hexdigest()116 files['C' + key] = md5(C + data).hexdigest()
117
107 return {118 return {
108 'integers': integers,119 'integers': integers,
109 'files': files,120 'files': files,
121 'personalization': {
122 'leaf': md5(PERS_LEAF).hexdigest(),
123 'root': md5(PERS_ROOT).hexdigest(),
124 }
110 }125 }
111126
112127
@@ -145,3 +160,8 @@
145 if path.isdir(self.parentdir):160 if path.isdir(self.parentdir):
146 shutil.rmtree(self.parentdir)161 shutil.rmtree(self.parentdir)
147162
163
164if __name__ == '__main__':
165 vectors = build_vectors(VERSION1)
166 print(dumps(vectors))
167
148168
=== modified file 'filestore/protocols.py'
--- filestore/protocols.py 2013-02-16 01:28:23 +0000
+++ filestore/protocols.py 2013-02-19 15:39:24 +0000
@@ -94,15 +94,16 @@
94we still feel this is a very import architecture to embrace now.94we still feel this is a very import architecture to embrace now.
95"""95"""
9696
97from base64 import b32encode, b32decode
98from _skein import skein51297from _skein import skein512
9998
10099
100__all__ = ('VERSION0', 'VERSION1')
101
101# Skein personalization strings used in the Dmedia Hashing Protocol:102# Skein personalization strings used in the Dmedia Hashing Protocol:
102PERS_LEAF = b'20110430 jderose@novacut.com dmedia/leaf'103PERS_LEAF = b'20110430 jderose@novacut.com dmedia/leaf'
103PERS_ROOT = b'20110430 jderose@novacut.com dmedia/root'104PERS_ROOT = b'20110430 jderose@novacut.com dmedia/root'
104105
105# Additional Skein personalization strings used only in the old protocol:106# Additional Skein personalization strings used only in the old V0 protocol:
106PERS_LEAF_INDEX = b'20110430 jderose@novacut.com dmedia/leaf-index'107PERS_LEAF_INDEX = b'20110430 jderose@novacut.com dmedia/leaf-index'
107PERS_FILE_SIZE = b'20110430 jderose@novacut.com dmedia/file-size'108PERS_FILE_SIZE = b'20110430 jderose@novacut.com dmedia/file-size'
108109
@@ -110,48 +111,45 @@
110TYPE_ERROR = '{}: need a {!r}; got a {!r}: {!r}'111TYPE_ERROR = '{}: need a {!r}; got a {!r}: {!r}'
111112
112# Handy:113# Handy:
113MiB = 1024**2114MiB = 1048576
114115
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,
116# we're setting a "soft" file-size limit at 2**53 bytes (the max integer that117# we're setting a "soft" file-size limit at 2**53 bytes (the max integer that
117# fully works in JavaScript). But this is quite big, approximately 9.01 PB for118# fully works in JavaScript). But this is quite big, approximately 9.01 PB for
118# a single file:119# a single file:
119MAX_FILE_SIZE = 2**53120MAX_FILE_SIZE = 9007199254740992
120121
121# A real protocol would unlikely use a leaf-size this small, but a 2 MiB122# A real protocol would unlikely use a leaf-size this small, but a 2 MiB
122# minimum means we can use a uint32_t for the leaf_index in C123# minimum means we can use a uint32_t for the leaf_index in C
123# implementations and still reach the above MAX_FILE_SIZE:124# implementations and still reach the above MAX_FILE_SIZE:
124MIN_LEAF_SIZE = 2 * MiB125MIN_LEAF_SIZE = 2097152
125126
126127
127def b32dumps(data):128def make_key(integer):
128 """129 """
129 Base32-encode *data*, return as a string.130 Return *integer* as it's UTF-8 encoded decimal representation.
130131
131 >>> b32dumps(b'bbbbb')132 For example:
132 'MJRGEYTC'133
133134 >>> make_key(1776)
134 """135 b'1776'
135 return b32encode(data).decode('utf-8')136
136137 """
137138 if not isinstance(integer, int):
138def b32loads(string):139 raise TypeError(
139 """140 TYPE_ERROR.format('integer', int, type(integer), integer)
140 Decode base32-encoded data in *string*.141 )
141142 if integer < 0:
142 >>> b32loads('MJRGEYTC')143 raise ValueError('integer: must be >= 0; got {}'.format(integer))
143 b'bbbbb'144 return str(integer).encode('utf-8')
144
145 """
146 return b32decode(string.encode('utf-8'))
147145
148146
149class Protocol:147class Protocol:
150 """148 """
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.
152150
153 This class provides a standard API that abstracts away the details of a151 This base class provides a standard API that abstracts away the details of
154 particular hashing protocol and allows multiple protocol versions to be152 a particular hashing protocol and allows multiple protocol versions to be
155 supported simultaneously.153 supported simultaneously.
156154
157 The API consists of two methods:155 The API consists of two methods:
@@ -174,7 +172,7 @@
174 (while still using the standard hashing functions), you can simply create172 (while still using the standard hashing functions), you can simply create
175 a `Protocol` instance like this:173 a `Protocol` instance like this:
176174
177 >>> experimental = Protocol(16 * MiB, 360)175 >>> experimental = SkeinProtocol(16 * MiB, 360)
178176
179 The `digest_bytes` and `digest_b32len` will be derived from the provided177 The `digest_bytes` and `digest_b32len` will be derived from the provided
180 *digest_bits*:178 *digest_bits*:
@@ -184,23 +182,15 @@
184 >>> experimental.digest_b32len182 >>> experimental.digest_b32len
185 72183 72
186184
187 In order to implement a protocol that uses different underlying hash185 This base class doesn't provide a working protocol, to do that you must
188 functions, you'll need to subclass `Protocol` and override the two186 create a subclass and implement these two methods:
189 low-level hashing methods:187
190188 `Protocol._hash_leaf(key_leaf_index, leaf_data, challange)`
191 `Protocol._hash_leaf(leaf_index, leaf_data, challange)`189
192190 `Protocol._hash_root(key_file_size, leaf_hashes)`
193 `Protocol._hash_root(file_size, leaf_hashes)`
194 """191 """
195192
196 def __init__(self, leaf_size, digest_bits):193 def __init__(self, leaf_size, digest_bits):
197 """
198 Initialize a new `Protocol` instance.
199
200 :param leaf_size: an ``int`` which is some multiple of MIN_LEAF_SIZE
201 and >= MIN_LEAF_SIZE
202 :param digest_bits: an ``int`` which is some multiple of 40 and >= 200
203 """
204 if not isinstance(leaf_size, int):194 if not isinstance(leaf_size, int):
205 raise TypeError(195 raise TypeError(
206 TYPE_ERROR.format('leaf_size', int, type(leaf_size), leaf_size)196 TYPE_ERROR.format('leaf_size', int, type(leaf_size), leaf_size)
@@ -238,56 +228,7 @@
238 self.digest_bytes = digest_bits // 8228 self.digest_bytes = digest_bits // 8
239 self.digest_b32len = digest_bits // 5229 self.digest_b32len = digest_bits // 5
240230
241 def encode(self, digest):
242 if not isinstance(digest, bytes):
243 raise TypeError(
244 TYPE_ERROR.format('digest', bytes, type(digest), digest)
245 )
246 if len(digest) != self.digest_bytes:
247 raise ValueError('len(digest) must be {}; got {}'.format(
248 self.digest_bytes, len(digest))
249 )
250 return self._encode(digest)
251
252 def _encode(self, digest):
253 return b32dumps(digest)
254
255 def decode(self, _id):
256 if not isinstance(_id, str):
257 raise TypeError(
258 TYPE_ERROR.format('_id', str, type(_id), _id)
259 )
260 if len(_id) != self.digest_b32len:
261 raise ValueError('len(_id) must be {}; got {}'.format(
262 self.digest_b32len, len(_id))
263 )
264 return self._decode(_id)
265
266 def _decode(self, _id):
267 return b32loads(_id)
268
269 def hash_leaf(self, leaf_index, leaf_data, challenge=b''):231 def hash_leaf(self, leaf_index, leaf_data, challenge=b''):
270 """
271 Hash the leaf at (zero) index *leaf_index* containing *leaf_data*.
272
273 For example:
274
275 >>> p = Protocol(MIN_LEAF_SIZE, 200)
276 >>> b32encode(p.hash_leaf(0, b'The Leaf Data'))
277 b'EFBXKNNDAMRFIIPTIL2DYZ36YYQDAX54AAOYA7WG'
278
279 The optional *challenge* keyword argument should be a random nonce used
280 to make a remote cloud storage service prove that they actually have
281 the complete, exact file stored. For example:
282
283 >>> b32encode(p.hash_leaf(0, b'The Leaf Data', b'Random Nonce'))
284 b'LBAECE4GW3UFXREWAFBMBGV2O4GISUTJ3ALZXGJW'
285
286 :param leaf_index: an ``int`` >= 0
287 :param leaf_data: a ``bytes`` instance with leaf content
288 :param challenge: a ``bytes`` instance containing a random nonce to be
289 used in a proof-of-storage challenge; default is ``b''``
290 """
291 if not isinstance(leaf_index, int):232 if not isinstance(leaf_index, int):
292 raise TypeError(233 raise TypeError(
293 TYPE_ERROR.format(234 TYPE_ERROR.format(
@@ -315,24 +256,15 @@
315 self.leaf_size, len(leaf_data)256 self.leaf_size, len(leaf_data)
316 )257 )
317 )258 )
318 digest = self._hash_leaf(leaf_index, leaf_data, challenge)259 return self._hash_leaf(make_key(leaf_index), leaf_data, challenge)
319 assert len(digest) == self.digest_bytes260
320 return digest261 def _hash_leaf(self, key_leaf_index, leaf_data, challenge):
262 name = self.__class__.__name__
263 raise NotImplementedError(
264 '{}._hash_leaf(key_leaf_index, leaf_data, challenge)'.format(name)
265 )
321266
322 def hash_root(self, file_size, leaf_hashes):267 def hash_root(self, file_size, leaf_hashes):
323 """
324 Return the root-hash for a file *file_size* bytes with *leaf_hashes*.
325
326 For example:
327
328 >>> p = Protocol(MIN_LEAF_SIZE, 200)
329 >>> p.hash_root(1776, b'DDDDDDDDDDDDDDDDDDDDDDDDD')
330 'SJGXSO43OF5424G6HAEG55X2SFCZRW7L7LG42UKN'
331
332 :param file_size: an ``int`` >= 1
333 :param leaf_hashes: a ``bytes`` instance containing the concatenated
334 leaf-hashes produced by `Protocol.hash_leaf()`.
335 """
336 if not isinstance(file_size, int):268 if not isinstance(file_size, int):
337 raise TypeError(269 raise TypeError(
338 TYPE_ERROR.format('file_size', int, type(file_size), file_size)270 TYPE_ERROR.format('file_size', int, type(file_size), file_size)
@@ -369,66 +301,41 @@
369 low, high, file_size301 low, high, file_size
370 )302 )
371 )303 )
372 digest = self._hash_root(file_size, leaf_hashes)304 return self._hash_root(make_key(file_size), leaf_hashes)
373 return self.encode(digest)305
374306 def _hash_root(self, key_file_size, leaf_hashes):
375 def _hash_leaf(self, leaf_index, leaf_data, challenge):307 name = self.__class__.__name__
376 """308 raise NotImplementedError(
377 Protocol version 1 leaf-hashing implementation.309 '{}._hash_root(key_file_size, leaf_hashes)'.format(name)
378310 )
379 Subclasses can override this to use different hash functions,311
380 configurations, etc.312
381 """313class SkeinProtocol(Protocol):
382 assert leaf_index >= 0314 def _hash_leaf(self, key_leaf_index, leaf_data, challenge):
383 assert 1 <= len(leaf_data) <= self.leaf_size
384 return skein512(leaf_data,315 return skein512(leaf_data,
385 digest_bits=self.digest_bits,316 digest_bits=self.digest_bits,
386 pers=PERS_LEAF,317 pers=PERS_LEAF,
387 key=str(leaf_index).encode('utf-8'),318 key=key_leaf_index,
388 nonce=challenge,319 nonce=challenge,
389 ).digest()320 ).digest()
390321
391 def _hash_root(self, file_size, leaf_hashes):322 def _hash_root(self, key_file_size, leaf_hashes):
392 """
393 Protocol version 1 root-hashing implementation.
394
395 Subclasses can override this to use different hash functions,
396 configurations, etc.
397 """
398 assert file_size >= 1
399 assert len(leaf_hashes) > 0
400 assert len(leaf_hashes) % self.digest_bytes == 0
401 return skein512(leaf_hashes,323 return skein512(leaf_hashes,
402 digest_bits=self.digest_bits,324 digest_bits=self.digest_bits,
403 pers=PERS_ROOT,325 pers=PERS_ROOT,
404 key=str(file_size).encode('utf-8'),326 key=key_file_size,
405 ).digest()327 ).digest()
406328
407329
408VERSION1 = Protocol(8 * MiB, 280)330class OldSkeinProtocol(Protocol):
409331 def _hash_leaf_index(self, key_leaf_index):
410332 return skein512(key_leaf_index,
411class OldProtocol(Protocol):
412 """
413 The unofficial version zero protocol.
414
415 Although no formal support commitments were ever made for this version zero
416 protocol, it has been used enough in the wild that we should really
417 continue to support it and provide a nice migration to the version one
418 protocol.
419 """
420
421 def _hash_leaf_index(self, leaf_index):
422 assert leaf_index >= 0
423 return skein512(str(leaf_index).encode('utf-8'),
424 digest_bits=self.digest_bits,333 digest_bits=self.digest_bits,
425 pers=PERS_LEAF_INDEX,334 pers=PERS_LEAF_INDEX,
426 ).digest()335 ).digest()
427336
428 def _hash_leaf(self, leaf_index, leaf_data, challenge):337 def _hash_leaf(self, key_leaf_index, leaf_data, challenge):
429 assert leaf_index >= 0338 skein = skein512(self._hash_leaf_index(key_leaf_index),
430 assert 1 <= len(leaf_data) <= self.leaf_size
431 skein = skein512(self._hash_leaf_index(leaf_index),
432 digest_bits=self.digest_bits,339 digest_bits=self.digest_bits,
433 pers=PERS_LEAF,340 pers=PERS_LEAF,
434 nonce=challenge,341 nonce=challenge,
@@ -436,18 +343,14 @@
436 skein.update(leaf_data)343 skein.update(leaf_data)
437 return skein.digest()344 return skein.digest()
438345
439 def _hash_file_size(self, file_size):346 def _hash_file_size(self, key_file_size):
440 assert file_size >= 1347 return skein512(key_file_size,
441 return skein512(str(file_size).encode('utf-8'),
442 digest_bits=self.digest_bits,348 digest_bits=self.digest_bits,
443 pers=PERS_FILE_SIZE,349 pers=PERS_FILE_SIZE,
444 ).digest()350 ).digest()
445351
446 def _hash_root(self, file_size, leaf_hashes):352 def _hash_root(self, key_file_size, leaf_hashes):
447 assert file_size >= 1353 skein = skein512(self._hash_file_size(key_file_size),
448 assert len(leaf_hashes) > 0
449 assert len(leaf_hashes) % self.digest_bytes == 0
450 skein = skein512(self._hash_file_size(file_size),
451 digest_bits=self.digest_bits,354 digest_bits=self.digest_bits,
452 pers=PERS_ROOT,355 pers=PERS_ROOT,
453 )356 )
@@ -455,6 +358,7 @@
455 return skein.digest()358 return skein.digest()
456359
457360
458VERSION0 = OldProtocol(8 * MiB, 240)361# Create the Protocol instances:
362VERSION0 = OldSkeinProtocol(8 * MiB, 240)
363VERSION1 = SkeinProtocol(8 * MiB, 240)
459364
460PROTOCOLS = (VERSION0, VERSION1)
461365
=== modified file 'filestore/tests/__init__.py'
--- filestore/tests/__init__.py 2013-02-18 12:40:06 +0000
+++ filestore/tests/__init__.py 2013-02-19 15:39:24 +0000
@@ -35,12 +35,10 @@
35from random import SystemRandom35from random import SystemRandom
3636
37from skein import skein51237from skein import skein512
38from dbase32.rfc3548 import b32enc, b32dec
38from dbase32 import isdb3239from dbase32 import isdb32
3940
40from filestore import protocols41from filestore import protocols, misc
41from filestore.protocols import PROTOCOLS, VERSION0, VERSION1
42from filestore.protocols import b32dumps, b32loads
43from filestore import misc
44import filestore42import filestore
4543
4644
@@ -69,7 +67,7 @@
69])67])
70ID = filestore.hash_root(FILE_SIZE, LEAF_HASHES)68ID = filestore.hash_root(FILE_SIZE, LEAF_HASHES)
71CH = filestore.ContentHash(ID, FILE_SIZE, LEAF_HASHES)69CH = filestore.ContentHash(ID, FILE_SIZE, LEAF_HASHES)
72assert CH.id == 'DMJGE4OTWZVKSX426GHE46GZCGICMQOVWNGRVB7E665Y2RAM', CH.id70#assert CH.id == 'DMJGE4OTWZVKSX426GHE46GZCGICMQOVWNGRVB7E665Y2RAM', CH.id
7371
7472
75def write_sample_file(fp):73def write_sample_file(fp):
@@ -110,17 +108,13 @@
110 def touch(self, *parts):108 def touch(self, *parts):
111 d = self.makedirs(*parts[:-1])109 d = self.makedirs(*parts[:-1])
112 f = self.join(*parts)110 f = self.join(*parts)
113 assert not path.exists(f)111 open(f, 'xb').close()
114 open(f, 'wb').close()
115 assert path.isfile(f) and not path.islink(f)
116 return f112 return f
117113
118 def write(self, content, *parts):114 def write(self, content, *parts):
119 d = self.makedirs(*parts[:-1])115 d = self.makedirs(*parts[:-1])
120 f = self.join(*parts)116 f = self.join(*parts)
121 assert not path.exists(f)117 open(f, 'xb').write(content)
122 open(f, 'wb').write(content)
123 assert path.isfile(f) and not path.islink(f)
124 return f118 return f
125119
126 def copy(self, src, *parts):120 def copy(self, src, *parts):
@@ -270,6 +264,33 @@
270 self.assertEqual(str(e), "'the id' not in 'the FileStore'")264 self.assertEqual(str(e), "'the id' not in 'the FileStore'")
271265
272266
267class TestConstants(TestCase):
268 def test_leaf_size(self):
269 self.assertIsInstance(filestore.LEAF_SIZE, int)
270 self.assertEqual(filestore.LEAF_SIZE, 8 * 1024 * 1024)
271 self.assertEqual(filestore.LEAF_SIZE, 2**23)
272 self.assertEqual(filestore.LEAF_SIZE, filestore.PROTOCOL.leaf_size)
273 self.assertEqual(filestore.LEAF_SIZE, protocols.VERSION0.leaf_size)
274 self.assertEqual(filestore.LEAF_SIZE, protocols.VERSION1.leaf_size)
275
276 def test_digest_bits(self):
277 self.assertIsInstance(filestore.DIGEST_BITS, int)
278 self.assertEqual(filestore.DIGEST_BITS % 40, 0)
279 self.assertEqual(filestore.DIGEST_BITS, filestore.PROTOCOL.digest_bits)
280
281 def test_digest_bytes(self):
282 self.assertIsInstance(filestore.DIGEST_BYTES, int)
283 self.assertEqual(filestore.DIGEST_BYTES % 5, 0)
284 self.assertEqual(filestore.DIGEST_BYTES, filestore.DIGEST_BITS // 8)
285 self.assertEqual(filestore.DIGEST_BYTES, filestore.PROTOCOL.digest_bytes)
286
287 def test_digest_b32len(self):
288 self.assertIsInstance(filestore.DIGEST_B32LEN, int)
289 self.assertEqual(filestore.DIGEST_B32LEN % 8, 0)
290 self.assertEqual(filestore.DIGEST_B32LEN, filestore.DIGEST_BITS // 5)
291 self.assertEqual(filestore.DIGEST_B32LEN, filestore.PROTOCOL.digest_b32len)
292
293
273class TestFunctions(TestCase):294class TestFunctions(TestCase):
274 def test_constants(self):295 def test_constants(self):
275 """296 """
@@ -361,7 +382,7 @@
361 digest = f(2, content)382 digest = f(2, content)
362 self.assertEqual(383 self.assertEqual(
363 digest,384 digest,
364 skein512(VERSION0._hash_leaf_index(2) + content,385 skein512(protocols.VERSION0._hash_leaf_index(b'2') + content,
365 digest_bits=240,386 digest_bits=240,
366 pers=protocols.PERS_LEAF,387 pers=protocols.PERS_LEAF,
367 ).digest()388 ).digest()
@@ -371,7 +392,7 @@
371 digest = f(2, content)392 digest = f(2, content)
372 self.assertEqual(393 self.assertEqual(
373 digest,394 digest,
374 skein512(VERSION0._hash_leaf_index(2) + content,395 skein512(protocols.VERSION0._hash_leaf_index(b'2') + content,
375 digest_bits=240,396 digest_bits=240,
376 pers=protocols.PERS_LEAF,397 pers=protocols.PERS_LEAF,
377 ).digest()398 ).digest()
@@ -499,7 +520,7 @@
499520
500 def test_check_leaf_hashes(self):521 def test_check_leaf_hashes(self):
501 # Test with wrong type:522 # Test with wrong type:
502 leaf_hashes = 'a' * 60523 leaf_hashes = 'a' * 30
503 with self.assertRaises(TypeError) as cm:524 with self.assertRaises(TypeError) as cm:
504 filestore.check_leaf_hashes(leaf_hashes)525 filestore.check_leaf_hashes(leaf_hashes)
505 self.assertEqual(526 self.assertEqual(
@@ -514,49 +535,30 @@
514 self.assertIs(cm.exception.leaf_hashes, leaf_hashes)535 self.assertIs(cm.exception.leaf_hashes, leaf_hashes)
515536
516 # Test with wrong length:537 # Test with wrong length:
517 leaf_hashes = b'a' * 61538 leaf_hashes = b'a' * 29
539 with self.assertRaises(filestore.LeafHashesError) as cm:
540 filestore.check_leaf_hashes(leaf_hashes)
541 self.assertIs(cm.exception.leaf_hashes, leaf_hashes)
542
543 leaf_hashes = b'a' * 31
544 with self.assertRaises(filestore.LeafHashesError) as cm:
545 filestore.check_leaf_hashes(leaf_hashes)
546 self.assertIs(cm.exception.leaf_hashes, leaf_hashes)
547
548 leaf_hashes = b'a' * (18 * 30 - 1)
518 with self.assertRaises(filestore.LeafHashesError) as cm:549 with self.assertRaises(filestore.LeafHashesError) as cm:
519 filestore.check_leaf_hashes(leaf_hashes)550 filestore.check_leaf_hashes(leaf_hashes)
520 self.assertIs(cm.exception.leaf_hashes, leaf_hashes)551 self.assertIs(cm.exception.leaf_hashes, leaf_hashes)
521552
522 # Test with good values:553 # Test with good values:
523 good30 = os.urandom(30)554 good = os.urandom(30)
524 self.assertIs(filestore.check_leaf_hashes(good30), good30)555 self.assertIs(filestore.check_leaf_hashes(good), good)
525 self.assertIs(556
526 filestore.check_leaf_hashes(good30, protocol=VERSION0),557 good = os.urandom(60)
527 good30558 self.assertIs(filestore.check_leaf_hashes(good), good)
528 )559
529 good35 = os.urandom(35)560 good = os.urandom(18 * 30)
530 self.assertIs(561 self.assertIs(filestore.check_leaf_hashes(good), good)
531 filestore.check_leaf_hashes(good35, protocol=VERSION1),
532 good35
533 )
534
535 # Test with wrong protocol version:
536 with self.assertRaises(filestore.LeafHashesError) as cm:
537 filestore.check_leaf_hashes(good30, protocol=VERSION1)
538 self.assertIs(cm.exception.leaf_hashes, good30)
539 with self.assertRaises(filestore.LeafHashesError) as cm:
540 filestore.check_leaf_hashes(good35, protocol=VERSION0)
541 self.assertIs(cm.exception.leaf_hashes, good35)
542
543 # Future proofing:
544 for protocol in PROTOCOLS:
545 good = os.urandom(protocol.digest_bytes)
546 self.assertIs(filestore.check_leaf_hashes(good, protocol), good)
547
548 good = os.urandom(18 * protocol.digest_bytes)
549 self.assertIs(filestore.check_leaf_hashes(good, protocol), good)
550
551 bad = os.urandom(protocol.digest_bytes + 1)
552 with self.assertRaises(filestore.LeafHashesError) as cm:
553 filestore.check_leaf_hashes(bad, protocol)
554 self.assertIs(cm.exception.leaf_hashes, bad)
555
556 bad = os.urandom(18 * protocol.digest_bytes - 1)
557 with self.assertRaises(filestore.LeafHashesError) as cm:
558 filestore.check_leaf_hashes(bad, protocol)
559 self.assertIs(cm.exception.leaf_hashes, bad)
560562
561 def test_check_root_hash(self):563 def test_check_root_hash(self):
562 f = filestore.check_root_hash564 f = filestore.check_root_hash
@@ -587,62 +589,6 @@
587 'OYESBWEZ4Y2AGSLMNZB4ZF75A2VG7NXVB4R25SSMRGXLN4CR'589 'OYESBWEZ4Y2AGSLMNZB4ZF75A2VG7NXVB4R25SSMRGXLN4CR'
588 )590 )
589591
590 def test_check_root_hash2(self):
591 for protocol in PROTOCOLS:
592 file_size = protocol.leaf_size + 1
593 leaf0_hash = os.urandom(protocol.digest_bytes)
594 leaf1_hash = os.urandom(protocol.digest_bytes)
595 leaf_hashes = leaf0_hash + leaf1_hash
596 _id = protocol.hash_root(file_size, leaf_hashes)
597
598 # Everything is correct:
599 ch = filestore.check_root_hash(_id, file_size, leaf_hashes)
600 self.assertIsInstance(ch, filestore.ContentHash)
601 self.assertEqual(ch.id, _id)
602 self.assertEqual(ch.file_size, file_size)
603 self.assertEqual(ch.leaf_hashes, leaf_hashes)
604 self.assertEqual(ch, (_id, file_size, leaf_hashes))
605
606 # Everything is correct, unpack=True
607 ch = filestore.check_root_hash(_id, file_size, leaf_hashes,
608 unpack=True
609 )
610 self.assertIsInstance(ch, filestore.ContentHash)
611 self.assertEqual(ch.id, _id)
612 self.assertEqual(ch.file_size, file_size)
613 self.assertEqual(ch.leaf_hashes, (leaf0_hash, leaf1_hash))
614 self.assertEqual(ch, (_id, file_size, (leaf0_hash, leaf1_hash)))
615
616 # Wrong ID:
617 bad_id = random_id(protocol.digest_bytes)
618 with self.assertRaises(filestore.RootHashError) as cm:
619 filestore.check_root_hash(bad_id, file_size, leaf_hashes)
620 self.assertEqual(cm.exception.id, bad_id)
621 self.assertEqual(cm.exception.file_size, file_size)
622 self.assertEqual(cm.exception.leaf_hashes, leaf_hashes)
623 self.assertEqual(cm.exception.bad_id, _id)
624
625 # Wrong file_size:
626 with self.assertRaises(filestore.RootHashError) as cm:
627 filestore.check_root_hash(_id, file_size + 1, leaf_hashes)
628 self.assertEqual(cm.exception.id, _id)
629 self.assertEqual(cm.exception.file_size, file_size + 1)
630 self.assertEqual(cm.exception.leaf_hashes, leaf_hashes)
631 self.assertEqual(cm.exception.bad_id,
632 protocol.hash_root(file_size + 1, leaf_hashes)
633 )
634
635 # Wrong leaf_hashes
636 bad_leaf_hashes = leaf0_hash + os.urandom(protocol.digest_bytes)
637 with self.assertRaises(filestore.RootHashError) as cm:
638 filestore.check_root_hash(_id, file_size, bad_leaf_hashes)
639 self.assertEqual(cm.exception.id, _id)
640 self.assertEqual(cm.exception.file_size, file_size)
641 self.assertEqual(cm.exception.leaf_hashes, bad_leaf_hashes)
642 self.assertEqual(cm.exception.bad_id,
643 protocol.hash_root(file_size, bad_leaf_hashes)
644 )
645
646 def test_enumerate_leaf_hashes(self):592 def test_enumerate_leaf_hashes(self):
647 f = filestore.enumerate_leaf_hashes593 f = filestore.enumerate_leaf_hashes
648 self.assertEqual(594 self.assertEqual(
@@ -772,7 +718,7 @@
772 # Test with wrong length:718 # Test with wrong length:
773 for i in range(321):719 for i in range(321):
774 value = 'N' * i720 value = 'N' * i
775 if len(value) in (48, 56):721 if len(value) == 48:
776 self.assertIs(filestore.check_id(value), value)722 self.assertIs(filestore.check_id(value), value)
777 continue723 continue
778 with self.assertRaises(filestore.IDError) as cm:724 with self.assertRaises(filestore.IDError) as cm:
@@ -782,18 +728,12 @@
782 # Test with 48 and 56 character:728 # Test with 48 and 56 character:
783 id48 = random_id(30)729 id48 = random_id(30)
784 self.assertIs(filestore.check_id(id48), id48)730 self.assertIs(filestore.check_id(id48), id48)
785 id56 = random_id(35)
786 self.assertIs(filestore.check_id(id56), id56)
787731
788 # Test case sensitivity:732 # Test case sensitivity:
789 bad48 = id48.lower()733 bad48 = id48.lower()
790 with self.assertRaises(filestore.IDError) as cm:734 with self.assertRaises(filestore.IDError) as cm:
791 filestore.check_id(bad48)735 filestore.check_id(bad48)
792 self.assertIs(cm.exception.id, bad48)736 self.assertIs(cm.exception.id, bad48)
793 bad56 = id56.lower()
794 with self.assertRaises(filestore.IDError) as cm:
795 filestore.check_id(bad56)
796 self.assertIs(cm.exception.id, bad56)
797737
798 def test_iter_files(self):738 def test_iter_files(self):
799 """739 """
@@ -1247,30 +1187,18 @@
1247 src_fp = open(dst, 'rb')1187 src_fp = open(dst, 'rb')
1248 self.assertEqual(filestore.hash_fp(src_fp), ch)1188 self.assertEqual(filestore.hash_fp(src_fp), ch)
12491189
1250 def test_hash_fp2(self):1190 leaves = misc.build_leaves(filestore.LEAF_SIZE)
1251 obj = misc.load_test_vectors()1191 C = leaves['C']
1252 for proto in PROTOCOLS:1192 for (L, data) in leaves.items():
1253 tmp = TempDir()1193 tmp.write(data, L)
1254 leaves = misc.build_test_leaves(proto.leaf_size)1194 tmp.write(C + data, 'C' + L)
1255 vectors = obj[str(proto.digest_b32len)]1195
1256 for (key, value) in vectors['root_hashes'].items():1196 vectors = misc.load_data('V0')
1257 name = tmp.join(key)1197 for name in ['A', 'B', 'C', 'CA', 'CB', 'CC']:
1258 assert not path.exists(name)1198 src_fp = open(tmp.join(name), 'rb')
1259 fp = open(name, 'wb')1199 ch = filestore.hash_fp(src_fp)
1260 for L in key:1200 self.assertIsInstance(ch, filestore.ContentHash)
1261 fp.write(leaves[L])1201 self.assertEqual(ch.id, vectors['root_hashes'][name])
1262 fp.close()
1263 fp = open(name, 'rb')
1264 ch = filestore.hash_fp(fp, protocol=proto)
1265 self.assertIsInstance(ch, filestore.ContentHash)
1266 self.assertEqual(ch.id, value)
1267 self.assertEqual(ch.file_size, path.getsize(name))
1268 self.assertEqual(ch.leaf_hashes,
1269 b''.join(
1270 b32loads(vectors['leaf_hashes'][L][i])
1271 for (i, L) in enumerate(key)
1272 )
1273 )
12741202
1275 def test_ensuredir(self):1203 def test_ensuredir(self):
1276 f = filestore.ensuredir1204 f = filestore.ensuredir
@@ -1594,22 +1522,6 @@
1594 stats.sort(key=lambda s: s.id)1522 stats.sort(key=lambda s: s.id)
1595 self.assertEqual(list(fs), stats)1523 self.assertEqual(list(fs), stats)
15961524
1597 # Add more valid files in (56 character IDs)
1598 for i in range(1000):
1599 _id = random_id(35)
1600 size = i + 1
1601 f = fs.path(_id)
1602 assert not path.exists(f)
1603 open(f, 'wb').write(b'N' * size)
1604 os.chmod(f, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
1605 self.assertEqual(path.getsize(f), size)
1606 st = filestore.Stat(_id, f, size, path.getmtime(f))
1607 self.assertEqual(fs.stat(_id), st)
1608 stats.append(st)
1609 self.assertNotEqual(list(fs), stats) # Sorting!
1610 stats.sort(key=lambda s: s.id)
1611 self.assertEqual(list(fs), stats)
1612
1613 # Should ignore symlinks, even if to valid files1525 # Should ignore symlinks, even if to valid files
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()
1615 for i in range(50):1527 for i in range(50):
@@ -1989,31 +1901,6 @@
1989 self.assertFalse(path.exists(canonical))1901 self.assertFalse(path.exists(canonical))
1990 self.assertTrue(path.isfile(corrupt))1902 self.assertTrue(path.isfile(corrupt))
19911903
1992 def test_verify2(self):
1993 tmp = TempDir()
1994 fs = filestore.FileStore(tmp.dir)
1995 obj = misc.load_test_vectors()
1996 for proto in PROTOCOLS:
1997 leaves = misc.build_test_leaves(proto.leaf_size)
1998 vectors = obj[str(proto.digest_b32len)]
1999 for (key, value) in vectors['root_hashes'].items():
2000 name = fs.path(value)
2001 assert not path.exists(name)
2002 fp = open(name, 'wb')
2003 for L in key:
2004 fp.write(leaves[L])
2005 fp.close()
2006 ch = fs.verify(value)
2007 self.assertIsInstance(ch, filestore.ContentHash)
2008 self.assertEqual(ch.id, value)
2009 self.assertEqual(ch.file_size, path.getsize(name))
2010 self.assertEqual(ch.leaf_hashes,
2011 b''.join(
2012 b32loads(vectors['leaf_hashes'][L][i])
2013 for (i, L) in enumerate(key)
2014 )
2015 )
2016
2017 def test_verify_iter(self):1904 def test_verify_iter(self):
2018 tmp = TempDir()1905 tmp = TempDir()
2019 fs = filestore.FileStore(tmp.dir)1906 fs = filestore.FileStore(tmp.dir)
20201907
=== modified file 'filestore/tests/test_misc.py'
--- filestore/tests/test_misc.py 2013-02-14 20:08:06 +0000
+++ filestore/tests/test_misc.py 2013-02-19 15:39:24 +0000
@@ -29,28 +29,44 @@
29import json29import json
30from hashlib import md530from hashlib import md5
3131
32from dbase32 import db32enc, db32dec
33from dbase32.rfc3548 import b32enc, b32dec
34
32import filestore35import filestore
33from filestore.protocols import MIN_LEAF_SIZE, Protocol, VERSION0, VERSION136from filestore.protocols import MIN_LEAF_SIZE
34from filestore.protocols import b32dumps, b32loads37from filestore import misc, protocols
35from filestore import misc
3638
3739
38class TestFunctions(TestCase):40class TestFunctions(TestCase):
39 def test_load_test_vectors(self):41 def test_load_data(self):
40 pkg = path.dirname(path.abspath(filestore.__file__))42 pkg = path.dirname(path.abspath(filestore.__file__))
41 self.assertTrue(path.isdir(pkg))43 self.assertTrue(path.isdir(pkg))
42 self.assertEqual(44 self.assertEqual(misc.DATADIR, path.join(pkg, 'data'))
43 misc.TEST_VECTORS,45 self.assertTrue(path.isdir(misc.DATADIR))
44 path.join(pkg, 'data', 'test-vectors.json')46
45 )47 V0 = path.join(misc.DATADIR, 'V0.json')
46 self.assertTrue(path.isfile(misc.TEST_VECTORS))48 self.assertTrue(path.isfile(V0))
47 self.assertEqual(49 self.assertEqual(
48 misc.load_test_vectors(),50 misc.load_data('V0'),
49 json.loads(open(misc.TEST_VECTORS, 'r').read())51 json.loads(open(V0, 'r').read())
50 )52 )
5153
52 def test_build_test_leaves(self):54 V1 = path.join(misc.DATADIR, 'V1.json')
53 obj = misc.build_test_leaves(MIN_LEAF_SIZE)55 self.assertTrue(path.isfile(V0))
56 self.assertEqual(
57 misc.load_data('V1'),
58 json.loads(open(V1, 'r').read())
59 )
60
61 MD5SUMS = path.join(misc.DATADIR, 'MD5SUMS.json')
62 self.assertTrue(path.isfile(MD5SUMS))
63 self.assertEqual(
64 misc.load_data('MD5SUMS'),
65 json.loads(open(MD5SUMS, 'r').read())
66 )
67
68 def test_build_leaves(self):
69 obj = misc.build_leaves(MIN_LEAF_SIZE)
54 self.assertEqual(obj,70 self.assertEqual(obj,
55 {71 {
56 'A': b'A',72 'A': b'A',
@@ -62,7 +78,7 @@
62 self.assertEqual(len(obj['B']), MIN_LEAF_SIZE - 1)78 self.assertEqual(len(obj['B']), MIN_LEAF_SIZE - 1)
63 self.assertEqual(len(obj['C']), MIN_LEAF_SIZE)79 self.assertEqual(len(obj['C']), MIN_LEAF_SIZE)
6480
65 obj = misc.build_test_leaves(2 * MIN_LEAF_SIZE)81 obj = misc.build_leaves(2 * MIN_LEAF_SIZE)
66 self.assertEqual(obj,82 self.assertEqual(obj,
67 {83 {
68 'A': b'A',84 'A': b'A',
@@ -74,59 +90,90 @@
74 self.assertEqual(len(obj['B']), 2 * MIN_LEAF_SIZE - 1)90 self.assertEqual(len(obj['B']), 2 * MIN_LEAF_SIZE - 1)
75 self.assertEqual(len(obj['C']), 2 * MIN_LEAF_SIZE)91 self.assertEqual(len(obj['C']), 2 * MIN_LEAF_SIZE)
7692
77 def test_build_test_vectors(self):93 def test_build_vectors(self):
78 proto = Protocol(MIN_LEAF_SIZE, 200)94 leaf_size = 2 * 1024 * 1024
79 leaves = misc.build_test_leaves(proto.leaf_size)95 proto = protocols.SkeinProtocol(leaf_size, 200)
96 leaves = misc.build_leaves(leaf_size)
97
80 A0 = proto.hash_leaf(0, leaves['A'])98 A0 = proto.hash_leaf(0, leaves['A'])
81 A1 = proto.hash_leaf(1, leaves['A'])99 A1 = proto.hash_leaf(1, leaves['A'])
82 B0 = proto.hash_leaf(0, leaves['B'])100 B0 = proto.hash_leaf(0, leaves['B'])
83 B1 = proto.hash_leaf(1, leaves['B'])101 B1 = proto.hash_leaf(1, leaves['B'])
84 C0 = proto.hash_leaf(0, leaves['C'])102 C0 = proto.hash_leaf(0, leaves['C'])
85 C1 = proto.hash_leaf(1, leaves['C'])103 C1 = proto.hash_leaf(1, leaves['C'])
86 self.assertEqual(104
87 misc.build_test_vectors(proto),105 del leaves
88 {106
89 'leaf_hashes': {107 A = proto.hash_root(1, A0)
90 'A': [b32dumps(A0), b32dumps(A1)],108 B = proto.hash_root(leaf_size - 1, B0)
91 'B': [b32dumps(B0), b32dumps(B1)],109 C = proto.hash_root(leaf_size, C0)
92 'C': [b32dumps(C0), b32dumps(C1)],110 CA = proto.hash_root(leaf_size + 1, C0 + A1)
93 },111 CB = proto.hash_root(2 * leaf_size - 1, C0 + B1)
94 'root_hashes': {112 CC = proto.hash_root(2 * leaf_size, C0 + C1)
95 'A': proto.hash_root(1, A0),113
96 'B': proto.hash_root(MIN_LEAF_SIZE - 1, B0),114 self.assertEqual(
97 'C': proto.hash_root(MIN_LEAF_SIZE, C0),115 misc.build_vectors(proto),
98 'CA': proto.hash_root(MIN_LEAF_SIZE + 1, C0 + A1),116 {
99 'CB': proto.hash_root(2 * MIN_LEAF_SIZE - 1, C0 + B1),117 'leaf_hashes': {
100 'CC': proto.hash_root(2 * MIN_LEAF_SIZE, C0 + C1),118 'A': [db32enc(A0), db32enc(A1)],
101 }119 'B': [db32enc(B0), db32enc(B1)],
102 }120 'C': [db32enc(C0), db32enc(C1)],
103 )121 },
104122 'root_hashes': {
105 self.assertEqual(123 'A': db32enc(A),
106 misc.load_test_vectors(),124 'B': db32enc(B),
107 {125 'C': db32enc(C),
108 '48': misc.build_test_vectors(VERSION0),126 'CA': db32enc(CA),
109 '56': misc.build_test_vectors(VERSION1),127 'CB': db32enc(CB),
110 }128 'CC': db32enc(CC),
111 )129 }
112130 }
113 def test_get_test_integers(self):131 )
114 self.assertEqual(132
115 misc.get_test_integers(100),133 self.assertEqual(
134 misc.build_vectors(proto, encoder=b32enc),
135 {
136 'leaf_hashes': {
137 'A': [b32enc(A0), b32enc(A1)],
138 'B': [b32enc(B0), b32enc(B1)],
139 'C': [b32enc(C0), b32enc(C1)],
140 },
141 'root_hashes': {
142 'A': b32enc(A),
143 'B': b32enc(B),
144 'C': b32enc(C),
145 'CA': b32enc(CA),
146 'CB': b32enc(CB),
147 'CC': b32enc(CC),
148 }
149 }
150 )
151
152 def test_get_integers(self):
153 self.assertEqual(
154 misc.get_integers(100),
116 [0, 1, 99, 100, 101, 199, 200]155 [0, 1, 99, 100, 101, 199, 200]
117 )156 )
118 self.assertEqual(157 self.assertEqual(
119 misc.get_test_integers(500),158 misc.get_integers(500),
120 [0, 1, 499, 500, 501, 999, 1000]159 [0, 1, 499, 500, 501, 999, 1000]
121 )160 )
122161
123 def test_build_test_md5(self):162 def test_build_md5sums(self):
124 A = b'A'163 A = b'A'
125 B = b'B' * 99164 B = b'B' * 99
126 C = b'C' * 100165 C = b'C' * 100
127 self.assertEqual(166 self.assertEqual(
128 misc.build_test_md5(100),167 misc.build_md5sums(100),
129 {168 {
169 'files': {
170 'A': md5(A).hexdigest(),
171 'B': md5(B).hexdigest(),
172 'C': md5(C).hexdigest(),
173 'CA': md5(C + A).hexdigest(),
174 'CB': md5(C + B).hexdigest(),
175 'CC': md5(C + C).hexdigest(),
176 },
130 'integers': {177 'integers': {
131 '0': md5(b'0').hexdigest(),178 '0': md5(b'0').hexdigest(),
132 '1': md5(b'1').hexdigest(),179 '1': md5(b'1').hexdigest(),
@@ -136,6 +183,19 @@
136 '199': md5(b'199').hexdigest(),183 '199': md5(b'199').hexdigest(),
137 '200': md5(b'200').hexdigest(),184 '200': md5(b'200').hexdigest(),
138 },185 },
186 'personalization': {
187 'leaf': md5(protocols.PERS_LEAF).hexdigest(),
188 'root': md5(protocols.PERS_ROOT).hexdigest(),
189 },
190 }
191 )
192
193 A = b'A'
194 B = b'B' * 499
195 C = b'C' * 500
196 self.assertEqual(
197 misc.build_md5sums(500),
198 {
139 'files': {199 'files': {
140 'A': md5(A).hexdigest(),200 'A': md5(A).hexdigest(),
141 'B': md5(B).hexdigest(),201 'B': md5(B).hexdigest(),
@@ -143,10 +203,28 @@
143 'CA': md5(C + A).hexdigest(),203 'CA': md5(C + A).hexdigest(),
144 'CB': md5(C + B).hexdigest(),204 'CB': md5(C + B).hexdigest(),
145 'CC': md5(C + C).hexdigest(),205 'CC': md5(C + C).hexdigest(),
146 }206 },
207 'integers': {
208 '0': md5(b'0').hexdigest(),
209 '1': md5(b'1').hexdigest(),
210 '499': md5(b'499').hexdigest(),
211 '500': md5(b'500').hexdigest(),
212 '501': md5(b'501').hexdigest(),
213 '999': md5(b'999').hexdigest(),
214 '1000': md5(b'1000').hexdigest(),
215 },
216 'personalization': {
217 'leaf': md5(protocols.PERS_LEAF).hexdigest(),
218 'root': md5(protocols.PERS_ROOT).hexdigest(),
219 },
147 } 220 }
148 )221 )
149222
223 self.assertEqual(
224 misc.build_md5sums(8 * 1024 * 1024),
225 misc.load_data('MD5SUMS')
226 )
227
150228
151class TestTempFileStore(TestCase):229class TestTempFileStore(TestCase):
152 def test_init(self):230 def test_init(self):
153231
=== modified file 'filestore/tests/test_protocols.py'
--- filestore/tests/test_protocols.py 2013-02-14 20:08:06 +0000
+++ filestore/tests/test_protocols.py 2013-02-19 15:39:24 +0000
@@ -25,73 +25,154 @@
2525
26from unittest import TestCase26from unittest import TestCase
27import os27import os
28from base64 import b32encode, b32decode28from random import SystemRandom
29from hashlib import md5
3029
31from skein import skein51230from skein import skein512
3231from dbase32 import db32enc, db32dec
33from filestore.protocols import b32dumps, b32loads32from dbase32.rfc3548 import b32enc, b32dec
34from filestore import protocols33
3534from filestore import protocols, misc
3635
36
37random = SystemRandom()
37MiB = 1024 * 102438MiB = 1024 * 1024
38TwoMiB = 2 * MiB39TwoMiB = 2 * MiB
39EightMiB = 8 * MiB40EightMiB = 8 * MiB
41GiB = 1024 * MiB
40COUNT = 50 * 100042COUNT = 50 * 1000
41TYPE_ERROR = '{}: need a {!r}; got a {!r}: {!r}'43TYPE_ERROR = '{}: need a {!r}; got a {!r}: {!r}'
4244
43A = b'A'45
44B = b'B' * (EightMiB - 1)46class DigestAccum(set):
45C = b'C' * EightMiB47 def add(self, digest):
4648 if not isinstance(digest, bytes):
47A0_B32 = 'XZ5I6KJTUSOIWVCEBOKUELTADZUXNHOAYO77NKKHWCIW3HYGYOPMX5JN'49 raise TypeError('digest must be bytes')
48A1_B32 = 'TEC7754ZNM26MTM6YQFI6TMVTTK4RKQEMPAGT2ROQZUBPUIHSJU2DDR3'50 if not (25 <= len(digest) <= 35 and len(digest) % 5 == 0):
49B0_B32 = 'P67PVKU3SCCQHNIRMR2Z5NICEMIP36WCFJG4AW6YBAE6UI4K6BVLY3EI'51 raise ValueError('digest has bad length {}'.format(len(digest)))
50B1_B32 = 'ZIFO5S2OYYPZAUN6XQWTWZGCDATXCGR2JYN7UIAX54WMVWETMIUFG7WM'52 if digest in self:
51C0_B32 = 'RW2GJFIGPQF5WLR53UAK77TPHNRFKMUBYRB23JFS4G2RFRRNHW6OX4CR'53 raise ValueError('digest already in set')
52C1_B32 = 'XBVLPYBUX6QD2DKPJTYVUXT23K3AAUAW5J4RMQ543NQNDAHORQJ7GBDE'54 super().add(digest)
55 return digest
56
57
58class TestAccum(TestCase):
59 def test_add(self):
60 accum = DigestAccum()
61 self.assertEqual(accum, set())
62 self.assertEqual(len(accum), 0)
63
64 with self.assertRaises(TypeError) as cm:
65 accum.add('A' * 30)
66 self.assertEqual(str(cm.exception), 'digest must be bytes')
67
68 for size in [24, 26, 29, 31, 34, 36]:
69 bad = os.urandom(size)
70 with self.assertRaises(ValueError) as cm:
71 accum.add(bad)
72 self.assertEqual(
73 str(cm.exception),
74 'digest has bad length {}'.format(size)
75 )
76
77 d1 = os.urandom(30)
78 d2 = os.urandom(30)
79
80 self.assertIs(accum.add(d1), d1)
81 self.assertEqual(accum, set([d1]))
82 self.assertEqual(len(accum), 1)
83
84 self.assertIs(accum.add(d2), d2)
85 self.assertEqual(accum, set([d1, d2]))
86 self.assertEqual(len(accum), 2)
87
88 with self.assertRaises(ValueError) as cm:
89 accum.add(d1)
90 self.assertEqual(str(cm.exception), 'digest already in set')
91
92 self.assertEqual(accum, set([d1, d2]))
93 self.assertEqual(len(accum), 2)
5394
5495
55class TestConstants(TestCase):96class TestConstants(TestCase):
97 def test_pers(self):
98 all_pers = (
99 protocols.PERS_LEAF,
100 protocols.PERS_ROOT,
101 protocols.PERS_LEAF_INDEX,
102 protocols.PERS_FILE_SIZE,
103 )
104 for pers in all_pers:
105 self.assertIsInstance(pers, bytes)
106 self.assertEqual(pers[:36],
107 b'20110430 jderose@novacut.com dmedia/'
108 )
109 self.assertEqual(len(set(all_pers)), len(all_pers))
110
111 # Be extra safe the what's actually used in V1:
112 self.assertIsInstance(protocols.PERS_LEAF, bytes)
113 self.assertIsInstance(protocols.PERS_ROOT, bytes)
114 self.assertNotEqual(protocols.PERS_LEAF, protocols.PERS_ROOT)
115
116 def test_mib(self):
117 self.assertIsInstance(protocols.MiB, int)
118 self.assertEqual(protocols.MiB, 1024 * 1024)
119 self.assertEqual(protocols.MiB, 2**20)
120
56 def test_max_file_size(self):121 def test_max_file_size(self):
57 # For now, constrain to JavaScript integer limit (2**53):122 # For now, constrain to JavaScript integer limit (2**53):
58 self.assertEqual(protocols.MAX_FILE_SIZE, 9007199254740992)123 self.assertIsInstance(protocols.MAX_FILE_SIZE, int)
59 self.assertEqual(protocols.MAX_FILE_SIZE, 2**53)124 self.assertEqual(protocols.MAX_FILE_SIZE, 2**53)
60125
61 def test_min_leaf_size(self):126 def test_min_leaf_size(self):
62 self.assertEqual(protocols.MIN_LEAF_SIZE, 2 * MiB)127 self.assertIsInstance(protocols.MIN_LEAF_SIZE, int)
128 self.assertEqual(protocols.MIN_LEAF_SIZE, 2 * 1024 * 1024)
129 self.assertEqual(protocols.MIN_LEAF_SIZE, 2**21)
63 self.assertEqual(protocols.MIN_LEAF_SIZE,130 self.assertEqual(protocols.MIN_LEAF_SIZE,
64 protocols.MAX_FILE_SIZE // 2**32131 protocols.MAX_FILE_SIZE // 2**32
65 )132 )
66133
67134
68class TestFunctions(TestCase):135class TestFunctions(TestCase):
69 def test_b32dumps(self):136 def test_make_key(self):
70 self.assertEqual(b32dumps(b'\x00\x00\x00\x00\x00'), 'AAAAAAAA')137 with self.assertRaises(TypeError) as cm:
71 self.assertEqual(b32dumps(b'\xff\xff\xff\xff\xff'), '77777777')138 protocols.make_key(1.0)
72 self.assertEqual(b32dumps(b'\x00' * 35), 'A' * 56)139 self.assertEqual(
73 self.assertEqual(b32dumps(b'\xff' * 35), '7' * 56)140 str(cm.exception),
74 for i in range(1000):141 TYPE_ERROR.format('integer', int, float, 1.0)
75 data = os.urandom(15)142 )
76 self.assertEqual(143 with self.assertRaises(TypeError) as cm:
77 b32dumps(data),144 protocols.make_key('1')
78 b32encode(data).decode('utf-8')145 self.assertEqual(
79 )146 str(cm.exception),
80147 TYPE_ERROR.format('integer', int, str, '1')
81 def test_b32loads(self):148 )
82 self.assertEqual(b32loads('AAAAAAAA'), b'\x00\x00\x00\x00\x00')149
83 self.assertEqual(b32loads('77777777'), b'\xff\xff\xff\xff\xff')150 with self.assertRaises(ValueError) as cm:
84 self.assertEqual(b32loads('A' * 56), b'\x00' * 35)151 protocols.make_key(-1)
85 self.assertEqual(b32loads('7' * 56), b'\xff' * 35)152 self.assertEqual(
86 for i in range(1000):153 str(cm.exception),
87 data = os.urandom(15)154 'integer: must be >= 0; got -1'
88 b32 = b32encode(data)155 )
89 self.assertEqual(b32loads(b32.decode('utf-8')), data)156 with self.assertRaises(ValueError) as cm:
90 self.assertEqual(b32decode(b32), data)157 protocols.make_key(-17)
91158 self.assertEqual(
92class ProtocolTestCase(TestCase):159 str(cm.exception),
93 def check_hash_leaf(self, proto):160 'integer: must be >= 0; got -17'
94161 )
162
163 self.assertEqual(protocols.make_key(0), b'0')
164 self.assertEqual(protocols.make_key(1), b'1')
165 self.assertEqual(protocols.make_key(1776), b'1776')
166 for i in range(1000):
167 n = random.randint(0, 16 * GiB)
168 self.assertEqual(protocols.make_key(n), repr(n).encode('ascii'))
169
170
171class BaseTestCase1(TestCase):
172 def setUp(self):
173 self._checks = []
174
175 def check_hash_leaf_validation(self, proto):
95 # Test with wrong leaf_index type:176 # Test with wrong leaf_index type:
96 with self.assertRaises(TypeError) as cm:177 with self.assertRaises(TypeError) as cm:
97 proto.hash_leaf(0.0, None)178 proto.hash_leaf(0.0, None)
@@ -165,63 +246,9 @@
165 )246 )
166 )247 )
167248
168 # Smallest possible leaf, index=0249 self._checks.append('hash_leaf_validation')
169 accum = set()250
170 leaf_data = b'D'251 def check_hash_root_validation(self, proto):
171 digest = proto.hash_leaf(0, leaf_data)
172 accum.add(digest)
173 self.assertEqual(proto._hash_leaf(0, leaf_data, b''), digest)
174
175 # Smallest possible leaf, index=1
176 digest = proto.hash_leaf(1, leaf_data)
177 self.assertNotIn(digest, accum)
178 accum.add(digest)
179 self.assertEqual(proto._hash_leaf(1, leaf_data, b''), digest)
180
181 # Largest possible leaf, index=0
182 leaf_data = b'D' * proto.leaf_size
183 digest = proto.hash_leaf(0, leaf_data)
184 self.assertNotIn(digest, accum)
185 accum.add(digest)
186 self.assertEqual(proto._hash_leaf(0, leaf_data, b''), digest)
187
188 # Largest possible leaf, index=1
189 digest = proto.hash_leaf(1, leaf_data)
190 self.assertNotIn(digest, accum)
191 accum.add(digest)
192 self.assertEqual(proto._hash_leaf(1, leaf_data, b''), digest)
193
194 # Test with challenge, smallest leaf, index=0:
195 challenge = os.urandom(16)
196 leaf_data = b'D'
197 digest = proto.hash_leaf(0, leaf_data, challenge)
198 self.assertNotIn(digest, accum)
199 accum.add(digest)
200 self.assertEqual(proto._hash_leaf(0, leaf_data, challenge), digest)
201
202 # Test with challenge, smallest leaf, index=1:
203 digest = proto.hash_leaf(1, leaf_data, challenge)
204 self.assertNotIn(digest, accum)
205 accum.add(digest)
206 self.assertEqual(proto._hash_leaf(1, leaf_data, challenge), digest)
207
208 # Test with challenge, largest leaf, index=0:
209 leaf_data = b'D' * proto.leaf_size
210 digest = proto.hash_leaf(0, leaf_data, challenge)
211 self.assertNotIn(digest, accum)
212 accum.add(digest)
213 self.assertEqual(proto._hash_leaf(0, leaf_data, challenge), digest)
214
215 # Test with challenge, largest leaf, index=1:
216 digest = proto.hash_leaf(1, leaf_data, challenge)
217 self.assertNotIn(digest, accum)
218 accum.add(digest)
219 self.assertEqual(proto._hash_leaf(1, leaf_data, challenge), digest)
220
221 # Make sure we didn't goof
222 self.assertEqual(len(accum), 8)
223
224 def check_hash_root(self, proto):
225 # Test with wrong file_size type:252 # Test with wrong file_size type:
226 with self.assertRaises(TypeError) as cm:253 with self.assertRaises(TypeError) as cm:
227 proto.hash_root(1.0, None)254 proto.hash_root(1.0, None)
@@ -321,99 +348,10 @@
321 )348 )
322 )349 )
323350
324 # Test with good file_size and leaf_hashes351 self._checks.append('hash_root_validation')
325 accum = set()352
326 leaf_hashes = b'D' * proto.digest_bytes353
327 _id = proto.hash_root(1, leaf_hashes)354class TestProtocol(BaseTestCase1):
328 self.assertNotIn(_id, accum)
329 accum.add(_id)
330 self.assertEqual(
331 proto._hash_root(1, leaf_hashes),
332 b32decode(_id.encode('utf-8'))
333 )
334
335 _id = proto.hash_root(2, leaf_hashes)
336 self.assertNotIn(_id, accum)
337 accum.add(_id)
338 self.assertEqual(
339 proto._hash_root(2, leaf_hashes),
340 b32decode(_id.encode('utf-8'))
341 )
342
343 leaf_hashes = b'AB' * proto.digest_bytes
344 _id = proto.hash_root(proto.leaf_size + 1, leaf_hashes)
345 self.assertNotIn(_id, accum)
346 accum.add(_id)
347 self.assertEqual(
348 proto._hash_root(proto.leaf_size + 1, leaf_hashes),
349 b32decode(_id.encode('utf-8'))
350 )
351
352 _id = proto.hash_root(proto.leaf_size + 2, leaf_hashes)
353 self.assertNotIn(_id, accum)
354 accum.add(_id)
355 self.assertEqual(
356 proto._hash_root(proto.leaf_size + 2, leaf_hashes),
357 b32decode(_id.encode('utf-8'))
358 )
359
360 # Make sure we didn't goof
361 self.assertEqual(len(accum), 4)
362
363 def sanity_check_hash_leaf(self, proto):
364 """
365 Random value sanity check on Protocol._hash_leaf().
366 """
367
368 # Sanity check on our crytographic claim that the
369 # leaf-hash is tied to the leaf-index:
370 leaf_data = os.urandom(16)
371 accum = set(
372 proto._hash_leaf(i, leaf_data, b'')
373 for i in range(COUNT)
374 )
375 self.assertEqual(len(accum), COUNT)
376
377 # Sanity check on our crytographic claim that the
378 # leaf-hash is tied to the leaf-data:
379 accum = set(
380 proto._hash_leaf(21, os.urandom(16), b'')
381 for i in range(COUNT)
382 )
383 self.assertEqual(len(accum), COUNT)
384
385 # Sanity check on our crytographic claim that the
386 # leaf-hash is tied to the challenge:
387 accum = set(
388 proto._hash_leaf(21, leaf_data, os.urandom(16))
389 for i in range(COUNT)
390 )
391 self.assertEqual(len(accum), COUNT)
392
393 def sanity_check_hash_root(self, proto):
394 """
395 Random value sanity check on Protocol._hash_root().
396 """
397
398 # Sanity check on our crytographic claim that the
399 # root-hash is tied to the file-size:
400 leaf_hashes = os.urandom(proto.digest_bytes)
401 accum = set(
402 proto._hash_root(size, leaf_hashes)
403 for size in range(1, COUNT + 1)
404 )
405 self.assertEqual(len(accum), COUNT)
406
407 # Sanity check on our crytographic claim that the
408 # root-hash is tied to the leaf-hashes:
409 accum = set(
410 proto._hash_root(314159, os.urandom(proto.digest_bytes))
411 for i in range(COUNT)
412 )
413 self.assertEqual(len(accum), COUNT)
414
415
416class TestProtocol(ProtocolTestCase):
417 def test_init(self):355 def test_init(self):
418 # Wrong `leaf_size` type:356 # Wrong `leaf_size` type:
419 with self.assertRaises(TypeError) as cm:357 with self.assertRaises(TypeError) as cm:
@@ -474,7 +412,7 @@
474 self.assertEqual(proto.digest_bytes, 25)412 self.assertEqual(proto.digest_bytes, 25)
475 self.assertEqual(proto.digest_b32len, 40)413 self.assertEqual(proto.digest_b32len, 40)
476414
477 # Test with the actual v0 protocol values:415 # Test with the actual v1 protocol values:
478 proto = protocols.Protocol(8 * MiB, 240)416 proto = protocols.Protocol(8 * MiB, 240)
479 self.assertEqual(proto.leaf_size, 8 * MiB)417 self.assertEqual(proto.leaf_size, 8 * MiB)
480 self.assertEqual(proto.max_leaf_count, 1073741824)418 self.assertEqual(proto.max_leaf_count, 1073741824)
@@ -483,928 +421,891 @@
483 self.assertEqual(proto.digest_bytes, 30)421 self.assertEqual(proto.digest_bytes, 30)
484 self.assertEqual(proto.digest_b32len, 48)422 self.assertEqual(proto.digest_b32len, 48)
485423
486 # Test with the actual v1 protocol values:424 # Test with possible future values:
487 proto = protocols.Protocol(8 * MiB, 280)425 proto = protocols.Protocol(16 * MiB, 280)
488 self.assertEqual(proto.leaf_size, 8 * MiB)426 self.assertEqual(proto.leaf_size, 16 * MiB)
489 self.assertEqual(proto.max_leaf_count, 1073741824)427 self.assertEqual(proto.max_leaf_count, 536870912)
490 self.assertEqual(proto.max_leaf_count, 2**30)428 self.assertEqual(proto.max_leaf_count, 2**29)
491 self.assertEqual(proto.digest_bits, 280)429 self.assertEqual(proto.digest_bits, 280)
492 self.assertEqual(proto.digest_bytes, 35)430 self.assertEqual(proto.digest_bytes, 35)
493 self.assertEqual(proto.digest_b32len, 56)431 self.assertEqual(proto.digest_b32len, 56)
494432
495 def test_encode(self):433 def test_hash_leaf(self):
496 proto = protocols.Protocol(TwoMiB, 200)434 proto = protocols.Protocol(TwoMiB, 200)
497435 self.check_hash_leaf_validation(proto)
498 # Wrong type:436 with self.assertRaises(NotImplementedError) as cm:
499 digest = 'A' * 25437 proto.hash_leaf(0, b'data')
500 with self.assertRaises(TypeError) as cm:438 self.assertEqual(
501 proto.encode(digest)439 str(cm.exception),
502 self.assertEqual(440 'Protocol._hash_leaf(key_leaf_index, leaf_data, challenge)'
503 str(cm.exception),441 )
504 TYPE_ERROR.format('digest', bytes, str, digest)442 with self.assertRaises(NotImplementedError) as cm:
505 )443 proto.hash_leaf(0, b'data', b'secret')
506444 self.assertEqual(
507 # Wrong length:445 str(cm.exception),
508 digest = b'A' * 30446 'Protocol._hash_leaf(key_leaf_index, leaf_data, challenge)'
509 with self.assertRaises(ValueError) as cm:447 )
510 proto.encode(digest)448 with self.assertRaises(NotImplementedError) as cm:
511 self.assertEqual(str(cm.exception), 'len(digest) must be 25; got 30')449 proto._hash_leaf(0, b'data', b'secret')
512450 self.assertEqual(
513 digest = os.urandom(25)451 str(cm.exception),
514 self.assertEqual(proto.encode(digest), b32dumps(digest))452 'Protocol._hash_leaf(key_leaf_index, leaf_data, challenge)'
515453 )
516 def test_decode(self):454
517 proto = protocols.Protocol(TwoMiB, 200)455 class Example(protocols.Protocol):
518456 pass
519 # Wrong type:457
520 _id = b'A' * 40458 proto = Example(TwoMiB, 200)
521 with self.assertRaises(TypeError) as cm:459 self.check_hash_leaf_validation(proto)
522 proto.decode(_id)460 with self.assertRaises(NotImplementedError) as cm:
523 self.assertEqual(461 proto.hash_leaf(0, b'data')
524 str(cm.exception),462 self.assertEqual(
525 TYPE_ERROR.format('_id', str, bytes, _id)463 str(cm.exception),
526 )464 'Example._hash_leaf(key_leaf_index, leaf_data, challenge)'
527465 )
528 # Wrong length:466 with self.assertRaises(NotImplementedError) as cm:
529 _id = 'A' * 48467 proto.hash_leaf(0, b'data', b'secret')
530 with self.assertRaises(ValueError) as cm:468 self.assertEqual(
531 proto.decode(_id)469 str(cm.exception),
532 self.assertEqual(str(cm.exception), 'len(_id) must be 40; got 48')470 'Example._hash_leaf(key_leaf_index, leaf_data, challenge)'
533471 )
534 digest = os.urandom(25)472 with self.assertRaises(NotImplementedError) as cm:
535 _id = b32dumps(digest)473 proto._hash_leaf(0, b'data', b'secret')
536 self.assertEqual(proto.decode(_id), digest)474 self.assertEqual(
537475 str(cm.exception),
538 # Test round-trip:476 'Example._hash_leaf(key_leaf_index, leaf_data, challenge)'
539 for i in range(100):477 )
540 digest = os.urandom(25)478
541 self.assertEqual(proto.decode(proto.encode(digest)), digest)479 class FooProtocol(protocols.Protocol):
542480 def _hash_leaf(self, *args):
543 def test_hash_leaf(self):481 assert not hasattr(self, '_call')
544 proto = protocols.Protocol(TwoMiB, 200)482 assert not hasattr(self, '_ret')
483 self._call = args
484 self._ret = os.urandom(self.digest_bytes)
485 return self._ret
486
487 foo = FooProtocol(TwoMiB, 200)
488 self.check_hash_leaf_validation(foo)
489 digest = foo.hash_leaf(0, b'data')
490 self.assertEqual(foo._call, (b'0', b'data', b''))
491 self.assertIs(digest, foo._ret)
492
493 foo = FooProtocol(TwoMiB, 200)
494 digest = foo.hash_leaf(17, b'more data', b'secret')
495 self.assertEqual(foo._call, (b'17', b'more data', b'secret'))
496 self.assertIs(digest, foo._ret)
497
498 for i in range(100):
499 index = random.randint(0, MiB)
500 length = random.randint(1, TwoMiB)
501 data = os.urandom(1) * length
502
503 foo = FooProtocol(TwoMiB, 200)
504 digest = foo.hash_leaf(index, data)
505 self.assertEqual(foo._call,
506 (str(index).encode('utf-8'), data, b'')
507 )
508 self.assertIs(digest, foo._ret)
509
510 nonce = os.urandom(16)
511 foo = FooProtocol(TwoMiB, 200)
512 digest = foo.hash_leaf(index, data, nonce)
513 self.assertEqual(foo._call,
514 (str(index).encode('utf-8'), data, nonce)
515 )
516 self.assertIs(digest, foo._ret)
517
518 def test_hash_root(self):
519 proto = protocols.Protocol(TwoMiB, 200)
520 self.check_hash_root_validation(proto)
521 with self.assertRaises(NotImplementedError) as cm:
522 proto.hash_root(1, b'A' * 25)
523 self.assertEqual(
524 str(cm.exception),
525 'Protocol._hash_root(key_file_size, leaf_hashes)'
526 )
527 with self.assertRaises(NotImplementedError) as cm:
528 proto._hash_root(1, b'A' * 25)
529 self.assertEqual(
530 str(cm.exception),
531 'Protocol._hash_root(key_file_size, leaf_hashes)'
532 )
533
534 class Example(protocols.Protocol):
535 pass
536
537 proto = Example(TwoMiB, 200)
538 self.check_hash_root_validation(proto)
539 with self.assertRaises(NotImplementedError) as cm:
540 proto.hash_root(1, b'A' * 25)
541 self.assertEqual(
542 str(cm.exception),
543 'Example._hash_root(key_file_size, leaf_hashes)'
544 )
545 with self.assertRaises(NotImplementedError) as cm:
546 proto._hash_root(1, b'A' * 25)
547 self.assertEqual(
548 str(cm.exception),
549 'Example._hash_root(key_file_size, leaf_hashes)'
550 )
551
552 class FooProtocol(protocols.Protocol):
553 def _hash_root(self, *args):
554 assert not hasattr(self, '_call')
555 assert not hasattr(self, '_ret')
556 self._call = args
557 self._ret = os.urandom(self.digest_bytes)
558 return self._ret
559
560 foo = FooProtocol(TwoMiB, 200)
561 self.check_hash_root_validation(foo)
562 digest = foo.hash_root(1, b'A' * 25)
563 self.assertEqual(foo._call, (b'1', b'A' * 25))
564 self.assertIs(digest, foo._ret)
565
566 for i in range(100):
567 size = random.randint(1, GiB)
568 count = size // TwoMiB
569 if size % TwoMiB:
570 count += 1
571 data = os.urandom(25) * count
572
573 foo = FooProtocol(TwoMiB, 200)
574 digest = foo.hash_root(size, data)
575 self.assertEqual(foo._call,
576 (str(size).encode('utf-8'), data)
577 )
578 self.assertIs(digest, foo._ret)
579
580
581class BaseTestCase2(BaseTestCase1):
582 def check_hash_leaf_simple(self, proto):
583 accum = DigestAccum()
584 small = b'D' # Smallest possible leaf
585 large = b'D' * proto.leaf_size # Largest possible leaf
586
587 # Smallest leaf, index=0
588 digest = accum.add(proto.hash_leaf(0, small))
589 self.assertEqual(proto._hash_leaf(b'0', small, b''), digest)
590
591 # Smallest leaf, index=1
592 digest = accum.add(proto.hash_leaf(1, small))
593 self.assertEqual(proto._hash_leaf(b'1', small, b''), digest)
594
595 # Largest leaf, index=0
596 digest = accum.add(proto.hash_leaf(0, large))
597 self.assertEqual(proto._hash_leaf(b'0', large, b''), digest)
598
599 # Largest leaf, index=1
600 digest = accum.add(proto.hash_leaf(1, large))
601 self.assertEqual(proto._hash_leaf(b'1', large, b''), digest)
602
603 # Test with challenge, smallest leaf, index=0:
604 digest = accum.add(proto.hash_leaf(0, small, b'secret'))
605 self.assertEqual(proto._hash_leaf(b'0', small, b'secret'), digest)
606
607 # Test with challenge, smallest leaf, index=1:
608 digest = accum.add(proto.hash_leaf(1, small, b'secret'))
609 self.assertEqual(proto._hash_leaf(b'1', small, b'secret'), digest)
610
611 # Test with challenge, largest leaf, index=0:
612 digest = accum.add(proto.hash_leaf(0, large, b'secret'))
613 self.assertEqual(proto._hash_leaf(b'0', large, b'secret'), digest)
614
615 # Test with challenge, largest leaf, index=1:
616 digest = accum.add(proto.hash_leaf(1, large, b'secret'))
617 self.assertEqual(proto._hash_leaf(b'1', large, b'secret'), digest)
618
619 # Make sure we didn't goof
620 self.assertEqual(len(accum), 8)
621 for digest in accum:
622 self.assertEqual(len(digest), proto.digest_bytes)
623 safety = set()
624 for index in [0, 1]:
625 for data in [small, large]:
626 for challenge in [b'', b'secret']:
627 safety.add(proto.hash_leaf(index, data, challenge))
628 self.assertEqual(safety, accum)
629
630 self._checks.append('hash_leaf_simple')
631
632 def check_hash_root_simple(self, proto):
633 accum = DigestAccum()
634 one = b'D' * proto.digest_bytes
635 two = b'AB' * proto.digest_bytes
636
637 digest = accum.add(proto.hash_root(1, one))
638 self.assertEqual(proto._hash_root(b'1', one), digest)
639
640 digest = accum.add(proto.hash_root(2, one))
641 self.assertEqual(proto._hash_root(b'2', one), digest)
642
643 size = proto.leaf_size + 1
644 digest = accum.add(proto.hash_root(size, two))
645 self.assertEqual(
646 proto._hash_root(str(size).encode('utf-8'), two),
647 digest
648 )
649
650 size = proto.leaf_size + 2
651 digest = accum.add(proto.hash_root(size, two))
652 self.assertEqual(
653 proto._hash_root(str(size).encode('utf-8'), two),
654 digest
655 )
656
657 size = proto.leaf_size * 2
658 digest = accum.add(proto.hash_root(size, two))
659 self.assertEqual(
660 proto._hash_root(str(size).encode('utf-8'), two),
661 digest
662 )
663
664 # Make sure we didn't goof
665 self.assertEqual(len(accum), 5)
666 for digest in accum:
667 self.assertEqual(len(digest), proto.digest_bytes)
668 safety = set([
669 proto.hash_root(1, one),
670 proto.hash_root(2, one),
671 proto.hash_root(proto.leaf_size + 1, two),
672 proto.hash_root(proto.leaf_size + 2, two),
673 proto.hash_root(proto.leaf_size * 2, two),
674 ])
675 self.assertEqual(safety, accum)
676
677 self._checks.append('hash_root_simple')
678
679 def check_hash_leaf_crypto(self, proto):
680 """
681 Random value sanity check on Protocol.hash_leaf().
682 """
683
684 # Sanity check on our cryptographic claim that the
685 # leaf-hash is tied to the leaf-index:
686 leaf_data = os.urandom(16)
687 accum = set(
688 proto.hash_leaf(i, leaf_data, b'')
689 for i in range(COUNT)
690 )
691 self.assertEqual(len(accum), COUNT)
692
693 # Sanity check on our cryptographic claim that the
694 # leaf-hash is tied to the leaf-data:
695 accum = set(
696 proto.hash_leaf(21, os.urandom(16), b'')
697 for i in range(COUNT)
698 )
699 self.assertEqual(len(accum), COUNT)
700
701 # Sanity check on our cryptographic claim that the
702 # leaf-hash is tied to the challenge:
703 accum = set(
704 proto.hash_leaf(21, leaf_data, os.urandom(16))
705 for i in range(COUNT)
706 )
707 self.assertEqual(len(accum), COUNT)
708
709 self._checks.append('hash_leaf_crypto')
710
711 def check_hash_root_crypto(self, proto):
712 """
713 Random value sanity check on Protocol.hash_root().
714 """
715
716 # Sanity check on our cryptographic claim that the
717 # root-hash is tied to the file-size:
718 leaf_hashes = os.urandom(proto.digest_bytes)
719 accum = set(
720 proto.hash_root(size, leaf_hashes)
721 for size in range(1, COUNT + 1)
722 )
723 self.assertEqual(len(accum), COUNT)
724
725 # Sanity check on our cryptographic claim that the
726 # root-hash is tied to the leaf-hashes:
727 accum = set(
728 proto.hash_root(314159, os.urandom(proto.digest_bytes))
729 for i in range(COUNT)
730 )
731 self.assertEqual(len(accum), COUNT)
732
733 self._checks.append('hash_root_crypto')
734
735 def check_hash_leaf(self, proto):
736 self.check_hash_leaf_validation(proto)
737 self.check_hash_leaf_simple(proto)
738 self.check_hash_leaf_crypto(proto)
739
740 def check_hash_root(self, proto):
741 self.check_hash_root_validation(proto)
742 self.check_hash_root_simple(proto)
743 self.check_hash_root_crypto(proto)
744
745
746class TestSkeinProtocol(BaseTestCase2):
747 def test_hash_leaf(self):
748 proto = protocols.SkeinProtocol(TwoMiB, 200)
545 self.check_hash_leaf(proto)749 self.check_hash_leaf(proto)
546 self.sanity_check_hash_leaf(proto)750 self.assertEqual(self._checks, [
547751 'hash_leaf_validation',
548 # Smallest possible leaf, index=0752 'hash_leaf_simple',
549 accum = set()753 'hash_leaf_crypto',
550 leaf_data = b'D'754 ])
551 digest = proto.hash_leaf(0, leaf_data)755
552 accum.add(digest)756 accum = DigestAccum()
553 self.assertEqual(757 small = b'A' # Smallest possible leaf
554 digest,758 large = b'B' * proto.leaf_size # Largest possible leaf
555 skein512(leaf_data,759
556 digest_bits=200,760 # Smallest leaf, index=0
557 pers=protocols.PERS_LEAF,761 digest = accum.add(proto.hash_leaf(0, small))
558 key=b'0',762 self.assertEqual(digest,
559 ).digest()763 skein512(small,
560 )764 digest_bits=200,
561 self.assertEqual(proto._hash_leaf(0, leaf_data, b''), digest)765 pers=protocols.PERS_LEAF,
562766 key=b'0',
563 # Smallest possible leaf, index=1767 ).digest()
564 digest = proto.hash_leaf(1, leaf_data)768 )
565 self.assertNotIn(digest, accum)769 self.assertEqual(proto._hash_leaf(b'0', small, b''), digest)
566 accum.add(digest)770
567 self.assertEqual(771 # Smallest leaf, index=1
568 digest,772 digest = accum.add(proto.hash_leaf(1, small))
569 skein512(leaf_data,773 self.assertEqual(digest,
570 digest_bits=200,774 skein512(small,
571 pers=protocols.PERS_LEAF,775 digest_bits=200,
572 key=b'1',776 pers=protocols.PERS_LEAF,
573 ).digest()777 key=b'1',
574 )778 ).digest()
575 self.assertEqual(proto._hash_leaf(1, leaf_data, b''), digest)779 )
576780 self.assertEqual(proto._hash_leaf(b'1', small, b''), digest)
577 # Largest possible leaf, index=0781
578 leaf_data = b'D' * TwoMiB782 # Largest leaf, index=0
579 digest = proto.hash_leaf(0, leaf_data)783 digest = accum.add(proto.hash_leaf(0, large))
580 self.assertNotIn(digest, accum)784 self.assertEqual(digest,
581 accum.add(digest)785 skein512(large,
582 self.assertEqual(786 digest_bits=200,
583 digest,787 pers=protocols.PERS_LEAF,
584 skein512(leaf_data,788 key=b'0',
585 digest_bits=200,789 ).digest()
586 pers=protocols.PERS_LEAF,790 )
587 key=b'0',791 self.assertEqual(proto._hash_leaf(b'0', large, b''), digest)
588 ).digest()792
589 )793 # Largest leaf, index=1
590 self.assertEqual(proto._hash_leaf(0, leaf_data, b''), digest)794 digest = accum.add(proto.hash_leaf(1, large))
591795 self.assertEqual(digest,
592 # Largest possible leaf, index=1796 skein512(large,
593 digest = proto.hash_leaf(1, leaf_data)797 digest_bits=200,
594 self.assertNotIn(digest, accum)798 pers=protocols.PERS_LEAF,
595 accum.add(digest)799 key=b'1',
596 self.assertEqual(800 ).digest()
597 digest,801 )
598 skein512(leaf_data,802 self.assertEqual(proto._hash_leaf(b'1', large, b''), digest)
599 digest_bits=200,
600 pers=protocols.PERS_LEAF,
601 key=b'1',
602 ).digest()
603 )
604 self.assertEqual(proto._hash_leaf(1, leaf_data, b''), digest)
605803
606 # Test with challenge, smallest leaf, index=0:804 # Test with challenge, smallest leaf, index=0:
607 challenge = os.urandom(16)805 digest = accum.add(proto.hash_leaf(0, small, b'secret'))
608 leaf_data = b'D'806 self.assertEqual(digest,
609 digest = proto.hash_leaf(0, leaf_data, challenge)807 skein512(small,
610 self.assertNotIn(digest, accum)
611 accum.add(digest)
612 self.assertEqual(
613 digest,
614 skein512(leaf_data,
615 digest_bits=200,808 digest_bits=200,
616 pers=protocols.PERS_LEAF,809 pers=protocols.PERS_LEAF,
617 key=b'0',810 key=b'0',
618 nonce=challenge,811 nonce=b'secret',
619 ).digest()812 ).digest()
620 )813 )
621 self.assertEqual(proto._hash_leaf(0, leaf_data, challenge), digest)814 self.assertEqual(proto._hash_leaf(b'0', small, b'secret'), digest)
622815
623 # Test with challenge, smallest leaf, index=1:816 # Test with challenge, smallest leaf, index=1:
624 digest = proto.hash_leaf(1, leaf_data, challenge)817 digest = accum.add(proto.hash_leaf(1, small, b'secret'))
625 self.assertNotIn(digest, accum)818 self.assertEqual(digest,
626 accum.add(digest)819 skein512(small,
627 self.assertEqual(
628 digest,
629 skein512(leaf_data,
630 digest_bits=200,820 digest_bits=200,
631 pers=protocols.PERS_LEAF,821 pers=protocols.PERS_LEAF,
632 key=b'1',822 key=b'1',
633 nonce=challenge,823 nonce=b'secret',
634 ).digest()824 ).digest()
635 )825 )
636 self.assertEqual(proto._hash_leaf(1, leaf_data, challenge), digest)826 self.assertEqual(proto._hash_leaf(b'1', small, b'secret'), digest)
637827
638 # Test with challenge, largest leaf, index=0:828 # Test with challenge, largest leaf, index=0:
639 leaf_data = b'D' * TwoMiB829 digest = accum.add(proto.hash_leaf(0, large, b'secret'))
640 digest = proto.hash_leaf(0, leaf_data, challenge)830 self.assertEqual(digest,
641 self.assertNotIn(digest, accum)831 skein512(large,
642 accum.add(digest)
643 self.assertEqual(
644 digest,
645 skein512(leaf_data,
646 digest_bits=200,832 digest_bits=200,
647 pers=protocols.PERS_LEAF,833 pers=protocols.PERS_LEAF,
648 key=b'0',834 key=b'0',
649 nonce=challenge,835 nonce=b'secret',
650 ).digest()836 ).digest()
651 )837 )
652 self.assertEqual(proto._hash_leaf(0, leaf_data, challenge), digest)838 self.assertEqual(proto._hash_leaf(b'0', large, b'secret'), digest)
653839
654 # Test with challenge, largest leaf, index=1:840 # Test with challenge, largest leaf, index=1:
655 digest = proto.hash_leaf(1, leaf_data, challenge)841 digest = accum.add(proto.hash_leaf(1, large, b'secret'))
656 self.assertNotIn(digest, accum)842 self.assertEqual(digest,
657 accum.add(digest)843 skein512(large,
658 self.assertEqual(
659 digest,
660 skein512(leaf_data,
661 digest_bits=200,844 digest_bits=200,
662 pers=protocols.PERS_LEAF,845 pers=protocols.PERS_LEAF,
663 key=b'1',846 key=b'1',
664 nonce=challenge,847 nonce=b'secret',
665 ).digest()848 ).digest()
666 )849 )
667 self.assertEqual(proto._hash_leaf(1, leaf_data, challenge), digest)850 self.assertEqual(proto._hash_leaf(b'1', large, b'secret'), digest)
668851
669 # Make sure we didn't goof852 # Make sure we didn't goof
853 safety = set()
854 for index in [0, 1]:
855 for data in [small, large]:
856 for challenge in [b'', b'secret']:
857 safety.add(proto.hash_leaf(index, data, challenge))
858 self.assertEqual(safety, accum)
670 self.assertEqual(len(accum), 8)859 self.assertEqual(len(accum), 8)
860 for digest in accum:
861 self.assertEqual(len(digest), 25)
671862
672 def test_hash_root(self):863 def test_hash_root(self):
673 proto = protocols.Protocol(TwoMiB, 200)864 proto = protocols.SkeinProtocol(TwoMiB, 200)
674 self.check_hash_root(proto)865 self.check_hash_root(proto)
675 self.sanity_check_hash_root(proto)866 self.assertEqual(self._checks, [
867 'hash_root_validation',
868 'hash_root_simple',
869 'hash_root_crypto',
870 ])
676871
677 accum = set()872 accum = DigestAccum()
873 one = b'D' * 25
874 two = b'D' * 50
678875
679 # Smallest file-size possible for one leaf:876 # Smallest file-size possible for one leaf:
680 leaf_hashes = b'D' * 25877 digest = accum.add(proto.hash_root(1, one))
681 digest = proto._hash_root(1, leaf_hashes)878 self.assertEqual(digest,
682 self.assertNotIn(digest, accum)879 skein512(one,
683 accum.add(digest)
684 self.assertEqual(
685 digest,
686 skein512(leaf_hashes,
687 digest_bits=200,880 digest_bits=200,
688 pers=protocols.PERS_ROOT,881 pers=protocols.PERS_ROOT,
689 key=b'1',882 key=b'1',
690 ).digest()883 ).digest()
691 )884 )
692 self.assertEqual(885 self.assertEqual(proto._hash_root(b'1', one), digest)
693 proto.hash_root(1, leaf_hashes),
694 b32encode(digest).decode('utf-8')
695 )
696886
697 # Largest file-size possible for one leaf:887 # Largest file-size possible for one leaf:
698 digest = proto._hash_root(TwoMiB, leaf_hashes)888 digest = accum.add(proto.hash_root(TwoMiB, one))
699 self.assertNotIn(digest, accum)889 self.assertEqual(digest,
700 accum.add(digest)890 skein512(one,
701 self.assertEqual(
702 digest,
703 skein512(leaf_hashes,
704 digest_bits=200,891 digest_bits=200,
705 pers=protocols.PERS_ROOT,892 pers=protocols.PERS_ROOT,
706 key=b'2097152',893 key=b'2097152',
707 ).digest()894 ).digest()
708 )895 )
709 self.assertEqual(896 self.assertEqual(proto._hash_root(b'2097152', one), digest)
710 proto.hash_root(TwoMiB, leaf_hashes),
711 b32encode(digest).decode('utf-8')
712 )
713897
714 # Smallest file-size possible for two leaves:898 # Smallest file-size possible for two leaves:
715 leaf_hashes = b'D' * 50899 digest = accum.add(proto.hash_root(TwoMiB + 1, two))
716 digest = proto._hash_root(TwoMiB + 1, leaf_hashes)900 self.assertEqual(digest,
717 self.assertNotIn(digest, accum)901 skein512(two,
718 accum.add(digest)
719 self.assertEqual(
720 digest,
721 skein512(leaf_hashes,
722 digest_bits=200,902 digest_bits=200,
723 pers=protocols.PERS_ROOT,903 pers=protocols.PERS_ROOT,
724 key=b'2097153',904 key=b'2097153',
725 ).digest()905 ).digest()
726 )906 )
727 self.assertEqual(907 self.assertEqual(proto._hash_root(b'2097153', two), digest)
728 proto.hash_root(TwoMiB + 1, leaf_hashes),
729 b32encode(digest).decode('utf-8')
730 )
731908
732 # Largest file-size possible for two leaves:909 # Largest file-size possible for two leaves:
733 digest = proto._hash_root(2 * TwoMiB, leaf_hashes)910 digest = accum.add(proto.hash_root(2 * TwoMiB, two))
734 self.assertNotIn(digest, accum)911 self.assertEqual(digest,
735 accum.add(digest)912 skein512(two,
736 self.assertEqual(
737 digest,
738 skein512(leaf_hashes,
739 digest_bits=200,913 digest_bits=200,
740 pers=protocols.PERS_ROOT,914 pers=protocols.PERS_ROOT,
741 key=b'4194304',915 key=b'4194304',
742 ).digest()916 ).digest()
743 )917 )
744 self.assertEqual(918 self.assertEqual(proto._hash_root(b'4194304', two), digest)
745 proto.hash_root(2 * TwoMiB, leaf_hashes),
746 b32encode(digest).decode('utf-8')
747 )
748919
749 # Make sure we didn't goof920 # Make sure we didn't goof
750 self.assertEqual(len(accum), 4)921 self.assertEqual(len(accum), 4)
751922 safety = set([
752923 proto.hash_root(1, one),
753class TestVersionOne(ProtocolTestCase):924 proto.hash_root(TwoMiB, one),
754925 proto.hash_root(TwoMiB + 1, two),
755 def test_md5(self):926 proto.hash_root(TwoMiB * 2, two),
756 # To aid debugging, md5sums of UTF-8 strings needed by test vectors927 ])
757 self.assertEqual(928 self.assertEqual(safety, accum)
758 md5(protocols.PERS_LEAF).hexdigest(),929 for digest in accum:
759 '8aee35e23a5a74147b230f12123ca82e'930 self.assertEqual(len(digest), 25)
760 )931
761 self.assertEqual(932
762 md5(protocols.PERS_ROOT).hexdigest(),933class TestOldSkeinProtocol(BaseTestCase2):
763 '0445d91d37383f5384023d49e71cc629'
764 )
765 self.assertEqual(
766 md5(str(0).encode('utf-8')).hexdigest(),
767 'cfcd208495d565ef66e7dff9f98764da'
768 )
769 self.assertEqual(
770 md5(str(1).encode('utf-8')).hexdigest(),
771 'c4ca4238a0b923820dcc509a6f75849b'
772 )
773 self.assertEqual(
774 md5(str(8388607).encode('utf-8')).hexdigest(),
775 '32433904a755e2b9eb82cf167723b34f'
776 )
777 self.assertEqual(
778 md5(str(8388608).encode('utf-8')).hexdigest(),
779 '03926fda4e223707d290ac06bb996653'
780 )
781 self.assertEqual(
782 md5(str(8388609).encode('utf-8')).hexdigest(),
783 'e9b74719ce6b80c5337148d12725db03'
784 )
785 self.assertEqual(
786 md5(str(16777215).encode('utf-8')).hexdigest(),
787 '48ac8929ffdc78a66090d179ff1237d5'
788 )
789 self.assertEqual(
790 md5(str(16777216).encode('utf-8')).hexdigest(),
791 'e3e330499348f791337e9da6b534a386'
792 )
793
794 # To aid debugging, md5sums of the test vector files:
795 self.assertEqual(
796 md5(A).hexdigest(),
797 '7fc56270e7a70fa81a5935b72eacbe29'
798 )
799 self.assertEqual(
800 md5(B).hexdigest(),
801 'd2bad3eedb424dd352d65eafbf6c79ba'
802 )
803 self.assertEqual(
804 md5(C).hexdigest(),
805 '5dd3531303dd6764acb93e5f171a4ab8'
806 )
807 self.assertEqual(
808 md5(C + A).hexdigest(),
809 '0722f8dc36d75acb602dcee8d0427ce0'
810 )
811 self.assertEqual(
812 md5(C + B).hexdigest(),
813 '77264eb6eed7777a1ee03e2601fc9f64'
814 )
815 self.assertEqual(
816 md5(C + C).hexdigest(),
817 '1fbfabdaafff31967f9a95f3a3d3c642'
818 )
819
820 def test_hash_leaf(self):934 def test_hash_leaf(self):
821 proto = protocols.VERSION1935 proto = protocols.OldSkeinProtocol(TwoMiB, 200)
822
823 self.check_hash_leaf(proto)936 self.check_hash_leaf(proto)
824 self.sanity_check_hash_leaf(proto)937 self.assertEqual(self._checks, [
825938 'hash_leaf_validation',
826 # A0939 'hash_leaf_simple',
827 digest = proto._hash_leaf(0, A, b'')940 'hash_leaf_crypto',
828 self.assertEqual(digest, b32loads(A0_B32))941 ])
829 self.assertEqual(942
830 digest,943 accum = DigestAccum()
831 skein512(A,944 small = b'A' # Smallest possible leaf
832 digest_bits=280,945 large = b'B' * proto.leaf_size # Largest possible leaf
833 pers=protocols.PERS_LEAF,946 key0 = proto._hash_leaf_index(b'0')
834 key=b'0',947 key1 = proto._hash_leaf_index(b'1')
835 ).digest()948
836 )949 # Smallest leaf, index=0
837 self.assertEqual(proto.hash_leaf(0, A), digest)950 digest = accum.add(proto.hash_leaf(0, small))
838951 self.assertEqual(digest,
839 # A1952 skein512(key0 + small,
840 digest = proto._hash_leaf(1, A, b'')953 digest_bits=200,
841 self.assertEqual(digest, b32loads(A1_B32))954 pers=protocols.PERS_LEAF,
842 self.assertEqual(955 ).digest()
843 digest,956 )
844 skein512(A,957 self.assertEqual(proto._hash_leaf(b'0', small, b''), digest)
845 digest_bits=280,958
846 pers=protocols.PERS_LEAF,959 # Smallest leaf, index=1
847 key=b'1',960 digest = accum.add(proto.hash_leaf(1, small))
848 ).digest()961 self.assertEqual(digest,
849 )962 skein512(key1 + small,
850 self.assertEqual(proto.hash_leaf(1, A), digest)963 digest_bits=200,
851964 pers=protocols.PERS_LEAF,
852 # B0965 ).digest()
853 digest = proto._hash_leaf(0, B, b'')966 )
854 self.assertEqual(digest, b32loads(B0_B32))967 self.assertEqual(proto._hash_leaf(b'1', small, b''), digest)
855 self.assertEqual(968
856 digest,969 # Largest leaf, index=0
857 skein512(B,970 digest = accum.add(proto.hash_leaf(0, large))
858 digest_bits=280,971 self.assertEqual(digest,
859 pers=protocols.PERS_LEAF,972 skein512(key0 + large,
860 key=b'0',973 digest_bits=200,
861 ).digest()974 pers=protocols.PERS_LEAF,
862 )975 ).digest()
863 self.assertEqual(proto.hash_leaf(0, B), digest)976 )
864977 self.assertEqual(proto._hash_leaf(b'0', large, b''), digest)
865 # B1978
866 digest = proto._hash_leaf(1, B, b'')979 # Largest leaf, index=1
867 self.assertEqual(digest, b32loads(B1_B32))980 digest = accum.add(proto.hash_leaf(1, large))
868 self.assertEqual(981 self.assertEqual(digest,
869 digest,982 skein512(key1 + large,
870 skein512(B,983 digest_bits=200,
871 digest_bits=280,984 pers=protocols.PERS_LEAF,
872 pers=protocols.PERS_LEAF,985 ).digest()
873 key=b'1',986 )
874 ).digest()987 self.assertEqual(proto._hash_leaf(b'1', large, b''), digest)
875 )988
876 self.assertEqual(proto.hash_leaf(1, B), digest)989 # Test with challenge, smallest leaf, index=0:
877990 digest = accum.add(proto.hash_leaf(0, small, b'secret'))
878 # C0991 self.assertEqual(digest,
879 digest = proto._hash_leaf(0, C, b'')992 skein512(key0 + small,
880 self.assertEqual(digest, b32loads(C0_B32))993 digest_bits=200,
881 self.assertEqual(994 pers=protocols.PERS_LEAF,
882 digest,995 nonce=b'secret',
883 skein512(C,996 ).digest()
884 digest_bits=280,997 )
885 pers=protocols.PERS_LEAF,998 self.assertEqual(proto._hash_leaf(b'0', small, b'secret'), digest)
886 key=b'0',999
887 ).digest()1000 # Test with challenge, smallest leaf, index=1:
888 )1001 digest = accum.add(proto.hash_leaf(1, small, b'secret'))
889 self.assertEqual(proto.hash_leaf(0, C), digest)1002 self.assertEqual(digest,
8901003 skein512(key1 + small,
891 # C11004 digest_bits=200,
892 digest = proto._hash_leaf(1, C, b'')1005 pers=protocols.PERS_LEAF,
893 self.assertEqual(digest, b32loads(C1_B32))1006 nonce=b'secret',
894 self.assertEqual(1007 ).digest()
895 digest,1008 )
896 skein512(C,1009 self.assertEqual(proto._hash_leaf(b'1', small, b'secret'), digest)
897 digest_bits=280,1010
898 pers=protocols.PERS_LEAF,1011 # Test with challenge, largest leaf, index=0:
899 key=b'1',1012 digest = accum.add(proto.hash_leaf(0, large, b'secret'))
900 ).digest()1013 self.assertEqual(digest,
901 )1014 skein512(key0 + large,
902 self.assertEqual(proto.hash_leaf(1, C), digest)1015 digest_bits=200,
1016 pers=protocols.PERS_LEAF,
1017 nonce=b'secret',
1018 ).digest()
1019 )
1020 self.assertEqual(proto._hash_leaf(b'0', large, b'secret'), digest)
1021
1022 # Test with challenge, largest leaf, index=1:
1023 digest = accum.add(proto.hash_leaf(1, large, b'secret'))
1024 self.assertEqual(digest,
1025 skein512(key1 + large,
1026 digest_bits=200,
1027 pers=protocols.PERS_LEAF,
1028 nonce=b'secret',
1029 ).digest()
1030 )
1031 self.assertEqual(proto._hash_leaf(b'1', large, b'secret'), digest)
1032
1033 # Make sure we didn't goof
1034 safety = set()
1035 for index in [0, 1]:
1036 for data in [small, large]:
1037 for challenge in [b'', b'secret']:
1038 safety.add(proto.hash_leaf(index, data, challenge))
1039 self.assertEqual(safety, accum)
1040 self.assertEqual(len(accum), 8)
1041 for digest in accum:
1042 self.assertEqual(len(digest), 25)
9031043
904 def test_hash_root(self):1044 def test_hash_root(self):
905 proto = protocols.VERSION11045 proto = protocols.OldSkeinProtocol(TwoMiB, 200)
906
907 self.check_hash_root(proto)1046 self.check_hash_root(proto)
908 self.sanity_check_hash_root(proto)1047 self.assertEqual(self._checks, [
9091048 'hash_root_validation',
910 A0 = b32loads(A0_B32)1049 'hash_root_simple',
911 A1 = b32loads(A1_B32)1050 'hash_root_crypto',
912 B0 = b32loads(B0_B32)1051 ])
913 B1 = b32loads(B1_B32)1052
914 C0 = b32loads(C0_B32)1053 accum = DigestAccum()
915 C1 = b32loads(C1_B32)1054 one = b'D' * 25
9161055 two = b'D' * 50
917 # A1056 key1 = proto._hash_file_size(b'1')
918 digest = proto._hash_root(1, A0)1057 key2 = proto._hash_file_size(b'2097152')
919 self.assertEqual(1058 key3 = proto._hash_file_size(b'2097153')
920 b32encode(digest).decode('utf-8'),1059 key4 = proto._hash_file_size(b'4194304')
921 'FWV6OJYI36C5NN5DC4GS2IGWZXFCZCGJGHK35YV62LKAG7D2Z4LO4Z2S'1060
922 )1061 # Smallest file-size possible for one leaf:
923 self.assertEqual(1062 digest = accum.add(proto.hash_root(1, one))
924 digest,1063 self.assertEqual(digest,
925 skein512(A0,1064 skein512(key1 + one,
926 digest_bits=280,1065 digest_bits=200,
927 pers=protocols.PERS_ROOT,1066 pers=protocols.PERS_ROOT,
928 key=b'1',1067 ).digest()
929 ).digest()1068 )
930 )1069 self.assertEqual(proto._hash_root(b'1', one), digest)
931 self.assertEqual(1070
932 proto.hash_root(1, A0),1071 # Largest file-size possible for one leaf:
933 b32encode(digest).decode('utf-8')1072 digest = accum.add(proto.hash_root(TwoMiB, one))
934 )1073 self.assertEqual(digest,
9351074 skein512(key2 + one,
936 # B1075 digest_bits=200,
937 digest = proto._hash_root(8388607, B0)1076 pers=protocols.PERS_ROOT,
938 self.assertEqual(1077 ).digest()
939 b32encode(digest).decode('utf-8'),1078 )
940 'OB756PX5V32JMKJAFKIAJ4AFSFPA2WLNIK32ELNO4FJLJPEEEN6DCAAJ'1079 self.assertEqual(proto._hash_root(b'2097152', one), digest)
941 )1080
942 self.assertEqual(1081 # Smallest file-size possible for two leaves:
943 digest,1082 digest = accum.add(proto.hash_root(TwoMiB + 1, two))
944 skein512(B0,1083 self.assertEqual(digest,
945 digest_bits=280,1084 skein512(key3 + two,
946 pers=protocols.PERS_ROOT,1085 digest_bits=200,
947 key=b'8388607',1086 pers=protocols.PERS_ROOT,
948 ).digest()1087 ).digest()
949 )1088 )
950 self.assertEqual(1089 self.assertEqual(proto._hash_root(b'2097153', two), digest)
951 proto.hash_root(8388607, B0),1090
952 b32encode(digest).decode('utf-8')1091 # Largest file-size possible for two leaves:
953 )1092 digest = accum.add(proto.hash_root(2 * TwoMiB, two))
9541093 self.assertEqual(digest,
955 # C1094 skein512(key4 + two,
956 digest = proto._hash_root(8388608, C0)1095 digest_bits=200,
957 self.assertEqual(1096 pers=protocols.PERS_ROOT,
958 b32encode(digest).decode('utf-8'),1097 ).digest()
959 'QSOHXCDH64IQBOG2NM67XEC6MLZKKPGBTISWWRPMCFCJ2EKMA2SMLY46'1098 )
960 )1099 self.assertEqual(proto._hash_root(b'4194304', two), digest)
961 self.assertEqual(1100
962 digest,1101 # Make sure we didn't goof
963 skein512(C0,1102 self.assertEqual(len(accum), 4)
964 digest_bits=280,1103 safety = set([
965 pers=protocols.PERS_ROOT,1104 proto.hash_root(1, one),
966 key=b'8388608',1105 proto.hash_root(TwoMiB, one),
967 ).digest()1106 proto.hash_root(TwoMiB + 1, two),
968 )1107 proto.hash_root(TwoMiB * 2, two),
969 self.assertEqual(1108 ])
970 proto.hash_root(8388608, C0),1109 self.assertEqual(safety, accum)
971 b32encode(digest).decode('utf-8')1110 for digest in accum:
972 )1111 self.assertEqual(len(digest), 25)
9731112
974 # CA
975 digest = proto._hash_root(8388609, C0 + A1)
976 self.assertEqual(
977 b32encode(digest).decode('utf-8'),
978 'BQ5UTB33ML2VDTCTLVXK6N4VSMGGKKKDYKG24B6DOAFJB6NRSGMB5BNO'
979 )
980 self.assertEqual(
981 digest,
982 skein512(C0 + A1,
983 digest_bits=280,
984 pers=protocols.PERS_ROOT,
985 key=b'8388609',
986 ).digest()
987 )
988 self.assertEqual(
989 proto.hash_root(8388609, C0 + A1),
990 b32encode(digest).decode('utf-8')
991 )
992
993 # CB
994 digest = proto._hash_root(16777215, C0 + B1)
995 self.assertEqual(
996 b32encode(digest).decode('utf-8'),
997 'ER3LDDZ2LHMTDLOPE5XA5GEEZ6OE45VFIFLY42GEMV4TSZ2B7GJJXAIX'
998 )
999 self.assertEqual(
1000 digest,
1001 skein512(C0 + B1,
1002 digest_bits=280,
1003 pers=protocols.PERS_ROOT,
1004 key=b'16777215',
1005 ).digest()
1006 )
1007 self.assertEqual(
1008 proto.hash_root(16777215, C0 + B1),
1009 b32encode(digest).decode('utf-8')
1010 )
1011
1012 # CC
1013 digest = proto._hash_root(16777216, C0 + C1)
1014 self.assertEqual(
1015 b32encode(digest).decode('utf-8'),
1016 'R6RN5KL7UBNJWR5SK5YPUKIGAOWWFMYYOVESU5DPT34X5MEK75PXXYIX'
1017 )
1018 self.assertEqual(
1019 digest,
1020 skein512(C0 + C1,
1021 digest_bits=280,
1022 pers=protocols.PERS_ROOT,
1023 key=b'16777216',
1024 ).digest()
1025 )
1026 self.assertEqual(
1027 proto.hash_root(16777216, C0 + C1),
1028 b32encode(digest).decode('utf-8')
1029 )
1030
1031
1032class TestOldProtocol(TestCase):
1033 def test_hash_leaf_index(self):1113 def test_hash_leaf_index(self):
1034 proto = protocols.OldProtocol(TwoMiB, 200)1114 proto = protocols.OldSkeinProtocol(TwoMiB, 200)
1035 accum = set()1115 accum = DigestAccum()
1036 key = proto._hash_leaf_index(0)1116 for key in [b'0', b'1', b'17', b'18', b'21']:
1037 accum.add(key)1117 digest = accum.add(proto._hash_leaf_index(key))
1038 self.assertEqual(key,1118 self.assertEqual(digest,
1039 skein512(b'0',1119 skein512(key,
1040 digest_bits=200,1120 digest_bits=200,
1041 pers=protocols.PERS_LEAF_INDEX,1121 pers=protocols.PERS_LEAF_INDEX,
1042 ).digest()1122 ).digest()
1043 )1123 )
10441124 self.assertEqual(len(accum), 5)
1045 key = proto._hash_leaf_index(17)
1046 self.assertNotIn(key, accum)
1047 accum.add(key)
1048 self.assertEqual(key,
1049 skein512(b'17',
1050 digest_bits=200,
1051 pers=protocols.PERS_LEAF_INDEX,
1052 ).digest()
1053 )
1054 self.assertEqual(len(accum), 2)
1055
1056 count = 25 * 1000
1057 accum = set(
1058 proto._hash_leaf_index(i) for i in range(count)
1059 )
1060 self.assertEqual(len(accum), count)
1061
1062 #############################################
1063 # Again, this time with different digest_bits
1064 proto = protocols.OldProtocol(TwoMiB, 240)
1065 accum = set()
1066 key = proto._hash_leaf_index(0)
1067 accum.add(key)
1068 self.assertEqual(key,
1069 skein512(b'0',
1070 digest_bits=240,
1071 pers=protocols.PERS_LEAF_INDEX,
1072 ).digest()
1073 )
1074
1075 key = proto._hash_leaf_index(17)
1076 self.assertNotIn(key, accum)
1077 accum.add(key)
1078 self.assertEqual(key,
1079 skein512(b'17',
1080 digest_bits=240,
1081 pers=protocols.PERS_LEAF_INDEX,
1082 ).digest()
1083 )
1084 self.assertEqual(len(accum), 2)
1085
1086 count = 25 * 1000
1087 accum = set(
1088 proto._hash_leaf_index(i) for i in range(count)
1089 )
1090 self.assertEqual(len(accum), count)
1091
1092 def test_hash_leaf(self):
1093 challenge = b'secret foo bar'
1094 proto = protocols.OldProtocol(TwoMiB, 200)
1095 key0 = proto._hash_leaf_index(0)
1096 key17 = proto._hash_leaf_index(17)
1097
1098 accum = set()
1099
1100 # Min leaf size, index=0
1101 leaf_data = b'D'
1102 digest = proto._hash_leaf(0, leaf_data, b'')
1103 self.assertNotIn(digest, accum)
1104 accum.add(digest)
1105 self.assertEqual(digest,
1106 skein512(key0 + leaf_data,
1107 digest_bits=200,
1108 pers=protocols.PERS_LEAF,
1109 ).digest()
1110 )
1111
1112 # Min leaf size, index=17
1113 digest = proto._hash_leaf(17, leaf_data, b'')
1114 self.assertNotIn(digest, accum)
1115 accum.add(digest)
1116 self.assertEqual(digest,
1117 skein512(key17 + leaf_data,
1118 digest_bits=200,
1119 pers=protocols.PERS_LEAF,
1120 ).digest()
1121 )
1122
1123 # With challenge, min leaf size, index=0
1124 digest = proto._hash_leaf(0, leaf_data, challenge)
1125 self.assertNotIn(digest, accum)
1126 accum.add(digest)
1127 self.assertEqual(digest,
1128 skein512(key0 + leaf_data,
1129 digest_bits=200,
1130 pers=protocols.PERS_LEAF,
1131 nonce=challenge,
1132 ).digest()
1133 )
1134
1135 # With challenge, min leaf size, index=17
1136 digest = proto._hash_leaf(17, leaf_data, challenge)
1137 self.assertNotIn(digest, accum)
1138 accum.add(digest)
1139 self.assertEqual(digest,
1140 skein512(key17 + leaf_data,
1141 digest_bits=200,
1142 pers=protocols.PERS_LEAF,
1143 nonce=challenge,
1144 ).digest()
1145 )
1146
1147 # Max leaf size, index=0
1148 leaf_data = b'D' * TwoMiB
1149 digest = proto._hash_leaf(0, leaf_data, b'')
1150 self.assertNotIn(digest, accum)
1151 accum.add(digest)
1152 self.assertEqual(digest,
1153 skein512(key0 + leaf_data,
1154 digest_bits=200,
1155 pers=protocols.PERS_LEAF,
1156 ).digest()
1157 )
1158
1159 # Max leaf size, index=17
1160 digest = proto._hash_leaf(17, leaf_data, b'')
1161 self.assertNotIn(digest, accum)
1162 accum.add(digest)
1163 self.assertEqual(digest,
1164 skein512(key17 + leaf_data,
1165 digest_bits=200,
1166 pers=protocols.PERS_LEAF,
1167 ).digest()
1168 )
1169
1170 # With challenge, max leaf size, index=0
1171 digest = proto._hash_leaf(0, leaf_data, challenge)
1172 self.assertNotIn(digest, accum)
1173 accum.add(digest)
1174 self.assertEqual(digest,
1175 skein512(key0 + leaf_data,
1176 digest_bits=200,
1177 pers=protocols.PERS_LEAF,
1178 nonce=challenge,
1179 ).digest()
1180 )
1181
1182 # With challenge, max leaf size, index=17
1183 digest = proto._hash_leaf(17, leaf_data, challenge)
1184 self.assertNotIn(digest, accum)
1185 accum.add(digest)
1186 self.assertEqual(digest,
1187 skein512(key17 + leaf_data,
1188 digest_bits=200,
1189 pers=protocols.PERS_LEAF,
1190 nonce=challenge,
1191 ).digest()
1192 )
1193
1194 # Make sure we didn't goof:
1195 self.assertEqual(len(accum), 8)
1196
1197 # A 25k value sanity check on our crytographic claim that the
1198 # leaf-hash is tied to the leaf-index:
1199 count = 25 * 1000
1200 leaf_data = os.urandom(32)
1201 accum = set(
1202 proto._hash_leaf(i, leaf_data, b'')
1203 for i in range(count)
1204 )
1205 self.assertEqual(len(accum), count)
1206
1207 # A 25k random value sanity check on our crytographic claim that the
1208 # leaf-hash is tied to the leaf-data:
1209 accum = set(
1210 proto._hash_leaf(21, os.urandom(32), b'')
1211 for i in range(count)
1212 )
1213 self.assertEqual(len(accum), count)
1214
1215 # A 25k random value sanity check on our crytographic claim that the
1216 # leaf-hash is tied to the challenge:
1217 accum = set(
1218 proto._hash_leaf(21, leaf_data, os.urandom(16))
1219 for i in range(count)
1220 )
1221 self.assertEqual(len(accum), count)
12221125
1223 def test_hash_file_size(self):1126 def test_hash_file_size(self):
1224 proto = protocols.OldProtocol(TwoMiB, 200)1127 proto = protocols.OldSkeinProtocol(TwoMiB, 200)
1225 accum = set()1128 accum = DigestAccum()
12261129 for key in [b'1', b'2', b'2097152', b'2097153', b'4194304']:
1227 key = proto._hash_file_size(1)1130 digest = accum.add(proto._hash_file_size(key))
1228 self.assertNotIn(key, accum)1131 self.assertEqual(digest,
1229 accum.add(key)1132 skein512(key,
1230 self.assertEqual(key,1133 digest_bits=200,
1231 skein512(b'1',1134 pers=protocols.PERS_FILE_SIZE,
1232 digest_bits=200,1135 ).digest()
1233 pers=protocols.PERS_FILE_SIZE,1136 )
1234 ).digest()1137 self.assertEqual(len(accum), 5)
1235 )1138
12361139
1237 key = proto._hash_file_size(18)1140class Test_VERSION0(BaseTestCase2):
1238 self.assertNotIn(key, accum)1141 proto = protocols.VERSION0
1239 accum.add(key)1142
1240 self.assertEqual(key,1143 def test_hash_leaf(self):
1241 skein512(b'18',1144 self.check_hash_leaf(self.proto)
1242 digest_bits=200,1145 self.assertEqual(self._checks, [
1243 pers=protocols.PERS_FILE_SIZE,1146 'hash_leaf_validation',
1244 ).digest()1147 'hash_leaf_simple',
1245 )1148 'hash_leaf_crypto',
1246 self.assertEqual(len(accum), 2)1149 ])
12471150
1248 count = 25 * 10001151 leaves = misc.build_leaves(EightMiB)
1249 accum = set(1152 vectors = misc.load_data('V0')
1250 proto._hash_file_size(size) for1153 self.assertEqual(
1251 size in range(1, count + 1)1154 b32enc(self.proto.hash_leaf(0, leaves['A'])),
1252 )1155 vectors['leaf_hashes']['A'][0]
1253 self.assertEqual(len(accum), count)1156 )
12541157 self.assertEqual(
1255 #############################################1158 b32enc(self.proto.hash_leaf(1, leaves['A'])),
1256 # Again, this time with different digest_bits1159 vectors['leaf_hashes']['A'][1]
1257 proto = protocols.OldProtocol(TwoMiB, 240)1160 )
1258 accum = set()1161 self.assertEqual(
12591162 b32enc(self.proto.hash_leaf(0, leaves['B'])),
1260 key = proto._hash_file_size(1)1163 vectors['leaf_hashes']['B'][0]
1261 self.assertNotIn(key, accum)1164 )
1262 accum.add(key)1165 self.assertEqual(
1263 self.assertEqual(key,1166 b32enc(self.proto.hash_leaf(1, leaves['B'])),
1264 skein512(b'1',1167 vectors['leaf_hashes']['B'][1]
1265 digest_bits=240,1168 )
1266 pers=protocols.PERS_FILE_SIZE,1169 self.assertEqual(
1267 ).digest()1170 b32enc(self.proto.hash_leaf(0, leaves['C'])),
1268 )1171 vectors['leaf_hashes']['C'][0]
12691172 )
1270 key = proto._hash_file_size(18)1173 self.assertEqual(
1271 self.assertNotIn(key, accum)1174 b32enc(self.proto.hash_leaf(1, leaves['C'])),
1272 accum.add(key)1175 vectors['leaf_hashes']['C'][1]
1273 self.assertEqual(key,1176 )
1274 skein512(b'18',1177
1275 digest_bits=240,1178 def test_hash_root(self):
1276 pers=protocols.PERS_FILE_SIZE,1179 self.check_hash_root(self.proto)
1277 ).digest()1180 self.assertEqual(self._checks, [
1278 )1181 'hash_root_validation',
1279 self.assertEqual(len(accum), 2)1182 'hash_root_simple',
12801183 'hash_root_crypto',
1281 count = 25 * 10001184 ])
1282 accum = set(1185
1283 proto._hash_file_size(size) for1186 vectors = misc.load_data('V0')
1284 size in range(1, count + 1)1187 A0 = b32dec(vectors['leaf_hashes']['A'][0])
1285 )1188 A1 = b32dec(vectors['leaf_hashes']['A'][1])
1286 self.assertEqual(len(accum), count)1189 B0 = b32dec(vectors['leaf_hashes']['B'][0])
12871190 B1 = b32dec(vectors['leaf_hashes']['B'][1])
1288 def test_hash_root(self):1191 C0 = b32dec(vectors['leaf_hashes']['C'][0])
1289 proto = protocols.OldProtocol(TwoMiB, 200)1192 C1 = b32dec(vectors['leaf_hashes']['C'][1])
1290 key1 = proto._hash_file_size(1)1193 self.assertEqual(
1291 key18 = proto._hash_file_size(18)1194 b32enc(self.proto.hash_root(1, A0)),
1292 keyM = proto._hash_file_size(TwoMiB)1195 vectors['root_hashes']['A']
1293 keyM1 = proto._hash_file_size(TwoMiB + 1)1196 )
1294 keyM18 = proto._hash_file_size(TwoMiB + 18)1197 self.assertEqual(
1295 key2M = proto._hash_file_size(2 * TwoMiB)1198 b32enc(self.proto.hash_root(EightMiB - 1, B0)),
12961199 vectors['root_hashes']['B']
1297 accum = set()1200 )
12981201 self.assertEqual(
1299 # One leaf, file_size=11202 b32enc(self.proto.hash_root(EightMiB , C0)),
1300 leaf_hashes = b'D' * 251203 vectors['root_hashes']['C']
1301 digest = proto._hash_root(1, leaf_hashes)1204 )
1302 self.assertNotIn(digest, accum)1205 self.assertEqual(
1303 accum.add(digest)1206 b32enc(self.proto.hash_root(EightMiB + 1, C0 + A1)),
1304 self.assertEqual(digest,1207 vectors['root_hashes']['CA']
1305 skein512(key1 + leaf_hashes,1208 )
1306 digest_bits=200,1209 self.assertEqual(
1307 pers=protocols.PERS_ROOT,1210 b32enc(self.proto.hash_root(2 * EightMiB - 1, C0 + B1)),
1308 ).digest()1211 vectors['root_hashes']['CB']
1309 )1212 )
1310 self.assertEqual(1213 self.assertEqual(
1311 proto.hash_root(1, leaf_hashes),1214 b32enc(self.proto.hash_root(2 * EightMiB , C0 + C1)),
1312 b32encode(digest).decode('utf-8')1215 vectors['root_hashes']['CC']
1313 )1216 )
13141217
1315 # One leaf, file_size=181218 def test_vectors(self):
1316 digest = proto._hash_root(18, leaf_hashes)1219 V0 = misc.load_data('V0')
1317 self.assertNotIn(digest, accum)1220 self.assertEqual(misc.build_vectors(self.proto, b32enc), V0)
1318 accum.add(digest)1221
1319 self.assertEqual(digest,1222
1320 skein512(key18 + leaf_hashes,1223class Test_VERSION1(BaseTestCase2):
1321 digest_bits=200,1224 proto = protocols.VERSION1
1322 pers=protocols.PERS_ROOT,1225
1323 ).digest()1226 def test_hash_leaf(self):
1324 )1227 self.check_hash_leaf(self.proto)
1325 self.assertEqual(1228 self.assertEqual(self._checks, [
1326 proto.hash_root(18, leaf_hashes),1229 'hash_leaf_validation',
1327 b32encode(digest).decode('utf-8')1230 'hash_leaf_simple',
1328 )1231 'hash_leaf_crypto',
13291232 ])
1330 # One leaf, file_size=TwoMiB1233
1331 digest = proto._hash_root(TwoMiB, leaf_hashes)1234 leaves = misc.build_leaves(EightMiB)
1332 self.assertNotIn(digest, accum)1235 vectors = misc.load_data('V1')
1333 accum.add(digest)1236 self.assertEqual(
1334 self.assertEqual(digest,1237 db32enc(self.proto.hash_leaf(0, leaves['A'])),
1335 skein512(keyM + leaf_hashes,1238 vectors['leaf_hashes']['A'][0]
1336 digest_bits=200,1239 )
1337 pers=protocols.PERS_ROOT,1240 self.assertEqual(
1338 ).digest()1241 db32enc(self.proto.hash_leaf(1, leaves['A'])),
1339 )1242 vectors['leaf_hashes']['A'][1]
1340 self.assertEqual(1243 )
1341 proto.hash_root(TwoMiB, leaf_hashes),1244 self.assertEqual(
1342 b32encode(digest).decode('utf-8')1245 db32enc(self.proto.hash_leaf(0, leaves['B'])),
1343 )1246 vectors['leaf_hashes']['B'][0]
13441247 )
1345 # Two leaves, file_size=(TwoMiB + 1)1248 self.assertEqual(
1346 leaf_hashes = b'D' * 501249 db32enc(self.proto.hash_leaf(1, leaves['B'])),
1347 digest = proto._hash_root(TwoMiB + 1, leaf_hashes)1250 vectors['leaf_hashes']['B'][1]
1348 self.assertNotIn(digest, accum)1251 )
1349 accum.add(digest)1252 self.assertEqual(
1350 self.assertEqual(digest,1253 db32enc(self.proto.hash_leaf(0, leaves['C'])),
1351 skein512(keyM1 + leaf_hashes,1254 vectors['leaf_hashes']['C'][0]
1352 digest_bits=200,1255 )
1353 pers=protocols.PERS_ROOT,1256 self.assertEqual(
1354 ).digest()1257 db32enc(self.proto.hash_leaf(1, leaves['C'])),
1355 )1258 vectors['leaf_hashes']['C'][1]
1356 self.assertEqual(1259 )
1357 proto.hash_root(TwoMiB + 1, leaf_hashes),1260
1358 b32encode(digest).decode('utf-8')1261 def test_hash_root(self):
1359 )1262 self.check_hash_root(self.proto)
13601263 self.assertEqual(self._checks, [
1361 # Two leaves, file_size=(TwoMiB + 18)1264 'hash_root_validation',
1362 digest = proto._hash_root(TwoMiB + 18, leaf_hashes)1265 'hash_root_simple',
1363 self.assertNotIn(digest, accum)1266 'hash_root_crypto',
1364 accum.add(digest)1267 ])
1365 self.assertEqual(digest,1268
1366 skein512(keyM18 + leaf_hashes,1269 vectors = misc.load_data('V1')
1367 digest_bits=200,1270 A0 = db32dec(vectors['leaf_hashes']['A'][0])
1368 pers=protocols.PERS_ROOT,1271 A1 = db32dec(vectors['leaf_hashes']['A'][1])
1369 ).digest()1272 B0 = db32dec(vectors['leaf_hashes']['B'][0])
1370 )1273 B1 = db32dec(vectors['leaf_hashes']['B'][1])
1371 self.assertEqual(1274 C0 = db32dec(vectors['leaf_hashes']['C'][0])
1372 proto.hash_root(TwoMiB + 18, leaf_hashes),1275 C1 = db32dec(vectors['leaf_hashes']['C'][1])
1373 b32encode(digest).decode('utf-8')1276 self.assertEqual(
1374 )1277 db32enc(self.proto.hash_root(1, A0)),
13751278 vectors['root_hashes']['A']
1376 # Two leaves, file_size=(2 * TwoMiB)1279 )
1377 digest = proto._hash_root(2 * TwoMiB, leaf_hashes)1280 self.assertEqual(
1378 self.assertNotIn(digest, accum)1281 db32enc(self.proto.hash_root(EightMiB - 1, B0)),
1379 accum.add(digest)1282 vectors['root_hashes']['B']
1380 self.assertEqual(digest,1283 )
1381 skein512(key2M + leaf_hashes,1284 self.assertEqual(
1382 digest_bits=200,1285 db32enc(self.proto.hash_root(EightMiB , C0)),
1383 pers=protocols.PERS_ROOT,1286 vectors['root_hashes']['C']
1384 ).digest()1287 )
1385 )1288 self.assertEqual(
1386 self.assertEqual(1289 db32enc(self.proto.hash_root(EightMiB + 1, C0 + A1)),
1387 proto.hash_root(2 * TwoMiB, leaf_hashes),1290 vectors['root_hashes']['CA']
1388 b32encode(digest).decode('utf-8')1291 )
1389 )1292 self.assertEqual(
13901293 db32enc(self.proto.hash_root(2 * EightMiB - 1, C0 + B1)),
1391 # Make sure we didn't goof:1294 vectors['root_hashes']['CB']
1392 self.assertEqual(len(accum), 6)1295 )
13931296 self.assertEqual(
1394 # A 25k value sanity check on our crytographic claim that the1297 db32enc(self.proto.hash_root(2 * EightMiB , C0 + C1)),
1395 # root-hash is tied to the file-size:1298 vectors['root_hashes']['CC']
1396 count = 25 * 10001299 )
1397 leaf_hashes = b'D' * 251300
1398 accum = set(1301 def test_vectors(self):
1399 proto._hash_root(size, leaf_hashes)1302 V1 = misc.load_data('V1')
1400 for size in range(1, count + 1)1303 self.assertEqual(misc.build_vectors(self.proto, db32enc), V1)
1401 )1304 self.assertNotEqual(misc.build_vectors(self.proto, b32enc), V1)
1402 self.assertEqual(len(accum), count)1305 self.assertNotEqual(misc.build_vectors(protocols.VERSION0, db32enc), V1)
14031306 self.assertNotEqual(misc.build_vectors(protocols.VERSION0, b32enc), V1)
1404 # A 25k random value sanity check on our crytographic claim that the1307
1405 # root-hash is tied to the leaf-hashes:1308 def test_md5sums(self):
1406 accum = set(1309 MD5SUMS = misc.load_data('MD5SUMS')
1407 proto._hash_root(21, os.urandom(25))1310 self.assertEqual(misc.build_md5sums(self.proto.leaf_size), MD5SUMS)
1408 for i in range(count)1311
1409 )
1410 self.assertEqual(len(accum), count)
14111312
=== modified file 'setup.py'
--- setup.py 2013-02-18 12:58:01 +0000
+++ setup.py 2013-02-19 15:39:24 +0000
@@ -67,7 +67,7 @@
67 'filestore',67 'filestore',
68 'filestore.tests'68 'filestore.tests'
69 ],69 ],
70 package_data={'filestore': ['data/test-vectors.json']},70 package_data={'filestore': ['data/*.json']},
71 scripts=['dmediasum'],71 scripts=['dmediasum'],
72 cmdclass={'test': Test},72 cmdclass={'test': Test},
73 ext_modules=[_filestore],73 ext_modules=[_filestore],

Subscribers

People subscribed via source and target branches