diff --git a/src/_zkapauthorizer/_storage_client.py b/src/_zkapauthorizer/_storage_client.py index 579312ea527aaa30a09304262aaf8fb55f63d1b0..182a739c3c171da1e6b45130ebca515d9eabc665 100644 --- a/src/_zkapauthorizer/_storage_client.py +++ b/src/_zkapauthorizer/_storage_client.py @@ -85,7 +85,7 @@ class ZKAPAuthorizerStorageClient(object): """ assert isinstance(message, unicode) return list( - t.text.encode("ascii") + t.pass_text.encode("ascii") for t in self._get_passes(message.encode("utf-8"), count) ) diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py index 32a52e94ad2f0721a8daca5381111721e2fb078d..5539cea4e03f306f07a388a57a57a07315d05e85 100644 --- a/src/_zkapauthorizer/controller.py +++ b/src/_zkapauthorizer/controller.py @@ -315,8 +315,18 @@ class DummyRedeemer(object): ) def tokens_to_passes(self, message, unblinded_tokens): + def token_to_pass(token): + # Smear the unblinded token value across the two new values we + # need. + bs = b64decode(token.unblinded_token.encode("ascii")) + preimage = bs[:48] + b"x" * 16 + signature = bs[48:] + b"y" * 16 + return Pass( + b64encode(preimage).decode("ascii"), + b64encode(signature).decode("ascii"), + ) return list( - Pass(token.unblinded_token) + token_to_pass(token) for token in unblinded_tokens ) @@ -487,16 +497,15 @@ class RistrettoRedeemer(object): for token in unblinded_tokens ) - marshaled_passes = list( - preimage.encode_base64() + b" " + signature.encode_base64() + passes = list( + Pass( + preimage.encode_base64().decode("ascii"), + signature.encode_base64().decode("ascii"), + ) for (preimage, signature) in zip(clients_preimages, clients_signatures) ) - return list( - Pass(p.decode("ascii")) - for p - in marshaled_passes - ) + return passes @attr.s diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py index 7b309a30c12527062998ec283de3472149c2d00f..114715b903dde719c1bde945ed61cb17bf492f45 100644 --- a/src/_zkapauthorizer/model.py +++ b/src/_zkapauthorizer/model.py @@ -665,14 +665,32 @@ class Pass(object): """ A ``Pass`` instance completely represents a single Zero-Knowledge Access Pass. - :ivar unicode text: The text value of the pass. This can be sent to a - service provider one time to anonymously prove a prior voucher + :ivar unicode pass_text: The text value of the pass. This can be sent to + a service provider one time to anonymously prove a prior voucher redemption. If it is sent more than once the service provider may choose to reject it and the anonymity property is compromised. Pass text should be kept secret. If pass text is divulged to third-parties the anonymity property may be compromised. """ - text = attr.ib(validator=attr.validators.instance_of(unicode)) + preimage = attr.ib( + validator=attr.validators.and_( + attr.validators.instance_of(unicode), + is_base64_encoded(), + has_length(88), + ), + ) + + signature = attr.ib( + validator=attr.validators.and_( + attr.validators.instance_of(unicode), + is_base64_encoded(), + has_length(88), + ), + ) + + @property + def pass_text(self): + return u"{} {}".format(self.preimage, self.signature) @attr.s(frozen=True) diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py index d62b84025900f9866983abef6b8f39633f3a14f7..a4f4eadd80644a13a045015aa8fbf21d0f3cc043 100644 --- a/src/_zkapauthorizer/tests/strategies.py +++ b/src/_zkapauthorizer/tests/strategies.py @@ -81,10 +81,14 @@ from ..model import ( # Sizes informed by # https://github.com/brave-intl/challenge-bypass-ristretto/blob/2f98b057d7f353c12b2b12d0f5ae9ad115f1d0ba/src/oprf.rs#L18-L33 +# The length of a `TokenPreimage`, in bytes. +_TOKEN_PREIMAGE_LENGTH = 64 # The length of a `Token`, in bytes. _TOKEN_LENGTH = 96 # The length of a `UnblindedToken`, in bytes. _UNBLINDED_TOKEN_LENGTH = 96 +# The length of a `VerificationSignature`, in bytes. +_VERIFICATION_SIGNATURE_LENGTH = 64 def _merge_dictionaries(dictionaries): result = {} @@ -339,16 +343,41 @@ def random_tokens(): ) +def token_preimages(): + """ + Build ``unicode`` strings representing base64-encoded token preimages. + """ + return byte_strings( + label=b"token-preimage", + length=_TOKEN_PREIMAGE_LENGTH, + entropy=4, + ).map( + lambda bs: b64encode(bs).decode("ascii"), + ) + + +def verification_signatures(): + """ + Build ``unicode`` strings representing base64-encoded verification + signatures. + """ + return byte_strings( + label=b"verification-signature", + length=_VERIFICATION_SIGNATURE_LENGTH, + entropy=4, + ).map( + lambda bs: b64encode(bs).decode("ascii"), + ) + + def zkaps(): """ Build random ZKAPs as ``Pass` instances. """ return builds( - lambda preimage, signature: Pass(u"{} {}".format(preimage, signature)), - # Sizes informed by - # https://github.com/brave-intl/challenge-bypass-ristretto/blob/2f98b057d7f353c12b2b12d0f5ae9ad115f1d0ba/src/oprf.rs#L18-L33 - preimage=binary(min_size=64, max_size=64).map(urlsafe_b64encode), - signature=binary(min_size=64, max_size=64).map(urlsafe_b64encode), + Pass, + preimage=token_preimages(), + signature=verification_signatures(), ) diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py index 5485b16bf2bcd0f0790ec7b2f123901c7fef88b2..dec0176991e75bc905b725fd6a08ba165c4b1d3c 100644 --- a/src/_zkapauthorizer/tests/test_controller.py +++ b/src/_zkapauthorizer/tests/test_controller.py @@ -469,7 +469,7 @@ def ristretto_verify(signing_key, message, marshaled_passes): VerificationSignature.decode_base64(s.encode("ascii")), ) servers_passes = list( - decode(marshaled_pass.text) + decode(marshaled_pass.pass_text) for marshaled_pass in marshaled_passes ) diff --git a/src/_zkapauthorizer/tests/test_storage_protocol.py b/src/_zkapauthorizer/tests/test_storage_protocol.py index d595e7c104c3ac0edf79d374131ed36a505f8711..faa4f8f76517062fbc1cea13279f830f73cd373c 100644 --- a/src/_zkapauthorizer/tests/test_storage_protocol.py +++ b/src/_zkapauthorizer/tests/test_storage_protocol.py @@ -211,7 +211,7 @@ class ShareTests(TestCase): def get_passes(message, count): self.spent_passes += count return list( - Pass(pass_.decode("ascii")) + Pass(*pass_.split(u" ")) for pass_ in make_passes( self.signing_key, diff --git a/src/_zkapauthorizer/tests/test_storage_server.py b/src/_zkapauthorizer/tests/test_storage_server.py index 8d28db831e18aa3dff385db6e12fe2d7208a68b2..4cef248a8ae6b09f38d7968c220903fa45480b42 100644 --- a/src/_zkapauthorizer/tests/test_storage_server.py +++ b/src/_zkapauthorizer/tests/test_storage_server.py @@ -127,7 +127,11 @@ class PassValidationTests(TestCase): message, list(RandomToken.create() for i in range(valid_count)), ) - all_passes = valid_passes + list(pass_.text.encode("ascii") for pass_ in invalid_passes) + all_passes = valid_passes + list( + pass_.pass_text.encode("ascii") + for pass_ + in invalid_passes + ) shuffle(all_passes) self.assertThat(