Internet-Draft | Privacy Pass Issuance | July 2022 |
Celi, et al. | Expires 7 January 2023 | [Page] |
This document specifies two variants of the the two-message issuance protocol for Privacy Pass tokens: one that produces tokens that are privately verifiable, and another that produces tokens that are publicly verifiable. The privately verifiable issuance protocol optionally supports public metadata during the issuance flow.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 7 January 2023.¶
Copyright (c) 2022 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
The Privacy Pass protocol provides a privacy-preserving authorization mechanism. In essence, the protocol allows clients to provide cryptographic tokens that prove nothing other than that they have been created by a given server in the past [I-D.ietf-privacypass-architecture].¶
This document describes the issuance protocol for Privacy Pass. It specifies two variants: one that is privately verifiable based on the oblivious pseudorandom function from [OPRF], and one that is publicly verifiable based on the blind RSA signature scheme [BLINDRSA].¶
This document DOES NOT cover the architectural framework required for running and maintaining the Privacy Pass protocol in the Internet setting. In addition, it DOES NOT cover the choices that are necessary for ensuring that client privacy leaks do not occur. Both of these considerations are covered in [I-D.ietf-privacypass-architecture].¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
The following terms are used throughout this document.¶
We assume that all protocol messages are encoded into raw byte format before being sent across the wire.¶
Issuers MUST provide two parameters for configuration:¶
The Issuer parameters can be obtained from an Issuer via a directory object, which is a JSON object whose values are other JSON objects and URLs for the parameters.¶
Field Name | Value |
---|---|
issuer-request-uri | Issuer Request URI resource URL as a JSON string |
token-keys | List of Issuer Public Key values, each as JSON objects |
Each "token-keys" JSON object contains the following fields and corresponding raw values.¶
Field Name | Value |
---|---|
token-type | Integer value of the Token Type, as defined in Section 8.1, as a JSON number |
token-key | The base64url encoding of the public key for use with the issuance protocol, including padding, as a JSON string |
Issuers MAY advertise multiple token-keys for the same token-type to support key rotation. In this case, Issuers indicate preference for which token key to use based on the order of keys in the list, with preference given to keys earlier in the list.¶
Altogether, the Issuer's JSON directory could look like:¶
{ "issuer-request-uri": "https://issuer.example.net/example-token-request", "token-keys": [ { "token-type": 2, "token-key": "MI...AB", }, { "token-type": 2, "token-key": "MI...AQ", } ] }¶
Issuer directory resources have the media type "application/json" and are located at the well-known location /.well-known/token-issuer-directory.¶
Clients receive challenges for tokens, as described in [AUTHSCHEME]. The basic token issuance protocols described in this document can be interactive or non-interactive, and per-origin or cross-origin.¶
The Privacy Pass issuance protocol is a two message protocol that takes as input a challenge from the redemption protocol and produces a token, as shown in the figure below.¶
Origin Client Issuer (pkI) (skI, pkI) +------------------------------------\ Challenge ----> TokenRequest -------------> | | (evaluate) | Token <----+ <--------------- TokenResponse | \------------------------------------/¶
Issuers provide a Private and Public Key, denoted skI and pkI, respectively, used to produce tokens as input to the protocol. See Section 5.5 for how this key pair is generated.¶
Clients provide the following as input to the issuance protocol:¶
key_id
computed as
described in Section 5.5.¶
challenge
, an opaque byte string. For example, this might
be provided by the redemption protocol in [HTTP-Authentication].¶
Given this configuration and these inputs, the two messages exchanged in this protocol are described below. This section uses notation described in [OPRF], Section 4, including SerializeElement and DeserializeElement, SerializeScalar and DeserializeScalar, and DeriveKeyPair.¶
The Client first creates a context as follows:¶
client_context = SetupVOPRFClient(0x0004, pkI)¶
Here, 0x0004 is the two-octet identifier corresponding to the OPRF(P-384, SHA-384) ciphersuite in [OPRF]. SetupVOPRFClient is defined in [OPRF], Section 3.2.¶
The Client then creates an issuance request message for a random value nonce
using the input challenge and Issuer key identifier as follows:¶
nonce = random(32) context = SHA256(challenge) token_input = concat(0x0001, nonce, context, key_id) blind, blinded_element = client_context.Blind(token_input)¶
The Blind function is defined in [OPRF], Section 3.3.2. If the Blind function fails, the Client aborts the protocol. Otherwise, the Client then creates a TokenRequest structured as follows:¶
struct { uint16_t token_type = 0x0001; uint8_t token_key_id; uint8_t blinded_msg[Ne]; } TokenRequest;¶
The structure fields are defined as follows:¶
key_id
in network byte order (in other words, the last 8 bits of key_id
).¶
SerializeElement(blinded_element)
. Ne is as defined in [OPRF], Section 4.¶
The values token_input
and blinded_element
are stored locally and used later
as described in Section 5.3. The Client then generates an HTTP POST request
to send to the Issuer, with the TokenRequest as the body. The media type for
this request is "message/token-request". An example request is shown below.¶
:method = POST :scheme = https :authority = issuer.example.net :path = /example-token-request accept = message/token-response cache-control = no-cache, no-store content-type = message/token-request content-length = <Length of TokenRequest> <Bytes containing the TokenRequest>¶
Upon receipt of the request, the Issuer validates the following conditions:¶
If any of these conditions is not met, the Issuer MUST return an HTTP 400 error to the client.¶
Upon receipt of a TokenRequest, the Issuer tries to deseralize TokenRequest.blinded_msg
using DeserializeElement from Section 2.1 of [OPRF], yielding blinded_element
.
If this fails, the Issuer MUST return an HTTP 400 error to the client. Otherwise, if the
Issuer is willing to produce a token token to the Client, the Issuer completes the issuance
flow by computing a blinded response as follows:¶
server_context = SetupVOPRFServer(0x0004, skI, pkI) evaluate_element, proof = server_context.Evaluate(skI, blinded_element)¶
SetupVOPRFServer is in [OPRF], Section 3.2 and Evaluate is defined in [OPRF], Section 3.3.2. The Issuer then creates a TokenResponse structured as follows:¶
struct { uint8_t evaluate_msg[Nk]; uint8_t evaluate_proof[Ns+Ns]; } TokenResponse;¶
The structure fields are defined as follows:¶
SerializeElement(evaluate_element)
.¶
concat(SerializeScalar(proof[0]), SerializeScalar(proof[1]))
,
where Ns is as defined in [OPRF], Section 4.¶
The Issuer generates an HTTP response with status code 200 whose body consists of TokenResponse, with the content type set as "message/token-response".¶
:status = 200 content-type = message/token-response content-length = <Length of TokenResponse> <Bytes containing the TokenResponse>¶
Upon receipt, the Client handles the response and, if successful, deserializes
the body values TokenResponse.evaluate_response and TokenResponse.evaluate_proof,
yielding evaluated_element
and proof
. If deserialization of either value fails,
the Client aborts the protocol. Otherwise, the Client processes the response as
follows:¶
authenticator = client_context.Finalize(token_input, blind, evaluated_element, blinded_element, proof)¶
The Finalize function is defined in [OPRF], Section 3.3.2. If this succeeds, the Client then constructs a Token as follows:¶
struct { uint16_t token_type = 0x0001 uint8_t nonce[32]; uint8_t challenge_digest[32]; uint8_t token_key_id[32]; uint8_t authenticator[Nk]; } Token;¶
Otherwise, the Client aborts the protocol.¶
To verify a token, a verifier creates a VOPRF context, evaluates the token contents, and compares the result against the token authenticator value, as follows:¶
server_context = SetupVOPRFServer(0x0004, skI, pkI) token_authenticator_input = concat(Token.token_type, Token.nonce, Token.challenge_digest, Token.token_key_id) token_authenticator = server_context.Evaluate(token_authenticator_input) valid = (token_authenticator == Token.authenticator)¶
Issuers are configured with Private and Public Key pairs, each denoted skI and pkI, respectively, used to produce tokens. Each key pair MUST be generated as follows:¶
seed = random(Ns) (skI, pkI) = DeriveKeyPair(seed, "PrivacyPass")¶
The key identifier for this specific key pair, denoted key_id
, is computed
as follows:¶
key_id = SHA256(concat(0x0001, SerializeElement(pkI)))¶
This section describes a variant of the issuance protocol in Section 5 for producing publicly verifiable tokens. It differs from the previous variant in two important ways:¶
The first property means that any Origin can select a given Issuer to produce tokens, as long as the Origin has the Issuer public key, without explicit coordination or permission from the Issuer. This is because the Issuer does not learn the Origin that requested the token during the issuance protocol.¶
Beyond these differences, the publicly verifiable issuance protocol variant is nearly identical to the privately verifiable issuance protocol variant. In particular, Issuers provide a Private and Public Key, denoted skI and pkI, respectively, used to produce tokens as input to the protocol. See Section 6.5 for how this key pair is generated.¶
Clients provide the following as input to the issuance protocol:¶
key_id
computed as
described in Section 6.5.¶
challenge
, an opaque byte string. For example, this might
be provided by the redemption protocol in [HTTP-Authentication].¶
Given this configuration and these inputs, the two messages exchanged in this protocol are described below.¶
The Client first creates an issuance request message for a random value
nonce
using the input challenge and Issuer key identifier as follows:¶
nonce = random(32) context = SHA256(challenge) token_input = concat(0x0002, nonce, context, key_id) blinded_msg, blind_inv = rsabssa_blind(pkI, token_input)¶
The rsabssa_blind function is defined in [BLINDRSA], Section 5.1.1.. The Client then creates a TokenRequest structured as follows:¶
struct { uint16_t token_type = 0x0002 uint8_t token_key_id; uint8_t blinded_msg[Nk]; } TokenRequest;¶
The structure fields are defined as follows:¶
key_id
in network byte order (in other words, the last 8 bits of key_id
).¶
The Client then generates an HTTP POST request to send to the Issuer, with the TokenRequest as the body. The media type for this request is "message/token-request". An example request is shown below, where Nk = 512.¶
:method = POST :scheme = https :authority = issuer.example.net :path = /example-token-request accept = message/token-response cache-control = no-cache, no-store content-type = message/token-request content-length = <Length of TokenRequest> <Bytes containing the TokenRequest>¶
Upon receipt of the request, the Issuer validates the following conditions:¶
If any of these conditions is not met, the Issuer MUST return an HTTP 400 error to the Client, which will forward the error to the client.¶
If the Issuer is willing to produce a token token to the Client, the Issuer completes the issuance flow by computing a blinded response as follows:¶
blind_sig = rsabssa_blind_sign(skI, TokenRequest.blinded_rmsg)¶
This is encoded and transmitted to the client in the following TokenResponse structure:¶
struct { uint8_t blind_sig[Nk]; } TokenResponse;¶
The rsabssa_blind_sign function is defined in [BLINDRSA], Section 5.1.2.. The Issuer generates an HTTP response with status code 200 whose body consists of TokenResponse, with the content type set as "message/token-response".¶
:status = 200 content-type = message/token-response content-length = <Length of TokenResponse> <Bytes containing the TokenResponse>¶
Upon receipt, the Client handles the response and, if successful, processes the body as follows:¶
authenticator = rsabssa_finalize(pkI, nonce, blind_sig, blind_inv)¶
The rsabssa_finalize function is defined in [BLINDRSA], Section 5.1.3.. If this succeeds, the Client then constructs a Token as described in [HTTP-Authentication] as follows:¶
struct { uint16_t token_type = 0x0002 uint8_t nonce[32]; uint8_t challenge_digest[32]; uint8_t token_key_id[32]; uint8_t authenticator[Nk]; } Token;¶
Otherwise, the Client aborts the protocol.¶
To verify a token, a verifier checks that Token.authenticator is a valid
signature over the remainder of the token input as described below. The
function RSA-Verify(msg, pk, sig)
is a procedure to verify a signature sig
over message msg
using the public key pk
. Its implementation is not
specified in this document.¶
token_authenticator_input = concat(Token.token_type, Token.nonce, Token.challenge_digest, Token.token_key_id) valid = RSA-Verify(token_authenticator_input, pkI, Token.authenticator)¶
Issuers are configured with Private and Public Key pairs, each denoted skI and pkI, respectively, used to produce tokens. Each key pair SHALL be generated as as specified in FIPS 186-4 [DSS].¶
The key identifier for a keypair (skI, pkI), denoted key_id
, is computed as
SHA256(encoded_key), where encoded_key is a DER-encoded SubjectPublicKeyInfo
(SPKI) object carrying pkI. The SPKI object MUST use the RSASSA-PSS OID [RFC5756],
which specifies the hash algorithm and salt size. The salt size MUST match the
output size of the hash function associated with the public key and token type.¶
This document outlines how to instantiate the Issuance protocol based on the VOPRF defined in [OPRF] and blind RSA protocol defnied in [BLINDRSA]. All security considerations described in the VOPRF document also apply in the Privacy Pass use-case. Considerations related to broader privacy and security concerns in a multi-Client and multi-Issuer setting are deferred to the Architecture document [I-D.ietf-privacypass-architecture].¶
This document updates the "Token Type" Registry with the following values.¶
Value | Name | Publicly Verifiable | Public Metadata | Private Metadata | Nk | Reference |
---|---|---|---|---|---|---|
0x0001 | VOPRF (P-384, SHA-384) | N | N | N | 48 | Section 5 |
0x0002 | Blind RSA (SHA-384, 2048-bit) | Y | N | N | 256 | Section 6 |
This specification defines the following protocol messages, along with their corresponding media types:¶
The definition for each media type is in the following subsections.¶
message¶
token-request¶
N/A¶
None¶
only "8bit" or "binary" is permitted¶
N/A¶
this specification¶
N/A¶
N/A¶
see Authors' Addresses section¶
COMMON¶
N/A¶
see Authors' Addresses section¶
IESG¶
message¶
access-token-response¶
N/A¶
None¶
only "8bit" or "binary" is permitted¶
N/A¶
this specification¶
N/A¶
N/A¶
see Authors' Addresses section¶
COMMON¶
N/A¶
see Authors' Addresses section¶
IESG¶
The authors of this document would like to acknowledge the helpful feedback and discussions from Benjamin Schwartz, Joseph Salowey, Sofia Celi, and Tara Whalen.¶
This section includes test vectors for the two basic issuance protocols specified in this document. Appendix B.1 contains test vectors for token issuance protocol 1 (0x0001), and Appendix B.2 contains test vectors for token issuance protocol 2 (0x0002).¶
The test vector below lists the following values:¶
skS: 0177781aeced893dccdf80713d318a801e2a0498240fdcf650304bbbfd0f8d3b5c0 cf6cfee457aaa983ec02ff283b7a9 pkS: 022c63f79ac59c0ba3d204245f676a2133bd6120c90d67afa05cd6f8614294b7366 c252c6458300551b79a4911c2590a36 challenge: a5d46383359ef34e3c4a7b8d1b3165778bffc9b70c9e6a60dd14143e4c9c9fbd nonce: 5d4799f8338ddc50a6685f83b8ecd264b2f157015229d12b3384c0f199efe7b8 blind: 0322fec505230992256296063d989b59cc03e83184eb6187076d264137622d202 48e4e525bdc007b80d1560e0a6f49d9 token_request: 00011a02861fd50d14be873611cff0131d2c872c79d0260c6763498a2 a3f14ca926009c0f247653406e1d52b68d61b7ed2bac9ea token_response: 038e3625b6a769668a99680e46cf9479f5dc1e86d57164ab3b4a569d dfc486bf1485d4916a5194fdc0518d3e8444968421ba36e8144aa7902705ff0f3cf40586 3d69451a2a7ba210cc45760c2f1a6045134d877b39e8bcbbf920e5de4a3372557debf211 765cd969976860bc039f9082d6a3e03f8e891246240173d2cf3d69a4613b0f8415979029 22e74c7a1f2e4639e4 token: 00015d4799f8338ddc50a6685f83b8ecd264b2f157015229d12b3384c0f199efe 7b8742cdfb0ed756ea680868ef109a280a393e001d2fa56b1be46ecb31fa25e76731a5b1 d698ea7ab843b8e8a71ed9b2fffa70457a43a8fc687939424b29a7554b40fde130ab7a82 2715909cb73f99a45b640ca1c85180ba9ca1a40bab8b664406a34bcbc63b5e2e5c455cea 00001a968f7¶
The test vector below lists the following values:¶
skS: 2d2d2d2d2d424547494e2050524956415445204b45592d2d2d2d2d0a4d494945765 149424144414e42676b71686b6947397730424151454641415343424b63776767536a416 74541416f49424151444c4775317261705831736334420a4f6b7a38717957355379356b6 f6a41303543554b66717444774e38366a424b5a4f76457245526b49314c527876734d645 3327961326333616b4745714c756b440a556a35743561496b3172417643655844644e445 03442325055707851436e6969396e6b492b6d67725769744444494871386139793137586 e6c5079596f784f530a646f6558563835464f314a752b62397336356d586d34516a75513 94559614971383371724450567a50335758712b524e4d636379323269686763624c766d4 2390a6a41355334475666325a6c74785954736f4c364872377a58696a4e3946374862716 5676f753967654b524d584645352f2b4a3956595a634a734a624c756570480a544f72535 a4d4948502b5358514d4166414f454a4547426d6d4430683566672f43473475676a79486 e4e51383733414e4b6a55716d3676574574413872514c620a4530742b496c706641674d4 241414543676745414c7a4362647a69316a506435384d6b562b434c6679665351322b726 6486e7266724665502f566344787275690a3270316153584a596962653645532b4d622f4 d4655646c485067414c773178513457657266366336444373686c6c784c5753563847734 2737663386f364750320a6359366f777042447763626168474b556b5030456b623953305 84c4a57634753473561556e484a585237696e7834635a6c666f4c6e72455165366855787 34d710a6230644878644844424d644766565777674b6f6a4f6a70532f39386d455579375 6422f3661326c7265676c766a632f326e4b434b7459373744376454716c47460a787a414 261577538364d435a342f5131334c762b426566627174493973715a5a776a72645568514 83856437872793251564d515751696e57684174364d7154340a53425354726f6c5a7a777 2716a65384d504a393175614e4d6458474c63484c49323673587a76374b53514b4267514 4766377735055557641395a325a583958350a6d49784d54424e6445467a56625550754b4 b413179576e31554d444e63556a71682b7a652f376b337946786b6830514633316271363 0654c393047495369414f0a354b4f574d39454b6f2b7841513262614b314d664f5931472 b386a7a42585570427339346b353353383879586d4b366e796467763730424a385a68356 66b55710a5732306f5362686b686a5264537a48326b52476972672b5553774b426751445 a4a4d6e7279324578612f3345713750626f737841504d69596e6b354a415053470a79327 a305a375455622b7548514f2f2b78504d376e433075794c494d44396c61544d48776e367 3372f4c62476f455031575267706f59482f4231346b2f526e360a667577524e3632496f3 97463392b41434c745542377674476179332b675277597453433262356564386c4969656 774546b6561306830754453527841745673330a6e356b796132513976514b4267464a754 67a4f5a742b7467596e576e51554567573850304f494a45484d45345554644f637743784 b7248527239334a6a7546320a453377644b6f546969375072774f59496f614a5468706a5 0634a62626462664b792b6e735170315947763977644a724d6156774a637649707756367 6315570660a56744c61646d316c6b6c7670717336474e4d386a6e4d30587833616a6d6d6 e66655739794758453570684d727a4c4a6c394630396349324c416f4742414e58760a756 75658727032627354316f6b6436755361427367704a6a5065774e526433635a4b397a306 153503144544131504e6b7065517748672f2b36665361564f487a0a79417844733968355 272627852614e6673542b7241554837783153594456565159564d68555262546f5a65364 72f6a716e544333664e6648563178745a666f740a306c6f4d4867776570362b53494d436 f6565325a6374755a5633326c63496166397262484f633764416f47416551386b3853494 c4e4736444f413331544535500a6d3031414a49597737416c5233756f2f524e61432b785 96450553354736b75414c78786944522f57734c455142436a6b46576d6d4a41576e51554 474626e594e0a536377523847324a36466e72454374627479733733574156476f6f465a6 e636d504c50386c784c79626c534244454c79615a762f624173506c4d4f39624435630a4 a2b4e534261612b6f694c6c31776d4361354d43666c633d0a2d2d2d2d2d454e442050524 956415445204b45592d2d2d2d2d0a pkS: 30820152303d06092a864886f70d01010a3030a00d300b060960864801650304020 2a11a301806092a864886f70d010108300b0609608648016503040202a20302013003820 10f003082010a0282010100cb1aed6b6a95f5b1ce013a4cfcab25b94b2e64a23034e4250 a7eab43c0df3a8c12993af12b111908d4b471bec31d4b6c9ad9cdda90612a2ee903523e6 de5a224d6b02f09e5c374d0cfe01d8f529c500a78a2f67908fa682b5a2b430c81eaf1af7 2d7b5e794fc98a3139276879757ce453b526ef9bf6ceb99979b8423b90f4461a22af37aa b0cf5733f7597abe44d31c732db68a181c6cbbe607d8c0e52e0655fd9996dc584eca0be8 7afbcd78a337d17b1dba9e828bbd81e291317144e7ff89f55619709b096cbb9ea474cead 264c2073fe49740c01f00e109106066983d21e5f83f086e2e823c879cd43cef700d2a352 a9babd612d03cad02db134b7e225a5f0203010001 challenge: 3f5a1c30d13f860622458ce836d8af325378054370fe8a3d771eebd67d4d810d nonce: c0fcbbb243d8f5d4f661dbdefca95879b39aeccb77b7db731b59c09688773125 blind: 04d00c700128b4b201b4bec4f05d942bc903d49c26568b5956e0827590d2e4b43 570105ae492f655d41a3d68f1cc6a9a2895c36fd45c88239257f2e6cee5bd88e7d870f35 67069d78f8b85947c7ab123b16c9f3b76d856112802dd0fefa800a9c3807fbb5d949481b 4f7a21da0269f17611b93dfa7197e87ef9c1ef9c2fd0f86119917cdf01284038435f2df3 f8ae2935ae0ef5440b3b4ac12fed83a03bc494abaa87241d624d2dcb0c6a64422eb63dbf ba0193161648e5b2afbdf3140901840c7d08a0e2953320fffa09641500122ba81c5907e7 ebd4d2384221ddb99439c2465138b98348b58a5f89b4e05b70856a270e1f5308512e368c fe6dfe4cf3759ed salt: 4daf07bc96a829736ce6386a4d3ed988192ea4f0acb3ed715dca2ae688c16ad346 ee5e2b3dd26eb2868639a778e3bc5d token_request: 0002ca832fffabdd44e2cd54e5e24d74519d297608aec9ab88e26b732 adcb382781e7e2657c8b94751b9fa6b2ed02cec383f8cd04e9627d5b62a7f1b7ea16b81e 46f35637cca49f8990d5359f8a7dcac1ba58fb685d4b32a67621d368cc112197d4f84ee5 241c359299cb5fc41182bd65bba112f35a4073d1231290447fb884888ba84eb5b4602534 787aa1e167bc1ddcceb7fb5ad43e2b242fd4b4939349897cfb911cf0f3785847edaeaca6 350c16cb05b7882ec076a3adde7c361f54d6eb67ec239aeafe8a4816b29e4c6aa8bf2873 ba36ec6e2b9596aa508b5e34543a469286be2404f1f481f6a274a2afb429d62377f7ab6d e56379d2c42f7205e3bf1c74d3159 token_response: 6e7d5334765bea44ea43b81ae8f41334fdac47b3dfaaeb2c3b99f42a 67d8239592ac4fa129a938e139bf052d85804bdaa90f7f54fdfa34d6efeaea0ccc15a500 fb2987b534d0558e8d32df68b3533f6cbc953dabcfff2ef6b6af336c1128f607f0796190 6a2fed919691340e751a8e2173e674569f7e4beb7ad0ee5c65ce82ad477d3e44b3755bcd 0f168ab85ce662d3f87c5634be036382d6ad4ab870ab975e8bffd0b95bcf457dc83337ff ea85b7c77d44e5cb4bddc5aecfc958cc822cc53ded3da699af86bfad3054fe49da8eeb55 162e444a3b4d438f9e3cbadd50cba56b4f3f0718a65e7d8dfc40762cdb9962edc731f6a7 ec8641cbf98a0ec9cdf8b7f6 token: 0002c0fcbbb243d8f5d4f661dbdefca95879b39aeccb77b7db731b59c09688773 125ad76ab53adc2ca44e4eaae3d71b9bf3fc9332122faeef07cb70d9e04da68c6a7ca572 f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd27080d1f816364e5d 4d516d2f3e80366e56edc1de4ba0d7aed2675c15156d774b311778091bf5f2aea9926156 2289459a41c5739dec6dc42447744fe07c53c9d090f053263d019255cdfc27739132bd68 21ad49f1a98db6873319d04c04703d74a8fe1d0806b2a25b46246c5bb2ff927463b03152 589068389df89494c6d82f3b92be773a9fe6bc1fed9cbf26bdfbae1ff369f20d0267cdd2 0f3bcba30f8b0c0e9d9a1a39a40156b0614030d5099aa36f085347681aef502f3d081b36 cd79f7ea14df1ca9694320fc44ccbc7c5d90aeedc915af3ac11a3baf562d38c8213e39f6 731fa5e701697d0bfbfcfc83b447945b351115a20770370226b52a19df939f3080e¶