def create_state(self, params, seed, which): self.multipliers, self.ph, self.params = params self.state = FFI.new("struct umash_state[1]") self.sink = FFI.addressof(self.state[0].sink) C.umash_init(self.state, self.params, seed, which) self.seed = seed self.which = which
def test_public_params_derive(bits, key): length = FFI.sizeof("struct umash_params") umash_key = b"Do not use UMASH VS adversaries." if key is not None: umash_key = key nonce = struct.pack("<Q", bits) expected = FFI.new("struct umash_params[1]") salsa_bytes = Salsa20.new(umash_key, nonce).encrypt(b"\x00" * length) FFI.memmove(expected, salsa_bytes, length) assert C.umash_params_prepare(expected) actual = FFI.new("struct umash_params[1]") if key is None: C.umash_params_derive(actual, bits, FFI.NULL) else: buf = FFI.new("char[]", len(key)) FFI.memmove(buf, key, len(key)) C.umash_params_derive(actual, bits, buf) # The values should all be the same. assert length % 8 == 0 for i in range(length // 8): assert FFI.cast("uint64_t *", actual)[i] == FFI.cast("uint64_t *", expected)[i]
def test_public_smoke_matches(random, seed, data): """Prepare a params struct, and make sure the UMASH function matches our reference.""" params = FFI.new("struct umash_params[1]") size = FFI.sizeof("struct umash_params") assert size % 8 == 0 for i in range(size // 8): FFI.cast("uint64_t *", params)[i] = random.getrandbits(64) # Pseudorandom input should always succeed. assert C.umash_params_prepare(params) == True assert_idempotent(params) expected0 = umash( UmashKey(params[0].poly[0][1], [params[0].oh[i] for i in range(OH_COUNT)]), seed, data, secondary=False, ) assert C.umash_full(params, seed, 0, data, len(data)) == expected0 expected1 = umash( UmashKey( params[0].poly[1][1], [params[0].oh[i] for i in range(OH_COUNT)], ), seed, data, secondary=True, ) assert C.umash_full(params, seed, 1, data, len(data)) == expected1
def test_vec_to_u64(data): """Make sure we expand to a uint64 correctly.""" n_bytes = len(data) # Copy to a malloc-ed buffer to help ASan. buf = FFI.new("char[]", n_bytes) FFI.memmove(buf, data, n_bytes) assert C.vec_to_u64(buf, n_bytes) == vec_to_u64(data)
def test_umash_short(seed, key, data): """Compare umash_short with the reference.""" expected = umash(UmashKey(poly=0, oh=key), seed, data) n_bytes = len(data) block = FFI.new("char[]", n_bytes) FFI.memmove(block, data, n_bytes) params = FFI.new("struct umash_params[1]") for i, param in enumerate(key): params[0].oh[i] = param assert C.umash_short(params[0].oh, seed, block, n_bytes) == expected
def assert_idempotent(params): """Asserts that calling `umash_params_prepare` on something that was successfully prepared is idempotent. """ size = FFI.sizeof("struct umash_params") copy = FFI.new("struct umash_params[1]") FFI.memmove(copy, params, size) assert C.umash_params_prepare(copy) == True # The copy should still be the same as params. assert size % 8 == 0 for i in range(size // 8): assert FFI.cast("uint64_t *", params)[i] == FFI.cast("uint64_t *", copy)[i]
def test_public_umash_full(seed, multiplier, key, data): """Compare umash_full with the reference.""" expected = umash(UmashKey(poly=multiplier, ph=key), seed, data) n_bytes = len(data) block = FFI.new("char[]", n_bytes) FFI.memmove(block, data, n_bytes) params = FFI.new("struct umash_params[1]") params[0].poly[0][0] = (multiplier ** 2) % FIELD params[0].poly[0][1] = multiplier for i, param in enumerate(key): params[0].ph[i] = param assert C.umash_full(params, seed, 0, block, n_bytes) == expected
def test_public_umash_full_shifted(seed, multiplier, key, data): """Compare umash_full(which=1) with the reference.""" expected = umash(UmashKey(poly=multiplier, oh=key), seed, data) n_bytes = len(data) block = FFI.new("char[]", n_bytes) FFI.memmove(block, data, n_bytes) params = FFI.new("struct umash_params[1]") params[0].poly[1][0] = (multiplier**2) % FIELD params[0].poly[1][1] = multiplier for i, param in enumerate(key): params[0].oh[i + C.UMASH_OH_TOEPLITZ_SHIFT] = param assert C.umash_full(params, seed, 1, block, n_bytes) == expected
def test_umash_medium(seed, multiplier, key, data): """Compare umash_medium with the reference.""" expected = umash(UmashKey(poly=multiplier, ph=key), seed, data) n_bytes = len(data) block = FFI.new("char[]", n_bytes) FFI.memmove(block, data, n_bytes) poly = FFI.new("uint64_t[2]") poly[0] = (multiplier**2) % FIELD poly[1] = multiplier params = FFI.new("struct umash_params[1]") for i, param in enumerate(key): params[0].ph[i] = param assert C.umash_medium(poly, params[0].ph, seed, block, n_bytes) == expected
def test_umash_long_repeat(seed, multiplier, key, data): """Compare umash_long on repeated strings with the reference.""" expected = umash(UmashKey(poly=multiplier, oh=key), seed, data, secondary=False) note(len(data)) n_bytes = len(data) block = FFI.new("char[]", n_bytes) FFI.memmove(block, data, n_bytes) poly = FFI.new("uint64_t[2]") poly[0] = (multiplier ** 2) % FIELD poly[1] = multiplier params = FFI.new("struct umash_params[1]") for i, param in enumerate(key): params[0].oh[i] = param assert C.umash_long(poly, params[0].oh, seed, block, n_bytes) == expected
def test_ph_one_block(seed, key, data): """Compare PH compression for full blocks.""" expected = ph_compress_one_block(key, seed, split_block(data)) # Copy to exactly-sized malloced buffers to help ASan. block = FFI.new("char[]", BLOCK_SIZE) FFI.memmove(block, data, BLOCK_SIZE) params = FFI.new("struct umash_params[1]") for i, param in enumerate(key): params[0].ph[i] = param actual = C.ph_one_block(params[0].ph, seed, block) assert expected == actual.bits[0] + (actual.bits[1] << 64), ( actual.bits[0], actual.bits[1], )
def test_oh_one_block(tag, key, data): """Compare OH compression for full blocks.""" # The C-side implicit accepts the high half of the tag, and leaves # the low half zeroed out. expected = oh_compress_one_block(key, split_block(data), tag << 64) # Copy to exactly-sized malloced buffers to help ASan. block = FFI.new("char[]", BLOCK_SIZE) FFI.memmove(block, data, BLOCK_SIZE) params = FFI.new("struct umash_params[1]") for i, param in enumerate(key): params[0].oh[i] = param actual = C.oh_one_block(params[0].oh, tag, block) assert expected == actual.bits[0] + (actual.bits[1] << 64), ( actual.bits[0], actual.bits[1], )
def test_public_umash_fprint(seed, multipliers, key, data): """Compare umash_fprint with two calls to the reference.""" expected = [ umash(UmashKey(poly=multipliers[0], oh=key[:-4]), seed, data), umash(UmashKey(poly=multipliers[1], oh=key[4:]), seed, data), ] n_bytes = len(data) block = FFI.new("char[]", n_bytes) FFI.memmove(block, data, n_bytes) params = FFI.new("struct umash_params[1]") for i, multiplier in enumerate(multipliers): params[0].poly[i][0] = (multiplier**2) % FIELD params[0].poly[i][1] = multiplier for i, param in enumerate(key): params[0].oh[i] = param actual = C.umash_fprint(params, seed, block, n_bytes) assert [actual.hash[0], actual.hash[1]] == expected
def test_ph_tail_large(seed, key, data): """Compare PH compression for the last block, when it has enough data to fully contain the last 16-byte chunk.""" expected = ph_compress_one_block(key, seed, split_block(data)) n_bytes = len(data) # Copy to exactly-sized malloced buffers to help ASan. block = FFI.new("char[]", n_bytes) FFI.memmove(block, data, n_bytes) params = FFI.new("struct umash_params[1]") for i, param in enumerate(key): params[0].ph[i] = param actual = C.ph_last_block(params[0].ph, seed, block, n_bytes) assert expected == actual.bits[0] + (actual.bits[1] << 64), ( actual.bits[0], actual.bits[1], )
def test_ph_tail_short(seed, key, prefix, data): """Compare PH compression for the last block, when we must steal some data from the previous chunk.""" expected = ph_compress_one_block( key, seed, split_block(prefix * (C.UMASH_PH_PARAM_COUNT // 2) + data) ) offset = len(prefix) n_bytes = len(data) # Copy to exactly-sized malloced buffers to help ASan. block = FFI.new("char[]", offset + n_bytes) FFI.memmove(block, prefix + data, offset + n_bytes) params = FFI.new("struct umash_params[1]") for i, param in enumerate(key): params[0].ph[i] = param actual = C.ph_last_block(params[0].ph, seed, block + offset, n_bytes) assert expected == actual.bits[0] + (actual.bits[1] << 64), ( actual.bits[0], actual.bits[1], )
def test_public_bad_oh(oh, random): """When the OH values repeat, we should replace them if we can. """ repeated_values = len(oh) - len(set(oh)) params = FFI.new("struct umash_params[1]") for i in range(2): params[0].poly[i][0] = random.getrandbits(64) params[0].poly[i][1] = random.getrandbits(64) for i, value in enumerate(oh): params[0].oh[i] = value result = C.umash_params_prepare(params) if repeated_values > 2: assert result == False if not result: return assert_idempotent(params) # On success, the OH parameters should be unique actual_oh = [params[0].oh[i] for i in range(OH_COUNT)] assert len(actual_oh) == len(set(actual_oh))
def test_public_multiplier_reduction(multipliers, random): """Make sure multipliers are correctly reduced and rejected.""" params = FFI.new("struct umash_params[1]") params[0].poly[0][0] = random.getrandbits(64) params[0].poly[0][1] = multipliers[0] params[0].poly[1][0] = random.getrandbits(64) params[0].poly[1][1] = multipliers[1] for i in range(OH_COUNT): params[0].oh[i] = i assert C.umash_params_prepare(params) == True assert_idempotent(params) for i in range(2): # If we passed in something clearly usable, it should be kept. if 0 < multipliers[i] < FIELD: assert params[0].poly[i][1] == multipliers[i] # The multipliers must be valid. assert 0 < params[0].poly[i][1] < FIELD assert params[0].poly[i][0] == (params[0].poly[i][1]**2) % FIELD # The OH params are valid. for i in range(C.UMASH_OH_PARAM_COUNT + C.UMASH_OH_TWISTING_COUNT): assert params[0].oh[i] == i
def create_state(self, params, seed): self.multipliers, self.oh, self.params = params self.state = FFI.new("struct umash_fp_state[1]") self.sink = FFI.addressof(self.state[0].sink) C.umash_fp_init(self.state, self.params, seed) self.seed = seed
def test_add_mod_fast_general(x, y): """Exercise the fast modular addition interface's claimed precondition.""" assume(x + y < 2**65 - 8) assert C.add_mod_fast(x, y) % MODULO == (x + y) % MODULO
def test_add_mod_slow(x, y): """Make sure the result of `add_mod_slow` is fully reduced.""" assert C.add_mod_slow(x, y) == (x + y) % MODULO
def test_finalize(x): assert C.finalize(x) == finalize(x)
def test_mul_mod_fast_general(m, x): """Check fast modular multiplication, for the case we about.""" assume(m * x < 2**125) assert C.mul_mod_fast(m, x) % MODULO == (m * x) % MODULO
def digest_value(self): result = C.umash_fp_digest(self.state) return [result.hash[0], result.hash[1]]
def batch_value(self): result = C.umash_fprint(self.params, self.seed, self.acc, len(self.acc)) return [result.hash[0], result.hash[1]]
def update(self, buf, n): C.umash_sink_update(self.sink, buf, n)
def digest_value(self): return C.umash_digest(self.state)
def batch_value(self): return C.umash_full(self.params, self.seed, self.which, self.acc, len(self.acc))
def test_horner_double_update(acc, m0, m1, x, y): expected = (m0 * (acc + x) + m1 * y) % MODULO assert C.horner_double_update(acc, m0, m1, x, y) == expected
def test_salsa20(length, nonce, key): expected = Salsa20.new(key, nonce).encrypt(b"\x00" * length) buf = FFI.new("char[]", length) C.salsa20_stream(buf, length, nonce, key) assert bytes(FFI.buffer(buf, length)) == expected
def test_add_mod_fast(x, y): """Check fast modular addition, for the case we care in practice.""" assert C.add_mod_fast(x, y) % MODULO == (x + y) % MODULO