diff --git a/draft-irtf-cfrg-vdaf.md b/draft-irtf-cfrg-vdaf.md index 34cb97f6..21245531 100644 --- a/draft-irtf-cfrg-vdaf.md +++ b/draft-irtf-cfrg-vdaf.md @@ -4994,7 +4994,7 @@ The public share of the IDPF scheme in {{idpf-bbcggi21}} consists of a sequence of "correction words". A correction word has three components: 1. the XOF seed of type `bytes`; -2. the control bits of type `tuple[Field2, Field2]`; and +2. the control bits of type `tuple[bool, bool]`; and 3. the payload of type `list[Field64]` for the first `BITS-1` words and `list[Field255]` for the last word. @@ -5032,7 +5032,7 @@ last bytes are not zero, it throws an error: ~~~ python control_bits = [] for i in range(length): - control_bits.append(Field2( + control_bits.append(bool( (packed_control_bits[i // 8] >> (i % 8)) & 1 )) leftover_bits = packed_control_bits[-1] >> ( @@ -5269,8 +5269,7 @@ full detail. The description of the IDPF-key generation algorithm makes use of auxiliary functions `extend()` and `convert()` defined in -{{idpf-bbcggi21-helper-functions}}. In the following, we let `Field2` denote -the field `GF(2)`. +{{idpf-bbcggi21-helper-functions}}. ~~~ python def gen( @@ -5296,34 +5295,34 @@ def gen( ] seed = key.copy() - ctrl = [Field2(0), Field2(1)] + ctrl = [False, True] public_share = [] for level in range(self.BITS): - keep = int(alpha[level]) + bit = alpha[level] + keep = int(bit) lose = 1 - keep - bit = Field2(keep) (s0, t0) = self.extend(level, seed[0], ctx, nonce) (s1, t1) = self.extend(level, seed[1], ctx, nonce) seed_cw = xor(s0[lose], s1[lose]) ctrl_cw = ( - t0[0] + t1[0] + bit + Field2(1), - t0[1] + t1[1] + bit, + t0[0] ^ t1[0] ^ (not bit), + t0[1] ^ t1[1] ^ bit, ) # Implementation note: these conditional XORs and # input-dependent array indices should be replaced with # constant-time selects in practice in order to reduce # leakage via timing side channels. - if ctrl[0].int(): + if ctrl[0]: x0 = xor(s0[keep], seed_cw) - ctrl[0] = t0[keep] + ctrl_cw[keep] + ctrl[0] = t0[keep] ^ ctrl_cw[keep] else: x0 = s0[keep] ctrl[0] = t0[keep] - if ctrl[1].int(): + if ctrl[1]: x1 = xor(s1[keep], seed_cw) - ctrl[1] = t1[keep] + ctrl_cw[keep] + ctrl[1] = t1[keep] ^ ctrl_cw[keep] else: x1 = s1[keep] ctrl[1] = t1[keep] @@ -5344,7 +5343,7 @@ def gen( # replaced with a constant time select or a constant time # multiplication in practice in order to reduce leakage via # timing side channels. - if ctrl[1].int(): + if ctrl[1]: for i in range(len(w_cw)): w_cw[i] = -w_cw[i] @@ -5386,7 +5385,7 @@ def eval( # `prefix`. Each node in the tree is represented by a seed # (`seed`) and a control bit (`ctrl`). seed = key - ctrl = Field2(agg_id) + ctrl = bool(agg_id) y: FieldVec for current_level in range(level + 1): bit = int(prefix[current_level]) @@ -5426,12 +5425,12 @@ def eval( def eval_next( self, prev_seed: bytes, - prev_ctrl: Field2, + prev_ctrl: bool, correction_word: CorrectionWord, level: int, bit: int, ctx: bytes, - nonce: bytes) -> tuple[bytes, Field2, FieldVec]: + nonce: bytes) -> tuple[bytes, bool, FieldVec]: """ Compute the next node in the IDPF tree along the path determined by a candidate prefix. The next node is determined by `bit`, the @@ -5447,11 +5446,11 @@ def eval_next( # input-dependent array indices should be replaced with # constant-time selects in practice in order to reduce leakage # via timing side channels. - if prev_ctrl.int(): + if prev_ctrl: s[0] = xor(s[0], seed_cw) s[1] = xor(s[1], seed_cw) - t[0] += ctrl_cw[0] - t[1] += ctrl_cw[1] + t[0] ^= ctrl_cw[0] + t[1] ^= ctrl_cw[1] next_ctrl = t[bit] convert_output = self.convert(level, s[bit], ctx, nonce) @@ -5460,7 +5459,7 @@ def eval_next( # Implementation note: this conditional addition should be # replaced with a constant-time select in practice in order to # reduce leakage via timing side channels. - if next_ctrl.int(): + if next_ctrl: for i in range(len(y)): y[i] += w_cw[i] @@ -5475,7 +5474,7 @@ def extend( level: int, seed: bytes, ctx: bytes, - nonce: bytes) -> tuple[list[bytes], list[Field2]]: + nonce: bytes) -> tuple[list[bytes], list[bool]]: xof = self.current_xof( level, seed, @@ -5489,7 +5488,7 @@ def extend( # Use the least significant bits as the control bit correction, # and then zero it out. This gives effectively 127 bits of # security, but reduces the number of AES calls needed by 1/3. - t = [Field2(s[0][0] & 1), Field2(s[1][0] & 1)] + t = [bool(s[0][0] & 1), bool(s[1][0] & 1)] s[0][0] &= 0xFE s[1][0] &= 0xFE return ([bytes(s[0]), bytes(s[1])], t) diff --git a/poc/tests/test_field.py b/poc/tests/test_field.py index 0c3aa3a4..9b8eaa80 100644 --- a/poc/tests/test_field.py +++ b/poc/tests/test_field.py @@ -1,8 +1,8 @@ import random import unittest -from vdaf_poc.field import (Field, Field2, Field64, Field96, Field128, - Field255, NttField, poly_eval, poly_interp) +from vdaf_poc.field import (Field, Field64, Field96, Field128, Field255, + NttField, poly_eval, poly_interp) class TestFields(unittest.TestCase): @@ -60,14 +60,6 @@ def test_field128(self) -> None: def test_field255(self) -> None: self.run_field_test(Field255) - def test_field2(self) -> None: - # Test GF(2). - self.assertEqual(Field2(1).int(), 1) - self.assertEqual(Field2(0).int(), 0) - self.assertEqual(Field2(1) + Field2(1), Field2(0)) - self.assertEqual(Field2(1) * Field2(1), Field2(1)) - self.assertEqual(-Field2(1), Field2(1)) - def test_interp(self) -> None: # Test polynomial interpolation. cls = Field64 diff --git a/poc/vdaf_poc/field.py b/poc/vdaf_poc/field.py index dc04ad85..3774ce4a 100644 --- a/poc/vdaf_poc/field.py +++ b/poc/vdaf_poc/field.py @@ -164,13 +164,6 @@ def gen(cls) -> Self: raise NotImplementedError() -class Field2(Field): - """The finite field GF(2).""" - - MODULUS = 2 - ENCODED_SIZE = 1 - - class Field64(NttField): """The finite field GF(2^32 * 4294967295 + 1).""" diff --git a/poc/vdaf_poc/idpf_bbcggi21.py b/poc/vdaf_poc/idpf_bbcggi21.py index 18e1493b..e98b536f 100644 --- a/poc/vdaf_poc/idpf_bbcggi21.py +++ b/poc/vdaf_poc/idpf_bbcggi21.py @@ -4,7 +4,7 @@ from typing import Sequence, TypeAlias, cast from vdaf_poc.common import format_dst, front, vec_add, vec_neg, vec_sub, xor -from vdaf_poc.field import Field, Field2, Field64, Field255 +from vdaf_poc.field import Field, Field64, Field255 from vdaf_poc.idpf import Idpf from vdaf_poc.xof import Xof, XofFixedKeyAes128, XofTurboShake128 @@ -24,7 +24,7 @@ # types, which aids with self-documentation. FieldVec: TypeAlias = list[Field64] | list[Field255] -CorrectionWord: TypeAlias = tuple[bytes, tuple[Field2, Field2], FieldVec] +CorrectionWord: TypeAlias = tuple[bytes, tuple[bool, bool], FieldVec] class IdpfBBCGGI21(Idpf[Field64, Field255, list[CorrectionWord]]): @@ -89,34 +89,34 @@ def gen( ] seed = key.copy() - ctrl = [Field2(0), Field2(1)] + ctrl = [False, True] public_share = [] for level in range(self.BITS): - keep = int(alpha[level]) + bit = alpha[level] + keep = int(bit) lose = 1 - keep - bit = Field2(keep) (s0, t0) = self.extend(level, seed[0], ctx, nonce) (s1, t1) = self.extend(level, seed[1], ctx, nonce) seed_cw = xor(s0[lose], s1[lose]) ctrl_cw = ( - t0[0] + t1[0] + bit + Field2(1), - t0[1] + t1[1] + bit, + t0[0] ^ t1[0] ^ (not bit), + t0[1] ^ t1[1] ^ bit, ) # Implementation note: these conditional XORs and # input-dependent array indices should be replaced with # constant-time selects in practice in order to reduce # leakage via timing side channels. - if ctrl[0].int(): + if ctrl[0]: x0 = xor(s0[keep], seed_cw) - ctrl[0] = t0[keep] + ctrl_cw[keep] + ctrl[0] = t0[keep] ^ ctrl_cw[keep] else: x0 = s0[keep] ctrl[0] = t0[keep] - if ctrl[1].int(): + if ctrl[1]: x1 = xor(s1[keep], seed_cw) - ctrl[1] = t1[keep] + ctrl_cw[keep] + ctrl[1] = t1[keep] ^ ctrl_cw[keep] else: x1 = s1[keep] ctrl[1] = t1[keep] @@ -137,7 +137,7 @@ def gen( # replaced with a constant time select or a constant time # multiplication in practice in order to reduce leakage via # timing side channels. - if ctrl[1].int(): + if ctrl[1]: for i in range(len(w_cw)): w_cw[i] = -w_cw[i] @@ -177,7 +177,7 @@ def eval( # `prefix`. Each node in the tree is represented by a seed # (`seed`) and a control bit (`ctrl`). seed = key - ctrl = Field2(agg_id) + ctrl = bool(agg_id) y: FieldVec for current_level in range(level + 1): bit = int(prefix[current_level]) @@ -217,12 +217,12 @@ def eval( def eval_next( self, prev_seed: bytes, - prev_ctrl: Field2, + prev_ctrl: bool, correction_word: CorrectionWord, level: int, bit: int, ctx: bytes, - nonce: bytes) -> tuple[bytes, Field2, FieldVec]: + nonce: bytes) -> tuple[bytes, bool, FieldVec]: """ Compute the next node in the IDPF tree along the path determined by a candidate prefix. The next node is determined by `bit`, the @@ -238,11 +238,11 @@ def eval_next( # input-dependent array indices should be replaced with # constant-time selects in practice in order to reduce leakage # via timing side channels. - if prev_ctrl.int(): + if prev_ctrl: s[0] = xor(s[0], seed_cw) s[1] = xor(s[1], seed_cw) - t[0] += ctrl_cw[0] - t[1] += ctrl_cw[1] + t[0] ^= ctrl_cw[0] + t[1] ^= ctrl_cw[1] next_ctrl = t[bit] convert_output = self.convert(level, s[bit], ctx, nonce) @@ -251,7 +251,7 @@ def eval_next( # Implementation note: this conditional addition should be # replaced with a constant-time select in practice in order to # reduce leakage via timing side channels. - if next_ctrl.int(): + if next_ctrl: for i in range(len(y)): y[i] += w_cw[i] @@ -267,7 +267,7 @@ def extend( level: int, seed: bytes, ctx: bytes, - nonce: bytes) -> tuple[list[bytes], list[Field2]]: + nonce: bytes) -> tuple[list[bytes], list[bool]]: xof = self.current_xof( level, seed, @@ -281,7 +281,7 @@ def extend( # Use the least significant bits as the control bit correction, # and then zero it out. This gives effectively 127 bits of # security, but reduces the number of AES calls needed by 1/3. - t = [Field2(s[0][0] & 1), Field2(s[1][0] & 1)] + t = [bool(s[0][0] & 1), bool(s[1][0] & 1)] s[0][0] &= 0xFE s[1][0] &= 0xFE return ([bytes(s[0]), bytes(s[1])], t) @@ -356,7 +356,7 @@ def test_vec_encode_public_share(self, public_share: list[CorrectionWord]) -> by return self.encode_public_share(public_share) -def pack_bits(control_bits: list[Field2]) -> bytes: +def pack_bits(control_bits: list[bool]) -> bytes: packed_len = (len(control_bits) + 7) // 8 # NOTE: The following is excerpted in the document, de-indented. Thee width # should be limited to 69 columns after de-indenting, or 73 columns before, @@ -364,20 +364,20 @@ def pack_bits(control_bits: list[Field2]) -> bytes: # =================================================================== packed_control_buf = [int(0)] * packed_len for i, bit in enumerate(control_bits): - packed_control_buf[i // 8] |= bit.int() << (i % 8) + packed_control_buf[i // 8] |= bit << (i % 8) packed_control_bits = bytes(packed_control_buf) # NOTE: End of exerpt. return packed_control_bits -def unpack_bits(packed_control_bits: bytes, length: int) -> list[Field2]: +def unpack_bits(packed_control_bits: bytes, length: int) -> list[bool]: # NOTE: The following is excerpted in the document, de-indented. Thee width # should be limited to 69 columns after de-indenting, or 73 columns before, # to avoid warnings from xml2rfc. # =================================================================== control_bits = [] for i in range(length): - control_bits.append(Field2( + control_bits.append(bool( (packed_control_bits[i // 8] >> (i % 8)) & 1 )) leftover_bits = packed_control_bits[-1] >> (