├── .github └── workflows │ ├── go.yml │ └── semgrep.yml ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── ecdsa ├── LICENSE ├── ecdsa.go ├── ecdsa_noasm.go ├── ecdsa_s390x.go ├── ecdsa_s390x_test.go ├── ecdsa_test.go ├── equal_test.go ├── example_test.go ├── randutil.go └── testdata │ └── SigVer.rsp.bz2 ├── ed25519 ├── LICENSE ├── ed25519.go ├── ed25519_test.go ├── internal │ └── edwards25519 │ │ ├── doc.go │ │ ├── edwards25519.go │ │ ├── edwards25519_test.go │ │ ├── field │ │ ├── fe.go │ │ ├── fe_alias_test.go │ │ ├── fe_amd64_noasm.go │ │ ├── fe_arm64_noasm.go │ │ ├── fe_bench_test.go │ │ ├── fe_generic.go │ │ └── fe_test.go │ │ ├── scalar.go │ │ ├── scalar_alias_test.go │ │ ├── scalar_test.go │ │ ├── scalarmult.go │ │ ├── scalarmult_test.go │ │ ├── tables.go │ │ └── tables_test.go └── testdata │ └── sign.input.gz ├── go.mod ├── go.sum ├── quicwire ├── wire.go └── wire_test.go ├── scripts ├── format_benchmarks.py ├── format_ecdsa_benchmarks.py ├── format_ed25519_benchmarks.py └── format_test_vectors.py ├── tokens ├── batched │ ├── batched-issuance-test-vectors-rust.json │ ├── batched-issuance-test-vectors.json │ ├── batched_test.go │ ├── client.go │ ├── issuer.go │ ├── token_request.go │ └── tokens.go ├── token-test-vectors.json ├── token.go ├── token_challenge.go ├── token_challenge_test.go ├── token_request.go ├── type1 │ ├── client.go │ ├── issuer.go │ ├── token.go │ ├── token_request.go │ ├── type1-issuance-test-vectors.json │ └── type1_test.go ├── type2 │ ├── client.go │ ├── issuer.go │ ├── token.go │ ├── token_request.go │ ├── type2-issuance-test-vectors.json │ └── type2_test.go ├── type3 │ ├── attester.go │ ├── client.go │ ├── crypto_test.go │ ├── encap_key.go │ ├── inner_token_request.go │ ├── issuer.go │ ├── token.go │ ├── token_request.go │ ├── token_request_test.go │ ├── type3-anon-origin-id-test-vectors.json │ ├── type3-ecdsa-blinding-test-vectors.json │ ├── type3-ed25519-blinding-test-vectors.json │ ├── type3-origin-encryption-test-vectors.json │ └── type3_test.go └── type5 │ ├── client.go │ ├── issuer.go │ ├── token.go │ ├── token_request.go │ ├── type5-issuance-test-vectors.json │ └── type5_test.go └── util ├── codec.go ├── x509util.go └── x509util_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v5 18 | with: 19 | go-version: 1.23 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: | 26 | go test -v --count=1 ./... 27 | make test 28 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: {} 3 | workflow_dispatch: {} 4 | push: 5 | branches: 6 | - main 7 | - master 8 | schedule: 9 | - cron: '0 0 * * *' 10 | name: Semgrep config 11 | jobs: 12 | semgrep: 13 | name: semgrep/ci 14 | runs-on: ubuntu-latest 15 | if: github.repository == 'cloudflare/pat-go' 16 | env: 17 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 18 | SEMGREP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 20 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 21 | container: 22 | image: semgrep/semgrep 23 | steps: 24 | - uses: actions/checkout@v4 25 | - run: semgrep ci 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Cloudflare, Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 7 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | go test ./... --count=1 3 | BATCHED_ISSUANCE_TEST_VECTORS_IN=batched-issuance-test-vectors-rust.json go test -v -run TestVectorVerifyBatchedIssuance ./tokens/batched 4 | 5 | vectors: test 6 | BATCHED_ISSUANCE_TEST_VECTORS_OUT=batched-issuance-test-vectors.json go test -v -run TestVectorGenerateBatchedIssuance ./... 7 | ED25519_BLINDING_TEST_VECTORS_OUT=type3-ed25519-blinding-test-vectors.json go test -v -run TestVectorGenerateEd25519Blinding ./... 8 | ECDSA_BLINDING_TEST_VECTORS_OUT=type3-ecdsa-blinding-test-vectors.json go test -v -run TestVectorGenerateECDSABlinding ./... 9 | TOKEN_TEST_VECTORS_OUT=token-test-vectors.json go test -v -run TestVectorGenerateToken ./... 10 | TYPE1_ISSUANCE_TEST_VECTORS_OUT=type1-issuance-test-vectors.json go test -v -run TestVectorGenerateBasicPrivateIssuance ./... 11 | TYPE5_ISSUANCE_TEST_VECTORS_OUT=type5-issuance-test-vectors.json go test -v -run TestVectorGenerateBatchedPrivateIssuance ./... 12 | TYPE2_ISSUANCE_TEST_VECTORS_OUT=type2-issuance-test-vectors.json go test -v -run TestVectorGenerateBasicIssuance ./... 13 | TYPE3_ANON_ORIGIN_ID_TEST_VECTORS_OUT=type3-anon-origin-id-test-vectors.json go test -v -run TestVectorGenerateAnonOriginID ./... 14 | TYPE3_ORIGIN_ENCRYPTION_TEST_VECTORS_OUT=type3-origin-encryption-test-vectors.json go test -v -run TestVectorGenerateOriginEncryption ./... 15 | 16 | bench: 17 | go test -bench=. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Private Access Tokens Go Library 2 | 3 | This repository provides a Go implementation of the [basic](https://ietf-wg-privacypass.github.io/base-drafts/draft-ietf-privacypass-protocol.html) and [rate-limited](https://ietf-wg-privacypass.github.io/draft-ietf-privacypass-rate-limit-tokens/draft-ietf-privacypass-rate-limit-tokens.html) Privacy Pass issuance protocols. It is meant for experimental and interop purposes, and not to be used in production. It is expected that changes in the code, repository, and API may occur in the future as the Privacy Pass standard evolves. 4 | 5 | ## Test vectors 6 | 7 | To generate test vectors, run: 8 | 9 | ``` 10 | $ make vectors 11 | ``` 12 | 13 | This will produce several JSON files: 14 | 15 | - anon-origin-id-test-vectors.json: Test vectors for computing the [Anonymous Issuer Origin ID value](https://ietf-wg-privacypass.github.io/draft-ietf-privacypass-rate-limit-tokens/draft-ietf-privacypass-rate-limit-tokens.html#name-anonymous-issuer-origin-id-) in the rate-limited issuance protocol. 16 | - basic-issuance-test-vectors.json: Test vectors for the [private basic issuance protocol](https://ietf-wg-privacypass.github.io/base-drafts/draft-ietf-privacypass-protocol.html#name-issuance-protocol-for-priva) (type 0x0001). 17 | - basic-public-issuance-test-vectors.json: Test vectors for the [private basic issuance protocol](https://ietf-wg-privacypass.github.io/base-drafts/draft-ietf-privacypass-protocol.html#name-issuance-protocol-for-publi) (type 0x0002). 18 | - ed25519-blinding-test-vectors.json: Test vectors for ed25519 key blinding and signing. 19 | - ecdsa-blinding-test-vectors.json: Test vectors for ECDSA key blinding and signing. 20 | - index-test-vectors.json: Test vectors for the client-origin index computation. 21 | - origin-encryption-test-vectors.json: Test vectors for origin name encrpytion. 22 | 23 | Examples for generating and verifying the test vectors can be found [in the Makefile](https://github.com/cloudflare/pat-go/blob/main/Makefile). 24 | 25 | ## Performance Benchmarks 26 | 27 | To compute performance benchmarks, run: 28 | 29 | ``` 30 | $ go test -bench=. 31 | ``` 32 | 33 | This will run benchmarks on each implemented protocol from end to end. As an example: 34 | 35 | ``` 36 | $ go test -bench=. 37 | goos: darwin 38 | goarch: amd64 39 | pkg: github.com/cloudflare/pat-go 40 | cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz 41 | BenchmarkPublicTokenRoundTrip/ClientRequest-12 1000000000 0.0001208 ns/op 42 | BenchmarkPublicTokenRoundTrip/IssuerEvaluate-12 1000000000 0.001364 ns/op 43 | BenchmarkPublicTokenRoundTrip/ClientFinalize-12 1000000000 0.0001122 ns/op 44 | BenchmarkRateLimitedTokenRoundTrip/ClientRequest-12 1000000000 0.01773 ns/op 45 | BenchmarkRateLimitedTokenRoundTrip/IssuerEvaluate-12 1000000000 0.01098 ns/op 46 | BenchmarkRateLimitedTokenRoundTrip/AttesterProcess-12 1000000000 0.006127 ns/op 47 | BenchmarkRateLimitedTokenRoundTrip/ClientFinalize-12 1000000000 0.0001258 ns/op 48 | PASS 49 | ok github.com/cloudflare/pat-go 0.685s 50 | ``` 51 | 52 | ### Formatting Results 53 | 54 | To produce a LaTeX table of the performance benchmarks, run the [scripts/format_benchmarks.py](format_benchmarks.py) script on the benchmark output, like so: 55 | 56 | ``` 57 | $ go test -bench=. | python3 scripts/format_benchmarks.py 58 | \begin{table}[ht!] 59 | \label{tab:bench-computation-overhead} 60 | \caption{Computation cost for basic and rate-limited issuance protocols 61 | \begin{tabular}{|l|c|} 62 | {\bf Operation} & {\bf Time (ns/op)} \hline 63 | \hline 64 | Basic Client Request & $0.0001206 $ \ \hline 65 | Basic Issuer Evaluate & $0.001389 $ \ \hline 66 | Basic Client Finalize & $0.0001130 $ \ \hline 67 | Rate-Limited Client Request & $0.01281 $ \ \hline 68 | Rate-Limited Issuer Evaluate & $0.01089 $ \ \hline 69 | Rate-Limited Attester Process & $0.006324 $ \ \hline 70 | Rate-Limited Client Finalize & $0.0001205 $ \ \hline 71 | \end{tabular} 72 | \end{table} 73 | ``` -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Vulnerabilities 2 | 3 | Please see [this page](https://www.cloudflare.com/.well-known/security.txt) for information on how to report a vulnerability to Cloudflare. Thanks! 4 | -------------------------------------------------------------------------------- /ecdsa/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Cloudflare, Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 7 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /ecdsa/ecdsa_noasm.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !s390x 6 | 7 | package ecdsa 8 | 9 | import ( 10 | "crypto/cipher" 11 | "crypto/elliptic" 12 | "math/big" 13 | ) 14 | 15 | func sign(priv *PrivateKey, csprng *cipher.StreamReader, c elliptic.Curve, hash []byte) (r, s *big.Int, err error) { 16 | return signGeneric(priv, csprng, c, hash) 17 | } 18 | 19 | func verify(pub *PublicKey, c elliptic.Curve, hash []byte, r, s *big.Int) bool { 20 | return verifyGeneric(pub, c, hash, r, s) 21 | } 22 | -------------------------------------------------------------------------------- /ecdsa/ecdsa_s390x.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ecdsa 6 | 7 | import ( 8 | "crypto/cipher" 9 | "crypto/elliptic" 10 | "internal/cpu" 11 | "math/big" 12 | ) 13 | 14 | // kdsa invokes the "compute digital signature authentication" 15 | // instruction with the given function code and 4096 byte 16 | // parameter block. 17 | // 18 | // The return value corresponds to the condition code set by the 19 | // instruction. Interrupted invocations are handled by the 20 | // function. 21 | //go:noescape 22 | func kdsa(fc uint64, params *[4096]byte) (errn uint64) 23 | 24 | // testingDisableKDSA forces the generic fallback path. It must only be set in tests. 25 | var testingDisableKDSA bool 26 | 27 | // canUseKDSA checks if KDSA instruction is available, and if it is, it checks 28 | // the name of the curve to see if it matches the curves supported(P-256, P-384, P-521). 29 | // Then, based on the curve name, a function code and a block size will be assigned. 30 | // If KDSA instruction is not available or if the curve is not supported, canUseKDSA 31 | // will set ok to false. 32 | func canUseKDSA(c elliptic.Curve) (functionCode uint64, blockSize int, ok bool) { 33 | if testingDisableKDSA { 34 | return 0, 0, false 35 | } 36 | if !cpu.S390X.HasECDSA { 37 | return 0, 0, false 38 | } 39 | switch c.Params().Name { 40 | case "P-256": 41 | return 1, 32, true 42 | case "P-384": 43 | return 2, 48, true 44 | case "P-521": 45 | return 3, 80, true 46 | } 47 | return 0, 0, false // A mismatch 48 | } 49 | 50 | func hashToBytes(dst, hash []byte, c elliptic.Curve) { 51 | l := len(dst) 52 | if n := c.Params().N.BitLen(); n == l*8 { 53 | // allocation free path for curves with a length that is a whole number of bytes 54 | if len(hash) >= l { 55 | // truncate hash 56 | copy(dst, hash[:l]) 57 | return 58 | } 59 | // pad hash with leading zeros 60 | p := l - len(hash) 61 | for i := 0; i < p; i++ { 62 | dst[i] = 0 63 | } 64 | copy(dst[p:], hash) 65 | return 66 | } 67 | // TODO(mundaym): avoid hashToInt call here 68 | hashToInt(hash, c).FillBytes(dst) 69 | } 70 | 71 | func sign(priv *PrivateKey, csprng *cipher.StreamReader, c elliptic.Curve, hash []byte) (r, s *big.Int, err error) { 72 | if functionCode, blockSize, ok := canUseKDSA(c); ok { 73 | for { 74 | var k *big.Int 75 | k, err = randFieldElement(c, *csprng) 76 | if err != nil { 77 | return nil, nil, err 78 | } 79 | 80 | // The parameter block looks like the following for sign. 81 | // +---------------------+ 82 | // | Signature(R) | 83 | // +---------------------+ 84 | // | Signature(S) | 85 | // +---------------------+ 86 | // | Hashed Message | 87 | // +---------------------+ 88 | // | Private Key | 89 | // +---------------------+ 90 | // | Random Number | 91 | // +---------------------+ 92 | // | | 93 | // | ... | 94 | // | | 95 | // +---------------------+ 96 | // The common components(signatureR, signatureS, hashedMessage, privateKey and 97 | // random number) each takes block size of bytes. The block size is different for 98 | // different curves and is set by canUseKDSA function. 99 | var params [4096]byte 100 | 101 | // Copy content into the parameter block. In the sign case, 102 | // we copy hashed message, private key and random number into 103 | // the parameter block. 104 | hashToBytes(params[2*blockSize:3*blockSize], hash, c) 105 | priv.D.FillBytes(params[3*blockSize : 4*blockSize]) 106 | k.FillBytes(params[4*blockSize : 5*blockSize]) 107 | // Convert verify function code into a sign function code by adding 8. 108 | // We also need to set the 'deterministic' bit in the function code, by 109 | // adding 128, in order to stop the instruction using its own random number 110 | // generator in addition to the random number we supply. 111 | switch kdsa(functionCode+136, ¶ms) { 112 | case 0: // success 113 | r = new(big.Int) 114 | r.SetBytes(params[:blockSize]) 115 | s = new(big.Int) 116 | s.SetBytes(params[blockSize : 2*blockSize]) 117 | return 118 | case 1: // error 119 | return nil, nil, errZeroParam 120 | case 2: // retry 121 | continue 122 | } 123 | panic("unreachable") 124 | } 125 | } 126 | return signGeneric(priv, csprng, c, hash) 127 | } 128 | 129 | func verify(pub *PublicKey, c elliptic.Curve, hash []byte, r, s *big.Int) bool { 130 | if functionCode, blockSize, ok := canUseKDSA(c); ok { 131 | // The parameter block looks like the following for verify: 132 | // +---------------------+ 133 | // | Signature(R) | 134 | // +---------------------+ 135 | // | Signature(S) | 136 | // +---------------------+ 137 | // | Hashed Message | 138 | // +---------------------+ 139 | // | Public Key X | 140 | // +---------------------+ 141 | // | Public Key Y | 142 | // +---------------------+ 143 | // | | 144 | // | ... | 145 | // | | 146 | // +---------------------+ 147 | // The common components(signatureR, signatureS, hashed message, public key X, 148 | // and public key Y) each takes block size of bytes. The block size is different for 149 | // different curves and is set by canUseKDSA function. 150 | var params [4096]byte 151 | 152 | // Copy content into the parameter block. In the verify case, 153 | // we copy signature (r), signature(s), hashed message, public key x component, 154 | // and public key y component into the parameter block. 155 | r.FillBytes(params[0*blockSize : 1*blockSize]) 156 | s.FillBytes(params[1*blockSize : 2*blockSize]) 157 | hashToBytes(params[2*blockSize:3*blockSize], hash, c) 158 | pub.X.FillBytes(params[3*blockSize : 4*blockSize]) 159 | pub.Y.FillBytes(params[4*blockSize : 5*blockSize]) 160 | return kdsa(functionCode, ¶ms) == 0 161 | } 162 | return verifyGeneric(pub, c, hash, r, s) 163 | } 164 | -------------------------------------------------------------------------------- /ecdsa/ecdsa_s390x_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build s390x 6 | 7 | package ecdsa 8 | 9 | import ( 10 | "crypto/elliptic" 11 | "testing" 12 | ) 13 | 14 | func TestNoAsm(t *testing.T) { 15 | testingDisableKDSA = true 16 | defer func() { testingDisableKDSA = false }() 17 | 18 | curves := [...]elliptic.Curve{ 19 | elliptic.P256(), 20 | elliptic.P384(), 21 | elliptic.P521(), 22 | } 23 | 24 | for _, curve := range curves { 25 | name := curve.Params().Name 26 | t.Run(name, func(t *testing.T) { testKeyGeneration(t, curve) }) 27 | t.Run(name, func(t *testing.T) { testSignAndVerify(t, curve) }) 28 | t.Run(name, func(t *testing.T) { testNonceSafety(t, curve) }) 29 | t.Run(name, func(t *testing.T) { testINDCCA(t, curve) }) 30 | t.Run(name, func(t *testing.T) { testNegativeInputs(t, curve) }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ecdsa/equal_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ecdsa_test 6 | 7 | import ( 8 | "crypto" 9 | "crypto/ecdsa" 10 | "crypto/elliptic" 11 | "crypto/rand" 12 | "crypto/x509" 13 | "testing" 14 | ) 15 | 16 | func testEqual(t *testing.T, c elliptic.Curve) { 17 | private, _ := ecdsa.GenerateKey(c, rand.Reader) 18 | public := &private.PublicKey 19 | 20 | if !public.Equal(public) { 21 | t.Errorf("public key is not equal to itself: %v", public) 22 | } 23 | if !public.Equal(crypto.Signer(private).Public().(*ecdsa.PublicKey)) { 24 | t.Errorf("private.Public() is not Equal to public: %q", public) 25 | } 26 | if !private.Equal(private) { 27 | t.Errorf("private key is not equal to itself: %v", private) 28 | } 29 | 30 | enc, err := x509.MarshalPKCS8PrivateKey(private) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | decoded, err := x509.ParsePKCS8PrivateKey(enc) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | if !public.Equal(decoded.(crypto.Signer).Public()) { 39 | t.Errorf("public key is not equal to itself after decoding: %v", public) 40 | } 41 | if !private.Equal(decoded) { 42 | t.Errorf("private key is not equal to itself after decoding: %v", private) 43 | } 44 | 45 | other, _ := ecdsa.GenerateKey(c, rand.Reader) 46 | if public.Equal(other.Public()) { 47 | t.Errorf("different public keys are Equal") 48 | } 49 | if private.Equal(other) { 50 | t.Errorf("different private keys are Equal") 51 | } 52 | 53 | // Ensure that keys with the same coordinates but on different curves 54 | // aren't considered Equal. 55 | differentCurve := &ecdsa.PublicKey{} 56 | *differentCurve = *public // make a copy of the public key 57 | if differentCurve.Curve == elliptic.P256() { 58 | differentCurve.Curve = elliptic.P224() 59 | } else { 60 | differentCurve.Curve = elliptic.P256() 61 | } 62 | if public.Equal(differentCurve) { 63 | t.Errorf("public keys with different curves are Equal") 64 | } 65 | } 66 | 67 | func TestEqual(t *testing.T) { 68 | t.Run("P224", func(t *testing.T) { testEqual(t, elliptic.P224()) }) 69 | if testing.Short() { 70 | return 71 | } 72 | t.Run("P256", func(t *testing.T) { testEqual(t, elliptic.P256()) }) 73 | t.Run("P384", func(t *testing.T) { testEqual(t, elliptic.P384()) }) 74 | t.Run("P521", func(t *testing.T) { testEqual(t, elliptic.P521()) }) 75 | } 76 | -------------------------------------------------------------------------------- /ecdsa/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ecdsa_test 6 | 7 | import ( 8 | "crypto/elliptic" 9 | "crypto/rand" 10 | "crypto/sha256" 11 | "fmt" 12 | 13 | "github.com/cloudflare/pat-go/ecdsa" 14 | ) 15 | 16 | func Example() { 17 | privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | msg := "hello, world" 23 | hash := sha256.Sum256([]byte(msg)) 24 | 25 | sig, err := ecdsa.SignASN1(rand.Reader, privateKey, hash[:]) 26 | if err != nil { 27 | panic(err) 28 | } 29 | fmt.Printf("signature: %x\n", sig) 30 | 31 | valid := ecdsa.VerifyASN1(&privateKey.PublicKey, hash[:], sig) 32 | fmt.Println("signature verified:", valid) 33 | } 34 | -------------------------------------------------------------------------------- /ecdsa/randutil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package randutil contains internal randomness utilities for various 6 | // crypto packages. 7 | package ecdsa 8 | 9 | import ( 10 | "io" 11 | "sync" 12 | ) 13 | 14 | var ( 15 | closedChanOnce sync.Once 16 | closedChan chan struct{} 17 | ) 18 | 19 | // MaybeReadByte reads a single byte from r with ~50% probability. This is used 20 | // to ensure that callers do not depend on non-guaranteed behaviour, e.g. 21 | // assuming that rsa.GenerateKey is deterministic w.r.t. a given random stream. 22 | // 23 | // This does not affect tests that pass a stream of fixed bytes as the random 24 | // source (e.g. a zeroReader). 25 | func MaybeReadByte(r io.Reader) { 26 | closedChanOnce.Do(func() { 27 | closedChan = make(chan struct{}) 28 | close(closedChan) 29 | }) 30 | 31 | select { 32 | case <-closedChan: 33 | return 34 | case <-closedChan: 35 | var buf [1]byte 36 | r.Read(buf[:]) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ecdsa/testdata/SigVer.rsp.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/pat-go/656110b5f12112add4d912180a00b895cd6cf60e/ecdsa/testdata/SigVer.rsp.bz2 -------------------------------------------------------------------------------- /ed25519/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /ed25519/ed25519_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ed25519 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "compress/gzip" 11 | "crypto" 12 | "crypto/rand" 13 | "encoding/hex" 14 | "os" 15 | "strings" 16 | "testing" 17 | ) 18 | 19 | type zeroReader struct{} 20 | 21 | func (zeroReader) Read(buf []byte) (int, error) { 22 | for i := range buf { 23 | buf[i] = 0 24 | } 25 | return len(buf), nil 26 | } 27 | 28 | func TestSignVerify(t *testing.T) { 29 | var zero zeroReader 30 | public, private, _ := GenerateKey(zero) 31 | 32 | message := []byte("test message") 33 | sig := Sign(private, message) 34 | if !Verify(public, message, sig) { 35 | t.Errorf("valid signature rejected") 36 | } 37 | 38 | wrongMessage := []byte("wrong message") 39 | if Verify(public, wrongMessage, sig) { 40 | t.Errorf("signature of different message accepted") 41 | } 42 | } 43 | 44 | func TestMaskSignVerify(t *testing.T) { 45 | public, private, _ := GenerateKey(rand.Reader) 46 | 47 | blind := make([]byte, 32) 48 | rand.Reader.Read(blind) 49 | 50 | blindedKey, err := BlindPublicKey(public, blind) 51 | if err != nil { 52 | t.Errorf("failed to blind public key") 53 | } 54 | 55 | message := []byte("test message") 56 | sig := BlindKeySign(private, message, blind) 57 | if !Verify(blindedKey, message, sig) { 58 | t.Errorf("valid signature rejected") 59 | } 60 | 61 | wrongMessage := []byte("wrong message") 62 | if Verify(blindedKey, wrongMessage, sig) { 63 | t.Errorf("signature of different message accepted") 64 | } 65 | } 66 | 67 | func TestBlindUnblindKey(t *testing.T) { 68 | publicKey, _, _ := GenerateKey(rand.Reader) 69 | 70 | blind := make([]byte, 32) 71 | rand.Reader.Read(blind) 72 | 73 | blindedKey, err := BlindPublicKey(publicKey, blind) 74 | if err != nil { 75 | t.Errorf("failed to blind public key") 76 | } 77 | 78 | unblindedKey, err := UnblindPublicKey(blindedKey, blind) 79 | if err != nil { 80 | t.Errorf("failed to blind public key") 81 | } 82 | 83 | if !bytes.Equal(publicKey, unblindedKey) { 84 | t.Fatal("Blind-unblind mismatch") 85 | } 86 | } 87 | 88 | func TestBlindUnblindKeyWithContext(t *testing.T) { 89 | publicKey, _, _ := GenerateKey(rand.Reader) 90 | 91 | blind := make([]byte, 32) 92 | rand.Reader.Read(blind) 93 | 94 | context := make([]byte, 32) 95 | rand.Reader.Read(context) 96 | 97 | blindedKey, err := BlindPublicKeyWithContext(publicKey, blind, context) 98 | if err != nil { 99 | t.Errorf("failed to blind public key") 100 | } 101 | 102 | unblindedKey, err := UnblindPublicKeyWithContext(blindedKey, blind, context) 103 | if err != nil { 104 | t.Errorf("failed to blind public key") 105 | } 106 | 107 | if !bytes.Equal(publicKey, unblindedKey) { 108 | t.Fatal("Blind-unblind mismatch") 109 | } 110 | 111 | context[0] ^= 0xFF 112 | invalidKey, err := UnblindPublicKeyWithContext(blindedKey, blind, context) 113 | if err != nil { 114 | t.Errorf("failed to blind public key") 115 | } 116 | 117 | if bytes.Equal(publicKey, invalidKey) { 118 | t.Fatal("Invalid Blind-unblind mismatch") 119 | } 120 | } 121 | 122 | func TestCryptoSigner(t *testing.T) { 123 | var zero zeroReader 124 | public, private, _ := GenerateKey(zero) 125 | 126 | signer := crypto.Signer(private) 127 | 128 | publicInterface := signer.Public() 129 | public2, ok := publicInterface.(PublicKey) 130 | if !ok { 131 | t.Fatalf("expected PublicKey from Public() but got %T", publicInterface) 132 | } 133 | 134 | if !bytes.Equal(public, public2) { 135 | t.Errorf("public keys do not match: original:%x vs Public():%x", public, public2) 136 | } 137 | 138 | message := []byte("message") 139 | var noHash crypto.Hash 140 | signature, err := signer.Sign(zero, message, noHash) 141 | if err != nil { 142 | t.Fatalf("error from Sign(): %s", err) 143 | } 144 | 145 | if !Verify(public, message, signature) { 146 | t.Errorf("Verify failed on signature from Sign()") 147 | } 148 | } 149 | 150 | func TestEqual(t *testing.T) { 151 | public, private, _ := GenerateKey(rand.Reader) 152 | 153 | if !public.Equal(public) { 154 | t.Errorf("public key is not equal to itself: %q", public) 155 | } 156 | if !public.Equal(crypto.Signer(private).Public()) { 157 | t.Errorf("private.Public() is not Equal to public: %q", public) 158 | } 159 | if !private.Equal(private) { 160 | t.Errorf("private key is not equal to itself: %q", private) 161 | } 162 | 163 | otherPub, otherPriv, _ := GenerateKey(rand.Reader) 164 | if public.Equal(otherPub) { 165 | t.Errorf("different public keys are Equal") 166 | } 167 | if private.Equal(otherPriv) { 168 | t.Errorf("different private keys are Equal") 169 | } 170 | } 171 | 172 | func TestGolden(t *testing.T) { 173 | // sign.input.gz is a selection of test cases from 174 | // https://ed25519.cr.yp.to/python/sign.input 175 | testDataZ, err := os.Open("testdata/sign.input.gz") 176 | if err != nil { 177 | t.Fatal(err) 178 | } 179 | defer testDataZ.Close() 180 | testData, err := gzip.NewReader(testDataZ) 181 | if err != nil { 182 | t.Fatal(err) 183 | } 184 | defer testData.Close() 185 | 186 | scanner := bufio.NewScanner(testData) 187 | lineNo := 0 188 | 189 | for scanner.Scan() { 190 | lineNo++ 191 | 192 | line := scanner.Text() 193 | parts := strings.Split(line, ":") 194 | if len(parts) != 5 { 195 | t.Fatalf("bad number of parts on line %d", lineNo) 196 | } 197 | 198 | privBytes, _ := hex.DecodeString(parts[0]) 199 | pubKey, _ := hex.DecodeString(parts[1]) 200 | msg, _ := hex.DecodeString(parts[2]) 201 | sig, _ := hex.DecodeString(parts[3]) 202 | // The signatures in the test vectors also include the message 203 | // at the end, but we just want R and S. 204 | sig = sig[:SignatureSize] 205 | 206 | if l := len(pubKey); l != PublicKeySize { 207 | t.Fatalf("bad public key length on line %d: got %d bytes", lineNo, l) 208 | } 209 | 210 | var priv [PrivateKeySize]byte 211 | copy(priv[:], privBytes) 212 | copy(priv[32:], pubKey) 213 | 214 | sig2 := Sign(priv[:], msg) 215 | if !bytes.Equal(sig, sig2[:]) { 216 | t.Errorf("different signature result on line %d: %x vs %x", lineNo, sig, sig2) 217 | } 218 | 219 | if !Verify(pubKey, msg, sig2) { 220 | t.Errorf("signature failed to verify on line %d", lineNo) 221 | } 222 | 223 | priv2 := NewKeyFromSeed(priv[:32]) 224 | if !bytes.Equal(priv[:], priv2) { 225 | t.Errorf("recreating key pair gave different private key on line %d: %x vs %x", lineNo, priv[:], priv2) 226 | } 227 | 228 | if pubKey2 := priv2.Public().(PublicKey); !bytes.Equal(pubKey, pubKey2) { 229 | t.Errorf("recreating key pair gave different public key on line %d: %x vs %x", lineNo, pubKey, pubKey2) 230 | } 231 | 232 | if seed := priv2.Seed(); !bytes.Equal(priv[:32], seed) { 233 | t.Errorf("recreating key pair gave different seed on line %d: %x vs %x", lineNo, priv[:32], seed) 234 | } 235 | } 236 | 237 | if err := scanner.Err(); err != nil { 238 | t.Fatalf("error reading test data: %s", err) 239 | } 240 | } 241 | 242 | func TestMalleability(t *testing.T) { 243 | // https://tools.ietf.org/html/rfc8032#section-5.1.7 adds an additional test 244 | // that s be in [0, order). This prevents someone from adding a multiple of 245 | // order to s and obtaining a second valid signature for the same message. 246 | msg := []byte{0x54, 0x65, 0x73, 0x74} 247 | sig := []byte{ 248 | 0x7c, 0x38, 0xe0, 0x26, 0xf2, 0x9e, 0x14, 0xaa, 0xbd, 0x05, 0x9a, 249 | 0x0f, 0x2d, 0xb8, 0xb0, 0xcd, 0x78, 0x30, 0x40, 0x60, 0x9a, 0x8b, 250 | 0xe6, 0x84, 0xdb, 0x12, 0xf8, 0x2a, 0x27, 0x77, 0x4a, 0xb0, 0x67, 251 | 0x65, 0x4b, 0xce, 0x38, 0x32, 0xc2, 0xd7, 0x6f, 0x8f, 0x6f, 0x5d, 252 | 0xaf, 0xc0, 0x8d, 0x93, 0x39, 0xd4, 0xee, 0xf6, 0x76, 0x57, 0x33, 253 | 0x36, 0xa5, 0xc5, 0x1e, 0xb6, 0xf9, 0x46, 0xb3, 0x1d, 254 | } 255 | publicKey := []byte{ 256 | 0x7d, 0x4d, 0x0e, 0x7f, 0x61, 0x53, 0xa6, 0x9b, 0x62, 0x42, 0xb5, 257 | 0x22, 0xab, 0xbe, 0xe6, 0x85, 0xfd, 0xa4, 0x42, 0x0f, 0x88, 0x34, 258 | 0xb1, 0x08, 0xc3, 0xbd, 0xae, 0x36, 0x9e, 0xf5, 0x49, 0xfa, 259 | } 260 | 261 | if Verify(publicKey, msg, sig) { 262 | t.Fatal("non-canonical signature accepted") 263 | } 264 | } 265 | 266 | // func TestAllocations(t *testing.T) { 267 | // if strings.HasSuffix(os.Getenv("GO_BUILDER_NAME"), "-noopt") { 268 | // t.Skip("skipping allocations test without relevant optimizations") 269 | // } 270 | // if allocs := testing.AllocsPerRun(100, func() { 271 | // seed := make([]byte, SeedSize) 272 | // message := []byte("Hello, world!") 273 | // priv := NewKeyFromSeed(seed) 274 | // pub := priv.Public().(PublicKey) 275 | // signature := Sign(priv, message) 276 | // if !Verify(pub, message, signature) { 277 | // t.Fatal("signature didn't verify") 278 | // } 279 | // }); allocs > 0 { 280 | // t.Errorf("expected zero allocations, got %0.1v", allocs) 281 | // } 282 | // } 283 | 284 | func BenchmarkKeyGeneration(b *testing.B) { 285 | var zero zeroReader 286 | for i := 0; i < b.N; i++ { 287 | if _, _, err := GenerateKey(zero); err != nil { 288 | b.Fatal(err) 289 | } 290 | } 291 | } 292 | 293 | func BenchmarkNewKeyFromSeed(b *testing.B) { 294 | seed := make([]byte, SeedSize) 295 | for i := 0; i < b.N; i++ { 296 | _ = NewKeyFromSeed(seed) 297 | } 298 | } 299 | 300 | func BenchmarkSigning(b *testing.B) { 301 | var zero zeroReader 302 | _, priv, err := GenerateKey(zero) 303 | if err != nil { 304 | b.Fatal(err) 305 | } 306 | message := []byte("Hello, world!") 307 | b.ResetTimer() 308 | for i := 0; i < b.N; i++ { 309 | Sign(priv, message) 310 | } 311 | } 312 | 313 | func BenchmarkVerification(b *testing.B) { 314 | var zero zeroReader 315 | pub, priv, err := GenerateKey(zero) 316 | if err != nil { 317 | b.Fatal(err) 318 | } 319 | message := []byte("Hello, world!") 320 | signature := Sign(priv, message) 321 | b.ResetTimer() 322 | for i := 0; i < b.N; i++ { 323 | Verify(pub, message, signature) 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /ed25519/internal/edwards25519/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package edwards25519 implements group logic for the twisted Edwards curve 6 | // 7 | // -x^2 + y^2 = 1 + -(121665/121666)*x^2*y^2 8 | // 9 | // This is better known as the Edwards curve equivalent to Curve25519, and is 10 | // the curve used by the Ed25519 signature scheme. 11 | // 12 | // Most users don't need this package, and should instead use crypto/ed25519 for 13 | // signatures, golang.org/x/crypto/curve25519 for Diffie-Hellman, or 14 | // github.com/gtank/ristretto255 for prime order group logic. 15 | // 16 | // However, developers who do need to interact with low-level edwards25519 17 | // operations can use filippo.io/edwards25519, an extended version of this 18 | // package repackaged as an importable module. 19 | // 20 | // (Note that filippo.io/edwards25519 and github.com/gtank/ristretto255 are not 21 | // maintained by the Go team and are not covered by the Go 1 Compatibility Promise.) 22 | package edwards25519 23 | -------------------------------------------------------------------------------- /ed25519/internal/edwards25519/field/fe_alias_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package field 6 | 7 | import ( 8 | "testing" 9 | "testing/quick" 10 | ) 11 | 12 | func checkAliasingOneArg(f func(v, x *Element) *Element) func(v, x Element) bool { 13 | return func(v, x Element) bool { 14 | x1, v1 := x, x 15 | 16 | // Calculate a reference f(x) without aliasing. 17 | if out := f(&v, &x); out != &v && isInBounds(out) { 18 | return false 19 | } 20 | 21 | // Test aliasing the argument and the receiver. 22 | if out := f(&v1, &v1); out != &v1 || v1 != v { 23 | return false 24 | } 25 | 26 | // Ensure the arguments was not modified. 27 | return x == x1 28 | } 29 | } 30 | 31 | func checkAliasingTwoArgs(f func(v, x, y *Element) *Element) func(v, x, y Element) bool { 32 | return func(v, x, y Element) bool { 33 | x1, y1, v1 := x, y, Element{} 34 | 35 | // Calculate a reference f(x, y) without aliasing. 36 | if out := f(&v, &x, &y); out != &v && isInBounds(out) { 37 | return false 38 | } 39 | 40 | // Test aliasing the first argument and the receiver. 41 | v1 = x 42 | if out := f(&v1, &v1, &y); out != &v1 || v1 != v { 43 | return false 44 | } 45 | // Test aliasing the second argument and the receiver. 46 | v1 = y 47 | if out := f(&v1, &x, &v1); out != &v1 || v1 != v { 48 | return false 49 | } 50 | 51 | // Calculate a reference f(x, x) without aliasing. 52 | if out := f(&v, &x, &x); out != &v { 53 | return false 54 | } 55 | 56 | // Test aliasing the first argument and the receiver. 57 | v1 = x 58 | if out := f(&v1, &v1, &x); out != &v1 || v1 != v { 59 | return false 60 | } 61 | // Test aliasing the second argument and the receiver. 62 | v1 = x 63 | if out := f(&v1, &x, &v1); out != &v1 || v1 != v { 64 | return false 65 | } 66 | // Test aliasing both arguments and the receiver. 67 | v1 = x 68 | if out := f(&v1, &v1, &v1); out != &v1 || v1 != v { 69 | return false 70 | } 71 | 72 | // Ensure the arguments were not modified. 73 | return x == x1 && y == y1 74 | } 75 | } 76 | 77 | // TestAliasing checks that receivers and arguments can alias each other without 78 | // leading to incorrect results. That is, it ensures that it's safe to write 79 | // 80 | // v.Invert(v) 81 | // 82 | // or 83 | // 84 | // v.Add(v, v) 85 | // 86 | // without any of the inputs getting clobbered by the output being written. 87 | func TestAliasing(t *testing.T) { 88 | type target struct { 89 | name string 90 | oneArgF func(v, x *Element) *Element 91 | twoArgsF func(v, x, y *Element) *Element 92 | } 93 | for _, tt := range []target{ 94 | {name: "Absolute", oneArgF: (*Element).Absolute}, 95 | {name: "Invert", oneArgF: (*Element).Invert}, 96 | {name: "Negate", oneArgF: (*Element).Negate}, 97 | {name: "Set", oneArgF: (*Element).Set}, 98 | {name: "Square", oneArgF: (*Element).Square}, 99 | {name: "Multiply", twoArgsF: (*Element).Multiply}, 100 | {name: "Add", twoArgsF: (*Element).Add}, 101 | {name: "Subtract", twoArgsF: (*Element).Subtract}, 102 | { 103 | name: "Select0", 104 | twoArgsF: func(v, x, y *Element) *Element { 105 | return (*Element).Select(v, x, y, 0) 106 | }, 107 | }, 108 | { 109 | name: "Select1", 110 | twoArgsF: func(v, x, y *Element) *Element { 111 | return (*Element).Select(v, x, y, 1) 112 | }, 113 | }, 114 | } { 115 | var err error 116 | switch { 117 | case tt.oneArgF != nil: 118 | err = quick.Check(checkAliasingOneArg(tt.oneArgF), &quick.Config{MaxCountScale: 1 << 8}) 119 | case tt.twoArgsF != nil: 120 | err = quick.Check(checkAliasingTwoArgs(tt.twoArgsF), &quick.Config{MaxCountScale: 1 << 8}) 121 | } 122 | if err != nil { 123 | t.Errorf("%v: %v", tt.name, err) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /ed25519/internal/edwards25519/field/fe_amd64_noasm.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package field 6 | 7 | func feMul(v, x, y *Element) { feMulGeneric(v, x, y) } 8 | 9 | func feSquare(v, x *Element) { feSquareGeneric(v, x) } 10 | -------------------------------------------------------------------------------- /ed25519/internal/edwards25519/field/fe_arm64_noasm.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package field 6 | 7 | func (v *Element) carryPropagate() *Element { 8 | return v.carryPropagateGeneric() 9 | } 10 | -------------------------------------------------------------------------------- /ed25519/internal/edwards25519/field/fe_bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package field 6 | 7 | import "testing" 8 | 9 | func BenchmarkAdd(b *testing.B) { 10 | var x, y Element 11 | x.One() 12 | y.Add(feOne, feOne) 13 | b.ResetTimer() 14 | for i := 0; i < b.N; i++ { 15 | x.Add(&x, &y) 16 | } 17 | } 18 | 19 | func BenchmarkMultiply(b *testing.B) { 20 | var x, y Element 21 | x.One() 22 | y.Add(feOne, feOne) 23 | b.ResetTimer() 24 | for i := 0; i < b.N; i++ { 25 | x.Multiply(&x, &y) 26 | } 27 | } 28 | 29 | func BenchmarkMult32(b *testing.B) { 30 | var x Element 31 | x.One() 32 | b.ResetTimer() 33 | for i := 0; i < b.N; i++ { 34 | x.Mult32(&x, 0xaa42aa42) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ed25519/internal/edwards25519/field/fe_generic.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package field 6 | 7 | import "math/bits" 8 | 9 | // uint128 holds a 128-bit number as two 64-bit limbs, for use with the 10 | // bits.Mul64 and bits.Add64 intrinsics. 11 | type uint128 struct { 12 | lo, hi uint64 13 | } 14 | 15 | // mul64 returns a * b. 16 | func mul64(a, b uint64) uint128 { 17 | hi, lo := bits.Mul64(a, b) 18 | return uint128{lo, hi} 19 | } 20 | 21 | // addMul64 returns v + a * b. 22 | func addMul64(v uint128, a, b uint64) uint128 { 23 | hi, lo := bits.Mul64(a, b) 24 | lo, c := bits.Add64(lo, v.lo, 0) 25 | hi, _ = bits.Add64(hi, v.hi, c) 26 | return uint128{lo, hi} 27 | } 28 | 29 | // shiftRightBy51 returns a >> 51. a is assumed to be at most 115 bits. 30 | func shiftRightBy51(a uint128) uint64 { 31 | return (a.hi << (64 - 51)) | (a.lo >> 51) 32 | } 33 | 34 | func feMulGeneric(v, a, b *Element) { 35 | a0 := a.l0 36 | a1 := a.l1 37 | a2 := a.l2 38 | a3 := a.l3 39 | a4 := a.l4 40 | 41 | b0 := b.l0 42 | b1 := b.l1 43 | b2 := b.l2 44 | b3 := b.l3 45 | b4 := b.l4 46 | 47 | // Limb multiplication works like pen-and-paper columnar multiplication, but 48 | // with 51-bit limbs instead of digits. 49 | // 50 | // a4 a3 a2 a1 a0 x 51 | // b4 b3 b2 b1 b0 = 52 | // ------------------------ 53 | // a4b0 a3b0 a2b0 a1b0 a0b0 + 54 | // a4b1 a3b1 a2b1 a1b1 a0b1 + 55 | // a4b2 a3b2 a2b2 a1b2 a0b2 + 56 | // a4b3 a3b3 a2b3 a1b3 a0b3 + 57 | // a4b4 a3b4 a2b4 a1b4 a0b4 = 58 | // ---------------------------------------------- 59 | // r8 r7 r6 r5 r4 r3 r2 r1 r0 60 | // 61 | // We can then use the reduction identity (a * 2²⁵⁵ + b = a * 19 + b) to 62 | // reduce the limbs that would overflow 255 bits. r5 * 2²⁵⁵ becomes 19 * r5, 63 | // r6 * 2³⁰⁶ becomes 19 * r6 * 2⁵¹, etc. 64 | // 65 | // Reduction can be carried out simultaneously to multiplication. For 66 | // example, we do not compute r5: whenever the result of a multiplication 67 | // belongs to r5, like a1b4, we multiply it by 19 and add the result to r0. 68 | // 69 | // a4b0 a3b0 a2b0 a1b0 a0b0 + 70 | // a3b1 a2b1 a1b1 a0b1 19×a4b1 + 71 | // a2b2 a1b2 a0b2 19×a4b2 19×a3b2 + 72 | // a1b3 a0b3 19×a4b3 19×a3b3 19×a2b3 + 73 | // a0b4 19×a4b4 19×a3b4 19×a2b4 19×a1b4 = 74 | // -------------------------------------- 75 | // r4 r3 r2 r1 r0 76 | // 77 | // Finally we add up the columns into wide, overlapping limbs. 78 | 79 | a1_19 := a1 * 19 80 | a2_19 := a2 * 19 81 | a3_19 := a3 * 19 82 | a4_19 := a4 * 19 83 | 84 | // r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1) 85 | r0 := mul64(a0, b0) 86 | r0 = addMul64(r0, a1_19, b4) 87 | r0 = addMul64(r0, a2_19, b3) 88 | r0 = addMul64(r0, a3_19, b2) 89 | r0 = addMul64(r0, a4_19, b1) 90 | 91 | // r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2) 92 | r1 := mul64(a0, b1) 93 | r1 = addMul64(r1, a1, b0) 94 | r1 = addMul64(r1, a2_19, b4) 95 | r1 = addMul64(r1, a3_19, b3) 96 | r1 = addMul64(r1, a4_19, b2) 97 | 98 | // r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3) 99 | r2 := mul64(a0, b2) 100 | r2 = addMul64(r2, a1, b1) 101 | r2 = addMul64(r2, a2, b0) 102 | r2 = addMul64(r2, a3_19, b4) 103 | r2 = addMul64(r2, a4_19, b3) 104 | 105 | // r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4 106 | r3 := mul64(a0, b3) 107 | r3 = addMul64(r3, a1, b2) 108 | r3 = addMul64(r3, a2, b1) 109 | r3 = addMul64(r3, a3, b0) 110 | r3 = addMul64(r3, a4_19, b4) 111 | 112 | // r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0 113 | r4 := mul64(a0, b4) 114 | r4 = addMul64(r4, a1, b3) 115 | r4 = addMul64(r4, a2, b2) 116 | r4 = addMul64(r4, a3, b1) 117 | r4 = addMul64(r4, a4, b0) 118 | 119 | // After the multiplication, we need to reduce (carry) the five coefficients 120 | // to obtain a result with limbs that are at most slightly larger than 2⁵¹, 121 | // to respect the Element invariant. 122 | // 123 | // Overall, the reduction works the same as carryPropagate, except with 124 | // wider inputs: we take the carry for each coefficient by shifting it right 125 | // by 51, and add it to the limb above it. The top carry is multiplied by 19 126 | // according to the reduction identity and added to the lowest limb. 127 | // 128 | // The largest coefficient (r0) will be at most 111 bits, which guarantees 129 | // that all carries are at most 111 - 51 = 60 bits, which fits in a uint64. 130 | // 131 | // r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1) 132 | // r0 < 2⁵²×2⁵² + 19×(2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵²) 133 | // r0 < (1 + 19 × 4) × 2⁵² × 2⁵² 134 | // r0 < 2⁷ × 2⁵² × 2⁵² 135 | // r0 < 2¹¹¹ 136 | // 137 | // Moreover, the top coefficient (r4) is at most 107 bits, so c4 is at most 138 | // 56 bits, and c4 * 19 is at most 61 bits, which again fits in a uint64 and 139 | // allows us to easily apply the reduction identity. 140 | // 141 | // r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0 142 | // r4 < 5 × 2⁵² × 2⁵² 143 | // r4 < 2¹⁰⁷ 144 | // 145 | 146 | c0 := shiftRightBy51(r0) 147 | c1 := shiftRightBy51(r1) 148 | c2 := shiftRightBy51(r2) 149 | c3 := shiftRightBy51(r3) 150 | c4 := shiftRightBy51(r4) 151 | 152 | rr0 := r0.lo&maskLow51Bits + c4*19 153 | rr1 := r1.lo&maskLow51Bits + c0 154 | rr2 := r2.lo&maskLow51Bits + c1 155 | rr3 := r3.lo&maskLow51Bits + c2 156 | rr4 := r4.lo&maskLow51Bits + c3 157 | 158 | // Now all coefficients fit into 64-bit registers but are still too large to 159 | // be passed around as a Element. We therefore do one last carry chain, 160 | // where the carries will be small enough to fit in the wiggle room above 2⁵¹. 161 | *v = Element{rr0, rr1, rr2, rr3, rr4} 162 | v.carryPropagate() 163 | } 164 | 165 | func feSquareGeneric(v, a *Element) { 166 | l0 := a.l0 167 | l1 := a.l1 168 | l2 := a.l2 169 | l3 := a.l3 170 | l4 := a.l4 171 | 172 | // Squaring works precisely like multiplication above, but thanks to its 173 | // symmetry we get to group a few terms together. 174 | // 175 | // l4 l3 l2 l1 l0 x 176 | // l4 l3 l2 l1 l0 = 177 | // ------------------------ 178 | // l4l0 l3l0 l2l0 l1l0 l0l0 + 179 | // l4l1 l3l1 l2l1 l1l1 l0l1 + 180 | // l4l2 l3l2 l2l2 l1l2 l0l2 + 181 | // l4l3 l3l3 l2l3 l1l3 l0l3 + 182 | // l4l4 l3l4 l2l4 l1l4 l0l4 = 183 | // ---------------------------------------------- 184 | // r8 r7 r6 r5 r4 r3 r2 r1 r0 185 | // 186 | // l4l0 l3l0 l2l0 l1l0 l0l0 + 187 | // l3l1 l2l1 l1l1 l0l1 19×l4l1 + 188 | // l2l2 l1l2 l0l2 19×l4l2 19×l3l2 + 189 | // l1l3 l0l3 19×l4l3 19×l3l3 19×l2l3 + 190 | // l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4 = 191 | // -------------------------------------- 192 | // r4 r3 r2 r1 r0 193 | // 194 | // With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with 195 | // only three Mul64 and four Add64, instead of five and eight. 196 | 197 | l0_2 := l0 * 2 198 | l1_2 := l1 * 2 199 | 200 | l1_38 := l1 * 38 201 | l2_38 := l2 * 38 202 | l3_38 := l3 * 38 203 | 204 | l3_19 := l3 * 19 205 | l4_19 := l4 * 19 206 | 207 | // r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3) 208 | r0 := mul64(l0, l0) 209 | r0 = addMul64(r0, l1_38, l4) 210 | r0 = addMul64(r0, l2_38, l3) 211 | 212 | // r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3 213 | r1 := mul64(l0_2, l1) 214 | r1 = addMul64(r1, l2_38, l4) 215 | r1 = addMul64(r1, l3_19, l3) 216 | 217 | // r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4 218 | r2 := mul64(l0_2, l2) 219 | r2 = addMul64(r2, l1, l1) 220 | r2 = addMul64(r2, l3_38, l4) 221 | 222 | // r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4 223 | r3 := mul64(l0_2, l3) 224 | r3 = addMul64(r3, l1_2, l2) 225 | r3 = addMul64(r3, l4_19, l4) 226 | 227 | // r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2 228 | r4 := mul64(l0_2, l4) 229 | r4 = addMul64(r4, l1_2, l3) 230 | r4 = addMul64(r4, l2, l2) 231 | 232 | c0 := shiftRightBy51(r0) 233 | c1 := shiftRightBy51(r1) 234 | c2 := shiftRightBy51(r2) 235 | c3 := shiftRightBy51(r3) 236 | c4 := shiftRightBy51(r4) 237 | 238 | rr0 := r0.lo&maskLow51Bits + c4*19 239 | rr1 := r1.lo&maskLow51Bits + c0 240 | rr2 := r2.lo&maskLow51Bits + c1 241 | rr3 := r3.lo&maskLow51Bits + c2 242 | rr4 := r4.lo&maskLow51Bits + c3 243 | 244 | *v = Element{rr0, rr1, rr2, rr3, rr4} 245 | v.carryPropagate() 246 | } 247 | 248 | // carryPropagate brings the limbs below 52 bits by applying the reduction 249 | // identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry. 250 | func (v *Element) carryPropagateGeneric() *Element { 251 | c0 := v.l0 >> 51 252 | c1 := v.l1 >> 51 253 | c2 := v.l2 >> 51 254 | c3 := v.l3 >> 51 255 | c4 := v.l4 >> 51 256 | 257 | v.l0 = v.l0&maskLow51Bits + c4*19 258 | v.l1 = v.l1&maskLow51Bits + c0 259 | v.l2 = v.l2&maskLow51Bits + c1 260 | v.l3 = v.l3&maskLow51Bits + c2 261 | v.l4 = v.l4&maskLow51Bits + c3 262 | 263 | return v 264 | } 265 | -------------------------------------------------------------------------------- /ed25519/internal/edwards25519/scalar_alias_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package edwards25519 6 | 7 | import ( 8 | "testing" 9 | "testing/quick" 10 | ) 11 | 12 | func TestScalarAliasing(t *testing.T) { 13 | checkAliasingOneArg := func(f func(v, x *Scalar) *Scalar, v, x Scalar) bool { 14 | x1, v1 := x, x 15 | 16 | // Calculate a reference f(x) without aliasing. 17 | if out := f(&v, &x); out != &v || !isReduced(out) { 18 | return false 19 | } 20 | 21 | // Test aliasing the argument and the receiver. 22 | if out := f(&v1, &v1); out != &v1 || v1 != v || !isReduced(out) { 23 | return false 24 | } 25 | 26 | // Ensure the arguments was not modified. 27 | return x == x1 28 | } 29 | 30 | checkAliasingTwoArgs := func(f func(v, x, y *Scalar) *Scalar, v, x, y Scalar) bool { 31 | x1, y1, v1 := x, y, Scalar{} 32 | 33 | // Calculate a reference f(x, y) without aliasing. 34 | if out := f(&v, &x, &y); out != &v || !isReduced(out) { 35 | return false 36 | } 37 | 38 | // Test aliasing the first argument and the receiver. 39 | v1 = x 40 | if out := f(&v1, &v1, &y); out != &v1 || v1 != v || !isReduced(out) { 41 | return false 42 | } 43 | // Test aliasing the second argument and the receiver. 44 | v1 = y 45 | if out := f(&v1, &x, &v1); out != &v1 || v1 != v || !isReduced(out) { 46 | return false 47 | } 48 | 49 | // Calculate a reference f(x, x) without aliasing. 50 | if out := f(&v, &x, &x); out != &v || !isReduced(out) { 51 | return false 52 | } 53 | 54 | // Test aliasing the first argument and the receiver. 55 | v1 = x 56 | if out := f(&v1, &v1, &x); out != &v1 || v1 != v || !isReduced(out) { 57 | return false 58 | } 59 | // Test aliasing the second argument and the receiver. 60 | v1 = x 61 | if out := f(&v1, &x, &v1); out != &v1 || v1 != v || !isReduced(out) { 62 | return false 63 | } 64 | // Test aliasing both arguments and the receiver. 65 | v1 = x 66 | if out := f(&v1, &v1, &v1); out != &v1 || v1 != v || !isReduced(out) { 67 | return false 68 | } 69 | 70 | // Ensure the arguments were not modified. 71 | return x == x1 && y == y1 72 | } 73 | 74 | for name, f := range map[string]any{ 75 | "Negate": func(v, x Scalar) bool { 76 | return checkAliasingOneArg((*Scalar).Negate, v, x) 77 | }, 78 | "Multiply": func(v, x, y Scalar) bool { 79 | return checkAliasingTwoArgs((*Scalar).Multiply, v, x, y) 80 | }, 81 | "Add": func(v, x, y Scalar) bool { 82 | return checkAliasingTwoArgs((*Scalar).Add, v, x, y) 83 | }, 84 | "Subtract": func(v, x, y Scalar) bool { 85 | return checkAliasingTwoArgs((*Scalar).Subtract, v, x, y) 86 | }, 87 | } { 88 | err := quick.Check(f, &quick.Config{MaxCountScale: 1 << 5}) 89 | if err != nil { 90 | t.Errorf("%v: %v", name, err) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /ed25519/internal/edwards25519/scalar_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package edwards25519 6 | 7 | import ( 8 | "bytes" 9 | "crypto/rand" 10 | "encoding/hex" 11 | "math/big" 12 | mathrand "math/rand" 13 | "reflect" 14 | "testing" 15 | "testing/quick" 16 | ) 17 | 18 | // Generate returns a valid (reduced modulo l) Scalar with a distribution 19 | // weighted towards high, low, and edge values. 20 | func (Scalar) Generate(rand *mathrand.Rand, size int) reflect.Value { 21 | s := scZero 22 | diceRoll := rand.Intn(100) 23 | switch { 24 | case diceRoll == 0: 25 | case diceRoll == 1: 26 | s = scOne 27 | case diceRoll == 2: 28 | s = scMinusOne 29 | case diceRoll < 5: 30 | // Generate a low scalar in [0, 2^125). 31 | rand.Read(s.s[:16]) 32 | s.s[15] &= (1 << 5) - 1 33 | case diceRoll < 10: 34 | // Generate a high scalar in [2^252, 2^252 + 2^124). 35 | s.s[31] = 1 << 4 36 | rand.Read(s.s[:16]) 37 | s.s[15] &= (1 << 4) - 1 38 | default: 39 | // Generate a valid scalar in [0, l) by returning [0, 2^252) which has a 40 | // negligibly different distribution (the former has a 2^-127.6 chance 41 | // of being out of the latter range). 42 | rand.Read(s.s[:]) 43 | s.s[31] &= (1 << 4) - 1 44 | } 45 | return reflect.ValueOf(s) 46 | } 47 | 48 | // quickCheckConfig1024 will make each quickcheck test run (1024 * -quickchecks) 49 | // times. The default value of -quickchecks is 100. 50 | var quickCheckConfig1024 = &quick.Config{MaxCountScale: 1 << 10} 51 | 52 | func TestScalarGenerate(t *testing.T) { 53 | f := func(sc Scalar) bool { 54 | return isReduced(&sc) 55 | } 56 | if err := quick.Check(f, quickCheckConfig1024); err != nil { 57 | t.Errorf("generated unreduced scalar: %v", err) 58 | } 59 | } 60 | 61 | func TestScalarSetCanonicalBytes(t *testing.T) { 62 | f1 := func(in [32]byte, sc Scalar) bool { 63 | // Mask out top 4 bits to guarantee value falls in [0, l). 64 | in[len(in)-1] &= (1 << 4) - 1 65 | if _, err := sc.SetCanonicalBytes(in[:]); err != nil { 66 | return false 67 | } 68 | return bytes.Equal(in[:], sc.Bytes()) && isReduced(&sc) 69 | } 70 | if err := quick.Check(f1, quickCheckConfig1024); err != nil { 71 | t.Errorf("failed bytes->scalar->bytes round-trip: %v", err) 72 | } 73 | 74 | f2 := func(sc1, sc2 Scalar) bool { 75 | if _, err := sc2.SetCanonicalBytes(sc1.Bytes()); err != nil { 76 | return false 77 | } 78 | return sc1 == sc2 79 | } 80 | if err := quick.Check(f2, quickCheckConfig1024); err != nil { 81 | t.Errorf("failed scalar->bytes->scalar round-trip: %v", err) 82 | } 83 | 84 | b := scMinusOne.s 85 | b[31] += 1 86 | s := scOne 87 | if out, err := s.SetCanonicalBytes(b[:]); err == nil { 88 | t.Errorf("SetCanonicalBytes worked on a non-canonical value") 89 | } else if s != scOne { 90 | t.Errorf("SetCanonicalBytes modified its receiver") 91 | } else if out != nil { 92 | t.Errorf("SetCanonicalBytes did not return nil with an error") 93 | } 94 | } 95 | 96 | func TestScalarSetUniformBytes(t *testing.T) { 97 | mod, _ := new(big.Int).SetString("27742317777372353535851937790883648493", 10) 98 | mod.Add(mod, new(big.Int).Lsh(big.NewInt(1), 252)) 99 | f := func(in [64]byte, sc Scalar) bool { 100 | sc.SetUniformBytes(in[:]) 101 | if !isReduced(&sc) { 102 | return false 103 | } 104 | scBig := bigIntFromLittleEndianBytes(sc.s[:]) 105 | inBig := bigIntFromLittleEndianBytes(in[:]) 106 | return inBig.Mod(inBig, mod).Cmp(scBig) == 0 107 | } 108 | if err := quick.Check(f, quickCheckConfig1024); err != nil { 109 | t.Error(err) 110 | } 111 | } 112 | 113 | func TestScalarSetBytesWithClamping(t *testing.T) { 114 | // Generated with libsodium.js 1.0.18 crypto_scalarmult_ed25519_base. 115 | 116 | random := "633d368491364dc9cd4c1bf891b1d59460face1644813240a313e61f2c88216e" 117 | s := new(Scalar).SetBytesWithClamping(decodeHex(random)) 118 | p := new(Point).ScalarBaseMult(s) 119 | want := "1d87a9026fd0126a5736fe1628c95dd419172b5b618457e041c9c861b2494a94" 120 | if got := hex.EncodeToString(p.Bytes()); got != want { 121 | t.Errorf("random: got %q, want %q", got, want) 122 | } 123 | 124 | zero := "0000000000000000000000000000000000000000000000000000000000000000" 125 | s = new(Scalar).SetBytesWithClamping(decodeHex(zero)) 126 | p = new(Point).ScalarBaseMult(s) 127 | want = "693e47972caf527c7883ad1b39822f026f47db2ab0e1919955b8993aa04411d1" 128 | if got := hex.EncodeToString(p.Bytes()); got != want { 129 | t.Errorf("zero: got %q, want %q", got, want) 130 | } 131 | 132 | one := "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 133 | s = new(Scalar).SetBytesWithClamping(decodeHex(one)) 134 | p = new(Point).ScalarBaseMult(s) 135 | want = "12e9a68b73fd5aacdbcaf3e88c46fea6ebedb1aa84eed1842f07f8edab65e3a7" 136 | if got := hex.EncodeToString(p.Bytes()); got != want { 137 | t.Errorf("one: got %q, want %q", got, want) 138 | } 139 | } 140 | 141 | func bigIntFromLittleEndianBytes(b []byte) *big.Int { 142 | bb := make([]byte, len(b)) 143 | for i := range b { 144 | bb[i] = b[len(b)-i-1] 145 | } 146 | return new(big.Int).SetBytes(bb) 147 | } 148 | 149 | func TestScalarMultiplyDistributesOverAdd(t *testing.T) { 150 | multiplyDistributesOverAdd := func(x, y, z Scalar) bool { 151 | // Compute t1 = (x+y)*z 152 | var t1 Scalar 153 | t1.Add(&x, &y) 154 | t1.Multiply(&t1, &z) 155 | 156 | // Compute t2 = x*z + y*z 157 | var t2 Scalar 158 | var t3 Scalar 159 | t2.Multiply(&x, &z) 160 | t3.Multiply(&y, &z) 161 | t2.Add(&t2, &t3) 162 | 163 | return t1 == t2 && isReduced(&t1) && isReduced(&t3) 164 | } 165 | 166 | if err := quick.Check(multiplyDistributesOverAdd, quickCheckConfig1024); err != nil { 167 | t.Error(err) 168 | } 169 | } 170 | 171 | func TestScalarAddLikeSubNeg(t *testing.T) { 172 | addLikeSubNeg := func(x, y Scalar) bool { 173 | // Compute t1 = x - y 174 | var t1 Scalar 175 | t1.Subtract(&x, &y) 176 | 177 | // Compute t2 = -y + x 178 | var t2 Scalar 179 | t2.Negate(&y) 180 | t2.Add(&t2, &x) 181 | 182 | return t1 == t2 && isReduced(&t1) 183 | } 184 | 185 | if err := quick.Check(addLikeSubNeg, quickCheckConfig1024); err != nil { 186 | t.Error(err) 187 | } 188 | } 189 | 190 | func TestScalarNonAdjacentForm(t *testing.T) { 191 | s := Scalar{[32]byte{ 192 | 0x1a, 0x0e, 0x97, 0x8a, 0x90, 0xf6, 0x62, 0x2d, 193 | 0x37, 0x47, 0x02, 0x3f, 0x8a, 0xd8, 0x26, 0x4d, 194 | 0xa7, 0x58, 0xaa, 0x1b, 0x88, 0xe0, 0x40, 0xd1, 195 | 0x58, 0x9e, 0x7b, 0x7f, 0x23, 0x76, 0xef, 0x09, 196 | }} 197 | expectedNaf := [256]int8{ 198 | 0, 13, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, -11, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 199 | 0, 0, 0, 0, 9, 0, 0, 0, 0, -5, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 11, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 200 | -9, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 9, 0, 201 | 0, 0, 0, -15, 0, 0, 0, 0, -7, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, -3, 0, 202 | 0, 0, 0, -11, 0, 0, 0, 0, -7, 0, 0, 0, 0, -13, 0, 0, 0, 0, 11, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 1, 0, 0, 203 | 0, 0, 0, -15, 0, 0, 0, 0, 1, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 13, 0, 0, 0, 204 | 0, 0, 0, 11, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 7, 205 | 0, 0, 0, 0, 0, -15, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 15, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 206 | } 207 | 208 | sNaf := s.nonAdjacentForm(5) 209 | 210 | for i := 0; i < 256; i++ { 211 | if expectedNaf[i] != sNaf[i] { 212 | t.Errorf("Wrong digit at position %d, got %d, expected %d", i, sNaf[i], expectedNaf[i]) 213 | } 214 | } 215 | } 216 | 217 | type notZeroScalar Scalar 218 | 219 | func (notZeroScalar) Generate(rand *mathrand.Rand, size int) reflect.Value { 220 | var s Scalar 221 | for s == scZero { 222 | s = Scalar{}.Generate(rand, size).Interface().(Scalar) 223 | } 224 | return reflect.ValueOf(notZeroScalar(s)) 225 | } 226 | 227 | func TestScalarEqual(t *testing.T) { 228 | if scOne.Equal(&scMinusOne) == 1 { 229 | t.Errorf("scOne.Equal(&scMinusOne) is true") 230 | } 231 | if scMinusOne.Equal(&scMinusOne) == 0 { 232 | t.Errorf("scMinusOne.Equal(&scMinusOne) is false") 233 | } 234 | } 235 | 236 | func TestModInverse(t *testing.T) { 237 | seed := make([]byte, 32) 238 | rand.Reader.Read(seed) 239 | 240 | x := NewScalar().SetBytesWithClamping(seed) 241 | y := NewScalar().Set(x).ModInverse() 242 | z := NewScalar().Multiply(x, y) 243 | if z.Equal(&scOne) == 0 { 244 | t.Errorf("Failed to compute inverse") 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /ed25519/internal/edwards25519/scalarmult.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package edwards25519 6 | 7 | import "sync" 8 | 9 | // basepointTable is a set of 32 affineLookupTables, where table i is generated 10 | // from 256i * basepoint. It is precomputed the first time it's used. 11 | func basepointTable() *[32]affineLookupTable { 12 | basepointTablePrecomp.initOnce.Do(func() { 13 | p := NewGeneratorPoint() 14 | for i := 0; i < 32; i++ { 15 | basepointTablePrecomp.table[i].FromP3(p) 16 | for j := 0; j < 8; j++ { 17 | p.Add(p, p) 18 | } 19 | } 20 | }) 21 | return &basepointTablePrecomp.table 22 | } 23 | 24 | var basepointTablePrecomp struct { 25 | table [32]affineLookupTable 26 | initOnce sync.Once 27 | } 28 | 29 | // ScalarBaseMult sets v = x * B, where B is the canonical generator, and 30 | // returns v. 31 | // 32 | // The scalar multiplication is done in constant time. 33 | func (v *Point) ScalarBaseMult(x *Scalar) *Point { 34 | basepointTable := basepointTable() 35 | 36 | // Write x = sum(x_i * 16^i) so x*B = sum( B*x_i*16^i ) 37 | // as described in the Ed25519 paper 38 | // 39 | // Group even and odd coefficients 40 | // x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B 41 | // + x_1*16^1*B + x_3*16^3*B + ... + x_63*16^63*B 42 | // x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B 43 | // + 16*( x_1*16^0*B + x_3*16^2*B + ... + x_63*16^62*B) 44 | // 45 | // We use a lookup table for each i to get x_i*16^(2*i)*B 46 | // and do four doublings to multiply by 16. 47 | digits := x.signedRadix16() 48 | 49 | multiple := &affineCached{} 50 | tmp1 := &projP1xP1{} 51 | tmp2 := &projP2{} 52 | 53 | // Accumulate the odd components first 54 | v.Set(NewIdentityPoint()) 55 | for i := 1; i < 64; i += 2 { 56 | basepointTable[i/2].SelectInto(multiple, digits[i]) 57 | tmp1.AddAffine(v, multiple) 58 | v.fromP1xP1(tmp1) 59 | } 60 | 61 | // Multiply by 16 62 | tmp2.FromP3(v) // tmp2 = v in P2 coords 63 | tmp1.Double(tmp2) // tmp1 = 2*v in P1xP1 coords 64 | tmp2.FromP1xP1(tmp1) // tmp2 = 2*v in P2 coords 65 | tmp1.Double(tmp2) // tmp1 = 4*v in P1xP1 coords 66 | tmp2.FromP1xP1(tmp1) // tmp2 = 4*v in P2 coords 67 | tmp1.Double(tmp2) // tmp1 = 8*v in P1xP1 coords 68 | tmp2.FromP1xP1(tmp1) // tmp2 = 8*v in P2 coords 69 | tmp1.Double(tmp2) // tmp1 = 16*v in P1xP1 coords 70 | v.fromP1xP1(tmp1) // now v = 16*(odd components) 71 | 72 | // Accumulate the even components 73 | for i := 0; i < 64; i += 2 { 74 | basepointTable[i/2].SelectInto(multiple, digits[i]) 75 | tmp1.AddAffine(v, multiple) 76 | v.fromP1xP1(tmp1) 77 | } 78 | 79 | return v 80 | } 81 | 82 | // ScalarMult sets v = x * q, and returns v. 83 | // 84 | // The scalar multiplication is done in constant time. 85 | func (v *Point) ScalarMult(x *Scalar, q *Point) *Point { 86 | checkInitialized(q) 87 | 88 | var table projLookupTable 89 | table.FromP3(q) 90 | 91 | // Write x = sum(x_i * 16^i) 92 | // so x*Q = sum( Q*x_i*16^i ) 93 | // = Q*x_0 + 16*(Q*x_1 + 16*( ... + Q*x_63) ... ) 94 | // <------compute inside out--------- 95 | // 96 | // We use the lookup table to get the x_i*Q values 97 | // and do four doublings to compute 16*Q 98 | digits := x.signedRadix16() 99 | 100 | // Unwrap first loop iteration to save computing 16*identity 101 | multiple := &projCached{} 102 | tmp1 := &projP1xP1{} 103 | tmp2 := &projP2{} 104 | table.SelectInto(multiple, digits[63]) 105 | 106 | v.Set(NewIdentityPoint()) 107 | tmp1.Add(v, multiple) // tmp1 = x_63*Q in P1xP1 coords 108 | for i := 62; i >= 0; i-- { 109 | tmp2.FromP1xP1(tmp1) // tmp2 = (prev) in P2 coords 110 | tmp1.Double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords 111 | tmp2.FromP1xP1(tmp1) // tmp2 = 2*(prev) in P2 coords 112 | tmp1.Double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords 113 | tmp2.FromP1xP1(tmp1) // tmp2 = 4*(prev) in P2 coords 114 | tmp1.Double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords 115 | tmp2.FromP1xP1(tmp1) // tmp2 = 8*(prev) in P2 coords 116 | tmp1.Double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords 117 | v.fromP1xP1(tmp1) // v = 16*(prev) in P3 coords 118 | table.SelectInto(multiple, digits[i]) 119 | tmp1.Add(v, multiple) // tmp1 = x_i*Q + 16*(prev) in P1xP1 coords 120 | } 121 | v.fromP1xP1(tmp1) 122 | return v 123 | } 124 | 125 | // basepointNafTable is the nafLookupTable8 for the basepoint. 126 | // It is precomputed the first time it's used. 127 | func basepointNafTable() *nafLookupTable8 { 128 | basepointNafTablePrecomp.initOnce.Do(func() { 129 | basepointNafTablePrecomp.table.FromP3(NewGeneratorPoint()) 130 | }) 131 | return &basepointNafTablePrecomp.table 132 | } 133 | 134 | var basepointNafTablePrecomp struct { 135 | table nafLookupTable8 136 | initOnce sync.Once 137 | } 138 | 139 | // VarTimeDoubleScalarBaseMult sets v = a * A + b * B, where B is the canonical 140 | // generator, and returns v. 141 | // 142 | // Execution time depends on the inputs. 143 | func (v *Point) VarTimeDoubleScalarBaseMult(a *Scalar, A *Point, b *Scalar) *Point { 144 | checkInitialized(A) 145 | 146 | // Similarly to the single variable-base approach, we compute 147 | // digits and use them with a lookup table. However, because 148 | // we are allowed to do variable-time operations, we don't 149 | // need constant-time lookups or constant-time digit 150 | // computations. 151 | // 152 | // So we use a non-adjacent form of some width w instead of 153 | // radix 16. This is like a binary representation (one digit 154 | // for each binary place) but we allow the digits to grow in 155 | // magnitude up to 2^{w-1} so that the nonzero digits are as 156 | // sparse as possible. Intuitively, this "condenses" the 157 | // "mass" of the scalar onto sparse coefficients (meaning 158 | // fewer additions). 159 | 160 | basepointNafTable := basepointNafTable() 161 | var aTable nafLookupTable5 162 | aTable.FromP3(A) 163 | // Because the basepoint is fixed, we can use a wider NAF 164 | // corresponding to a bigger table. 165 | aNaf := a.nonAdjacentForm(5) 166 | bNaf := b.nonAdjacentForm(8) 167 | 168 | // Find the first nonzero coefficient. 169 | i := 255 170 | for j := i; j >= 0; j-- { 171 | if aNaf[j] != 0 || bNaf[j] != 0 { 172 | break 173 | } 174 | } 175 | 176 | multA := &projCached{} 177 | multB := &affineCached{} 178 | tmp1 := &projP1xP1{} 179 | tmp2 := &projP2{} 180 | tmp2.Zero() 181 | 182 | // Move from high to low bits, doubling the accumulator 183 | // at each iteration and checking whether there is a nonzero 184 | // coefficient to look up a multiple of. 185 | for ; i >= 0; i-- { 186 | tmp1.Double(tmp2) 187 | 188 | // Only update v if we have a nonzero coeff to add in. 189 | if aNaf[i] > 0 { 190 | v.fromP1xP1(tmp1) 191 | aTable.SelectInto(multA, aNaf[i]) 192 | tmp1.Add(v, multA) 193 | } else if aNaf[i] < 0 { 194 | v.fromP1xP1(tmp1) 195 | aTable.SelectInto(multA, -aNaf[i]) 196 | tmp1.Sub(v, multA) 197 | } 198 | 199 | if bNaf[i] > 0 { 200 | v.fromP1xP1(tmp1) 201 | basepointNafTable.SelectInto(multB, bNaf[i]) 202 | tmp1.AddAffine(v, multB) 203 | } else if bNaf[i] < 0 { 204 | v.fromP1xP1(tmp1) 205 | basepointNafTable.SelectInto(multB, -bNaf[i]) 206 | tmp1.SubAffine(v, multB) 207 | } 208 | 209 | tmp2.FromP1xP1(tmp1) 210 | } 211 | 212 | v.fromP2(tmp2) 213 | return v 214 | } 215 | -------------------------------------------------------------------------------- /ed25519/internal/edwards25519/scalarmult_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package edwards25519 6 | 7 | import ( 8 | "testing" 9 | "testing/quick" 10 | ) 11 | 12 | var ( 13 | // quickCheckConfig32 will make each quickcheck test run (32 * -quickchecks) 14 | // times. The default value of -quickchecks is 100. 15 | quickCheckConfig32 = &quick.Config{MaxCountScale: 1 << 5} 16 | 17 | // a random scalar generated using dalek. 18 | dalekScalar = Scalar{[32]byte{219, 106, 114, 9, 174, 249, 155, 89, 69, 203, 201, 93, 92, 116, 234, 187, 78, 115, 103, 172, 182, 98, 62, 103, 187, 136, 13, 100, 248, 110, 12, 4}} 19 | // the above, times the edwards25519 basepoint. 20 | dalekScalarBasepoint, _ = new(Point).SetBytes([]byte{0xf4, 0xef, 0x7c, 0xa, 0x34, 0x55, 0x7b, 0x9f, 0x72, 0x3b, 0xb6, 0x1e, 0xf9, 0x46, 0x9, 0x91, 0x1c, 0xb9, 0xc0, 0x6c, 0x17, 0x28, 0x2d, 0x8b, 0x43, 0x2b, 0x5, 0x18, 0x6a, 0x54, 0x3e, 0x48}) 21 | ) 22 | 23 | func TestScalarMultSmallScalars(t *testing.T) { 24 | var z Scalar 25 | var p Point 26 | p.ScalarMult(&z, B) 27 | if I.Equal(&p) != 1 { 28 | t.Error("0*B != 0") 29 | } 30 | checkOnCurve(t, &p) 31 | 32 | z = Scalar{[32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}} 33 | p.ScalarMult(&z, B) 34 | if B.Equal(&p) != 1 { 35 | t.Error("1*B != 1") 36 | } 37 | checkOnCurve(t, &p) 38 | } 39 | 40 | func TestScalarMultVsDalek(t *testing.T) { 41 | var p Point 42 | p.ScalarMult(&dalekScalar, B) 43 | if dalekScalarBasepoint.Equal(&p) != 1 { 44 | t.Error("Scalar mul does not match dalek") 45 | } 46 | checkOnCurve(t, &p) 47 | } 48 | 49 | func TestBaseMultVsDalek(t *testing.T) { 50 | var p Point 51 | p.ScalarBaseMult(&dalekScalar) 52 | if dalekScalarBasepoint.Equal(&p) != 1 { 53 | t.Error("Scalar mul does not match dalek") 54 | } 55 | checkOnCurve(t, &p) 56 | } 57 | 58 | func TestVarTimeDoubleBaseMultVsDalek(t *testing.T) { 59 | var p Point 60 | var z Scalar 61 | p.VarTimeDoubleScalarBaseMult(&dalekScalar, B, &z) 62 | if dalekScalarBasepoint.Equal(&p) != 1 { 63 | t.Error("VarTimeDoubleScalarBaseMult fails with b=0") 64 | } 65 | checkOnCurve(t, &p) 66 | p.VarTimeDoubleScalarBaseMult(&z, B, &dalekScalar) 67 | if dalekScalarBasepoint.Equal(&p) != 1 { 68 | t.Error("VarTimeDoubleScalarBaseMult fails with a=0") 69 | } 70 | checkOnCurve(t, &p) 71 | } 72 | 73 | func TestScalarMultDistributesOverAdd(t *testing.T) { 74 | scalarMultDistributesOverAdd := func(x, y Scalar) bool { 75 | var z Scalar 76 | z.Add(&x, &y) 77 | var p, q, r, check Point 78 | p.ScalarMult(&x, B) 79 | q.ScalarMult(&y, B) 80 | r.ScalarMult(&z, B) 81 | check.Add(&p, &q) 82 | checkOnCurve(t, &p, &q, &r, &check) 83 | return check.Equal(&r) == 1 84 | } 85 | 86 | if err := quick.Check(scalarMultDistributesOverAdd, quickCheckConfig32); err != nil { 87 | t.Error(err) 88 | } 89 | } 90 | 91 | func TestScalarMultNonIdentityPoint(t *testing.T) { 92 | // Check whether p.ScalarMult and q.ScalaBaseMult give the same, 93 | // when p and q are originally set to the base point. 94 | 95 | scalarMultNonIdentityPoint := func(x Scalar) bool { 96 | var p, q Point 97 | p.Set(B) 98 | q.Set(B) 99 | 100 | p.ScalarMult(&x, B) 101 | q.ScalarBaseMult(&x) 102 | 103 | checkOnCurve(t, &p, &q) 104 | 105 | return p.Equal(&q) == 1 106 | } 107 | 108 | if err := quick.Check(scalarMultNonIdentityPoint, quickCheckConfig32); err != nil { 109 | t.Error(err) 110 | } 111 | } 112 | 113 | func TestBasepointTableGeneration(t *testing.T) { 114 | // The basepoint table is 32 affineLookupTables, 115 | // corresponding to (16^2i)*B for table i. 116 | basepointTable := basepointTable() 117 | 118 | tmp1 := &projP1xP1{} 119 | tmp2 := &projP2{} 120 | tmp3 := &Point{} 121 | tmp3.Set(B) 122 | table := make([]affineLookupTable, 32) 123 | for i := 0; i < 32; i++ { 124 | // Build the table 125 | table[i].FromP3(tmp3) 126 | // Assert equality with the hardcoded one 127 | if table[i] != basepointTable[i] { 128 | t.Errorf("Basepoint table %d does not match", i) 129 | } 130 | 131 | // Set p = (16^2)*p = 256*p = 2^8*p 132 | tmp2.FromP3(tmp3) 133 | for j := 0; j < 7; j++ { 134 | tmp1.Double(tmp2) 135 | tmp2.FromP1xP1(tmp1) 136 | } 137 | tmp1.Double(tmp2) 138 | tmp3.fromP1xP1(tmp1) 139 | checkOnCurve(t, tmp3) 140 | } 141 | } 142 | 143 | func TestScalarMultMatchesBaseMult(t *testing.T) { 144 | scalarMultMatchesBaseMult := func(x Scalar) bool { 145 | var p, q Point 146 | p.ScalarMult(&x, B) 147 | q.ScalarBaseMult(&x) 148 | checkOnCurve(t, &p, &q) 149 | return p.Equal(&q) == 1 150 | } 151 | 152 | if err := quick.Check(scalarMultMatchesBaseMult, quickCheckConfig32); err != nil { 153 | t.Error(err) 154 | } 155 | } 156 | 157 | func TestBasepointNafTableGeneration(t *testing.T) { 158 | var table nafLookupTable8 159 | table.FromP3(B) 160 | 161 | if table != *basepointNafTable() { 162 | t.Error("BasepointNafTable does not match") 163 | } 164 | } 165 | 166 | func TestVarTimeDoubleBaseMultMatchesBaseMult(t *testing.T) { 167 | varTimeDoubleBaseMultMatchesBaseMult := func(x, y Scalar) bool { 168 | var p, q1, q2, check Point 169 | 170 | p.VarTimeDoubleScalarBaseMult(&x, B, &y) 171 | 172 | q1.ScalarBaseMult(&x) 173 | q2.ScalarBaseMult(&y) 174 | check.Add(&q1, &q2) 175 | 176 | checkOnCurve(t, &p, &check, &q1, &q2) 177 | return p.Equal(&check) == 1 178 | } 179 | 180 | if err := quick.Check(varTimeDoubleBaseMultMatchesBaseMult, quickCheckConfig32); err != nil { 181 | t.Error(err) 182 | } 183 | } 184 | 185 | // Benchmarks. 186 | 187 | func BenchmarkScalarBaseMult(t *testing.B) { 188 | var p Point 189 | 190 | for i := 0; i < t.N; i++ { 191 | p.ScalarBaseMult(&dalekScalar) 192 | } 193 | } 194 | 195 | func BenchmarkScalarMult(t *testing.B) { 196 | var p Point 197 | 198 | for i := 0; i < t.N; i++ { 199 | p.ScalarMult(&dalekScalar, B) 200 | } 201 | } 202 | 203 | func BenchmarkVarTimeDoubleScalarBaseMult(t *testing.B) { 204 | var p Point 205 | 206 | for i := 0; i < t.N; i++ { 207 | p.VarTimeDoubleScalarBaseMult(&dalekScalar, B, &dalekScalar) 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /ed25519/internal/edwards25519/tables.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package edwards25519 6 | 7 | import ( 8 | "crypto/subtle" 9 | ) 10 | 11 | // A dynamic lookup table for variable-base, constant-time scalar muls. 12 | type projLookupTable struct { 13 | points [8]projCached 14 | } 15 | 16 | // A precomputed lookup table for fixed-base, constant-time scalar muls. 17 | type affineLookupTable struct { 18 | points [8]affineCached 19 | } 20 | 21 | // A dynamic lookup table for variable-base, variable-time scalar muls. 22 | type nafLookupTable5 struct { 23 | points [8]projCached 24 | } 25 | 26 | // A precomputed lookup table for fixed-base, variable-time scalar muls. 27 | type nafLookupTable8 struct { 28 | points [64]affineCached 29 | } 30 | 31 | // Constructors. 32 | 33 | // Builds a lookup table at runtime. Fast. 34 | func (v *projLookupTable) FromP3(q *Point) { 35 | // Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q 36 | // This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q 37 | v.points[0].FromP3(q) 38 | tmpP3 := Point{} 39 | tmpP1xP1 := projP1xP1{} 40 | for i := 0; i < 7; i++ { 41 | // Compute (i+1)*Q as Q + i*Q and convert to a ProjCached 42 | // This is needlessly complicated because the API has explicit 43 | // receivers instead of creating stack objects and relying on RVO 44 | v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(q, &v.points[i]))) 45 | } 46 | } 47 | 48 | // This is not optimised for speed; fixed-base tables should be precomputed. 49 | func (v *affineLookupTable) FromP3(q *Point) { 50 | // Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q 51 | // This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q 52 | v.points[0].FromP3(q) 53 | tmpP3 := Point{} 54 | tmpP1xP1 := projP1xP1{} 55 | for i := 0; i < 7; i++ { 56 | // Compute (i+1)*Q as Q + i*Q and convert to AffineCached 57 | v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(q, &v.points[i]))) 58 | } 59 | } 60 | 61 | // Builds a lookup table at runtime. Fast. 62 | func (v *nafLookupTable5) FromP3(q *Point) { 63 | // Goal: v.points[i] = (2*i+1)*Q, i.e., Q, 3Q, 5Q, ..., 15Q 64 | // This allows lookup of -15Q, ..., -3Q, -Q, 0, Q, 3Q, ..., 15Q 65 | v.points[0].FromP3(q) 66 | q2 := Point{} 67 | q2.Add(q, q) 68 | tmpP3 := Point{} 69 | tmpP1xP1 := projP1xP1{} 70 | for i := 0; i < 7; i++ { 71 | v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(&q2, &v.points[i]))) 72 | } 73 | } 74 | 75 | // This is not optimised for speed; fixed-base tables should be precomputed. 76 | func (v *nafLookupTable8) FromP3(q *Point) { 77 | v.points[0].FromP3(q) 78 | q2 := Point{} 79 | q2.Add(q, q) 80 | tmpP3 := Point{} 81 | tmpP1xP1 := projP1xP1{} 82 | for i := 0; i < 63; i++ { 83 | v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(&q2, &v.points[i]))) 84 | } 85 | } 86 | 87 | // Selectors. 88 | 89 | // Set dest to x*Q, where -8 <= x <= 8, in constant time. 90 | func (v *projLookupTable) SelectInto(dest *projCached, x int8) { 91 | // Compute xabs = |x| 92 | xmask := x >> 7 93 | xabs := uint8((x + xmask) ^ xmask) 94 | 95 | dest.Zero() 96 | for j := 1; j <= 8; j++ { 97 | // Set dest = j*Q if |x| = j 98 | cond := subtle.ConstantTimeByteEq(xabs, uint8(j)) 99 | dest.Select(&v.points[j-1], dest, cond) 100 | } 101 | // Now dest = |x|*Q, conditionally negate to get x*Q 102 | dest.CondNeg(int(xmask & 1)) 103 | } 104 | 105 | // Set dest to x*Q, where -8 <= x <= 8, in constant time. 106 | func (v *affineLookupTable) SelectInto(dest *affineCached, x int8) { 107 | // Compute xabs = |x| 108 | xmask := x >> 7 109 | xabs := uint8((x + xmask) ^ xmask) 110 | 111 | dest.Zero() 112 | for j := 1; j <= 8; j++ { 113 | // Set dest = j*Q if |x| = j 114 | cond := subtle.ConstantTimeByteEq(xabs, uint8(j)) 115 | dest.Select(&v.points[j-1], dest, cond) 116 | } 117 | // Now dest = |x|*Q, conditionally negate to get x*Q 118 | dest.CondNeg(int(xmask & 1)) 119 | } 120 | 121 | // Given odd x with 0 < x < 2^4, return x*Q (in variable time). 122 | func (v *nafLookupTable5) SelectInto(dest *projCached, x int8) { 123 | *dest = v.points[x/2] 124 | } 125 | 126 | // Given odd x with 0 < x < 2^7, return x*Q (in variable time). 127 | func (v *nafLookupTable8) SelectInto(dest *affineCached, x int8) { 128 | *dest = v.points[x/2] 129 | } 130 | -------------------------------------------------------------------------------- /ed25519/internal/edwards25519/tables_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package edwards25519 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestProjLookupTable(t *testing.T) { 12 | var table projLookupTable 13 | table.FromP3(B) 14 | 15 | var tmp1, tmp2, tmp3 projCached 16 | table.SelectInto(&tmp1, 6) 17 | table.SelectInto(&tmp2, -2) 18 | table.SelectInto(&tmp3, -4) 19 | // Expect T1 + T2 + T3 = identity 20 | 21 | var accP1xP1 projP1xP1 22 | accP3 := NewIdentityPoint() 23 | 24 | accP1xP1.Add(accP3, &tmp1) 25 | accP3.fromP1xP1(&accP1xP1) 26 | accP1xP1.Add(accP3, &tmp2) 27 | accP3.fromP1xP1(&accP1xP1) 28 | accP1xP1.Add(accP3, &tmp3) 29 | accP3.fromP1xP1(&accP1xP1) 30 | 31 | if accP3.Equal(I) != 1 { 32 | t.Errorf("Consistency check on ProjLookupTable.SelectInto failed! %x %x %x", tmp1, tmp2, tmp3) 33 | } 34 | } 35 | 36 | func TestAffineLookupTable(t *testing.T) { 37 | var table affineLookupTable 38 | table.FromP3(B) 39 | 40 | var tmp1, tmp2, tmp3 affineCached 41 | table.SelectInto(&tmp1, 3) 42 | table.SelectInto(&tmp2, -7) 43 | table.SelectInto(&tmp3, 4) 44 | // Expect T1 + T2 + T3 = identity 45 | 46 | var accP1xP1 projP1xP1 47 | accP3 := NewIdentityPoint() 48 | 49 | accP1xP1.AddAffine(accP3, &tmp1) 50 | accP3.fromP1xP1(&accP1xP1) 51 | accP1xP1.AddAffine(accP3, &tmp2) 52 | accP3.fromP1xP1(&accP1xP1) 53 | accP1xP1.AddAffine(accP3, &tmp3) 54 | accP3.fromP1xP1(&accP1xP1) 55 | 56 | if accP3.Equal(I) != 1 { 57 | t.Errorf("Consistency check on ProjLookupTable.SelectInto failed! %x %x %x", tmp1, tmp2, tmp3) 58 | } 59 | } 60 | 61 | func TestNafLookupTable5(t *testing.T) { 62 | var table nafLookupTable5 63 | table.FromP3(B) 64 | 65 | var tmp1, tmp2, tmp3, tmp4 projCached 66 | table.SelectInto(&tmp1, 9) 67 | table.SelectInto(&tmp2, 11) 68 | table.SelectInto(&tmp3, 7) 69 | table.SelectInto(&tmp4, 13) 70 | // Expect T1 + T2 = T3 + T4 71 | 72 | var accP1xP1 projP1xP1 73 | lhs := NewIdentityPoint() 74 | rhs := NewIdentityPoint() 75 | 76 | accP1xP1.Add(lhs, &tmp1) 77 | lhs.fromP1xP1(&accP1xP1) 78 | accP1xP1.Add(lhs, &tmp2) 79 | lhs.fromP1xP1(&accP1xP1) 80 | 81 | accP1xP1.Add(rhs, &tmp3) 82 | rhs.fromP1xP1(&accP1xP1) 83 | accP1xP1.Add(rhs, &tmp4) 84 | rhs.fromP1xP1(&accP1xP1) 85 | 86 | if lhs.Equal(rhs) != 1 { 87 | t.Errorf("Consistency check on nafLookupTable5 failed") 88 | } 89 | } 90 | 91 | func TestNafLookupTable8(t *testing.T) { 92 | var table nafLookupTable8 93 | table.FromP3(B) 94 | 95 | var tmp1, tmp2, tmp3, tmp4 affineCached 96 | table.SelectInto(&tmp1, 49) 97 | table.SelectInto(&tmp2, 11) 98 | table.SelectInto(&tmp3, 35) 99 | table.SelectInto(&tmp4, 25) 100 | // Expect T1 + T2 = T3 + T4 101 | 102 | var accP1xP1 projP1xP1 103 | lhs := NewIdentityPoint() 104 | rhs := NewIdentityPoint() 105 | 106 | accP1xP1.AddAffine(lhs, &tmp1) 107 | lhs.fromP1xP1(&accP1xP1) 108 | accP1xP1.AddAffine(lhs, &tmp2) 109 | lhs.fromP1xP1(&accP1xP1) 110 | 111 | accP1xP1.AddAffine(rhs, &tmp3) 112 | rhs.fromP1xP1(&accP1xP1) 113 | accP1xP1.AddAffine(rhs, &tmp4) 114 | rhs.fromP1xP1(&accP1xP1) 115 | 116 | if lhs.Equal(rhs) != 1 { 117 | t.Errorf("Consistency check on nafLookupTable8 failed") 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /ed25519/testdata/sign.input.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/pat-go/656110b5f12112add4d912180a00b895cd6cf60e/ed25519/testdata/sign.input.gz -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cloudflare/pat-go 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/cisco/go-hpke v0.0.0-20210524174249-dd22b38cf960 9 | github.com/cloudflare/circl v1.3.7 10 | golang.org/x/crypto v0.35.0 11 | ) 12 | 13 | require ( 14 | git.schwanenlied.me/yawning/x448.git v0.0.0-20170617130356-01b048fb03d6 // indirect 15 | github.com/bwesterb/go-ristretto v1.2.3 // indirect 16 | github.com/cisco/go-tls-syntax v0.0.0-20200617162716-46b0cfb76b9b // indirect 17 | github.com/stretchr/testify v1.9.0 // indirect 18 | golang.org/x/sys v0.30.0 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | git.schwanenlied.me/yawning/x448.git v0.0.0-20170617130356-01b048fb03d6 h1:w8IZgCntCe0RuBJp+dENSMwEBl/k8saTgJ5hPca5IWw= 2 | git.schwanenlied.me/yawning/x448.git v0.0.0-20170617130356-01b048fb03d6/go.mod h1:wQaGCqEu44ykB17jZHCevrgSVl3KJnwQBObUtrKU4uU= 3 | github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw= 4 | github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= 5 | github.com/cisco/go-hpke v0.0.0-20210524174249-dd22b38cf960 h1:eh0zzzreqmFagQ5lzYQmVUrPqJ9moWAn7zz4NiEqsGA= 6 | github.com/cisco/go-hpke v0.0.0-20210524174249-dd22b38cf960/go.mod h1:RSsoIHRMBe69FbF/fIbmWYa3rrC6vuPyC0MbNUpel3Q= 7 | github.com/cisco/go-tls-syntax v0.0.0-20200617162716-46b0cfb76b9b h1:Ves2turKTX7zruivAcUOQg155xggcbv3suVdbKCBQNM= 8 | github.com/cisco/go-tls-syntax v0.0.0-20200617162716-46b0cfb76b9b/go.mod h1:0AZAV7lYvynZQ5ErHlGMKH+4QYMyNCFd+AiL9MlrCYA= 9 | github.com/cloudflare/circl v1.0.0/go.mod h1:MhjB3NEEhJbTOdLLq964NIUisXDxaE1WkQPUxtgZXiY= 10 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 11 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 18 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 19 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 20 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 21 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 22 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 23 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= 24 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 25 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 26 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 27 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 28 | golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 30 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 31 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 32 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 33 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 34 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 35 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 36 | -------------------------------------------------------------------------------- /quicwire/wire.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build go1.21 6 | 7 | // Package quicwire encodes and decode QUIC/HTTP3 wire encoding types, 8 | // particularly variable-length integers. 9 | package quicwire 10 | 11 | import "encoding/binary" 12 | 13 | const ( 14 | MaxVarintSize = 8 // encoded size in bytes 15 | MaxVarint = (1 << 62) - 1 16 | ) 17 | 18 | // ConsumeVarint parses a variable-length integer, reporting its length. 19 | // It returns a negative length upon an error. 20 | // 21 | // https://www.rfc-editor.org/rfc/rfc9000.html#section-16 22 | func ConsumeVarint(b []byte) (v uint64, n int) { 23 | if len(b) < 1 { 24 | return 0, -1 25 | } 26 | b0 := b[0] & 0x3f 27 | switch b[0] >> 6 { 28 | case 0: 29 | return uint64(b0), 1 30 | case 1: 31 | if len(b) < 2 { 32 | return 0, -1 33 | } 34 | return uint64(b0)<<8 | uint64(b[1]), 2 35 | case 2: 36 | if len(b) < 4 { 37 | return 0, -1 38 | } 39 | return uint64(b0)<<24 | uint64(b[1])<<16 | uint64(b[2])<<8 | uint64(b[3]), 4 40 | case 3: 41 | if len(b) < 8 { 42 | return 0, -1 43 | } 44 | return uint64(b0)<<56 | uint64(b[1])<<48 | uint64(b[2])<<40 | uint64(b[3])<<32 | uint64(b[4])<<24 | uint64(b[5])<<16 | uint64(b[6])<<8 | uint64(b[7]), 8 45 | } 46 | return 0, -1 47 | } 48 | 49 | // consumeVarintInt64 parses a variable-length integer as an int64. 50 | func ConsumeVarintInt64(b []byte) (v int64, n int) { 51 | u, n := ConsumeVarint(b) 52 | // QUIC varints are 62-bits large, so this conversion can never overflow. 53 | return int64(u), n 54 | } 55 | 56 | // AppendVarint appends a variable-length integer to b. 57 | // 58 | // https://www.rfc-editor.org/rfc/rfc9000.html#section-16 59 | func AppendVarint(b []byte, v uint64) []byte { 60 | switch { 61 | case v <= 63: 62 | return append(b, byte(v)) 63 | case v <= 16383: 64 | return append(b, (1<<6)|byte(v>>8), byte(v)) 65 | case v <= 1073741823: 66 | return append(b, (2<<6)|byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) 67 | case v <= 4611686018427387903: 68 | return append(b, (3<<6)|byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) 69 | default: 70 | panic("varint too large") 71 | } 72 | } 73 | 74 | // SizeVarint returns the size of the variable-length integer encoding of f. 75 | func SizeVarint(v uint64) int { 76 | switch { 77 | case v <= 63: 78 | return 1 79 | case v <= 16383: 80 | return 2 81 | case v <= 1073741823: 82 | return 4 83 | case v <= 4611686018427387903: 84 | return 8 85 | default: 86 | panic("varint too large") 87 | } 88 | } 89 | 90 | // ConsumeUint32 parses a 32-bit fixed-length, big-endian integer, reporting its length. 91 | // It returns a negative length upon an error. 92 | func ConsumeUint32(b []byte) (uint32, int) { 93 | if len(b) < 4 { 94 | return 0, -1 95 | } 96 | return binary.BigEndian.Uint32(b), 4 97 | } 98 | 99 | // ConsumeUint64 parses a 64-bit fixed-length, big-endian integer, reporting its length. 100 | // It returns a negative length upon an error. 101 | func ConsumeUint64(b []byte) (uint64, int) { 102 | if len(b) < 8 { 103 | return 0, -1 104 | } 105 | return binary.BigEndian.Uint64(b), 8 106 | } 107 | 108 | // ConsumeUint8Bytes parses a sequence of bytes prefixed with an 8-bit length, 109 | // reporting the total number of bytes consumed. 110 | // It returns a negative length upon an error. 111 | func ConsumeUint8Bytes(b []byte) ([]byte, int) { 112 | if len(b) < 1 { 113 | return nil, -1 114 | } 115 | size := int(b[0]) 116 | const n = 1 117 | if size > len(b[n:]) { 118 | return nil, -1 119 | } 120 | return b[n:][:size], size + n 121 | } 122 | 123 | // AppendUint8Bytes appends a sequence of bytes prefixed by an 8-bit length. 124 | func AppendUint8Bytes(b, v []byte) []byte { 125 | if len(v) > 0xff { 126 | panic("uint8-prefixed bytes too large") 127 | } 128 | b = append(b, uint8(len(v))) 129 | b = append(b, v...) 130 | return b 131 | } 132 | 133 | // ConsumeVarintBytes parses a sequence of bytes preceded by a variable-length integer length, 134 | // reporting the total number of bytes consumed. 135 | // It returns a negative length upon an error. 136 | func ConsumeVarintBytes(b []byte) ([]byte, int) { 137 | size, n := ConsumeVarint(b) 138 | if n < 0 { 139 | return nil, -1 140 | } 141 | if size > uint64(len(b[n:])) { 142 | return nil, -1 143 | } 144 | return b[n:][:size], int(size) + n 145 | } 146 | 147 | // AppendVarintBytes appends a sequence of bytes prefixed by a variable-length integer length. 148 | func AppendVarintBytes(b, v []byte) []byte { 149 | b = AppendVarint(b, uint64(len(v))) 150 | b = append(b, v...) 151 | return b 152 | } 153 | -------------------------------------------------------------------------------- /quicwire/wire_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build go1.21 6 | 7 | package quicwire 8 | 9 | import ( 10 | "bytes" 11 | "testing" 12 | ) 13 | 14 | func TestConsumeVarint(t *testing.T) { 15 | for _, test := range []struct { 16 | b []byte 17 | want uint64 18 | wantLen int 19 | }{ 20 | {[]byte{0x00}, 0, 1}, 21 | {[]byte{0x3f}, 63, 1}, 22 | {[]byte{0x40, 0x00}, 0, 2}, 23 | {[]byte{0x7f, 0xff}, 16383, 2}, 24 | {[]byte{0x80, 0x00, 0x00, 0x00}, 0, 4}, 25 | {[]byte{0xbf, 0xff, 0xff, 0xff}, 1073741823, 4}, 26 | {[]byte{0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 0, 8}, 27 | {[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 4611686018427387903, 8}, 28 | // Example cases from https://www.rfc-editor.org/rfc/rfc9000.html#section-a.1 29 | {[]byte{0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c}, 151288809941952652, 8}, 30 | {[]byte{0x9d, 0x7f, 0x3e, 0x7d}, 494878333, 4}, 31 | {[]byte{0x7b, 0xbd}, 15293, 2}, 32 | {[]byte{0x25}, 37, 1}, 33 | {[]byte{0x40, 0x25}, 37, 2}, 34 | } { 35 | got, gotLen := ConsumeVarint(test.b) 36 | if got != test.want || gotLen != test.wantLen { 37 | t.Errorf("ConsumeVarint(%x) = %v, %v; want %v, %v", test.b, got, gotLen, test.want, test.wantLen) 38 | } 39 | // Extra data in the buffer is ignored. 40 | b := append(test.b, 0) 41 | got, gotLen = ConsumeVarint(b) 42 | if got != test.want || gotLen != test.wantLen { 43 | t.Errorf("ConsumeVarint(%x) = %v, %v; want %v, %v", b, got, gotLen, test.want, test.wantLen) 44 | } 45 | // Short buffer results in an error. 46 | for i := 1; i <= len(test.b); i++ { 47 | b = test.b[:len(test.b)-i] 48 | got, gotLen = ConsumeVarint(b) 49 | if got != 0 || gotLen >= 0 { 50 | t.Errorf("ConsumeVarint(%x) = %v, %v; want 0, -1", b, got, gotLen) 51 | } 52 | } 53 | } 54 | } 55 | 56 | func TestAppendVarint(t *testing.T) { 57 | for _, test := range []struct { 58 | v uint64 59 | want []byte 60 | }{ 61 | {0, []byte{0x00}}, 62 | {63, []byte{0x3f}}, 63 | {16383, []byte{0x7f, 0xff}}, 64 | {1073741823, []byte{0xbf, 0xff, 0xff, 0xff}}, 65 | {4611686018427387903, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, 66 | // Example cases from https://www.rfc-editor.org/rfc/rfc9000.html#section-a.1 67 | {151288809941952652, []byte{0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c}}, 68 | {494878333, []byte{0x9d, 0x7f, 0x3e, 0x7d}}, 69 | {15293, []byte{0x7b, 0xbd}}, 70 | {37, []byte{0x25}}, 71 | } { 72 | got := AppendVarint([]byte{}, test.v) 73 | if !bytes.Equal(got, test.want) { 74 | t.Errorf("AppendVarint(nil, %v) = %x, want %x", test.v, got, test.want) 75 | } 76 | if gotLen, wantLen := SizeVarint(test.v), len(got); gotLen != wantLen { 77 | t.Errorf("SizeVarint(%v) = %v, want %v", test.v, gotLen, wantLen) 78 | } 79 | } 80 | } 81 | 82 | func TestConsumeUint32(t *testing.T) { 83 | for _, test := range []struct { 84 | b []byte 85 | want uint32 86 | wantLen int 87 | }{ 88 | {[]byte{0x01, 0x02, 0x03, 0x04}, 0x01020304, 4}, 89 | {[]byte{0x01, 0x02, 0x03}, 0, -1}, 90 | } { 91 | if got, n := ConsumeUint32(test.b); got != test.want || n != test.wantLen { 92 | t.Errorf("ConsumeUint32(%x) = %v, %v; want %v, %v", test.b, got, n, test.want, test.wantLen) 93 | } 94 | } 95 | } 96 | 97 | func TestConsumeUint64(t *testing.T) { 98 | for _, test := range []struct { 99 | b []byte 100 | want uint64 101 | wantLen int 102 | }{ 103 | {[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, 0x0102030405060708, 8}, 104 | {[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, 0, -1}, 105 | } { 106 | if got, n := ConsumeUint64(test.b); got != test.want || n != test.wantLen { 107 | t.Errorf("ConsumeUint32(%x) = %v, %v; want %v, %v", test.b, got, n, test.want, test.wantLen) 108 | } 109 | } 110 | } 111 | 112 | func TestConsumeVarintBytes(t *testing.T) { 113 | for _, test := range []struct { 114 | b []byte 115 | want []byte 116 | wantLen int 117 | }{ 118 | {[]byte{0x00}, []byte{}, 1}, 119 | {[]byte{0x40, 0x00}, []byte{}, 2}, 120 | {[]byte{0x04, 0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}, 5}, 121 | {[]byte{0x40, 0x04, 0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}, 6}, 122 | } { 123 | got, gotLen := ConsumeVarintBytes(test.b) 124 | if !bytes.Equal(got, test.want) || gotLen != test.wantLen { 125 | t.Errorf("ConsumeVarintBytes(%x) = {%x}, %v; want {%x}, %v", test.b, got, gotLen, test.want, test.wantLen) 126 | } 127 | // Extra data in the buffer is ignored. 128 | b := append(test.b, 0) 129 | got, gotLen = ConsumeVarintBytes(b) 130 | if !bytes.Equal(got, test.want) || gotLen != test.wantLen { 131 | t.Errorf("ConsumeVarintBytes(%x) = {%x}, %v; want {%x}, %v", b, got, gotLen, test.want, test.wantLen) 132 | } 133 | // Short buffer results in an error. 134 | for i := 1; i <= len(test.b); i++ { 135 | b = test.b[:len(test.b)-i] 136 | got, gotLen := ConsumeVarintBytes(b) 137 | if len(got) > 0 || gotLen > 0 { 138 | t.Errorf("ConsumeVarintBytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen) 139 | } 140 | } 141 | 142 | } 143 | } 144 | 145 | func TestConsumeVarintBytesErrors(t *testing.T) { 146 | for _, b := range [][]byte{ 147 | {0x01}, 148 | {0x40, 0x01}, 149 | } { 150 | got, gotLen := ConsumeVarintBytes(b) 151 | if len(got) > 0 || gotLen > 0 { 152 | t.Errorf("ConsumeVarintBytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen) 153 | } 154 | } 155 | } 156 | 157 | func TestConsumeUint8Bytes(t *testing.T) { 158 | for _, test := range []struct { 159 | b []byte 160 | want []byte 161 | wantLen int 162 | }{ 163 | {[]byte{0x00}, []byte{}, 1}, 164 | {[]byte{0x01, 0x00}, []byte{0x00}, 2}, 165 | {[]byte{0x04, 0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}, 5}, 166 | } { 167 | got, gotLen := ConsumeUint8Bytes(test.b) 168 | if !bytes.Equal(got, test.want) || gotLen != test.wantLen { 169 | t.Errorf("ConsumeUint8Bytes(%x) = {%x}, %v; want {%x}, %v", test.b, got, gotLen, test.want, test.wantLen) 170 | } 171 | // Extra data in the buffer is ignored. 172 | b := append(test.b, 0) 173 | got, gotLen = ConsumeUint8Bytes(b) 174 | if !bytes.Equal(got, test.want) || gotLen != test.wantLen { 175 | t.Errorf("ConsumeUint8Bytes(%x) = {%x}, %v; want {%x}, %v", b, got, gotLen, test.want, test.wantLen) 176 | } 177 | // Short buffer results in an error. 178 | for i := 1; i <= len(test.b); i++ { 179 | b = test.b[:len(test.b)-i] 180 | got, gotLen := ConsumeUint8Bytes(b) 181 | if len(got) > 0 || gotLen > 0 { 182 | t.Errorf("ConsumeUint8Bytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen) 183 | } 184 | } 185 | 186 | } 187 | } 188 | 189 | func TestConsumeUint8BytesErrors(t *testing.T) { 190 | for _, b := range [][]byte{ 191 | {0x01}, 192 | {0x04, 0x01, 0x02, 0x03}, 193 | } { 194 | got, gotLen := ConsumeUint8Bytes(b) 195 | if len(got) > 0 || gotLen > 0 { 196 | t.Errorf("ConsumeUint8Bytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen) 197 | } 198 | } 199 | } 200 | 201 | func TestAppendUint8Bytes(t *testing.T) { 202 | var got []byte 203 | got = AppendUint8Bytes(got, []byte{}) 204 | got = AppendUint8Bytes(got, []byte{0xaa, 0xbb}) 205 | want := []byte{ 206 | 0x00, 207 | 0x02, 0xaa, 0xbb, 208 | } 209 | if !bytes.Equal(got, want) { 210 | t.Errorf("AppendUint8Bytes {}, {aabb} = {%x}; want {%x}", got, want) 211 | } 212 | } 213 | 214 | func TestAppendVarintBytes(t *testing.T) { 215 | var got []byte 216 | got = AppendVarintBytes(got, []byte{}) 217 | got = AppendVarintBytes(got, []byte{0xaa, 0xbb}) 218 | want := []byte{ 219 | 0x00, 220 | 0x02, 0xaa, 0xbb, 221 | } 222 | if !bytes.Equal(got, want) { 223 | t.Errorf("AppendVarintBytes {}, {aabb} = {%x}; want {%x}", got, want) 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /scripts/format_benchmarks.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | nameMap = { 4 | "BenchmarkPublicTokenRoundTrip/ClientRequest": "Basic Client Request", 5 | "BenchmarkPublicTokenRoundTrip/IssuerEvaluate": "Basic Issuer Evaluate", 6 | "BenchmarkPublicTokenRoundTrip/ClientFinalize": "Basic Client Finalize", 7 | "BenchmarkRateLimitedTokenRoundTrip/ClientRequest": "Rate-Limited Client Request", 8 | "BenchmarkRateLimitedTokenRoundTrip/AttesterRequest": "Rate-Limited Attester Request", 9 | "BenchmarkRateLimitedTokenRoundTrip/IssuerEvaluate": "Rate-Limited Issuer Evaluate", 10 | "BenchmarkRateLimitedTokenRoundTrip/AttesterEvaluate": "Rate-Limited Attester Evaluate", 11 | "BenchmarkRateLimitedTokenRoundTrip/ClientFinalize": "Rate-Limited Client Finalize", 12 | } 13 | 14 | orderedNames = [ 15 | "Basic Client Request", 16 | "Basic Issuer Evaluate", 17 | "Basic Client Finalize", 18 | "Rate-Limited Client Request", 19 | "Rate-Limited Attester Request", 20 | "Rate-Limited Issuer Evaluate", 21 | "Rate-Limited Attester Evaluate", 22 | "Rate-Limited Client Finalize", 23 | ] 24 | 25 | def mapName(name): 26 | for key in nameMap: 27 | if name.startswith(key): 28 | return nameMap[key] 29 | raise Exception("Unknown operation", name) 30 | 31 | costs = {} 32 | for line in sys.stdin: 33 | if line.startswith("Benchmark"): 34 | line = line.strip().split("\t") 35 | try: 36 | name, cost = mapName(line[0].strip()), line[2].strip().replace("ns/op", "") 37 | costs[name] = cost 38 | except: 39 | # Ignore other benchmarks 40 | pass 41 | 42 | print("\\begin{table}[ht!]") 43 | print("\\label{tab:bench-computation-overhead}") 44 | print("\\caption{Computation cost for basic and rate-limited issuance protocols}") 45 | print("\\begin{tabular}{| l | c |}") 46 | print("\hline") 47 | print("\\textbf{Operation} & \\textbf{Time (ns/op)} \\\\ \hline") 48 | for name in orderedNames: 49 | print(" %s & $%s$ \\\\ \hline" % (name, costs[name])) 50 | print("\end{tabular}") 51 | print("\end{table}") -------------------------------------------------------------------------------- /scripts/format_ecdsa_benchmarks.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | blindgenToken = "$\\blindgen$" 4 | blindkeyToken = "$\\blindkey$" 5 | unblindkeyToken = "$\\unblindkey$" 6 | blindkeySignToken = "$\\blindkeysign$" 7 | signToken = "$\\sign$" 8 | 9 | nameMap = { 10 | "BenchmarkECDSA/KeyGen": blindgenToken, 11 | "BenchmarkECDSA/BlindPublicKey": blindkeyToken, 12 | "BenchmarkECDSA/UnblindPublicKey": unblindkeyToken, 13 | "BenchmarkECDSA/BlindKeySign": blindkeySignToken, 14 | "BenchmarkECDSA/Sign": signToken, 15 | } 16 | 17 | orderedNames = [ 18 | blindgenToken, 19 | blindkeyToken, 20 | unblindkeyToken, 21 | blindkeySignToken, 22 | signToken, 23 | ] 24 | 25 | def mapName(name): 26 | for key in nameMap: 27 | if name.startswith(key): 28 | return nameMap[key] 29 | raise Exception("Unknown operation", name) 30 | 31 | costs = {} 32 | for line in sys.stdin: 33 | if line.startswith("Benchmark"): 34 | line = line.strip().split("\t") 35 | try: 36 | name, cost = mapName(line[0].strip()), line[2].strip().replace("ns/op", "") 37 | costs[name] = cost 38 | except: 39 | # Ignore other benchmarks 40 | pass 41 | 42 | print("\\begin{table}[ht!]") 43 | print("\\label{tab:bench-ecdsa}") 44 | print("\\caption{Computation cost for ECDSA signing with key blinding}") 45 | print("\\begin{tabular}{| l | c |}") 46 | print("\hline") 47 | print("\\textbf{Operation} & \\textbf{Time (ns/op)} \\\\ \hline") 48 | for name in orderedNames: 49 | print(" %s & $%s$ \\\\ \hline" % (name, costs[name])) 50 | print("\end{tabular}") 51 | print("\end{table}") -------------------------------------------------------------------------------- /scripts/format_ed25519_benchmarks.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | blindgenToken = "$\\blindgen$" 4 | blindkeyToken = "$\\blindkey$" 5 | unblindkeyToken = "$\\unblindkey$" 6 | blindkeySignToken = "$\\blindkeysign$" 7 | signToken = "$\\sign$" 8 | 9 | nameMap = { 10 | "BenchmarkEd25519/KeyGen": blindgenToken, 11 | "BenchmarkEd25519/BlindPublicKey": blindkeyToken, 12 | "BenchmarkEd25519/UnblindPublicKey": unblindkeyToken, 13 | "BenchmarkEd25519/BlindKeySign": blindkeySignToken, 14 | "BenchmarkEd25519/Sign": signToken, 15 | } 16 | 17 | orderedNames = [ 18 | blindgenToken, 19 | blindkeyToken, 20 | unblindkeyToken, 21 | blindkeySignToken, 22 | signToken, 23 | ] 24 | 25 | def mapName(name): 26 | for key in nameMap: 27 | if name.startswith(key): 28 | return nameMap[key] 29 | raise Exception("Unknown operation", name) 30 | 31 | costs = {} 32 | for line in sys.stdin: 33 | if line.startswith("Benchmark"): 34 | line = line.strip().split("\t") 35 | try: 36 | name, cost = mapName(line[0].strip()), line[2].strip().replace("ns/op", "") 37 | costs[name] = cost 38 | except: 39 | # Ignore other benchmarks 40 | pass 41 | 42 | print("\\begin{table}[ht!]") 43 | print("\\label{tab:bench-ed25519}") 44 | print("\\caption{Computation cost for Ed25519 signing with key blinding}") 45 | print("\\begin{tabular}{| l | c |}") 46 | print("\hline") 47 | print("\\textbf{Operation} & \\textbf{Time (ns/op)} \\\\ \hline") 48 | for name in orderedNames: 49 | print(" %s & $%s$ \\\\ \hline" % (name, costs[name])) 50 | print("\end{tabular}") 51 | print("\end{table}") -------------------------------------------------------------------------------- /scripts/format_test_vectors.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import textwrap 4 | 5 | DEFAULT_WIDTH = 65 6 | 7 | def wrap_line(value, width=DEFAULT_WIDTH): 8 | return textwrap.fill(value, width) 9 | 10 | def format_vector_keys(vector_keys, entry, indent_level = 0, is_array_element = False): 11 | indent = " " * indent_level 12 | array_indent = "- " if is_array_element else "" 13 | formatted = "" 14 | if "comment" in vector_keys: 15 | formatted += indent + entry["comment"] + "\n" 16 | for key in vector_keys: 17 | if type(key) == type(()): 18 | formatted += key[0] + ":\n" 19 | if type(entry[key[0]]) == type([]): 20 | for e in entry[key[0]]: 21 | formatted += format_vector_keys(key[1], e, indent_level + 1, True) 22 | else: 23 | formatted += format_vector_keys(key[1], entry[key[0]], indent_level + 1) 24 | elif key in entry: 25 | if key == "comment": 26 | continue 27 | if type(entry[key]) == type(""): 28 | initial_indent = indent + array_indent 29 | f = wrap_line(key + ": " + str(entry[key]), DEFAULT_WIDTH-len(initial_indent)) 30 | formatted += "\n".join(f"{initial_indent}{line}" for line in f.splitlines()) + "\n" 31 | elif type(entry[key] == type([])): 32 | formatted += indent + array_indent + key + ":\n" 33 | off_indent = "" 34 | if array_indent != "": 35 | off_indent = " " 36 | initial_indent = indent + off_indent + " " 37 | for e in entry[key]: 38 | f = wrap_line("- " + e, DEFAULT_WIDTH-len(initial_indent)) 39 | formatted += "\n".join(f"{initial_indent}{line}" for line in f.splitlines()) + "\n" 40 | else: 41 | initial_indent = indent + array_indent 42 | f = wrap_line(key + ": " + str(",".join(entry[key])), DEFAULT_WIDTH-len(initial_indent)) 43 | formatted += "\n".join(f"{initial_indent}{line}" for line in f.splitlines()) + "\n" 44 | 45 | if array_indent != "": 46 | array_indent = " " 47 | return formatted 48 | 49 | def format_vector(vector_keys, vector_fname): 50 | with open(vector_fname, "r") as fh: 51 | data = json.load(fh) 52 | formatted = "~~~\n" 53 | for i, entry in enumerate(data): 54 | formatted += ("// Test vector %d:" % (i+1)) + "\n" 55 | formatted += format_vector_keys(vector_keys, entry) 56 | formatted += "\n" 57 | print(formatted + "~~~\n") 58 | 59 | if "type3-ed25519-blinding" in sys.argv[1]: 60 | ordered_keys = [ 61 | "skS", "pkS", "bk", "pkR", "message", "context", "signature", 62 | ] 63 | format_vector(ordered_keys, sys.argv[1]) 64 | 65 | if "type3-ecdsa-blinding" in sys.argv[1]: 66 | ordered_keys = [ 67 | "skS", "pkS", "bk", "pkR", "message", "context", "signature", 68 | ] 69 | format_vector(ordered_keys, sys.argv[1]) 70 | 71 | if "type2-issuance" in sys.argv[1]: 72 | ordered_keys = [ 73 | "skS", "pkS", "token_challenge", "nonce", "blind", "salt", "token_request", "token_response", "token" 74 | ] 75 | format_vector(ordered_keys, sys.argv[1]) 76 | 77 | if "type5-issuance" in sys.argv[1]: 78 | ordered_keys = [ 79 | "skS", "pkS", "token_challenge", "nonces", "blinds", "salt", "token_request", "token_response", "tokens" 80 | ] 81 | format_vector(ordered_keys, sys.argv[1]) 82 | 83 | if "type1-issuance" in sys.argv[1]: 84 | ordered_keys = [ 85 | "skS", "pkS", "token_challenge", "nonce", "blind", "token_request", "token_response", "token" 86 | ] 87 | format_vector(ordered_keys, sys.argv[1]) 88 | 89 | if "batched-issuance-test-vectors.json" in sys.argv[1]: 90 | ordered_keys = [ 91 | ("issuance", ["type", "skS", "pkS", "token_challenge", "nonce", "nonces", "blind", "blinds", "token", "tokens"]), 92 | "token_request", 93 | "token_response", 94 | ] 95 | format_vector(ordered_keys, sys.argv[1]) 96 | 97 | if "type3-origin-encryption-test-vectors.json" in sys.argv[1]: 98 | ordered_keys = [ 99 | "origin_name", "kem_id", "kdf_id", "aead_id", "issuer_encap_key_seed", "issuer_encap_key", "token_type", "token_key_id", "blinded_msg", "request_key", "issuer_encap_key_id", "encrypted_token_request" 100 | ] 101 | format_vector(ordered_keys, sys.argv[1]) 102 | 103 | if "type3-anon-origin-id-test-vectors.json" in sys.argv[1]: 104 | ordered_keys = [ 105 | "sk_sign", "pk_sign", "sk_origin", "request_blind", "request_key", "index_key", "issuer_origin_alias" 106 | ] 107 | format_vector(ordered_keys, sys.argv[1]) 108 | 109 | if "token-test-vectors" in sys.argv[1]: 110 | ordered_keys = [ 111 | "comment", "token_type", "issuer_name", "redemption_context", "origin_info", "nonce", "token_key_id", "token_authenticator_input" 112 | ] 113 | format_vector(ordered_keys, sys.argv[1]) -------------------------------------------------------------------------------- /tokens/batched/client.go: -------------------------------------------------------------------------------- 1 | package batched 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cloudflare/pat-go/tokens" 7 | "github.com/cloudflare/pat-go/tokens/type1" 8 | "github.com/cloudflare/pat-go/tokens/type2" 9 | ) 10 | 11 | type BatchedClient struct { 12 | } 13 | 14 | func NewBasicClient() BatchedClient { 15 | return BatchedClient{} 16 | } 17 | 18 | // https://datatracker.ietf.org/doc/html/draft-ietf-privacypass-batched-tokens-03 19 | func (c BatchedClient) CreateTokenRequest(tokenRequests []tokens.TokenRequestWithDetails) (*BatchedTokenRequest, error) { 20 | if len(tokenRequests) == 0 { 21 | return nil, fmt.Errorf("no token requests") 22 | } 23 | 24 | for _, tokenRequest := range tokenRequests { 25 | switch tokenRequest.Type() { 26 | case type1.BasicPrivateTokenType: 27 | casted, ok := tokenRequest.(*type1.BasicPrivateTokenRequest) 28 | if !ok || casted.Type() != type1.BasicPrivateTokenType { 29 | return nil, fmt.Errorf("invalid token request type") 30 | } 31 | case type2.BasicPublicTokenType: 32 | casted, ok := tokenRequest.(*type2.BasicPublicTokenRequest) 33 | if !ok || casted.Type() != type2.BasicPublicTokenType { 34 | return nil, fmt.Errorf("invalid token request type") 35 | } 36 | default: 37 | return nil, fmt.Errorf("unknown token type %d", tokenRequest.Type()) 38 | } 39 | } 40 | 41 | request := BatchedTokenRequest{ 42 | token_requests: tokenRequests, 43 | } 44 | 45 | return &request, nil 46 | } 47 | -------------------------------------------------------------------------------- /tokens/batched/issuer.go: -------------------------------------------------------------------------------- 1 | package batched 2 | 3 | import ( 4 | "github.com/cloudflare/pat-go/quicwire" 5 | "github.com/cloudflare/pat-go/tokens" 6 | "golang.org/x/crypto/cryptobyte" 7 | ) 8 | 9 | type TokenStatus uint8 10 | 11 | const ( 12 | TokenStatusAbsent TokenStatus = iota 13 | TokenStatusPresent 14 | ) 15 | 16 | type Issuer interface { 17 | Evaluate(req tokens.TokenRequest) ([]byte, error) 18 | TokenKeyID() []byte 19 | Type() uint16 20 | } 21 | 22 | type BatchedIssuer interface { 23 | EvaluateBatch(req *BatchedTokenRequest) ([]byte, error) 24 | } 25 | 26 | type BasicBatchedIssuer struct { 27 | issuers map[uint16][]Issuer 28 | } 29 | 30 | func NewBasicBatchedIssuer(issuersArgs ...Issuer) *BasicBatchedIssuer { 31 | issuers := make(map[uint16][]Issuer) 32 | 33 | for _, issuer := range issuersArgs { 34 | token_type := issuer.Type() 35 | _, ok := issuers[token_type] 36 | if !ok { 37 | issuers[token_type] = make([]Issuer, 0) 38 | } 39 | issuers[token_type] = append(issuers[token_type], issuer) 40 | } 41 | 42 | return &BasicBatchedIssuer{ 43 | issuers, 44 | } 45 | } 46 | 47 | func (i BasicBatchedIssuer) EvaluateBatch(req *BatchedTokenRequest) ([]byte, error) { 48 | RESPONSE_ERROR := []byte{0} 49 | 50 | responses := make([][]byte, len(req.token_requests)) 51 | for iReq, req := range req.token_requests { 52 | token_type := req.Type() 53 | issuers, ok := i.issuers[token_type] 54 | if !ok { 55 | // token type not supported 56 | responses[iReq] = RESPONSE_ERROR 57 | } else { 58 | // in case no issuer is found 59 | responses[iReq] = RESPONSE_ERROR 60 | 61 | for _, issuer := range issuers { 62 | issuerKey := issuer.TokenKeyID() 63 | if req.TruncatedTokenKeyID() != issuerKey[len(issuerKey)-1] { 64 | continue 65 | } 66 | req := req.(tokens.TokenRequest) 67 | resp, err := issuer.Evaluate(req) 68 | if err == nil { 69 | responses[iReq] = resp 70 | break 71 | } 72 | } 73 | } 74 | } 75 | 76 | // Build RFC 9000 varint 77 | bResps := cryptobyte.NewBuilder(nil) 78 | for i, response := range responses { 79 | if len(response) > 0 { 80 | bResps.AddUint8(uint8(TokenStatusPresent)) 81 | bResps.AddUint16(req.token_requests[i].Type()) 82 | bResps.AddBytes(response) 83 | } else { 84 | bResps.AddUint8(uint8(TokenStatusAbsent)) 85 | } 86 | } 87 | rawBResps := bResps.BytesOrPanic() 88 | l := quicwire.AppendVarint([]byte{}, uint64(len(rawBResps))) 89 | 90 | b := cryptobyte.NewBuilder(nil) 91 | b.AddBytes(l) 92 | b.AddBytes(rawBResps) 93 | 94 | return b.Bytes() 95 | } 96 | -------------------------------------------------------------------------------- /tokens/batched/token_request.go: -------------------------------------------------------------------------------- 1 | package batched 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/cloudflare/pat-go/quicwire" 7 | "github.com/cloudflare/pat-go/tokens" 8 | "github.com/cloudflare/pat-go/tokens/type1" 9 | "github.com/cloudflare/pat-go/tokens/type2" 10 | "golang.org/x/crypto/cryptobyte" 11 | ) 12 | 13 | type BatchedTokenRequest struct { 14 | raw []byte 15 | token_requests []tokens.TokenRequestWithDetails 16 | } 17 | 18 | func (r BatchedTokenRequest) Marshal() []byte { 19 | if r.raw != nil { 20 | return r.raw 21 | } 22 | 23 | bReqs := cryptobyte.NewBuilder(nil) 24 | for _, token_request := range r.token_requests { 25 | bReqs.AddBytes(token_request.Marshal()) 26 | } 27 | 28 | rawBReqs := bReqs.BytesOrPanic() 29 | l := quicwire.AppendVarint([]byte{}, uint64(len(rawBReqs))) 30 | 31 | b := cryptobyte.NewBuilder(nil) 32 | b.AddBytes(l) 33 | b.AddBytes(rawBReqs) 34 | 35 | r.raw = b.BytesOrPanic() 36 | return r.raw 37 | } 38 | 39 | func (r *BatchedTokenRequest) Unmarshal(data []byte) bool { 40 | // At most, a quic varint is 4 byte long. copy them to read the length 41 | if len(data) < 4 { 42 | return false 43 | } 44 | 45 | l, offset := quicwire.ConsumeVarint(data) 46 | 47 | r.token_requests = make([]tokens.TokenRequestWithDetails, 0) 48 | i := offset 49 | for i < offset+int(l) { 50 | var token_request tokens.TokenRequestWithDetails 51 | token_type := binary.BigEndian.Uint16(data[i : i+2]) 52 | switch token_type { 53 | case type1.BasicPrivateTokenType: 54 | token_request = new(type1.BasicPrivateTokenRequest) 55 | case type2.BasicPublicTokenType: 56 | token_request = new(type2.BasicPublicTokenRequest) 57 | default: 58 | return false 59 | } 60 | if !token_request.Unmarshal(data[i:]) { 61 | return false 62 | } 63 | r.token_requests = append(r.token_requests, token_request) 64 | // Super inefficient but we don't know how many bytes have been read otherwise 65 | i += len(token_request.Marshal()) 66 | } 67 | 68 | return true 69 | } 70 | -------------------------------------------------------------------------------- /tokens/batched/tokens.go: -------------------------------------------------------------------------------- 1 | package batched 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cloudflare/pat-go/quicwire" 7 | "github.com/cloudflare/pat-go/tokens/type1" 8 | "github.com/cloudflare/pat-go/tokens/type2" 9 | "golang.org/x/crypto/cryptobyte" 10 | ) 11 | 12 | func UnmarshalBatchedTokenResponses(data []byte) ([][]byte, error) { 13 | s := cryptobyte.String(data) 14 | 15 | l, offset := quicwire.ConsumeVarint(data) 16 | s.Skip(offset) 17 | 18 | token_responses_data := data[offset:(offset + int(l))] 19 | 20 | token_responses_string := cryptobyte.String(token_responses_data) 21 | 22 | var token_responses [][]byte 23 | for !token_responses_string.Empty() { 24 | var present uint8 25 | 26 | if !token_responses_string.ReadUint8(&present) { 27 | return nil, fmt.Errorf("invalid Token encoding") 28 | } 29 | 30 | if present == uint8(TokenStatusAbsent) { 31 | token_responses = append(token_responses, []byte{}) 32 | } else if present == uint8(TokenStatusPresent) { 33 | var token_type uint16 34 | if !token_responses_string.ReadUint16(&token_type) { 35 | return nil, fmt.Errorf("invalid Token encoding") 36 | } 37 | 38 | var token_response_length uint16 39 | switch token_type { 40 | case type1.BasicPrivateTokenType: 41 | token_response_length = uint16(type1.Ne + 2*type1.Nk) // Ne + 2*Ns 42 | case type2.BasicPublicTokenType: 43 | token_response_length = uint16(type2.Nk) // Nk 44 | default: 45 | return nil, fmt.Errorf("invalid Token encoding") 46 | } 47 | 48 | var token_response []byte 49 | // incorrect, we need to know the token type 50 | if !token_responses_string.ReadBytes(&token_response, int(token_response_length)) { 51 | return nil, fmt.Errorf("invalid Token encoding") 52 | } 53 | token_responses = append(token_responses, token_response) 54 | } else { 55 | return nil, fmt.Errorf("invalid Token encoding") 56 | } 57 | } 58 | 59 | return token_responses, nil 60 | } 61 | -------------------------------------------------------------------------------- /tokens/token-test-vectors.json: -------------------------------------------------------------------------------- 1 | [{"comment":"// token_type(0002), issuer_name(issuer.example),\n// origin_info(origin.example), redemption_context(non-empty)","token_type":"0002","issuer_name":"6973737565722e6578616d706c65","redemption_context":"476ac2c935f458e9b2d7af32dacfbd22dd6023ef5887a789f1abe004e79bb5bb","origin_info":"6f726967696e2e6578616d706c65","nonce":"e01978182c469e5e026d66558ee186568614f235e41ef7e2378e6f202688abab","token_key_id":"ca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd2708","token_authenticator_input":"0002e01978182c469e5e026d66558ee186568614f235e41ef7e2378e6f202688abab8e1d5518ec82964255526efd8f9db88205a8ddd3ffb1db298fcc3ad36c42388fca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd2708"},{"comment":"// token_type(0002), issuer_name(issuer.example),\n// origin_info(origin.example), redemption_context(empty)","token_type":"0002","issuer_name":"6973737565722e6578616d706c65","redemption_context":"","origin_info":"6f726967696e2e6578616d706c65","nonce":"e01978182c469e5e026d66558ee186568614f235e41ef7e2378e6f202688abab","token_key_id":"ca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd2708","token_authenticator_input":"0002e01978182c469e5e026d66558ee186568614f235e41ef7e2378e6f202688abab11e15c91a7c2ad02abd66645802373db1d823bea80f08d452541fb2b62b5898bca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd2708"},{"comment":"// token_type(0002), issuer_name(issuer.example),\n// origin_info(), redemption_context(empty)","token_type":"0002","issuer_name":"6973737565722e6578616d706c65","redemption_context":"","origin_info":"","nonce":"e01978182c469e5e026d66558ee186568614f235e41ef7e2378e6f202688abab","token_key_id":"ca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd2708","token_authenticator_input":"0002e01978182c469e5e026d66558ee186568614f235e41ef7e2378e6f202688ababb741ec1b6fd05f1e95f8982906aec1612896d9ca97d53eef94ad3c9fe023f7a4ca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd2708"},{"comment":"// token_type(0002), issuer_name(issuer.example),\n// origin_info(), redemption_context(non-empty)","token_type":"0002","issuer_name":"6973737565722e6578616d706c65","redemption_context":"476ac2c935f458e9b2d7af32dacfbd22dd6023ef5887a789f1abe004e79bb5bb","origin_info":"","nonce":"e01978182c469e5e026d66558ee186568614f235e41ef7e2378e6f202688abab","token_key_id":"ca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd2708","token_authenticator_input":"0002e01978182c469e5e026d66558ee186568614f235e41ef7e2378e6f202688ababb85fb5bc06edeb0e8e8bdb5b3bea8c4fa40837c82e8bcaf5882c81e14817ea18ca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd2708"},{"comment":"// token_type(0002), issuer_name(issuer.example),\n// origin_info(foo.example,bar.example),\n// redemption_context(non-empty)","token_type":"0002","issuer_name":"6973737565722e6578616d706c65","redemption_context":"476ac2c935f458e9b2d7af32dacfbd22dd6023ef5887a789f1abe004e79bb5bb","origin_info":"666f6f2e6578616d706c652c6261722e6578616d706c65","nonce":"e01978182c469e5e026d66558ee186568614f235e41ef7e2378e6f202688abab","token_key_id":"ca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd2708","token_authenticator_input":"0002e01978182c469e5e026d66558ee186568614f235e41ef7e2378e6f202688ababa2a775866b6ae0f98944910c8f48728d8a2735b9157762ddbf803f70e2e8ba3eca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd2708"}] -------------------------------------------------------------------------------- /tokens/token.go: -------------------------------------------------------------------------------- 1 | package tokens 2 | 3 | import ( 4 | "golang.org/x/crypto/cryptobyte" 5 | ) 6 | 7 | // struct { 8 | // uint16_t token_type; 9 | // uint8_t nonce[32]; 10 | // uint8_t context[32]; 11 | // uint8_t key_id[32]; 12 | // uint8_t authenticator[Nk]; 13 | // } Token; 14 | 15 | type Token struct { 16 | TokenType uint16 17 | Nonce []byte 18 | Context []byte 19 | KeyID []byte 20 | Authenticator []byte 21 | } 22 | 23 | func (t Token) AuthenticatorInput() []byte { 24 | b := cryptobyte.NewBuilder(nil) 25 | b.AddUint16(t.TokenType) 26 | b.AddBytes(t.Nonce) 27 | b.AddBytes(t.Context) 28 | b.AddBytes(t.KeyID) 29 | return b.BytesOrPanic() 30 | } 31 | 32 | func (t Token) Marshal() []byte { 33 | b := cryptobyte.NewBuilder(nil) 34 | b.AddUint16(t.TokenType) 35 | b.AddBytes(t.Nonce) 36 | b.AddBytes(t.Context) 37 | b.AddBytes(t.KeyID) 38 | b.AddBytes(t.Authenticator) 39 | return b.BytesOrPanic() 40 | } 41 | -------------------------------------------------------------------------------- /tokens/token_challenge.go: -------------------------------------------------------------------------------- 1 | package tokens 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | "strings" 8 | 9 | "golang.org/x/crypto/cryptobyte" 10 | ) 11 | 12 | // struct { 13 | // uint16_t token_type; 14 | // opaque issuer_name<1..2^16-1>; 15 | // opaque redemption_nonce<0..32>; 16 | // opaque origin_name<0..2^16-1>; 17 | // } TokenChallenge; 18 | type TokenChallenge struct { 19 | TokenType uint16 20 | IssuerName string 21 | RedemptionNonce []byte 22 | OriginInfo []string 23 | } 24 | 25 | func (c TokenChallenge) Equals(o TokenChallenge) bool { 26 | if c.TokenType == o.TokenType && 27 | c.IssuerName == o.IssuerName && 28 | bytes.Equal(c.RedemptionNonce, o.RedemptionNonce) && 29 | reflect.DeepEqual(c.OriginInfo, o.OriginInfo) { 30 | return true 31 | } 32 | return false 33 | } 34 | 35 | func (c TokenChallenge) Marshal() []byte { 36 | b := cryptobyte.NewBuilder(nil) 37 | b.AddUint16(c.TokenType) 38 | b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { 39 | b.AddBytes([]byte(c.IssuerName)) 40 | }) 41 | b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { 42 | b.AddBytes(c.RedemptionNonce) 43 | }) 44 | b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { 45 | b.AddBytes([]byte(strings.Join(c.OriginInfo, ","))) 46 | }) 47 | return b.BytesOrPanic() 48 | } 49 | 50 | func UnmarshalTokenChallenge(data []byte) (TokenChallenge, error) { 51 | s := cryptobyte.String(data) 52 | 53 | challenge := TokenChallenge{} 54 | 55 | if !s.ReadUint16(&challenge.TokenType) { 56 | return TokenChallenge{}, fmt.Errorf("invalid TokenChallenge encoding") 57 | } 58 | 59 | var issuerName cryptobyte.String 60 | if !s.ReadUint16LengthPrefixed(&issuerName) || issuerName.Empty() { 61 | return TokenChallenge{}, fmt.Errorf("invalid TokenChallenge encoding") 62 | } 63 | challenge.IssuerName = string(issuerName) 64 | 65 | var redemptionNonce cryptobyte.String 66 | if !s.ReadUint8LengthPrefixed(&redemptionNonce) { 67 | return TokenChallenge{}, fmt.Errorf("invalid TokenChallenge encoding") 68 | } 69 | challenge.RedemptionNonce = make([]byte, len(redemptionNonce)) 70 | copy(challenge.RedemptionNonce, redemptionNonce) 71 | 72 | var originInfo cryptobyte.String 73 | if !s.ReadUint16LengthPrefixed(&originInfo) { 74 | return TokenChallenge{}, fmt.Errorf("invalid TokenRequest encoding") 75 | } 76 | challenge.OriginInfo = strings.Split(string(originInfo), ",") 77 | 78 | return challenge, nil 79 | } 80 | -------------------------------------------------------------------------------- /tokens/token_request.go: -------------------------------------------------------------------------------- 1 | package tokens 2 | 3 | type TokenRequest interface { 4 | Marshal() []byte 5 | Unmarshal(data []byte) bool 6 | } 7 | 8 | type TokenRequestWithDetails interface { 9 | TokenRequest 10 | TruncatedTokenKeyID() uint8 11 | Type() uint16 12 | } 13 | -------------------------------------------------------------------------------- /tokens/type1/client.go: -------------------------------------------------------------------------------- 1 | package type1 2 | 3 | import ( 4 | "crypto/sha256" 5 | 6 | "github.com/cloudflare/circl/group" 7 | "github.com/cloudflare/circl/oprf" 8 | "github.com/cloudflare/circl/zk/dleq" 9 | "github.com/cloudflare/pat-go/tokens" 10 | ) 11 | 12 | type BasicPrivateClient struct { 13 | } 14 | 15 | func NewBasicPrivateClient() BasicPrivateClient { 16 | return BasicPrivateClient{} 17 | } 18 | 19 | type BasicPrivateTokenRequestState struct { 20 | tokenInput []byte 21 | request *BasicPrivateTokenRequest 22 | client oprf.VerifiableClient 23 | verificationKey *oprf.PublicKey 24 | verifier *oprf.FinalizeData 25 | } 26 | 27 | func (s BasicPrivateTokenRequestState) Request() *BasicPrivateTokenRequest { 28 | return s.request 29 | } 30 | 31 | func (s BasicPrivateTokenRequestState) ForTestsOnlyVerifier() *oprf.FinalizeData { 32 | return s.verifier 33 | } 34 | 35 | func (s BasicPrivateTokenRequestState) FinalizeToken(tokenResponseEnc []byte) (tokens.Token, error) { 36 | evaluatedElement := group.P384.NewElement() 37 | err := evaluatedElement.UnmarshalBinary(tokenResponseEnc[:group.P384.Params().CompressedElementLength]) 38 | if err != nil { 39 | return tokens.Token{}, err 40 | } 41 | 42 | proof := new(dleq.Proof) 43 | err = proof.UnmarshalBinary(group.P384, tokenResponseEnc[group.P384.Params().CompressedElementLength:]) 44 | if err != nil { 45 | return tokens.Token{}, err 46 | } 47 | 48 | evaluation := &oprf.Evaluation{ 49 | Elements: []oprf.Evaluated{evaluatedElement}, 50 | Proof: proof, 51 | } 52 | outputs, err := s.client.Finalize(s.verifier, evaluation) 53 | if err != nil { 54 | return tokens.Token{}, err 55 | } 56 | 57 | tokenData := append(s.tokenInput, outputs[0]...) 58 | token, err := UnmarshalPrivateToken(tokenData) 59 | if err != nil { 60 | return tokens.Token{}, err 61 | } 62 | 63 | return token, nil 64 | } 65 | 66 | // https://ietf-wg-privacypass.github.io/base-drafts/caw/pp-issuance/draft-ietf-privacypass-protocol.html#name-issuance-protocol-for-publi 67 | func (c BasicPrivateClient) CreateTokenRequest(challenge, nonce []byte, tokenKeyID []byte, verificationKey *oprf.PublicKey) (BasicPrivateTokenRequestState, error) { 68 | client := oprf.NewVerifiableClient(oprf.SuiteP384, verificationKey) 69 | 70 | context := sha256.Sum256(challenge) 71 | token := tokens.Token{ 72 | TokenType: BasicPrivateTokenType, 73 | Nonce: nonce, 74 | Context: context[:], 75 | KeyID: tokenKeyID, 76 | Authenticator: nil, // No OPRF computed yet 77 | } 78 | tokenInput := token.AuthenticatorInput() 79 | finalizeData, evalRequest, err := client.Blind([][]byte{tokenInput}) 80 | if err != nil { 81 | return BasicPrivateTokenRequestState{}, err 82 | } 83 | 84 | encRequest, err := evalRequest.Elements[0].MarshalBinaryCompress() 85 | if err != nil { 86 | return BasicPrivateTokenRequestState{}, err 87 | } 88 | request := &BasicPrivateTokenRequest{ 89 | TokenKeyID: tokenKeyID[len(tokenKeyID)-1], 90 | BlindedReq: encRequest, 91 | } 92 | 93 | requestState := BasicPrivateTokenRequestState{ 94 | tokenInput: tokenInput, 95 | request: request, 96 | client: client, 97 | verifier: finalizeData, 98 | verificationKey: verificationKey, 99 | } 100 | 101 | return requestState, nil 102 | } 103 | 104 | func (c BasicPrivateClient) CreateTokenRequestWithBlind(challenge, nonce []byte, tokenKeyID []byte, verificationKey *oprf.PublicKey, blindEnc []byte) (BasicPrivateTokenRequestState, error) { 105 | client := oprf.NewVerifiableClient(oprf.SuiteP384, verificationKey) 106 | 107 | context := sha256.Sum256(challenge) 108 | token := tokens.Token{ 109 | TokenType: BasicPrivateTokenType, 110 | Nonce: nonce, 111 | Context: context[:], 112 | KeyID: tokenKeyID, 113 | Authenticator: nil, // No OPRF output computed yet 114 | } 115 | tokenInput := token.AuthenticatorInput() 116 | 117 | blind := group.P384.NewScalar() 118 | err := blind.UnmarshalBinary(blindEnc) 119 | if err != nil { 120 | return BasicPrivateTokenRequestState{}, err 121 | } 122 | 123 | finalizeData, evalRequest, err := client.DeterministicBlind([][]byte{tokenInput}, []oprf.Blind{blind}) 124 | if err != nil { 125 | return BasicPrivateTokenRequestState{}, err 126 | } 127 | 128 | encRequest, err := evalRequest.Elements[0].MarshalBinaryCompress() 129 | if err != nil { 130 | return BasicPrivateTokenRequestState{}, err 131 | } 132 | request := &BasicPrivateTokenRequest{ 133 | TokenKeyID: tokenKeyID[len(tokenKeyID)-1], 134 | BlindedReq: encRequest, 135 | } 136 | 137 | requestState := BasicPrivateTokenRequestState{ 138 | tokenInput: tokenInput, 139 | request: request, 140 | client: client, 141 | verifier: finalizeData, 142 | verificationKey: verificationKey, 143 | } 144 | 145 | return requestState, nil 146 | } 147 | -------------------------------------------------------------------------------- /tokens/type1/issuer.go: -------------------------------------------------------------------------------- 1 | package type1 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "fmt" 7 | 8 | "github.com/cloudflare/circl/group" 9 | "github.com/cloudflare/circl/oprf" 10 | "github.com/cloudflare/pat-go/tokens" 11 | ) 12 | 13 | type BasicPrivateIssuer struct { 14 | tokenKey *oprf.PrivateKey 15 | } 16 | 17 | func NewBasicPrivateIssuer(key *oprf.PrivateKey) *BasicPrivateIssuer { 18 | return &BasicPrivateIssuer{ 19 | tokenKey: key, 20 | } 21 | } 22 | 23 | func (i *BasicPrivateIssuer) TokenKey() *oprf.PublicKey { 24 | return i.tokenKey.Public() 25 | } 26 | 27 | func (i *BasicPrivateIssuer) TokenKeyID() []byte { 28 | pkIEnc, err := i.tokenKey.Public().MarshalBinary() 29 | if err != nil { 30 | panic(err) 31 | } 32 | keyID := sha256.Sum256(pkIEnc) 33 | return keyID[:] 34 | } 35 | 36 | func (i BasicPrivateIssuer) Evaluate(req *BasicPrivateTokenRequest) ([]byte, error) { 37 | server := oprf.NewVerifiableServer(oprf.SuiteP384, i.tokenKey) 38 | 39 | e := group.P384.NewElement() 40 | err := e.UnmarshalBinary(req.BlindedReq) 41 | if err != nil { 42 | return nil, err 43 | } 44 | evalRequest := &oprf.EvaluationRequest{ 45 | Elements: []oprf.Blinded{e}, 46 | } 47 | 48 | // Evaluate the input 49 | evaluation, err := server.Evaluate(evalRequest) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | // Build TokenResponse 55 | // XXX(caw) move this to token_response.go 56 | encEvaluatedElement, err := evaluation.Elements[0].MarshalBinaryCompress() 57 | if err != nil { 58 | return nil, err 59 | } 60 | encProof, err := evaluation.Proof.MarshalBinary() 61 | if err != nil { 62 | return nil, err 63 | } 64 | tokenResponse := append(encEvaluatedElement, encProof...) 65 | 66 | return tokenResponse, nil 67 | } 68 | 69 | func (i BasicPrivateIssuer) Type() uint16 { 70 | return BasicPrivateTokenType 71 | } 72 | 73 | func (i BasicPrivateIssuer) Verify(token tokens.Token) error { 74 | server := oprf.NewVerifiableServer(oprf.SuiteP384, i.tokenKey) 75 | 76 | tokenInput := token.AuthenticatorInput() 77 | output, err := server.FullEvaluate(tokenInput) 78 | if err != nil { 79 | return err 80 | } 81 | if !bytes.Equal(output, token.Authenticator) { 82 | return fmt.Errorf("token authentication mismatch") 83 | } 84 | 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /tokens/type1/token.go: -------------------------------------------------------------------------------- 1 | package type1 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cloudflare/pat-go/tokens" 7 | "golang.org/x/crypto/cryptobyte" 8 | ) 9 | 10 | const Nk int = 48 // defined in RFC 9578 Section 8.2.1 https://datatracker.ietf.org/doc/html/rfc9578#name-token-type-voprfp-384-sha-3 11 | const Ne int = 49 // defined in RFC 9497 Section 4.4 https://datatracker.ietf.org/doc/html/rfc9497#name-oprfp-384-sha-384 12 | 13 | func UnmarshalPrivateToken(data []byte) (tokens.Token, error) { 14 | s := cryptobyte.String(data) 15 | 16 | token := tokens.Token{} 17 | if !s.ReadUint16(&token.TokenType) || 18 | !s.ReadBytes(&token.Nonce, 32) || 19 | !s.ReadBytes(&token.Context, 32) || 20 | !s.ReadBytes(&token.KeyID, 32) || 21 | !s.ReadBytes(&token.Authenticator, Nk) { 22 | return tokens.Token{}, fmt.Errorf("invalid Token encoding") 23 | } 24 | 25 | return token, nil 26 | } 27 | -------------------------------------------------------------------------------- /tokens/type1/token_request.go: -------------------------------------------------------------------------------- 1 | package type1 2 | 3 | import ( 4 | "bytes" 5 | 6 | "golang.org/x/crypto/cryptobyte" 7 | ) 8 | 9 | const BasicPrivateTokenType = uint16(0x0001) 10 | 11 | type BasicPrivateTokenRequest struct { 12 | raw []byte 13 | TokenKeyID uint8 14 | BlindedReq []byte // 48 bytes 15 | } 16 | 17 | func (r *BasicPrivateTokenRequest) TruncatedTokenKeyID() uint8 { 18 | return r.TokenKeyID 19 | } 20 | 21 | func (r *BasicPrivateTokenRequest) Type() uint16 { 22 | return BasicPrivateTokenType 23 | } 24 | 25 | func (r BasicPrivateTokenRequest) Equal(r2 BasicPrivateTokenRequest) bool { 26 | if r.TokenKeyID == r2.TokenKeyID && 27 | bytes.Equal(r.BlindedReq, r2.BlindedReq) { 28 | return true 29 | } 30 | return false 31 | } 32 | 33 | func (r *BasicPrivateTokenRequest) Marshal() []byte { 34 | if r.raw != nil { 35 | return r.raw 36 | } 37 | 38 | b := cryptobyte.NewBuilder(nil) 39 | b.AddUint16(BasicPrivateTokenType) 40 | b.AddUint8(r.TokenKeyID) 41 | b.AddBytes(r.BlindedReq) 42 | 43 | r.raw = b.BytesOrPanic() 44 | return r.raw 45 | } 46 | 47 | func (r *BasicPrivateTokenRequest) Unmarshal(data []byte) bool { 48 | s := cryptobyte.String(data) 49 | 50 | var tokenType uint16 51 | if !s.ReadUint16(&tokenType) || 52 | tokenType != BasicPrivateTokenType || 53 | !s.ReadUint8(&r.TokenKeyID) || 54 | !s.ReadBytes(&r.BlindedReq, 48) { 55 | return false 56 | } 57 | 58 | return true 59 | } 60 | -------------------------------------------------------------------------------- /tokens/type1/type1-issuance-test-vectors.json: -------------------------------------------------------------------------------- 1 | [{"skS":"39b0d04d3732459288fc5edb89bb02c2aa42e06709f201d6c518871d518114910bee3c919bed1bbffe3fc1b87d53240a","pkS":"02d45bf522425cdd2227d3f27d245d9d563008829252172d34e48469290c21da1a46d42ca38f7beabdf05c074aee1455bf","token_challenge":"0001000e6973737565722e6578616d706c65205de58a52fcdaef25ca3f65448d04e040fb1924e8264acfccfc6c5ad451d582b3000e6f726967696e2e6578616d706c65","nonce":"ccfd6265c63b03e5e5c968a87eb6a598e1a98ecce5682e81dd3853d8178c06b4","blind":"e1797b2f73f3ad79f7c5f752adb77fe638f8ce86fdb074657847474af2bc3a893a1e735944b4f98eb77edbe5cbb0d4b8","token_request":"0001f40380ccc863f840e25de6fa3dd13b310718db08adde13a7c9e43610ec424c070a1ef91496d4e0d4ae077023ab0ca8147b09","token_response":"03af73d07367f721bacbf2d1bb0568a5bff892356ab637bcbe057a15a75ee5b132e227ab3c1f30b2825e0edbb87782ab7f21078b0634b630441925162597d9a8b1306aac69604ebc1e6620d3526134515fc511e1c5143bcac035fbe7efff09a8cc7bcf02ef380c8771ebc925ac56c9a126d23fd8f7ef3308e12e2eb7959cd95e0530e1e6316c86344856f23301795d0af3","token":"0001ccfd6265c63b03e5e5c968a87eb6a598e1a98ecce5682e81dd3853d8178c06b4501370b494089dc462802af545e63809581ee6ef57890a12105c28368169514bf260d0792bf7f46c9866a6d37c3032d8714415f87f5f6903d7fb071e253be2f4c0fee46fa639b6ddf7ad1aab35937c4fed19059c5328930433b1e21884b54262751b17c56aabbaebc71ce69ae4acb9cd"},{"skS":"39efed331527cc4ddff9722ab5cd35aeafe7c27520b0cfa2eedbdc298dc3b12bc8298afcc46558af1e2eeacc5307d865","pkS":"038017e005904c6146b37109d6c2a72b95a183aaa9ed951b8d8fb1ed9033f68033284d175e7df89849475cd67a86bfbf4e","token_challenge":"0001000e6973737565722e6578616d706c6500000e6f726967696e2e6578616d706c65","nonce":"76be1bfac6ece0ef5ad5ce7cf147d90e2cf648f8da665f052ca2325bdff0d7f3","blind":"7f91842a30b9a3d41a5de04bcc6e83cf5a933bfb6fc1752b8a95b61090ceead95e924bbf2c204c0ad7db5bf4a9a9008f","token_request":"0001330316af122718f63648650cb180dd6b871d08ae4f2b5be3297fec41c07e417f7fff7a3186b907539ce9b161f29811ed6b21","token_response":"02eea29e3e14f8882c575ebfa2a3a96fc12ed3ab4902f9b257ed856f6a33bb64e3e7239964bfcc5fa9a1347ea317729f5a680ac322b0bcaca4e8f37eb73ab8ff6bb70fbd8e87eb6c4b48dc016642b63e457feb0400316e72e0f5da75957553268693061db57e7841d662f4ab6511e3c5153f6cfcfc55d70d6a0311af0bdfa6e015c57822a9c94abb83fb901216ada7dbd5","token":"000176be1bfac6ece0ef5ad5ce7cf147d90e2cf648f8da665f052ca2325bdff0d7f3c994f7d5cdc2fb970b13d4e8eb6e6d8f9dcdaa65851fb091025dfe134bd5a62a116477bc9e1a205cca95d0c92335ca7a3e71063b2ac020bdd231c66097f1233316ebed0af6ddd0cc2e79b6af00c167072b2f130ee3d229ddaee6061d7156254c32906eff06cfabb67226dc8ff84803d2"},{"skS":"2b7709595b62b784f14946ae828f65e6caeba6eefe732c86e9ae50e818c055b3d7ca3a5f2beecaa859a62ff7199d35cc","pkS":"03a0de1bf3fd0a73384283b648884ba9fa5dee190f9d7ad4292c2fd49d8b4d64db674059df67f5bd7e626475c78934ae8d","token_challenge":"0001000e6973737565722e6578616d706c65000017666f6f2e6578616d706c652c6261722e6578616d706c65","nonce":"07e61cf25be4b0234a9b47f0edb57bae839adf783a49836d322ff9a10e7bd598","blind":"02afc1cf12a88580e84c9d48a9a3f58122355c14fa7e182e51fcbc3d4e3dc6ae53204024dc0b3a80f839301d80cdf7d2","token_request":"0001c803540dd281e10a5b4e1e0dc823f7210085414ee1163c824803187fd97ada3bb7f67dfd60d6f7b10ac70e4bc6f77856e575","token_response":"03e8fc911a8367d3575bb30beb4511eab262b83170d55184cfad4d20735b2af93bb524b2b98eaf93770005b581796954253918c64056ba98bc27d39d99d9a765aad96c6765c68a4cf18b9e6ec7c79c6d286edf870e8be0aaebb73b5829441349968d5ac47c385cea8ec9a4fae33d4610826f66eeabd35131541587e0f8de27abbc7c70a59f3d21693e41eb9a1409169b90","token":"000107e61cf25be4b0234a9b47f0edb57bae839adf783a49836d322ff9a10e7bd5981949fd455872478ba87e2e6c513c3261cddbe57220581245e4c9c911dd1c0bb865785bff8f3cfe08cccbb3a7b8e41d23a172871be4828cc54582d87bc7cfc5c8b62005f594c4bc94d1a70a515e445b68af4956b50ee70a83b6e43912fe8ab718fefdfd1c7c8722cca591a2dbdaf85f40"},{"skS":"22e237b7b983d77474e4495aff2fc1e10422b1d955192e0fbf2b7b618fba625fcb94b599da9113da49c495a48fbf7f7f","pkS":"028cd68715caa20d19b2b20d017d6a0a42b9f2b0a47db65e5e763e23744fe14d74e374bbc93a2ec3970eb53c8aa765ee21","token_challenge":"0001000e6973737565722e6578616d706c65000000","nonce":"2b498013e04112965b912d5ac0ab086a0d1f1c42b44de387712b93373d1237b0","blind":"7e3243ddc98e3b73221227b4330aca7f1a580139bda84c4e028b4e2eb882ea994084c07be80c8e3e0d074ed9efb961b4","token_request":"0001a5039c4264c5dd5f3de17e774ee364ad05cb1d2b009e18171f82cdd84afed6d8e6b7f55eb26844aa4593915214365b870b53","token_response":"02e8dc08758edc9bfa1026cc75843c989b59afee5f83fc004c4f4f340cfe84e1c488c6c935bfb80afec4c45a5eee80f5bb63f7d91a37cac26049b3c14c6b0bde50b6c156b846e35ffb87597c8c47a13ca5574c9226a1afa848adfe788d8df46c7aeb7e65816a1a9c990989d6634481b1884d5c82657d09c82754867c3f4476a44a2c69b53c6f64b9b640f27a6f6436de1e","token":"00012b498013e04112965b912d5ac0ab086a0d1f1c42b44de387712b93373d1237b0085cb06952044c7655b412ab7d484c97b97c48c79c568140b8d49a02ca47a9cfb0a5cfb861290c4dbd8fd9b60ee9b1a1a54cf47c98531fe253f1ed6d875de5a595439e2fdba1abe68ba8a27638b3d6a40e5b17bc03676ee72d72b8961d072fc8e1dbd2550dce3f117d55a498f9c20af3"},{"skS":"46f3d4f562002b85ffcfdb4d06835fb9b2e24372861ecaa11357fd1f29f9ed26e44715549ccedeb39257f095110f0159","pkS":"02fbe9da0b7cabe3ec51c36c8487b10909142b59af030c728a5e87bb3b30f54c06415d22e03d9212bd3d9a17d5520d4d0f","token_challenge":"0001000e6973737565722e6578616d706c65205de58a52fcdaef25ca3f65448d04e040fb1924e8264acfccfc6c5ad451d582b30000","nonce":"0a563c032f5dbd512132036ef3b4ef3aa940771bfd5acbf8e56fcb745f1bba1a","blind":"378ee7ede8a18c3134414d793dfd7d62749c0b6c2fa68b149e3bd39d84057ca19e88b1300c82b979358b928b64d168b8","token_request":"0001e102c9de95936befa913acd0660bb815ae33d23291be8abb4542ed1be732102de328848eb9ed2bd6547b6375efecdabb2d5e","token_response":"0382eba9c8c7d06237e94fcbd4b5998bff6e849d51a92b30f595e01fd4f5993107c31152900070db5529349bf52a11c51c0d3cf48eee851df191d89234a30dda1a739d901399b057270eaf16c4b590347f5cbce3a55acd2e8136d0d6b355432ce2b799c534245a6a92c5e10a7995c1fad964706f4a1acbd22340ab7c2246166b34afeb97f7e8c92ccd758c7392b99559a8","token":"00010a563c032f5dbd512132036ef3b4ef3aa940771bfd5acbf8e56fcb745f1bba1ad4380df12a1727f4e2ca1ee0d7abea0d0fb1e9506507a4dd618f9b87e79f9f3521a7c9134d6722925bf622a994041cdb1b082cdf1309af32f0ce00ca1dab63e14bf7a15d0a8654d1bd4ecbb0f23ccdf13621b983c0be9a9fec7169591c05b0d9ef2415c50b81f81e491897d3da4ea7f5"}] -------------------------------------------------------------------------------- /tokens/type1/type1_test.go: -------------------------------------------------------------------------------- 1 | package type1 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "crypto/sha256" 7 | "encoding/json" 8 | "os" 9 | "testing" 10 | 11 | "github.com/cloudflare/circl/oprf" 12 | "golang.org/x/crypto/hkdf" 13 | 14 | "github.com/cloudflare/pat-go/tokens" 15 | "github.com/cloudflare/pat-go/util" 16 | ) 17 | 18 | const ( 19 | outputBasicPrivateIssuanceTestVectorEnvironmentKey = "TYPE1_ISSUANCE_TEST_VECTORS_OUT" 20 | inputBasicPrivateIssuanceTestVectorEnvironmentKey = "TYPE1_ISSUANCE_TEST_VECTORS_IN" 21 | ) 22 | 23 | func createTokenChallenge(tokenType uint16, redemptionContext []byte, issuerName string, originInfo []string) tokens.TokenChallenge { 24 | challenge := tokens.TokenChallenge{ 25 | TokenType: tokenType, 26 | RedemptionNonce: make([]byte, len(redemptionContext)), 27 | IssuerName: issuerName, 28 | OriginInfo: originInfo, 29 | } 30 | copy(challenge.RedemptionNonce, redemptionContext) 31 | return challenge 32 | } 33 | 34 | func TestBasicPrivateIssuanceRoundTrip(t *testing.T) { 35 | tokenKey, err := oprf.GenerateKey(oprf.SuiteP384, rand.Reader) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | 40 | issuer := NewBasicPrivateIssuer(tokenKey) 41 | client := BasicPrivateClient{} 42 | 43 | challenge := make([]byte, 32) 44 | util.MustRead(t, rand.Reader, challenge) 45 | 46 | nonce := make([]byte, 32) 47 | util.MustRead(t, rand.Reader, nonce) 48 | 49 | tokenKeyID := issuer.TokenKeyID() 50 | tokenPublicKey := issuer.TokenKey() 51 | 52 | requestState, err := client.CreateTokenRequest(challenge, nonce, tokenKeyID, tokenPublicKey) 53 | if err != nil { 54 | t.Error(err) 55 | } 56 | 57 | blindedSignature, err := issuer.Evaluate(requestState.Request()) 58 | if err != nil { 59 | t.Error(err) 60 | } 61 | 62 | token, err := requestState.FinalizeToken(blindedSignature) 63 | if err != nil { 64 | t.Error(err) 65 | } 66 | 67 | err = issuer.Verify(token) 68 | if err != nil { 69 | t.Error(err) 70 | } 71 | } 72 | 73 | // ///// 74 | // Basic issuance test vector 75 | type rawBasicPrivateIssuanceTestVector struct { 76 | PrivateKey string `json:"skS"` 77 | PublicKey string `json:"pkS"` 78 | Challenge string `json:"token_challenge"` 79 | Nonce string `json:"nonce"` 80 | Blind string `json:"blind"` 81 | TokenRequest string `json:"token_request"` 82 | TokenResponse string `json:"token_response"` 83 | Token string `json:"token"` 84 | } 85 | 86 | type BasicPrivateIssuanceTestVector struct { 87 | t *testing.T 88 | skS *oprf.PrivateKey 89 | challenge []byte 90 | nonce []byte 91 | blind []byte 92 | tokenRequest []byte 93 | tokenResponse []byte 94 | token []byte 95 | } 96 | 97 | type BasicPrivateIssuanceTestVectorArray struct { 98 | t *testing.T 99 | vectors []BasicPrivateIssuanceTestVector 100 | } 101 | 102 | func (tva BasicPrivateIssuanceTestVectorArray) MarshalJSON() ([]byte, error) { 103 | return json.Marshal(tva.vectors) 104 | } 105 | 106 | func (tva *BasicPrivateIssuanceTestVectorArray) UnmarshalJSON(data []byte) error { 107 | err := json.Unmarshal(data, &tva.vectors) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | for i := range tva.vectors { 113 | tva.vectors[i].t = tva.t 114 | } 115 | return nil 116 | } 117 | 118 | func (etv BasicPrivateIssuanceTestVector) MarshalJSON() ([]byte, error) { 119 | return json.Marshal(rawBasicPrivateIssuanceTestVector{ 120 | PrivateKey: util.MustHex(util.MustMarshalPrivateOPRFKey(etv.skS)), 121 | PublicKey: util.MustHex(util.MustMarshalPublicOPRFKey(etv.skS.Public())), 122 | Challenge: util.MustHex(etv.challenge), 123 | Nonce: util.MustHex(etv.nonce), 124 | Blind: util.MustHex(etv.blind), 125 | TokenRequest: util.MustHex(etv.tokenRequest), 126 | TokenResponse: util.MustHex(etv.tokenResponse), 127 | Token: util.MustHex(etv.token), 128 | }) 129 | } 130 | 131 | func (etv *BasicPrivateIssuanceTestVector) UnmarshalJSON(data []byte) error { 132 | raw := rawBasicPrivateIssuanceTestVector{} 133 | err := json.Unmarshal(data, &raw) 134 | if err != nil { 135 | return err 136 | } 137 | 138 | etv.skS = util.MustUnmarshalPrivateOPRFKey(util.MustUnhex(nil, raw.PrivateKey)) 139 | etv.challenge = util.MustUnhex(nil, raw.Challenge) 140 | etv.nonce = util.MustUnhex(nil, raw.Nonce) 141 | etv.blind = util.MustUnhex(nil, raw.Blind) 142 | etv.tokenRequest = util.MustUnhex(nil, raw.TokenRequest) 143 | etv.tokenResponse = util.MustUnhex(nil, raw.TokenResponse) 144 | etv.token = util.MustUnhex(nil, raw.Token) 145 | 146 | return nil 147 | } 148 | 149 | func generateBasicPrivateIssuanceBlindingTestVector(t *testing.T, client *BasicPrivateClient, issuer *BasicPrivateIssuer, tokenChallenge tokens.TokenChallenge) BasicPrivateIssuanceTestVector { 150 | challenge := tokenChallenge.Marshal() 151 | 152 | nonce := make([]byte, 32) 153 | util.MustRead(t, rand.Reader, nonce) 154 | 155 | tokenKeyID := issuer.TokenKeyID() 156 | tokenPublicKey := issuer.TokenKey() 157 | 158 | requestState, err := client.CreateTokenRequest(challenge, nonce, tokenKeyID, tokenPublicKey) 159 | if err != nil { 160 | t.Error(err) 161 | } 162 | 163 | blindedSignature, err := issuer.Evaluate(requestState.Request()) 164 | if err != nil { 165 | t.Error(err) 166 | } 167 | 168 | token, err := requestState.FinalizeToken(blindedSignature) 169 | if err != nil { 170 | t.Error(err) 171 | } 172 | 173 | blinds := requestState.verifier.CopyBlinds() 174 | blindEnc, err := blinds[0].MarshalBinary() 175 | if err != nil { 176 | t.Error(err) 177 | } 178 | 179 | return BasicPrivateIssuanceTestVector{ 180 | t: t, 181 | skS: issuer.tokenKey, 182 | challenge: challenge, 183 | nonce: nonce, 184 | blind: blindEnc, 185 | tokenRequest: requestState.Request().Marshal(), 186 | tokenResponse: blindedSignature, 187 | token: token.Marshal(), 188 | } 189 | } 190 | 191 | func verifyBasicPrivateIssuanceTestVector(t *testing.T, vector BasicPrivateIssuanceTestVector) { 192 | issuer := NewBasicPrivateIssuer(vector.skS) 193 | client := BasicPrivateClient{} 194 | 195 | tokenKeyID := issuer.TokenKeyID() 196 | tokenPublicKey := issuer.TokenKey() 197 | 198 | requestState, err := client.CreateTokenRequestWithBlind(vector.challenge, vector.nonce, tokenKeyID, tokenPublicKey, vector.blind) 199 | if err != nil { 200 | t.Error(err) 201 | } 202 | 203 | blindedSignature, err := issuer.Evaluate(requestState.Request()) 204 | if err != nil { 205 | t.Error(err) 206 | } 207 | 208 | token, err := requestState.FinalizeToken(blindedSignature) 209 | if err != nil { 210 | t.Error(err) 211 | } 212 | 213 | if !bytes.Equal(token.Marshal(), vector.token) { 214 | t.Fatal("Token mismatch") 215 | } 216 | } 217 | 218 | func verifyBasicPrivateIssuanceTestVectors(t *testing.T, encoded []byte) { 219 | vectors := BasicPrivateIssuanceTestVectorArray{t: t} 220 | err := json.Unmarshal(encoded, &vectors) 221 | if err != nil { 222 | t.Fatalf("Error decoding test vector string: %v", err) 223 | } 224 | 225 | for _, vector := range vectors.vectors { 226 | verifyBasicPrivateIssuanceTestVector(t, vector) 227 | } 228 | } 229 | 230 | func TestVectorGenerateBasicPrivateIssuance(t *testing.T) { 231 | hash := sha256.New 232 | secret := []byte("test vector secret") 233 | hkdf := hkdf.New(hash, secret, nil, []byte{0x00, byte(BasicPrivateTokenType & 0xFF)}) 234 | 235 | redemptionContext := make([]byte, 32) 236 | util.MustRead(t, hkdf, redemptionContext) 237 | 238 | challenges := []tokens.TokenChallenge{ 239 | createTokenChallenge(BasicPrivateTokenType, redemptionContext, "issuer.example", []string{"origin.example"}), 240 | createTokenChallenge(BasicPrivateTokenType, nil, "issuer.example", []string{"origin.example"}), 241 | createTokenChallenge(BasicPrivateTokenType, nil, "issuer.example", []string{"foo.example,bar.example"}), 242 | createTokenChallenge(BasicPrivateTokenType, nil, "issuer.example", []string{}), 243 | createTokenChallenge(BasicPrivateTokenType, redemptionContext, "issuer.example", []string{}), 244 | } 245 | 246 | vectors := make([]BasicPrivateIssuanceTestVector, len(challenges)) 247 | for i := 0; i < len(challenges); i++ { 248 | challenge := challenges[i] 249 | challengeEnc := challenge.Marshal() 250 | 251 | tokenKey, err := oprf.DeriveKey(oprf.SuiteP384, oprf.VerifiableMode, []byte("fixed seed"), challengeEnc) 252 | if err != nil { 253 | t.Fatal(err) 254 | } 255 | 256 | issuer := NewBasicPrivateIssuer(tokenKey) 257 | client := &BasicPrivateClient{} 258 | 259 | vectors[i] = generateBasicPrivateIssuanceBlindingTestVector(t, client, issuer, challenge) 260 | } 261 | 262 | // Encode the test vectors 263 | encoded, err := json.Marshal(vectors) 264 | if err != nil { 265 | t.Fatalf("Error producing test vectors: %v", err) 266 | } 267 | 268 | // Verify that we process them correctly 269 | verifyBasicPrivateIssuanceTestVectors(t, encoded) 270 | 271 | var outputFile string 272 | if outputFile = os.Getenv(outputBasicPrivateIssuanceTestVectorEnvironmentKey); len(outputFile) > 0 { 273 | err := os.WriteFile(outputFile, encoded, 0644) 274 | if err != nil { 275 | t.Fatalf("Error writing test vectors: %v", err) 276 | } 277 | } 278 | } 279 | 280 | func TestVectorVerifyBasicPrivateIssuance(t *testing.T) { 281 | var inputFile string 282 | if inputFile = os.Getenv(inputBasicPrivateIssuanceTestVectorEnvironmentKey); len(inputFile) == 0 { 283 | t.Skip("Test vectors were not provided") 284 | } 285 | 286 | encoded, err := os.ReadFile(inputFile) 287 | if err != nil { 288 | t.Fatalf("Failed reading test vectors: %v", err) 289 | } 290 | 291 | verifyBasicPrivateIssuanceTestVectors(t, encoded) 292 | } 293 | -------------------------------------------------------------------------------- /tokens/type2/client.go: -------------------------------------------------------------------------------- 1 | package type2 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/sha256" 8 | "crypto/sha512" 9 | 10 | "github.com/cloudflare/circl/blindsign/blindrsa" 11 | "github.com/cloudflare/pat-go/tokens" 12 | ) 13 | 14 | type BasicPublicClient struct { 15 | } 16 | 17 | func NewBasicPublicClient() BasicPublicClient { 18 | return BasicPublicClient{} 19 | } 20 | 21 | type BasicPublicTokenRequestState struct { 22 | tokenInput []byte 23 | request *BasicPublicTokenRequest 24 | verificationKey *rsa.PublicKey 25 | verifier blindrsa.VerifierState 26 | } 27 | 28 | func (s BasicPublicTokenRequestState) Request() *BasicPublicTokenRequest { 29 | return s.request 30 | } 31 | 32 | func (s BasicPublicTokenRequestState) ForTestsOnlyVerifier() blindrsa.VerifierState { 33 | return s.verifier 34 | } 35 | 36 | func (s BasicPublicTokenRequestState) FinalizeToken(blindSignature []byte) (tokens.Token, error) { 37 | signature, err := s.verifier.Finalize(blindSignature) 38 | if err != nil { 39 | return tokens.Token{}, err 40 | } 41 | 42 | tokenData := append(s.tokenInput, signature...) 43 | token, err := UnmarshalToken(tokenData) 44 | if err != nil { 45 | return tokens.Token{}, err 46 | } 47 | 48 | // Sanity check: verify the token signature 49 | hash := sha512.New384() 50 | _, err = hash.Write(token.AuthenticatorInput()) 51 | if err != nil { 52 | return tokens.Token{}, err 53 | } 54 | digest := hash.Sum(nil) 55 | 56 | err = rsa.VerifyPSS(s.verificationKey, crypto.SHA384, digest, token.Authenticator, &rsa.PSSOptions{ 57 | Hash: crypto.SHA384, 58 | SaltLength: crypto.SHA384.Size(), 59 | }) 60 | if err != nil { 61 | return tokens.Token{}, err 62 | } 63 | 64 | return token, nil 65 | } 66 | 67 | // https://ietf-wg-privacypass.github.io/base-drafts/caw/pp-issuance/draft-ietf-privacypass-protocol.html#name-issuance-protocol-for-publi 68 | func (c BasicPublicClient) CreateTokenRequest(challenge, nonce []byte, tokenKeyID []byte, tokenKey *rsa.PublicKey) (BasicPublicTokenRequestState, error) { 69 | verifier := blindrsa.NewVerifier(tokenKey, crypto.SHA384) 70 | 71 | context := sha256.Sum256(challenge) 72 | token := tokens.Token{ 73 | TokenType: BasicPublicTokenType, 74 | Nonce: nonce, 75 | Context: context[:], 76 | KeyID: tokenKeyID, 77 | Authenticator: nil, // No signature computed yet 78 | } 79 | tokenInput := token.AuthenticatorInput() 80 | 81 | blindedMessage, verifierState, err := verifier.Blind(rand.Reader, tokenInput) 82 | if err != nil { 83 | return BasicPublicTokenRequestState{}, err 84 | } 85 | 86 | request := &BasicPublicTokenRequest{ 87 | TokenKeyID: tokenKeyID[len(tokenKeyID)-1], 88 | BlindedReq: blindedMessage, 89 | } 90 | 91 | requestState := BasicPublicTokenRequestState{ 92 | tokenInput: tokenInput, 93 | request: request, 94 | verifier: verifierState, 95 | verificationKey: tokenKey, 96 | } 97 | 98 | return requestState, nil 99 | } 100 | 101 | func (c BasicPublicClient) CreateTokenRequestWithBlind(challenge, nonce []byte, tokenKeyID []byte, tokenKey *rsa.PublicKey, blind, salt []byte) (BasicPublicTokenRequestState, error) { 102 | verifier := blindrsa.NewVerifier(tokenKey, crypto.SHA384) 103 | 104 | context := sha256.Sum256(challenge) 105 | token := tokens.Token{ 106 | TokenType: BasicPublicTokenType, 107 | Nonce: nonce, 108 | Context: context[:], 109 | KeyID: tokenKeyID, 110 | Authenticator: nil, // No signature computed yet 111 | } 112 | tokenInput := token.AuthenticatorInput() 113 | blindedMessage, verifierState, err := verifier.FixedBlind(tokenInput, blind, salt) 114 | if err != nil { 115 | return BasicPublicTokenRequestState{}, err 116 | } 117 | 118 | request := &BasicPublicTokenRequest{ 119 | TokenKeyID: tokenKeyID[len(tokenKeyID)-1], 120 | BlindedReq: blindedMessage, 121 | } 122 | 123 | requestState := BasicPublicTokenRequestState{ 124 | tokenInput: tokenInput, 125 | request: request, 126 | verifier: verifierState, 127 | verificationKey: tokenKey, 128 | } 129 | 130 | return requestState, nil 131 | } 132 | -------------------------------------------------------------------------------- /tokens/type2/issuer.go: -------------------------------------------------------------------------------- 1 | package type2 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/sha256" 6 | 7 | "github.com/cloudflare/circl/blindsign/blindrsa" 8 | "github.com/cloudflare/pat-go/util" 9 | ) 10 | 11 | type BasicPublicIssuer struct { 12 | tokenKey *rsa.PrivateKey 13 | } 14 | 15 | func NewBasicPublicIssuer(key *rsa.PrivateKey) *BasicPublicIssuer { 16 | return &BasicPublicIssuer{ 17 | tokenKey: key, 18 | } 19 | } 20 | 21 | func (i *BasicPublicIssuer) TokenKey() *rsa.PublicKey { 22 | return &i.tokenKey.PublicKey 23 | } 24 | 25 | func (i *BasicPublicIssuer) TokenKeyID() []byte { 26 | publicKeyEnc, err := util.MarshalTokenKeyPSSOID(&i.tokenKey.PublicKey) 27 | if err != nil { 28 | panic(err) 29 | } 30 | keyID := sha256.Sum256(publicKeyEnc) 31 | return keyID[:] 32 | } 33 | 34 | func (i BasicPublicIssuer) Evaluate(req *BasicPublicTokenRequest) ([]byte, error) { 35 | signer := blindrsa.NewSigner(i.tokenKey) 36 | blindSignature, err := signer.BlindSign(req.BlindedReq) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | return blindSignature, nil 42 | } 43 | 44 | func (i BasicPublicIssuer) Type() uint16 { 45 | return BasicPublicTokenType 46 | } 47 | -------------------------------------------------------------------------------- /tokens/type2/token.go: -------------------------------------------------------------------------------- 1 | package type2 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cloudflare/pat-go/tokens" 7 | "golang.org/x/crypto/cryptobyte" 8 | ) 9 | 10 | const Nk int = 256 // defined in RFC 9578 Section 8.2.2 https://datatracker.ietf.org/doc/html/rfc9578#name-token-type-blind-rsa-2048-b 11 | 12 | func UnmarshalToken(data []byte) (tokens.Token, error) { 13 | s := cryptobyte.String(data) 14 | 15 | token := tokens.Token{} 16 | if !s.ReadUint16(&token.TokenType) || 17 | !s.ReadBytes(&token.Nonce, 32) || 18 | !s.ReadBytes(&token.Context, 32) || 19 | !s.ReadBytes(&token.KeyID, 32) || 20 | !s.ReadBytes(&token.Authenticator, 256) { 21 | return tokens.Token{}, fmt.Errorf("invalid Token encoding") 22 | } 23 | 24 | return token, nil 25 | } 26 | -------------------------------------------------------------------------------- /tokens/type2/token_request.go: -------------------------------------------------------------------------------- 1 | package type2 2 | 3 | import ( 4 | "bytes" 5 | 6 | "golang.org/x/crypto/cryptobyte" 7 | ) 8 | 9 | const BasicPublicTokenType = uint16(0x0002) 10 | 11 | type BasicPublicTokenRequest struct { 12 | raw []byte 13 | TokenKeyID uint8 14 | BlindedReq []byte // 256 bytes 15 | } 16 | 17 | func (r *BasicPublicTokenRequest) TruncatedTokenKeyID() uint8 { 18 | return r.TokenKeyID 19 | } 20 | 21 | func (r *BasicPublicTokenRequest) Type() uint16 { 22 | return BasicPublicTokenType 23 | } 24 | 25 | func (r BasicPublicTokenRequest) Equal(r2 BasicPublicTokenRequest) bool { 26 | if r.TokenKeyID == r2.TokenKeyID && 27 | bytes.Equal(r.BlindedReq, r2.BlindedReq) { 28 | return true 29 | } 30 | return false 31 | } 32 | 33 | func (r *BasicPublicTokenRequest) Marshal() []byte { 34 | if r.raw != nil { 35 | return r.raw 36 | } 37 | 38 | b := cryptobyte.NewBuilder(nil) 39 | b.AddUint16(BasicPublicTokenType) 40 | b.AddUint8(r.TokenKeyID) 41 | b.AddBytes(r.BlindedReq) 42 | 43 | r.raw = b.BytesOrPanic() 44 | return r.raw 45 | } 46 | 47 | func (r *BasicPublicTokenRequest) Unmarshal(data []byte) bool { 48 | s := cryptobyte.String(data) 49 | 50 | var tokenType uint16 51 | if !s.ReadUint16(&tokenType) || 52 | tokenType != BasicPublicTokenType || 53 | !s.ReadUint8(&r.TokenKeyID) || 54 | !s.ReadBytes(&r.BlindedReq, 256) { 55 | return false 56 | } 57 | 58 | return true 59 | } 60 | -------------------------------------------------------------------------------- /tokens/type3/attester.go: -------------------------------------------------------------------------------- 1 | package type3 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/elliptic" 7 | "crypto/sha512" 8 | "encoding/hex" 9 | "fmt" 10 | "io" 11 | "math/big" 12 | 13 | "golang.org/x/crypto/cryptobyte" 14 | "golang.org/x/crypto/hkdf" 15 | 16 | "github.com/cloudflare/pat-go/ecdsa" 17 | ) 18 | 19 | var ( 20 | labelResponseKey = "key" 21 | labelResponseNonce = "nonce" 22 | ) 23 | 24 | type ClientState struct { 25 | originIndices map[string]string // map from anonymous origin ID to anonymous issuer origin ID 26 | clientIndices map[string]string // map from anonymous issuer origin ID to anonyous origin ID 27 | originCounts map[string]int // map from anonymous issuer origin ID to per-origin count 28 | } 29 | 30 | type RateLimitedAttester struct { 31 | cache ClientStateCache 32 | } 33 | 34 | type ClientStateCache interface { 35 | Get(clientID string) (*ClientState, bool) 36 | Put(clientID string, state *ClientState) 37 | } 38 | 39 | func NewRateLimitedAttester(cache ClientStateCache) *RateLimitedAttester { 40 | return &RateLimitedAttester{ 41 | cache: cache, 42 | } 43 | } 44 | 45 | func unmarshalPublicKey(curve elliptic.Curve, encodedKey []byte) (*ecdsa.PublicKey, error) { 46 | x, y := elliptic.UnmarshalCompressed(curve, encodedKey) 47 | if x == nil || y == nil { 48 | return nil, fmt.Errorf("invalid public key") 49 | } 50 | publicKey := &ecdsa.PublicKey{ 51 | Curve: curve, X: x, Y: y, 52 | } 53 | return publicKey, nil 54 | } 55 | 56 | func (a *RateLimitedAttester) innerVerifyRequest(tokenRequest RateLimitedTokenRequest) error { 57 | // Deserialize the request key 58 | curve := elliptic.P384() 59 | requestKey, err := unmarshalPublicKey(curve, tokenRequest.RequestKey) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | scalarLen := (curve.Params().Params().BitSize + 7) / 8 65 | r := new(big.Int).SetBytes(tokenRequest.Signature[:scalarLen]) 66 | s := new(big.Int).SetBytes(tokenRequest.Signature[scalarLen:]) 67 | 68 | // Verify the request signature 69 | b := cryptobyte.NewBuilder(nil) 70 | b.AddUint16(RateLimitedTokenType) 71 | b.AddBytes(tokenRequest.RequestKey) 72 | b.AddBytes(tokenRequest.NameKeyID) 73 | b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { 74 | b.AddBytes(tokenRequest.EncryptedTokenRequest) 75 | }) 76 | message := b.BytesOrPanic() 77 | 78 | hash := sha512.New384() 79 | hash.Write(message) 80 | digest := hash.Sum(nil) 81 | 82 | valid := ecdsa.Verify(requestKey, digest, r, s) 83 | if !valid { 84 | return fmt.Errorf("Request signature invalid") 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func (a *RateLimitedAttester) VerifyRequest(tokenRequest RateLimitedTokenRequest, blindKeyEnc, clientKeyEnc, anonymousOrigin []byte) error { 91 | err := a.innerVerifyRequest(tokenRequest) 92 | if err != nil { 93 | return nil 94 | } 95 | 96 | curve := elliptic.P384() 97 | clientKey, err := unmarshalPublicKey(curve, clientKeyEnc) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | blindKey, err := ecdsa.CreateKey(curve, blindKeyEnc) 103 | if err != nil { 104 | return nil 105 | } 106 | 107 | b := cryptobyte.NewBuilder(nil) 108 | b.AddUint16(RateLimitedTokenType) 109 | b.AddBytes([]byte("ClientBlind")) 110 | ctx := b.BytesOrPanic() 111 | blindedPublicKey, err := ecdsa.BlindPublicKeyWithContext(curve, clientKey, blindKey, ctx) 112 | if err != nil { 113 | return err 114 | } 115 | blindedPublicKeyEnc := elliptic.MarshalCompressed(curve, blindedPublicKey.X, blindedPublicKey.Y) 116 | if !bytes.Equal(blindedPublicKeyEnc, tokenRequest.RequestKey) { 117 | return fmt.Errorf("Mismatch blinded public key") 118 | } 119 | 120 | cacheKey := hex.EncodeToString(clientKeyEnc) 121 | _, ok := a.cache.Get(hex.EncodeToString(clientKeyEnc)) 122 | if !ok { 123 | state := &ClientState{ 124 | originIndices: make(map[string]string), 125 | clientIndices: make(map[string]string), 126 | originCounts: make(map[string]int), 127 | } 128 | a.cache.Put(cacheKey, state) 129 | } 130 | 131 | return nil 132 | } 133 | 134 | func computeIndex(clientKey, indexKey []byte) ([]byte, error) { 135 | hkdf := hkdf.New(sha512.New384, indexKey, clientKey, []byte("IssuerOriginAlias")) 136 | clientOriginIndex := make([]byte, crypto.SHA384.Size()) 137 | if _, err := io.ReadFull(hkdf, clientOriginIndex); err != nil { 138 | return nil, err 139 | } 140 | return clientOriginIndex, nil 141 | } 142 | 143 | // https://ietf-wg-privacypass.github.io/draft-ietf-privacypass-rate-limit-tokens/draft-ietf-privacypass-rate-limit-tokens.html#name-attester-behavior-index-com 144 | func (a *RateLimitedAttester) FinalizeIndex(clientKey, blindEnc, blindedRequestKeyEnc, anonOriginId []byte) ([]byte, error) { 145 | curve := elliptic.P384() 146 | blindedRequestKey, err := unmarshalPublicKey(curve, blindedRequestKeyEnc) 147 | if err != nil { 148 | return nil, err 149 | } 150 | 151 | blindKey, err := ecdsa.CreateKey(curve, blindEnc) 152 | if err != nil { 153 | return nil, err 154 | } 155 | 156 | b := cryptobyte.NewBuilder(nil) 157 | b.AddUint16(RateLimitedTokenType) 158 | b.AddBytes([]byte("ClientBlind")) 159 | ctx := b.BytesOrPanic() 160 | indexKey, err := ecdsa.UnblindPublicKeyWithContext(curve, blindedRequestKey, blindKey, ctx) 161 | if err != nil { 162 | return nil, err 163 | } 164 | 165 | // Compute the anonymous issuer origin ID (index) 166 | indexKeyEnc := elliptic.MarshalCompressed(curve, indexKey.X, indexKey.Y) 167 | index, err := computeIndex(clientKey, indexKeyEnc) 168 | if err != nil { 169 | return nil, err 170 | } 171 | 172 | // Look up per-client cached state 173 | clientKeyEnc := hex.EncodeToString(clientKey) 174 | state, ok := a.cache.Get(clientKeyEnc) 175 | if !ok { 176 | return nil, fmt.Errorf("Unknown client ID: %s", clientKeyEnc) 177 | } 178 | 179 | // Check to make sure anonymous origin ID and anonymous issuer origin ID invariants are not violated 180 | anonOriginIdEnc := hex.EncodeToString(anonOriginId) 181 | indexEnc := hex.EncodeToString(index) 182 | _, ok = state.originIndices[anonOriginIdEnc] 183 | if !ok { 184 | // This is a newly visited origin, so initialize it as such 185 | state.originIndices[anonOriginIdEnc] = indexEnc 186 | } 187 | 188 | // Check for anonymous origin ID and anonymous issuer origin ID invariant violation 189 | expectedOriginID, ok := state.clientIndices[indexEnc] 190 | if ok && expectedOriginID != anonOriginIdEnc { 191 | // There was an anonymous origin ID that had the same anonymous issuer origin ID, so fail 192 | return nil, fmt.Errorf("Repeated anonymous origin ID across client-committed origins") 193 | } else { 194 | // Otherwise, set the anonymous issuer origin ID and anonymous origin ID pair 195 | state.clientIndices[indexEnc] = anonOriginIdEnc 196 | } 197 | 198 | return index, nil 199 | } 200 | -------------------------------------------------------------------------------- /tokens/type3/client.go: -------------------------------------------------------------------------------- 1 | package type3 2 | 3 | import ( 4 | "crypto" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "crypto/rsa" 8 | "crypto/sha256" 9 | "crypto/sha512" 10 | 11 | hpke "github.com/cisco/go-hpke" 12 | "github.com/cloudflare/circl/blindsign/blindrsa" 13 | "github.com/cloudflare/pat-go/ecdsa" 14 | "github.com/cloudflare/pat-go/tokens" 15 | "golang.org/x/crypto/cryptobyte" 16 | ) 17 | 18 | type RateLimitedClient struct { 19 | curve elliptic.Curve 20 | secretKey *ecdsa.PrivateKey 21 | } 22 | 23 | func NewRateLimitedClientFromSecret(secret []byte) RateLimitedClient { 24 | curve := elliptic.P384() 25 | secretKey, err := ecdsa.CreateKey(curve, secret) 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | return RateLimitedClient{ 31 | curve: elliptic.P384(), 32 | secretKey: secretKey, 33 | } 34 | } 35 | 36 | func padOriginName(originName string) []byte { 37 | N := 31 - ((len(originName) - 1) % 32) 38 | zeroes := make([]byte, N) 39 | return append([]byte(originName), zeroes...) 40 | } 41 | 42 | func unpadOriginName(paddedOriginName []byte) string { 43 | lastNonZero := len(paddedOriginName) - 1 44 | for { 45 | if lastNonZero < 0 { 46 | // The plaintext was empty, so the input was the empty string 47 | return "" 48 | } 49 | if paddedOriginName[lastNonZero] != 0x00 { 50 | break 51 | } 52 | lastNonZero-- 53 | } 54 | return string(paddedOriginName[0 : lastNonZero+1]) 55 | } 56 | 57 | // https://ietf-wg-privacypass.github.io/draft-ietf-privacypass-rate-limit-tokens/draft-ietf-privacypass-rate-limit-tokens.html#name-encrypting-origin-token-req 58 | func encryptOriginTokenRequest(nameKey EncapKey, tokenKeyID uint8, blindedMessage []byte, requestKey []byte, originName string) ([]byte, []byte, []byte, error) { 59 | issuerKeyEnc := nameKey.Marshal() 60 | issuerKeyID := sha256.Sum256(issuerKeyEnc) 61 | 62 | enc, context, err := hpke.SetupBaseS(nameKey.suite, rand.Reader, nameKey.publicKey, []byte("TokenRequest")) 63 | if err != nil { 64 | return nil, nil, nil, err 65 | } 66 | 67 | b := cryptobyte.NewBuilder(nil) 68 | b.AddUint8(nameKey.id) 69 | b.AddUint16(uint16(nameKey.suite.KEM.ID())) 70 | b.AddUint16(uint16(nameKey.suite.KDF.ID())) 71 | b.AddUint16(uint16(nameKey.suite.AEAD.ID())) 72 | b.AddUint16(RateLimitedTokenType) 73 | b.AddBytes(requestKey) 74 | b.AddBytes(issuerKeyID[:]) 75 | 76 | tokenRequest := InnerTokenRequest{ 77 | blindedMsg: blindedMessage, 78 | tokenKeyId: tokenKeyID, 79 | paddedOrigin: padOriginName(originName), 80 | } 81 | input := tokenRequest.Marshal() 82 | 83 | aad := b.BytesOrPanic() 84 | 85 | ct := context.Seal(aad, input) 86 | encryptedTokenRequest := append(enc, ct...) 87 | secret := context.Export([]byte("TokenResponse"), nameKey.suite.AEAD.KeySize()) 88 | 89 | return issuerKeyID[:], encryptedTokenRequest, secret, nil 90 | } 91 | 92 | type RateLimitedTokenRequestState struct { 93 | tokenInput []byte 94 | clientKey []byte 95 | blindedRequestKey []byte 96 | request *RateLimitedTokenRequest 97 | encapSecret []byte 98 | encapEnc []byte 99 | nameKey EncapKey 100 | verificationKey *rsa.PublicKey 101 | verifier blindrsa.VerifierState 102 | } 103 | 104 | func (s RateLimitedTokenRequestState) Request() *RateLimitedTokenRequest { 105 | return s.request 106 | } 107 | 108 | func (s RateLimitedTokenRequestState) RequestKey() []byte { 109 | return s.blindedRequestKey 110 | } 111 | 112 | func (s RateLimitedTokenRequestState) ClientKey() []byte { 113 | return s.clientKey 114 | } 115 | 116 | // https://ietf-wg-privacypass.github.io/draft-ietf-privacypass-rate-limit-tokens/draft-ietf-privacypass-rate-limit-tokens.html#name-attester-to-client-response 117 | func (s RateLimitedTokenRequestState) FinalizeToken(encryptedtokenResponse []byte) (tokens.Token, error) { 118 | // response_nonce = random(max(Nn, Nk)), taken from the encapsualted response 119 | responseNonceLen := max(s.nameKey.suite.AEAD.KeySize(), s.nameKey.suite.AEAD.NonceSize()) 120 | 121 | // salt = concat(enc, response_nonce) 122 | salt := append(s.encapEnc, encryptedtokenResponse[:responseNonceLen]...) 123 | 124 | // prk = Extract(salt, secret) 125 | prk := s.nameKey.suite.KDF.Extract(salt, s.encapSecret) 126 | 127 | // aead_key = Expand(prk, "key", Nk) 128 | key := s.nameKey.suite.KDF.Expand(prk, []byte(labelResponseKey), s.nameKey.suite.AEAD.KeySize()) 129 | 130 | // aead_nonce = Expand(prk, "nonce", Nn) 131 | nonce := s.nameKey.suite.KDF.Expand(prk, []byte(labelResponseNonce), s.nameKey.suite.AEAD.NonceSize()) 132 | 133 | cipher, err := s.nameKey.suite.AEAD.New(key) 134 | if err != nil { 135 | return tokens.Token{}, err 136 | } 137 | 138 | // reponse, error = Open(aead_key, aead_nonce, "", ct) 139 | blindSignature, err := cipher.Open(nil, nonce, encryptedtokenResponse[responseNonceLen:], nil) 140 | if err != nil { 141 | return tokens.Token{}, err 142 | } 143 | 144 | signature, err := s.verifier.Finalize(blindSignature) 145 | if err != nil { 146 | return tokens.Token{}, err 147 | } 148 | 149 | tokenData := append(s.tokenInput, signature...) 150 | token, err := UnmarshalToken(tokenData) 151 | if err != nil { 152 | return tokens.Token{}, err 153 | } 154 | 155 | // Sanity check: verify the token signature 156 | hash := sha512.New384() 157 | _, err = hash.Write(token.AuthenticatorInput()) 158 | if err != nil { 159 | return tokens.Token{}, err 160 | } 161 | digest := hash.Sum(nil) 162 | 163 | err = rsa.VerifyPSS(s.verificationKey, crypto.SHA384, digest, token.Authenticator, &rsa.PSSOptions{ 164 | Hash: crypto.SHA384, 165 | SaltLength: crypto.SHA384.Size(), 166 | }) 167 | if err != nil { 168 | return tokens.Token{}, err 169 | } 170 | 171 | return token, nil 172 | } 173 | 174 | // https://ietf-wg-privacypass.github.io/draft-ietf-privacypass-rate-limit-tokens/draft-ietf-privacypass-rate-limit-tokens.html#name-client-to-attester-request 175 | func (c RateLimitedClient) CreateTokenRequest(challenge, nonce, blindKeyEnc []byte, tokenKeyID []byte, tokenKey *rsa.PublicKey, originName string, nameKey EncapKey) (RateLimitedTokenRequestState, error) { 176 | blindKey, err := ecdsa.CreateKey(c.curve, blindKeyEnc) 177 | if err != nil { 178 | return RateLimitedTokenRequestState{}, err 179 | } 180 | 181 | clientKeyEnc := elliptic.MarshalCompressed(c.curve, c.secretKey.PublicKey.X, c.secretKey.PublicKey.Y) 182 | 183 | b := cryptobyte.NewBuilder(nil) 184 | b.AddUint16(RateLimitedTokenType) 185 | b.AddBytes([]byte("ClientBlind")) 186 | ctx := b.BytesOrPanic() 187 | blindedPublicKey, err := ecdsa.BlindPublicKeyWithContext(c.curve, &c.secretKey.PublicKey, blindKey, ctx) 188 | if err != nil { 189 | return RateLimitedTokenRequestState{}, err 190 | } 191 | blindedPublicKeyEnc := elliptic.MarshalCompressed(c.curve, blindedPublicKey.X, blindedPublicKey.Y) 192 | 193 | verifier := blindrsa.NewVerifier(tokenKey, crypto.SHA384) 194 | 195 | context := sha256.Sum256(challenge) 196 | token := tokens.Token{ 197 | TokenType: RateLimitedTokenType, 198 | Nonce: nonce, 199 | Context: context[:], 200 | KeyID: tokenKeyID, 201 | Authenticator: nil, // No signature computed yet 202 | } 203 | tokenInput := token.AuthenticatorInput() 204 | blindedMessage, verifierState, err := verifier.Blind(rand.Reader, tokenInput) 205 | if err != nil { 206 | return RateLimitedTokenRequestState{}, err 207 | } 208 | 209 | nameKeyID, encryptedTokenRequest, secret, err := encryptOriginTokenRequest(nameKey, tokenKeyID[0], blindedMessage, blindedPublicKeyEnc, originName) 210 | if err != nil { 211 | return RateLimitedTokenRequestState{}, err 212 | } 213 | 214 | b = cryptobyte.NewBuilder(nil) 215 | b.AddUint16(RateLimitedTokenType) 216 | b.AddBytes(blindedPublicKeyEnc) 217 | b.AddBytes(nameKeyID) 218 | b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { 219 | b.AddBytes(encryptedTokenRequest) 220 | }) 221 | message := b.BytesOrPanic() 222 | 223 | hash := sha512.New384() 224 | hash.Write(message) 225 | digest := hash.Sum(nil) 226 | 227 | r, s, err := ecdsa.BlindKeySignWithContext(rand.Reader, c.secretKey, blindKey, digest, ctx) 228 | if err != nil { 229 | return RateLimitedTokenRequestState{}, err 230 | } 231 | scalarLen := (c.curve.Params().Params().BitSize + 7) / 8 232 | rEnc := make([]byte, scalarLen) 233 | sEnc := make([]byte, scalarLen) 234 | r.FillBytes(rEnc) 235 | s.FillBytes(sEnc) 236 | signature := append(rEnc, sEnc...) 237 | 238 | request := &RateLimitedTokenRequest{ 239 | RequestKey: blindedPublicKeyEnc, 240 | NameKeyID: nameKeyID, 241 | EncryptedTokenRequest: encryptedTokenRequest, 242 | Signature: signature, 243 | } 244 | 245 | requestState := RateLimitedTokenRequestState{ 246 | tokenInput: tokenInput, 247 | clientKey: clientKeyEnc, 248 | request: request, 249 | encapSecret: secret, 250 | encapEnc: encryptedTokenRequest[0:nameKey.suite.KEM.PublicKeySize()], 251 | nameKey: nameKey, 252 | verifier: verifierState, 253 | verificationKey: tokenKey, 254 | } 255 | 256 | return requestState, nil 257 | } 258 | -------------------------------------------------------------------------------- /tokens/type3/encap_key.go: -------------------------------------------------------------------------------- 1 | package type3 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | hpke "github.com/cisco/go-hpke" 8 | "golang.org/x/crypto/cryptobyte" 9 | ) 10 | 11 | var ( 12 | fixedKEM = hpke.DHKEM_X25519 13 | fixedKDF = hpke.KDF_HKDF_SHA256 14 | fixedAEAD = hpke.AEAD_AESGCM128 15 | ) 16 | 17 | // https://tfpauly.github.io/privacy-proxy/draft-privacypass-rate-limit-tokens.html#name-configuration 18 | type PrivateEncapKey struct { 19 | id uint8 20 | suite hpke.CipherSuite 21 | privateKey hpke.KEMPrivateKey 22 | publicKey hpke.KEMPublicKey 23 | } 24 | 25 | func CreatePrivateEncapKeyFromSeed(seed []byte) (PrivateEncapKey, error) { 26 | if len(seed) != 32 { 27 | return PrivateEncapKey{}, fmt.Errorf("Invalid seed length, expected 32 bytes") 28 | } 29 | 30 | suite, err := hpke.AssembleCipherSuite(fixedKEM, fixedKDF, fixedAEAD) 31 | if err != nil { 32 | return PrivateEncapKey{}, err 33 | } 34 | 35 | sk, pk, err := suite.KEM.DeriveKeyPair(seed) 36 | if err != nil { 37 | return PrivateEncapKey{}, err 38 | } 39 | 40 | return PrivateEncapKey{ 41 | id: 0x01, 42 | suite: suite, 43 | privateKey: sk, 44 | publicKey: pk, 45 | }, nil 46 | } 47 | 48 | type EncapKey struct { 49 | id uint8 50 | suite hpke.CipherSuite 51 | publicKey hpke.KEMPublicKey 52 | } 53 | 54 | func (k PrivateEncapKey) Public() EncapKey { 55 | return EncapKey{ 56 | id: k.id, 57 | suite: k.suite, 58 | publicKey: k.publicKey, 59 | } 60 | } 61 | 62 | func (k PrivateEncapKey) IsEqual(o PrivateEncapKey) bool { 63 | if k.id != o.id { 64 | return false 65 | } 66 | if k.suite != o.suite { 67 | return false 68 | } 69 | if !bytes.Equal(k.suite.KEM.SerializePublicKey(k.publicKey), k.suite.KEM.SerializePublicKey(o.publicKey)) { 70 | return false 71 | } 72 | 73 | return true 74 | } 75 | 76 | // opaque HpkePublicKey[Npk]; // defined in I-D.irtf-cfrg-hpke 77 | // uint16 HpkeKemId; // defined in I-D.irtf-cfrg-hpke 78 | // uint16 HpkeKdfId; // defined in I-D.irtf-cfrg-hpke 79 | // uint16 HpkeAeadId; // defined in I-D.irtf-cfrg-hpke 80 | // 81 | // struct { 82 | // uint8 key_id; 83 | // HpkeKemId kem_id; 84 | // HpkePublicKey public_key; 85 | // HpkeKdfId kdf_id; 86 | // HpkeAeadId aead_id; 87 | // } EncapKey; 88 | func (k EncapKey) Marshal() []byte { 89 | b := cryptobyte.NewBuilder(nil) 90 | 91 | b.AddUint8(k.id) 92 | b.AddUint16(uint16(k.suite.KEM.ID())) 93 | b.AddBytes(k.suite.KEM.SerializePublicKey(k.publicKey)) 94 | b.AddUint16(uint16(k.suite.KDF.ID())) 95 | b.AddUint16(uint16(k.suite.AEAD.ID())) 96 | return b.BytesOrPanic() 97 | } 98 | 99 | func UnmarshalEncapKey(data []byte) (EncapKey, error) { 100 | s := cryptobyte.String(data) 101 | 102 | var id uint8 103 | var kemID uint16 104 | if !s.ReadUint8(&id) || 105 | !s.ReadUint16(&kemID) { 106 | return EncapKey{}, fmt.Errorf("Invalid EncapKey") 107 | } 108 | 109 | kem := hpke.KEMID(kemID) 110 | suite, err := hpke.AssembleCipherSuite(kem, fixedKDF, fixedAEAD) 111 | if err != nil { 112 | return EncapKey{}, fmt.Errorf("Invalid EncapKey") 113 | } 114 | 115 | publicKeyBytes := make([]byte, suite.KEM.PublicKeySize()) 116 | if !s.ReadBytes(&publicKeyBytes, len(publicKeyBytes)) { 117 | return EncapKey{}, fmt.Errorf("Invalid EncapKey") 118 | } 119 | 120 | var kdfID uint16 121 | var aeadID uint16 122 | if !s.ReadUint16(&kdfID) || 123 | !s.ReadUint16(&aeadID) { 124 | return EncapKey{}, fmt.Errorf("Invalid EncapKey") 125 | } 126 | 127 | suite, err = hpke.AssembleCipherSuite(kem, hpke.KDFID(kdfID), hpke.AEADID(aeadID)) 128 | if err != nil { 129 | return EncapKey{}, fmt.Errorf("Invalid EncapKey") 130 | } 131 | 132 | publicKey, err := suite.KEM.DeserializePublicKey(publicKeyBytes) 133 | if err != nil { 134 | return EncapKey{}, fmt.Errorf("Invalid EncapKey") 135 | } 136 | 137 | return EncapKey{ 138 | id: id, 139 | suite: suite, 140 | publicKey: publicKey, 141 | }, nil 142 | } 143 | -------------------------------------------------------------------------------- /tokens/type3/inner_token_request.go: -------------------------------------------------------------------------------- 1 | package type3 2 | 3 | import "golang.org/x/crypto/cryptobyte" 4 | 5 | type InnerTokenRequest struct { 6 | raw []byte 7 | tokenKeyId uint8 8 | blindedMsg []byte 9 | paddedOrigin []byte 10 | } 11 | 12 | func (r *InnerTokenRequest) Marshal() []byte { 13 | if r.raw != nil { 14 | return r.raw 15 | } 16 | 17 | b := cryptobyte.NewBuilder(nil) 18 | b.AddUint8(r.tokenKeyId) 19 | b.AddBytes(r.blindedMsg) 20 | b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { 21 | b.AddBytes([]byte(r.paddedOrigin)) 22 | }) 23 | 24 | r.raw = b.BytesOrPanic() 25 | return r.raw 26 | } 27 | 28 | func (r *InnerTokenRequest) Unmarshal(data []byte) bool { 29 | s := cryptobyte.String(data) 30 | 31 | if !s.ReadUint8(&r.tokenKeyId) || !s.ReadBytes(&r.blindedMsg, 256) { 32 | return false 33 | } 34 | 35 | var paddedOriginName cryptobyte.String 36 | if !s.ReadUint16LengthPrefixed(&paddedOriginName) { 37 | return false 38 | } 39 | r.paddedOrigin = make([]byte, len(paddedOriginName)) 40 | copy(r.paddedOrigin, paddedOriginName) 41 | 42 | return true 43 | } 44 | -------------------------------------------------------------------------------- /tokens/type3/issuer.go: -------------------------------------------------------------------------------- 1 | package type3 2 | 3 | import ( 4 | "crypto/elliptic" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/sha256" 8 | "crypto/sha512" 9 | "fmt" 10 | "math/big" 11 | 12 | hpke "github.com/cisco/go-hpke" 13 | "github.com/cloudflare/circl/blindsign/blindrsa" 14 | "github.com/cloudflare/pat-go/ecdsa" 15 | "github.com/cloudflare/pat-go/util" 16 | "golang.org/x/crypto/cryptobyte" 17 | ) 18 | 19 | type RateLimitedIssuer struct { 20 | curve elliptic.Curve 21 | nameKey PrivateEncapKey 22 | tokenKey *rsa.PrivateKey 23 | originIndexKeys map[string]*ecdsa.PrivateKey 24 | } 25 | 26 | func NewRateLimitedIssuer(key *rsa.PrivateKey) *RateLimitedIssuer { 27 | suite, err := hpke.AssembleCipherSuite(hpke.DHKEM_X25519, hpke.KDF_HKDF_SHA256, hpke.AEAD_AESGCM128) 28 | if err != nil { 29 | return nil 30 | } 31 | 32 | ikm := make([]byte, suite.KEM.PrivateKeySize()) 33 | _, err = rand.Reader.Read(ikm) 34 | if err != nil { 35 | return nil 36 | } 37 | privateKey, publicKey, err := suite.KEM.DeriveKeyPair(ikm) 38 | if err != nil { 39 | return nil 40 | } 41 | 42 | nameKey := PrivateEncapKey{ 43 | id: 0x00, 44 | suite: suite, 45 | publicKey: publicKey, 46 | privateKey: privateKey, 47 | } 48 | 49 | return &RateLimitedIssuer{ 50 | curve: elliptic.P384(), 51 | nameKey: nameKey, 52 | tokenKey: key, 53 | originIndexKeys: make(map[string]*ecdsa.PrivateKey), 54 | } 55 | } 56 | 57 | func (i *RateLimitedIssuer) NameKey() EncapKey { 58 | return i.nameKey.Public() 59 | } 60 | 61 | func (i *RateLimitedIssuer) AddOrigin(origin string) error { 62 | privateKey, err := ecdsa.GenerateKey(i.curve, rand.Reader) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | i.originIndexKeys[origin] = privateKey 68 | 69 | return nil 70 | } 71 | 72 | func (i *RateLimitedIssuer) AddOriginWithIndexKey(origin string, privateKey *ecdsa.PrivateKey) error { 73 | i.originIndexKeys[origin] = privateKey 74 | return nil 75 | } 76 | 77 | func (i *RateLimitedIssuer) OriginIndexKey(origin string) *ecdsa.PrivateKey { 78 | key, ok := i.originIndexKeys[origin] 79 | if !ok { 80 | return nil 81 | } 82 | return key 83 | } 84 | 85 | func (i *RateLimitedIssuer) TokenKey() *rsa.PublicKey { 86 | return &i.tokenKey.PublicKey 87 | } 88 | 89 | func (i *RateLimitedIssuer) TokenKeyID() []byte { 90 | publicKey := i.TokenKey() 91 | publicKeyEnc, err := util.MarshalTokenKeyPSSOID(publicKey) 92 | if err != nil { 93 | panic(err) 94 | } 95 | keyID := sha256.Sum256(publicKeyEnc) 96 | return keyID[:] 97 | } 98 | 99 | func max(a, b int) int { 100 | if a > b { 101 | return a 102 | } 103 | return b 104 | } 105 | 106 | func decryptOriginTokenRequest(nameKey PrivateEncapKey, requestKey []byte, encryptedTokenRequest []byte) (InnerTokenRequest, []byte, error) { 107 | issuerConfigID := sha256.Sum256(nameKey.Public().Marshal()) 108 | 109 | // Decrypt the origin name 110 | b := cryptobyte.NewBuilder(nil) 111 | b.AddUint8(nameKey.id) 112 | b.AddUint16(uint16(nameKey.suite.KEM.ID())) 113 | b.AddUint16(uint16(nameKey.suite.KDF.ID())) 114 | b.AddUint16(uint16(nameKey.suite.AEAD.ID())) 115 | b.AddUint16(RateLimitedTokenType) 116 | b.AddBytes(requestKey) 117 | b.AddBytes(issuerConfigID[:]) 118 | aad := b.BytesOrPanic() 119 | 120 | enc := encryptedTokenRequest[0:nameKey.suite.KEM.PublicKeySize()] 121 | ct := encryptedTokenRequest[nameKey.suite.KEM.PublicKeySize():] 122 | 123 | context, err := hpke.SetupBaseR(nameKey.suite, nameKey.privateKey, enc, []byte("TokenRequest")) 124 | if err != nil { 125 | return InnerTokenRequest{}, nil, err 126 | } 127 | 128 | tokenRequestEnc, err := context.Open(aad, ct) 129 | if err != nil { 130 | return InnerTokenRequest{}, nil, err 131 | } 132 | 133 | tokenRequest := &InnerTokenRequest{} 134 | if !tokenRequest.Unmarshal(tokenRequestEnc) { 135 | return InnerTokenRequest{}, nil, err 136 | } 137 | 138 | secret := context.Export([]byte("TokenResponse"), nameKey.suite.AEAD.KeySize()) 139 | 140 | return *tokenRequest, secret, err 141 | } 142 | 143 | // https://ietf-wg-privacypass.github.io/draft-ietf-privacypass-rate-limit-tokens/draft-ietf-privacypass-rate-limit-tokens.html#name-issuer-to-attester-response 144 | func (i RateLimitedIssuer) Evaluate(encodedRequest []byte) ([]byte, []byte, error) { 145 | req := &RateLimitedTokenRequest{} 146 | if !req.Unmarshal(encodedRequest) { 147 | return nil, nil, fmt.Errorf("malformed request") 148 | } 149 | 150 | // Recover and validate the origin name 151 | originTokenRequest, secret, err := decryptOriginTokenRequest(i.nameKey, req.RequestKey, req.EncryptedTokenRequest) 152 | if err != nil { 153 | return nil, nil, err 154 | } 155 | originName := unpadOriginName(originTokenRequest.paddedOrigin) 156 | 157 | // Check to see if it's a registered origin 158 | originIndexKey, ok := i.originIndexKeys[originName] 159 | if !ok { 160 | return nil, nil, fmt.Errorf("unknown origin: %s", originName) 161 | } 162 | 163 | // Deserialize the request key 164 | requestKey, err := unmarshalPublicKey(i.curve, req.RequestKey) 165 | if err != nil { 166 | return nil, nil, err 167 | } 168 | 169 | scalarLen := (i.curve.Params().Params().BitSize + 7) / 8 170 | r := new(big.Int).SetBytes(req.Signature[:scalarLen]) 171 | s := new(big.Int).SetBytes(req.Signature[scalarLen:]) 172 | 173 | // Verify the request signature 174 | b := cryptobyte.NewBuilder(nil) 175 | b.AddUint16(RateLimitedTokenType) 176 | b.AddBytes(req.RequestKey) 177 | b.AddBytes(req.NameKeyID) 178 | b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { 179 | b.AddBytes(req.EncryptedTokenRequest) 180 | }) 181 | message := b.BytesOrPanic() 182 | 183 | hash := sha512.New384() 184 | hash.Write(message) 185 | digest := hash.Sum(nil) 186 | 187 | valid := ecdsa.Verify(requestKey, digest, r, s) 188 | if !valid { 189 | return nil, nil, fmt.Errorf("invalid request signature") 190 | } 191 | 192 | // Compute the request key 193 | b = cryptobyte.NewBuilder(nil) 194 | b.AddUint16(RateLimitedTokenType) 195 | b.AddBytes([]byte("IssuerBlind")) 196 | ctx := b.BytesOrPanic() 197 | blindedRequestKey, err := ecdsa.BlindPublicKeyWithContext(i.curve, requestKey, originIndexKey, ctx) 198 | if err != nil { 199 | return nil, nil, err 200 | } 201 | blindedRequestKeyEnc := elliptic.MarshalCompressed(i.curve, blindedRequestKey.X, blindedRequestKey.Y) 202 | 203 | // Compute the blinded signature 204 | signer := blindrsa.NewSigner(i.tokenKey) 205 | blindSignature, err := signer.BlindSign(originTokenRequest.blindedMsg) 206 | if err != nil { 207 | return nil, nil, err 208 | } 209 | 210 | // Generate a fresh nonce for encrypting the response back to the client 211 | responseNonceLen := max(i.nameKey.suite.AEAD.KeySize(), i.nameKey.suite.AEAD.NonceSize()) 212 | responseNonce := make([]byte, responseNonceLen) 213 | _, err = rand.Read(responseNonce) 214 | if err != nil { 215 | return nil, nil, err 216 | } 217 | 218 | enc := make([]byte, i.nameKey.suite.KEM.PublicKeySize()) 219 | copy(enc, req.EncryptedTokenRequest[0:i.nameKey.suite.KEM.PublicKeySize()]) 220 | salt := append(enc, responseNonce...) 221 | 222 | // Derive encryption secrets 223 | prk := i.nameKey.suite.KDF.Extract(salt, secret) 224 | key := i.nameKey.suite.KDF.Expand(prk, []byte(labelResponseKey), i.nameKey.suite.AEAD.KeySize()) 225 | nonce := i.nameKey.suite.KDF.Expand(prk, []byte(labelResponseNonce), i.nameKey.suite.AEAD.NonceSize()) 226 | 227 | cipher, err := i.nameKey.suite.AEAD.New(key) 228 | if err != nil { 229 | return nil, nil, err 230 | } 231 | encryptedTokenResponse := append(responseNonce, cipher.Seal(nil, nonce, blindSignature, nil)...) 232 | 233 | return encryptedTokenResponse, blindedRequestKeyEnc, nil 234 | } 235 | 236 | func (i RateLimitedIssuer) Type() uint16 { 237 | return RateLimitedTokenType 238 | } 239 | -------------------------------------------------------------------------------- /tokens/type3/token.go: -------------------------------------------------------------------------------- 1 | package type3 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cloudflare/pat-go/tokens" 7 | "golang.org/x/crypto/cryptobyte" 8 | ) 9 | 10 | func UnmarshalToken(data []byte) (tokens.Token, error) { 11 | s := cryptobyte.String(data) 12 | 13 | token := tokens.Token{} 14 | if !s.ReadUint16(&token.TokenType) || 15 | !s.ReadBytes(&token.Nonce, 32) || 16 | !s.ReadBytes(&token.Context, 32) || 17 | !s.ReadBytes(&token.KeyID, 32) || 18 | !s.ReadBytes(&token.Authenticator, 256) { 19 | return tokens.Token{}, fmt.Errorf("invalid Token encoding") 20 | } 21 | 22 | return token, nil 23 | } 24 | -------------------------------------------------------------------------------- /tokens/type3/token_request.go: -------------------------------------------------------------------------------- 1 | package type3 2 | 3 | import ( 4 | "bytes" 5 | 6 | "golang.org/x/crypto/cryptobyte" 7 | ) 8 | 9 | const RateLimitedTokenType = uint16(0x0003) 10 | 11 | // https://tfpauly.github.io/privacy-proxy/draft-privacypass-rate-limit-tokens.html#section-5.3 12 | type RateLimitedTokenRequest struct { 13 | raw []byte 14 | RequestKey []byte // Npk bytes 15 | NameKeyID []byte // 32 bytes 16 | EncryptedTokenRequest []byte // 16-bit length prefixed slice 17 | Signature []byte // Nsig bytes 18 | } 19 | 20 | func (r *RateLimitedTokenRequest) Type() uint16 { 21 | return RateLimitedTokenType 22 | } 23 | 24 | func (r RateLimitedTokenRequest) Equal(r2 RateLimitedTokenRequest) bool { 25 | if bytes.Equal(r.RequestKey, r2.RequestKey) && 26 | bytes.Equal(r.NameKeyID, r2.NameKeyID) && 27 | bytes.Equal(r.EncryptedTokenRequest, r2.EncryptedTokenRequest) && 28 | bytes.Equal(r.Signature, r2.Signature) { 29 | return true 30 | } 31 | 32 | return false 33 | } 34 | 35 | func (r *RateLimitedTokenRequest) Marshal() []byte { 36 | if r.raw != nil { 37 | return r.raw 38 | } 39 | 40 | b := cryptobyte.NewBuilder(nil) 41 | b.AddUint16(RateLimitedTokenType) 42 | b.AddBytes(r.RequestKey) 43 | b.AddBytes(r.NameKeyID) 44 | b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { 45 | b.AddBytes(r.EncryptedTokenRequest) 46 | }) 47 | b.AddBytes(r.Signature) 48 | 49 | r.raw = b.BytesOrPanic() 50 | return r.raw 51 | } 52 | 53 | func (r *RateLimitedTokenRequest) Unmarshal(data []byte) bool { 54 | s := cryptobyte.String(data) 55 | 56 | var tokenType uint16 57 | if !s.ReadUint16(&tokenType) || 58 | tokenType != RateLimitedTokenType || 59 | !s.ReadBytes(&r.RequestKey, 49) || 60 | !s.ReadBytes(&r.NameKeyID, 32) { 61 | return false 62 | } 63 | 64 | var encryptedTokenRequest cryptobyte.String 65 | if !s.ReadUint16LengthPrefixed(&encryptedTokenRequest) || encryptedTokenRequest.Empty() { 66 | return false 67 | } 68 | r.EncryptedTokenRequest = make([]byte, len(encryptedTokenRequest)) 69 | copy(r.EncryptedTokenRequest, encryptedTokenRequest) 70 | 71 | s.ReadBytes(&r.Signature, 96) 72 | return s.Empty() 73 | } 74 | -------------------------------------------------------------------------------- /tokens/type3/token_request_test.go: -------------------------------------------------------------------------------- 1 | package type3 2 | 3 | import ( 4 | "crypto/elliptic" 5 | "crypto/rand" 6 | "testing" 7 | 8 | "github.com/cloudflare/pat-go/ecdsa" 9 | "github.com/cloudflare/pat-go/util" 10 | ) 11 | 12 | func TestRequestMarshal(t *testing.T) { 13 | var err error 14 | issuer := NewRateLimitedIssuer(loadPrivateKey(t)) 15 | testOrigin := "origin.example" 16 | err = issuer.AddOrigin(testOrigin) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | curve := elliptic.P384() 22 | secretKey, err := ecdsa.GenerateKey(curve, rand.Reader) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | blindKey, err := ecdsa.GenerateKey(curve, rand.Reader) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | client := NewRateLimitedClientFromSecret(secretKey.D.Bytes()) 31 | 32 | challenge := make([]byte, 32) 33 | util.MustRead(t, rand.Reader, challenge) 34 | 35 | nonce := make([]byte, 32) 36 | util.MustRead(t, rand.Reader, nonce) 37 | 38 | tokenKeyID := issuer.TokenKeyID() 39 | tokenPublicKey := issuer.TokenKey() 40 | blindKeyEnc := blindKey.D.Bytes() 41 | 42 | requestState, err := client.CreateTokenRequest(challenge, nonce, blindKeyEnc, tokenKeyID, tokenPublicKey, testOrigin, issuer.NameKey()) 43 | if err != nil { 44 | t.Error(err) 45 | } 46 | 47 | tokenRequest := requestState.Request() 48 | tokenRequestEnc := tokenRequest.Marshal() 49 | var tokenRequestRecovered RateLimitedTokenRequest 50 | if !tokenRequestRecovered.Unmarshal(tokenRequestEnc) { 51 | t.Error("Failed to unmarshal TokenRequest") 52 | } 53 | if !tokenRequest.Equal(tokenRequestRecovered) { 54 | t.Fatal("Token marshal mismatch") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tokens/type3/type3-anon-origin-id-test-vectors.json: -------------------------------------------------------------------------------- 1 | [{"sk_sign":"f7a8996bfd0d4ef9af88b3eab73d9e05a2d8c557407236aa15e67b4c8972bc57afb6562f0341dcc5e80fbb71811b9bbe","pk_sign":"032d276595b188b428e954f0cf61bebea9663a7d6678042a54bdb1778fc88df7f03b83f7e2c15b14147f3487363f9dbd7a","sk_origin":"337d87ad143b414e05e7f764df402b8af14c20c34dc727dca027aa87a5e1099f3760985813549a451ec42b0d7a377fdf","request_blind":"7444e18d84cad471dc07d8210b714493254776ff897f040feb6e97a9a5f90f21d940ea7c50f8a5e3d9d8998c45ab7d42","request_key":"02168f9ec10377781d0b16370e7e97b02755741ad0e66e089696080b4412ce56e933d47c22ff08a5d5da1474aa6b899b0a","index_key":"0284e8d968c696e57194db7b7a37814a1d7c9c2216106530561d07adc87ff1b6b9c8b911711f5be66c165bbe90c280befb","issuer_origin_alias":"ee475b7c158ff52a89ae21e7178ce572124ba6012a58ba4124f0c691ffe4b40099637964891316264e8442f5f17aa5af"}] -------------------------------------------------------------------------------- /tokens/type3/type3-ecdsa-blinding-test-vectors.json: -------------------------------------------------------------------------------- 1 | [{"Curve":"P-384","Hash":"SHA-384","skS":"670d3972b6ebae5105a999d4ed50fb5d0b6ae8dd57225b4812b29515aa5f88789ede2eadbe59f396d1fdf8633c50c1bc","pkS":"031a7b923349ed81c0e79d38bce08a9e5538dbda3427319fe6c870e9f310f330b0cedd8e6d4e5d01172c8ddfe81d33dc58","bk":"ca3833486be51090c26f8165669411efa965e75ac605f305a701ea4b993209880b77d03548306d0b7ae70d05320179d5","pkR":"02285f249b5cf208b014ec1804a70a28b25160edb2acdcf1168f2b31eea49c0f18fca230abb89afde0454d7f64766d18f0","message":"68656c6c6f20776f726c64","context":"","signature":"18493e8f2b77d575e84b644a16cb354cdb6c2e99245f865a32656b146ee88cb141a46008d27adbef1f1961634d19015c9500d3d94cadbf4655216514bb62c367c7417f1c220aee5a17009be6b070c9001f0f40f9e459956a0928d836542221b0"},{"Curve":"P-384","Hash":"SHA-384","skS":"f494f1f7313d0535eac11cba627896bcea4aa5b3ed3007db882ef63da56e8e9bb22461798f8e3d7f2e62144e5da8c517","pkS":"030dc960d2db1435550d1a479b617074bae6b7362005708eb73fda1259b05426fcd971a804b2fdce1f8045b08bd83e65af","bk":"2299608b39247edd9b3cc4a7a1058185c899736a4e86706e4fd95feb36cc64523ce670b683dd41d77d38b0a26c0b3526","pkR":"038274af35bd5b63a3070b041c0f627925227f30de0a3ed5460899e92e3d9ce4f7e02035bc0458e449853b69f065fedf4f","message":"68656c6c6f20776f726c64","context":"d5c81e43e7a20cac5e116537a4f84b29ce214061e0512be2b53d5156126d578e","signature":"d31a0f993f53be5bd78f38952be6989197dac277b8efe663d5e738595274bd1b9ca62fb7d066d6e69154c81a85a514cbc7cc5b02660d43daecd554f2a63d5c2a600374a4f9dfccb6333400eb2b9db3a7654bbbd7f7bf808f624ea5fa2f003a31"}] -------------------------------------------------------------------------------- /tokens/type3/type3-ed25519-blinding-test-vectors.json: -------------------------------------------------------------------------------- 1 | [{"skS":"8a86ca13f3c47c0de87fd305f861d7a2d99cd54361ee1c954f0ff231f16cbaf2","pkS":"4a8135301b8c75e11c5b79a3a22b0ac5db75570994df79c187ad965e454ad68f","bk":"4e9416f0187e1c08fd46e4f012d120249ae9773553b36b2cb181e4e829249514","pkB":"","pkR":"d4e93272a3a62460f5abe74014d3724e48c9dc93bebc564d5287b93206acfecc","message":"68656c6c6f20776f726c64","context":"","signature":"8aa4d47ca29f1608482708fc18f1af9e0576dd4d4f6f2a2012c28198fbe16adf03c67d367b8cdfa7196b3ec1a41b349bf08894c62ef25fb15cf157eee87db00f"},{"skS":"7544848eb373ef7bf5a8e9203de33c548e6050c152d0fa8d384a1299d395866d","pkS":"51a3ce1cc2c5d0338ccb3ab3f6198380b9cda196b1b3e2a822b61dcdbf1ef3a3","bk":"0000000000000000000000000000000000000000000000000000000000000000","pkB":"","pkR":"3b23b3a95447c5a962fd907152115782517c7642f4d8a4f0e08a54ce22219576","message":"68656c6c6f20776f726c64","context":"","signature":"c532b944ddfbb4ae56970552ee387c71fc48f4449c4b6458f8f7d80fc0c6df448e82e0c6f994331e7152a5ecd0d80af758cdebc6fe4ecf6867c2f08bc343e908"},{"skS":"339755ef9be8f0410e744f06c88afad120a6a41db1da3ebb5c8b8bf3981def99","pkS":"0ad65f7e9d2517b12a25a31f3072b122528c2b6c27af8a551cff196b86e5940b","bk":"33a3492b4b3e4ee1aba7bc294b650a72e762f09f835cea4a30f50120118d8c51","pkB":"","pkR":"f0ad0e1e6aaa904ac19fb2d3e1bb56843a1cfcc8b4ceb4978a42c8baa85009dc","message":"68656c6c6f20776f726c64","context":"4a2d157f3d05428e4504a6d0dacf6e822803f310901676e1b7275e9298dfd3bd","signature":"748c1b8fa9d45c16765de094602a121f5a3b26a8fb46e77b936e8674b5bc9a4edb9611e31f7777be33437fbb41764e6f84ff159562b090bf1815ca3446060408"},{"skS":"84d3bcd99c8ea38996578f8b608e712961f8ac490951526a0e8448a302e5eaf9","pkS":"2a5e28a4b0dbcb92acc692bda2e37d24ae05dd1aed0a5b2fffe7f9a57cbfe3c8","bk":"0000000000000000000000000000000000000000000000000000000000000000","pkB":"","pkR":"dd7ff3830718dfb3e5abe0b8f621ac29e239de43b8d7f5f77f1890a9d03ce5df","message":"68656c6c6f20776f726c64","context":"8b2581a7366b5d30c77f84bc5bcd139f83a6a9b16d2635bc98cf58bf8b8a733c","signature":"4fd5e933e79f7cbc0325c8885e4da2f3907836edfb8b1bbf0cc6d47a2733e934110578be4812382c8e4524af245e0af15e0b6dfe9dd9a9afd1e047bf77409909"}] -------------------------------------------------------------------------------- /tokens/type3/type3-origin-encryption-test-vectors.json: -------------------------------------------------------------------------------- 1 | [{"kem_id":32,"kdf_id":1,"aead_id":1,"issuer_encap_key_seed":"e7baae6b39e56dc468a468c0392fedd014703827b6055c0915dd9a66f9e14079","issuer_encap_key":"010020b29eea29c403c4f5dba632a9ee36c7c4550f0e99a320371baad80445d73c425100010001","token_type":3,"issuer_encap_key_id":"8c17b9c7588c4b7570a56f5588f8e5b73081931091f5b45758693205992b76ff","request_key":"ade06034495ee93607423cee1ec33eb1ac5fedf4a1988808593b6cdebdcd9034c06e5818b6dd812870c69785c081479f44","token_key_id":135,"blinded_msg":"1fedf5359c52583a1f0c1f2b75b0bbd02acc6e56ef7894ca8915703bcd3084f485c0c411158ac114aad10e8cfba4821aff55c38059d7f7cb4fe9862ca1c141fe83d41919bab27c295d6de48b48eb41be261c0d1c9c75a6d9993c84b2f24ce15067daca2367c789a0dfcae0776d19cd411ab641c91c51dc26014c1cf026e7b3a098d87d0773d9b7160ff81ae9576e802b9d2fbf1bf33dd71198335a7add0edcfd16a9b686fc8a7bba71942b47a786c860005c64b6615aa315e3cd8c59ef6adb9ec9ac2d414885fbd69a910c2f526ce04790fd4fa6b5198d1b9a75390b5cef30c4f1230483003d9a581e954098d12a879a4850aad0051da5722ddc49ffa658b48f","origin_name":"746573742e6578616d706c65","encap_secret":"74257cca5a7e84318c22d6a5ba0ffe9e","encrypted_token_request":"a1e9274e2aab8d3acec28628ba668b493f505a7d8422cf4b88979768c4efb45cbb5cd6c5742ac44c2bfa2334db5dd2df7a765405543fc793746de9092e0711bb3619cee2636ef2d79a02be980e20ed1a2b04edae9cd94d8b0fb88fefb5b7dfb3e68276d192ead64d54f5d64b06b21be86c868877be85b50a0df86aa30b64e4ec5012f2e089aa3383fe50010b04f228dd0bd5826216758fcce3d543b0cbc97b3888ba55c3caf49c71b62626b7bbacf26de2a81dd4b98c5f8ed09d677cbe68aa23f40f44123fc39cfd87d087b3c5485185052ae18ed6d91b1fc992337b0640057a6b4d73daf8f2b78d4b1fa94d7f5321d07c818e6dc26765639b06e951791d2a193642e5869858049a9cc39df97ec8bfb87191ca948731fa35302799f0bd75169703570e249117eb2697249b0d6c46f8e978c356286e628e56a5160c47d3fb2aab1081345d8b53f8a9d9a5f3db3ee70b7dd7a8f5"}] -------------------------------------------------------------------------------- /tokens/type5/client.go: -------------------------------------------------------------------------------- 1 | package type5 2 | 3 | import ( 4 | "crypto/sha256" 5 | "fmt" 6 | 7 | "github.com/cloudflare/circl/group" 8 | "github.com/cloudflare/circl/oprf" 9 | "github.com/cloudflare/circl/zk/dleq" 10 | "github.com/cloudflare/pat-go/quicwire" 11 | "github.com/cloudflare/pat-go/tokens" 12 | "golang.org/x/crypto/cryptobyte" 13 | ) 14 | 15 | type BatchedPrivateClient struct { 16 | } 17 | 18 | func NewBatchedPrivateClient() BatchedPrivateClient { 19 | return BatchedPrivateClient{} 20 | } 21 | 22 | type BatchedPrivateTokenRequestState struct { 23 | tokenInputs [][]byte 24 | request *BatchedPrivateTokenRequest 25 | client oprf.VerifiableClient 26 | verificationKey *oprf.PublicKey 27 | verifier *oprf.FinalizeData 28 | } 29 | 30 | func (s BatchedPrivateTokenRequestState) Request() *BatchedPrivateTokenRequest { 31 | return s.request 32 | } 33 | 34 | func (s BatchedPrivateTokenRequestState) ForTestsOnlyVerifier() *oprf.FinalizeData { 35 | return s.verifier 36 | } 37 | 38 | func (s BatchedPrivateTokenRequestState) FinalizeTokens(tokenResponseEnc []byte) ([]tokens.Token, error) { 39 | reader := cryptobyte.String(tokenResponseEnc) 40 | 41 | l, offset := quicwire.ConsumeVarint(tokenResponseEnc) 42 | reader.Skip(offset) 43 | 44 | encodedElements := make([]byte, l) 45 | if !reader.ReadBytes(&encodedElements, len(encodedElements)) { 46 | return nil, fmt.Errorf("invalid batch token response list encoding") 47 | } 48 | 49 | elementLength := int(group.Ristretto255.Params().CompressedElementLength) 50 | if len(encodedElements)%elementLength != 0 { 51 | return nil, fmt.Errorf("invalid batch token response encoding") 52 | } 53 | numElements := len(encodedElements) / elementLength 54 | if numElements != len(s.tokenInputs) { 55 | return nil, fmt.Errorf("invalid batch token response") 56 | } 57 | elements := make([]group.Element, numElements) 58 | for i := 0; i < numElements; i++ { 59 | elements[i] = group.Ristretto255.NewElement() 60 | err := elements[i].UnmarshalBinary(encodedElements[i*elementLength : (i+1)*elementLength]) 61 | if err != nil { 62 | return nil, err 63 | } 64 | } 65 | 66 | // XXX(caw): should we have a ProofLength parameter on the OPRF interface? 67 | proofLength := int(2 * group.Ristretto255.Params().ScalarLength) 68 | proofEnc := make([]byte, proofLength) 69 | if !reader.ReadBytes(&proofEnc, proofLength) { 70 | return nil, fmt.Errorf("invalid batch token response proof encoding") 71 | } 72 | 73 | proof := new(dleq.Proof) 74 | err := proof.UnmarshalBinary(group.Ristretto255, proofEnc) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | evaluation := &oprf.Evaluation{ 80 | Elements: elements, 81 | Proof: proof, 82 | } 83 | outputs, err := s.client.Finalize(s.verifier, evaluation) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | tokens := make([]tokens.Token, numElements) 89 | for i := 0; i < numElements; i++ { 90 | tokenData := append(s.tokenInputs[i], outputs[i]...) 91 | tokens[i], err = UnmarshalBatchedPrivateToken(tokenData) 92 | if err != nil { 93 | return nil, err 94 | } 95 | } 96 | 97 | return tokens, nil 98 | } 99 | 100 | // https://datatracker.ietf.org/doc/html/draft-robert-privacypass-batched-tokens-00#name-client-to-issuer-request 101 | func (c BatchedPrivateClient) CreateTokenRequest(challenge []byte, nonce [][]byte, tokenKeyID []byte, verificationKey *oprf.PublicKey) (BatchedPrivateTokenRequestState, error) { 102 | client := oprf.NewVerifiableClient(oprf.SuiteRistretto255, verificationKey) 103 | 104 | numTokens := len(nonce) 105 | tokenInputs := make([][]byte, numTokens) 106 | for i := 0; i < numTokens; i++ { 107 | context := sha256.Sum256(challenge) 108 | token := tokens.Token{ 109 | TokenType: BatchedPrivateTokenType, 110 | Nonce: nonce[i], 111 | Context: context[:], 112 | KeyID: tokenKeyID, 113 | Authenticator: nil, // No OPRF computed yet 114 | } 115 | tokenInput := token.AuthenticatorInput() 116 | tokenInputs[i] = make([]byte, len(tokenInput)) 117 | copy(tokenInputs[i], tokenInput) 118 | } 119 | 120 | finalizeData, evalRequest, err := client.Blind(tokenInputs) 121 | if err != nil { 122 | return BatchedPrivateTokenRequestState{}, err 123 | } 124 | 125 | encodedElements := make([][]byte, numTokens) 126 | for i := 0; i < numTokens; i++ { 127 | encRequest, err := evalRequest.Elements[i].MarshalBinaryCompress() 128 | if err != nil { 129 | return BatchedPrivateTokenRequestState{}, err 130 | } 131 | encodedElements[i] = make([]byte, len(encRequest)) 132 | copy(encodedElements[i], encRequest) 133 | } 134 | 135 | request := &BatchedPrivateTokenRequest{ 136 | TokenKeyID: tokenKeyID[len(tokenKeyID)-1], 137 | BlindedReq: encodedElements, 138 | } 139 | 140 | requestState := BatchedPrivateTokenRequestState{ 141 | tokenInputs: tokenInputs, 142 | request: request, 143 | client: client, 144 | verificationKey: verificationKey, 145 | verifier: finalizeData, 146 | } 147 | 148 | return requestState, nil 149 | } 150 | 151 | func (c BatchedPrivateClient) CreateTokenRequestWithBlinds(challenge []byte, nonces [][]byte, tokenKeyID []byte, verificationKey *oprf.PublicKey, encodedBlinds [][]byte) (BatchedPrivateTokenRequestState, error) { 152 | client := oprf.NewVerifiableClient(oprf.SuiteRistretto255, verificationKey) 153 | 154 | numTokens := len(nonces) 155 | tokenInputs := make([][]byte, numTokens) 156 | blinds := make([]group.Scalar, numTokens) 157 | for i := 0; i < numTokens; i++ { 158 | context := sha256.Sum256(challenge) 159 | token := tokens.Token{ 160 | TokenType: BatchedPrivateTokenType, 161 | Nonce: nonces[i], 162 | Context: context[:], 163 | KeyID: tokenKeyID, 164 | Authenticator: nil, // No OPRF computed yet 165 | } 166 | tokenInput := token.AuthenticatorInput() 167 | tokenInputs[i] = make([]byte, len(tokenInput)) 168 | copy(tokenInputs[i], tokenInput) 169 | 170 | blinds[i] = group.Ristretto255.NewScalar() 171 | err := blinds[i].UnmarshalBinary(encodedBlinds[i]) 172 | if err != nil { 173 | return BatchedPrivateTokenRequestState{}, err 174 | } 175 | } 176 | 177 | finalizeData, evalRequest, err := client.DeterministicBlind(tokenInputs, blinds) 178 | if err != nil { 179 | return BatchedPrivateTokenRequestState{}, err 180 | } 181 | 182 | encodedElements := make([][]byte, numTokens) 183 | for i := 0; i < numTokens; i++ { 184 | encRequest, err := evalRequest.Elements[i].MarshalBinaryCompress() 185 | if err != nil { 186 | return BatchedPrivateTokenRequestState{}, err 187 | } 188 | encodedElements[i] = make([]byte, len(encRequest)) 189 | copy(encodedElements[i], encRequest) 190 | } 191 | 192 | request := &BatchedPrivateTokenRequest{ 193 | TokenKeyID: tokenKeyID[len(tokenKeyID)-1], 194 | BlindedReq: encodedElements, 195 | } 196 | 197 | requestState := BatchedPrivateTokenRequestState{ 198 | tokenInputs: tokenInputs, 199 | request: request, 200 | client: client, 201 | verificationKey: verificationKey, 202 | verifier: finalizeData, 203 | } 204 | 205 | return requestState, nil 206 | } 207 | -------------------------------------------------------------------------------- /tokens/type5/issuer.go: -------------------------------------------------------------------------------- 1 | package type5 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "fmt" 7 | 8 | "github.com/cloudflare/circl/group" 9 | "github.com/cloudflare/circl/oprf" 10 | "github.com/cloudflare/pat-go/quicwire" 11 | "github.com/cloudflare/pat-go/tokens" 12 | "golang.org/x/crypto/cryptobyte" 13 | ) 14 | 15 | type BatchedPrivateIssuer struct { 16 | tokenKey *oprf.PrivateKey 17 | } 18 | 19 | func NewBatchedPrivateIssuer(key *oprf.PrivateKey) *BatchedPrivateIssuer { 20 | return &BatchedPrivateIssuer{ 21 | tokenKey: key, 22 | } 23 | } 24 | 25 | func (i *BatchedPrivateIssuer) TokenKey() *oprf.PublicKey { 26 | return i.tokenKey.Public() 27 | } 28 | 29 | func (i *BatchedPrivateIssuer) TokenKeyID() []byte { 30 | pkIEnc, err := i.tokenKey.Public().MarshalBinary() 31 | if err != nil { 32 | panic(err) 33 | } 34 | keyID := sha256.Sum256(pkIEnc) 35 | return keyID[:] 36 | } 37 | 38 | func (i BatchedPrivateIssuer) Evaluate(req *BatchedPrivateTokenRequest) ([]byte, error) { 39 | server := oprf.NewVerifiableServer(oprf.SuiteRistretto255, i.tokenKey) 40 | 41 | elementLength := int(oprf.SuiteRistretto255.Group().Params().CompressedElementLength) 42 | numRequests := len(req.BlindedReq) 43 | elements := make([]group.Element, numRequests) 44 | for i := 0; i < numRequests; i++ { 45 | elements[i] = group.Ristretto255.NewElement() 46 | err := elements[i].UnmarshalBinary(req.BlindedReq[i]) 47 | if err != nil { 48 | return nil, err 49 | } 50 | } 51 | 52 | // Create the batch evaluation request 53 | evalRequest := &oprf.EvaluationRequest{ 54 | Elements: elements, 55 | } 56 | 57 | // Evaluate the input 58 | evaluation, err := server.Evaluate(evalRequest) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | // Build TokenResponse 64 | encodedElements := make([][]byte, numRequests) 65 | for i := 0; i < numRequests; i++ { 66 | encEvaluatedElement, err := evaluation.Elements[i].MarshalBinaryCompress() 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | encodedElements[i] = make([]byte, elementLength) 72 | copy(encodedElements[i], encEvaluatedElement) 73 | } 74 | 75 | encProof, err := evaluation.Proof.MarshalBinary() 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | // Build RFC 9000 varint 81 | bElmts := cryptobyte.NewBuilder(nil) 82 | for i := 0; i < numRequests; i++ { 83 | bElmts.AddBytes(encodedElements[i]) 84 | } 85 | rawBElmts := bElmts.BytesOrPanic() 86 | l := quicwire.AppendVarint([]byte{}, uint64(len(rawBElmts))) 87 | 88 | b := cryptobyte.NewBuilder(nil) 89 | b.AddBytes(l) 90 | b.AddBytes(rawBElmts) 91 | b.AddBytes(encProof) 92 | 93 | return b.BytesOrPanic(), nil 94 | } 95 | 96 | func (i BatchedPrivateIssuer) Type() uint16 { 97 | return BatchedPrivateTokenType 98 | } 99 | 100 | func (i BatchedPrivateIssuer) Verify(token tokens.Token) error { 101 | server := oprf.NewVerifiableServer(oprf.SuiteRistretto255, i.tokenKey) 102 | 103 | tokenInput := token.AuthenticatorInput() 104 | output, err := server.FullEvaluate(tokenInput) 105 | if err != nil { 106 | return err 107 | } 108 | if !bytes.Equal(output, token.Authenticator) { 109 | return fmt.Errorf("token authentication mismatch") 110 | } 111 | 112 | return nil 113 | } 114 | -------------------------------------------------------------------------------- /tokens/type5/token.go: -------------------------------------------------------------------------------- 1 | package type5 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cloudflare/pat-go/tokens" 7 | "golang.org/x/crypto/cryptobyte" 8 | ) 9 | 10 | func UnmarshalBatchedPrivateToken(data []byte) (tokens.Token, error) { 11 | s := cryptobyte.String(data) 12 | 13 | token := tokens.Token{} 14 | if !s.ReadUint16(&token.TokenType) || 15 | !s.ReadBytes(&token.Nonce, 32) || 16 | !s.ReadBytes(&token.Context, 32) || 17 | !s.ReadBytes(&token.KeyID, 32) || 18 | !s.ReadBytes(&token.Authenticator, 64) { 19 | return tokens.Token{}, fmt.Errorf("invalid Token encoding") 20 | } 21 | 22 | return token, nil 23 | } 24 | -------------------------------------------------------------------------------- /tokens/type5/token_request.go: -------------------------------------------------------------------------------- 1 | package type5 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/cloudflare/pat-go/quicwire" 7 | "golang.org/x/crypto/cryptobyte" 8 | ) 9 | 10 | const BatchedPrivateTokenType = uint16(0x0005) 11 | 12 | type BatchedPrivateTokenRequest struct { 13 | raw []byte 14 | TokenKeyID uint8 15 | BlindedReq [][]byte 16 | } 17 | 18 | func (r *BatchedPrivateTokenRequest) TruncatedTokenKeyID() uint8 { 19 | return r.TokenKeyID 20 | } 21 | 22 | func (r *BatchedPrivateTokenRequest) Type() uint16 { 23 | return BatchedPrivateTokenType 24 | } 25 | 26 | func (r BatchedPrivateTokenRequest) Equal(r2 BatchedPrivateTokenRequest) bool { 27 | if r.TokenKeyID == r2.TokenKeyID && len(r.BlindedReq) == len(r2.BlindedReq) { 28 | equal := true 29 | for i := 0; i < len(r.BlindedReq); i++ { 30 | if !bytes.Equal(r.BlindedReq[i], r2.BlindedReq[i]) { 31 | equal = false 32 | break 33 | } 34 | } 35 | return equal 36 | } 37 | return false 38 | } 39 | 40 | func (r *BatchedPrivateTokenRequest) Marshal() []byte { 41 | if r.raw != nil { 42 | return r.raw 43 | } 44 | 45 | b := cryptobyte.NewBuilder(nil) 46 | b.AddUint16(BatchedPrivateTokenType) 47 | b.AddUint8(r.TokenKeyID) 48 | 49 | bElmts := cryptobyte.NewBuilder(nil) 50 | for i := 0; i < len(r.BlindedReq); i++ { 51 | bElmts.AddBytes(r.BlindedReq[i]) 52 | } 53 | 54 | rawBElements := bElmts.BytesOrPanic() 55 | l := quicwire.AppendVarint([]byte{}, uint64(len(rawBElements))) 56 | 57 | b.AddBytes(l) 58 | b.AddBytes(rawBElements) 59 | 60 | r.raw = b.BytesOrPanic() 61 | return r.raw 62 | } 63 | 64 | func (r *BatchedPrivateTokenRequest) Unmarshal(data []byte) bool { 65 | s := cryptobyte.String(data) 66 | 67 | var tokenType uint16 68 | if !s.ReadUint16(&tokenType) || 69 | tokenType != BatchedPrivateTokenType || 70 | !s.ReadUint8(&r.TokenKeyID) { 71 | return false 72 | } 73 | 74 | l, offset := quicwire.ConsumeVarint(data[3:]) 75 | s.Skip(offset) 76 | blindedRequests := make([]byte, l) 77 | if !s.ReadBytes(&blindedRequests, len(blindedRequests)) { 78 | return false 79 | } 80 | if len(blindedRequests)%32 != 0 { 81 | return false 82 | } 83 | 84 | elementCount := len(blindedRequests) / 32 85 | r.BlindedReq = make([][]byte, elementCount) 86 | for i := 0; i < elementCount; i++ { 87 | r.BlindedReq[i] = make([]byte, 32) 88 | copy(r.BlindedReq[i], blindedRequests[(32*i):]) 89 | } 90 | 91 | return true 92 | } 93 | -------------------------------------------------------------------------------- /util/codec.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/rsa" 5 | "encoding/hex" 6 | "fmt" 7 | "io" 8 | "testing" 9 | ) 10 | 11 | // ///// 12 | // Infallible Serialize / Deserialize 13 | func fatalOnError(t testing.TB, err error, msg string) { 14 | realMsg := fmt.Sprintf("%s: %v", msg, err) 15 | if err != nil { 16 | if t != nil { 17 | t.Fatal(realMsg) 18 | } else { 19 | panic(realMsg) 20 | } 21 | } 22 | } 23 | 24 | func MustRead(t testing.TB, r io.Reader, b []byte) { 25 | _, err := r.Read(b) 26 | fatalOnError(t, err, "read failed") 27 | } 28 | 29 | func MustUnhex(t *testing.T, h string) []byte { 30 | out, err := hex.DecodeString(h) 31 | fatalOnError(t, err, "Unhex failed") 32 | return out 33 | } 34 | 35 | func MustUnhexList(t *testing.T, h []string) [][]byte { 36 | out := make([][]byte, len(h)) 37 | for i := 0; i < len(h); i++ { 38 | out[i] = MustUnhex(t, h[i]) 39 | } 40 | return out 41 | } 42 | 43 | func MustHex(d []byte) string { 44 | return hex.EncodeToString(d) 45 | } 46 | 47 | func MustHexList(d [][]byte) []string { 48 | hexValues := make([]string, len(d)) 49 | for i := 0; i < len(d); i++ { 50 | hexValues[i] = hex.EncodeToString(d[i]) 51 | } 52 | return hexValues 53 | } 54 | 55 | func MustMarshalPrivateKey(key *rsa.PrivateKey) []byte { 56 | encodedKey, err := marshalTokenPrivateKey(key) 57 | if err != nil { 58 | panic(err) 59 | } 60 | return encodedKey 61 | } 62 | 63 | func MustUnmarshalPrivateKey(data []byte) *rsa.PrivateKey { 64 | privateKey, err := unmarshalTokenPrivateKey(data) 65 | if err != nil { 66 | panic(err) 67 | } 68 | return privateKey 69 | } 70 | 71 | func MustMarshalPublicKey(key *rsa.PublicKey) []byte { 72 | encodedKey, err := MarshalTokenKeyPSSOID(key) 73 | if err != nil { 74 | panic(err) 75 | } 76 | return encodedKey 77 | } 78 | 79 | func MustUnmarshalPublicKey(data []byte) *rsa.PublicKey { 80 | publicKey, err := UnmarshalTokenKey(data) 81 | if err != nil { 82 | panic(err) 83 | } 84 | return publicKey 85 | } 86 | -------------------------------------------------------------------------------- /util/x509util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/x509" 6 | "encoding/asn1" 7 | "encoding/pem" 8 | "errors" 9 | "fmt" 10 | "math/big" 11 | 12 | "github.com/cloudflare/circl/oprf" 13 | "golang.org/x/crypto/cryptobyte" 14 | cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" 15 | ) 16 | 17 | // pkcs1PublicKey reflects the ASN.1 structure of a PKCS #1 public key. 18 | type pkcs1PSSPublicKey struct { 19 | N *big.Int 20 | E int 21 | } 22 | 23 | var ( 24 | oidPublicKeyRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} 25 | oidSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} 26 | oidPKCS1MGF = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8} 27 | ) 28 | 29 | func marshalTokenPrivateKey(key *rsa.PrivateKey) ([]byte, error) { 30 | der, err := x509.MarshalPKCS8PrivateKey(key) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | block := &pem.Block{ 36 | Type: "PRIVATE KEY", 37 | Bytes: der, 38 | } 39 | 40 | return pem.EncodeToMemory(block), nil 41 | } 42 | 43 | func unmarshalTokenPrivateKey(data []byte) (*rsa.PrivateKey, error) { 44 | block, _ := pem.Decode(data) 45 | if block == nil || block.Type != "PRIVATE KEY" { 46 | return nil, fmt.Errorf("invalid private key encoding") 47 | } 48 | 49 | privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | return privateKey.(*rsa.PrivateKey), nil 55 | } 56 | 57 | func MarshalTokenKeyPSSOID(key *rsa.PublicKey) ([]byte, error) { 58 | publicKeyBytes, err := asn1.Marshal(pkcs1PSSPublicKey{ 59 | N: key.N, 60 | E: key.E, 61 | }) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | var b cryptobyte.Builder 67 | b.AddASN1(cryptobyte_asn1.SEQUENCE.Constructed(), func(b *cryptobyte.Builder) { 68 | b.AddASN1(cryptobyte_asn1.SEQUENCE.Constructed(), func(b *cryptobyte.Builder) { 69 | b.AddASN1ObjectIdentifier(oidPublicKeyRSAPSS) 70 | b.AddASN1(cryptobyte_asn1.SEQUENCE.Constructed(), func(b *cryptobyte.Builder) { 71 | b.AddASN1(cryptobyte_asn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { 72 | b.AddASN1(cryptobyte_asn1.SEQUENCE.Constructed(), func(b *cryptobyte.Builder) { 73 | b.AddASN1ObjectIdentifier(oidSHA384) 74 | }) 75 | }) 76 | b.AddASN1(cryptobyte_asn1.Tag(1).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { 77 | b.AddASN1(cryptobyte_asn1.SEQUENCE.Constructed(), func(b *cryptobyte.Builder) { 78 | b.AddASN1ObjectIdentifier(oidPKCS1MGF) 79 | b.AddASN1(cryptobyte_asn1.SEQUENCE.Constructed(), func(b *cryptobyte.Builder) { 80 | b.AddASN1ObjectIdentifier(oidSHA384) 81 | }) 82 | }) 83 | }) 84 | b.AddASN1(cryptobyte_asn1.Tag(2).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { 85 | b.AddASN1Int64(48) 86 | }) 87 | }) 88 | }) 89 | b.AddASN1BitString(publicKeyBytes) 90 | }) 91 | 92 | return b.BytesOrPanic(), nil 93 | } 94 | 95 | func MarshalTokenKeyRSAEncryptionOID(key *rsa.PublicKey) ([]byte, error) { 96 | return x509.MarshalPKIXPublicKey(key) 97 | } 98 | 99 | func MarshalTokenKey(key *rsa.PublicKey, legacyFormat bool) ([]byte, error) { 100 | if legacyFormat { 101 | return MarshalTokenKeyRSAEncryptionOID(key) 102 | } else { 103 | return MarshalTokenKeyPSSOID(key) 104 | } 105 | } 106 | 107 | func UnmarshalTokenKey(data []byte) (*rsa.PublicKey, error) { 108 | s := cryptobyte.String(data) 109 | 110 | var sequenceString cryptobyte.String 111 | if !s.ReadASN1(&sequenceString, cryptobyte_asn1.SEQUENCE.Constructed()) { 112 | return nil, fmt.Errorf("invalid SPKI token key encoding (failed reading outer sequence)") 113 | } 114 | 115 | var paramsString cryptobyte.String 116 | if !sequenceString.ReadASN1(¶msString, cryptobyte_asn1.SEQUENCE.Constructed()) { 117 | return nil, fmt.Errorf("invalid SPKI token key encoding (failed reading parameters)") 118 | } 119 | 120 | var publicKeyString asn1.BitString 121 | if !sequenceString.ReadASN1BitString(&publicKeyString) { 122 | return nil, fmt.Errorf("invalid SPKI token key encoding (failed reading public key)") 123 | } 124 | 125 | der := cryptobyte.String(publicKeyString.RightAlign()) 126 | p := &pkcs1PSSPublicKey{N: new(big.Int)} 127 | if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { 128 | return nil, errors.New("x509: invalid RSA public key") 129 | } 130 | if !der.ReadASN1Integer(p.N) { 131 | return nil, errors.New("x509: invalid RSA modulus") 132 | } 133 | if !der.ReadASN1Integer(&p.E) { 134 | return nil, errors.New("x509: invalid RSA public exponent") 135 | } 136 | 137 | key := new(rsa.PublicKey) // Everything else is uninitialized 138 | key.N = p.N 139 | key.E = p.E 140 | 141 | return key, nil 142 | } 143 | 144 | func MustMarshalPrivateOPRFKey(key *oprf.PrivateKey) []byte { 145 | encodedKey, err := key.MarshalBinary() 146 | if err != nil { 147 | panic(err) 148 | } 149 | return encodedKey 150 | } 151 | 152 | func MustUnmarshalPrivateOPRFKey(data []byte) *oprf.PrivateKey { 153 | key := new(oprf.PrivateKey) 154 | err := key.UnmarshalBinary(oprf.SuiteP384, data) 155 | if err != nil { 156 | panic(err) 157 | } 158 | return key 159 | } 160 | 161 | func MustUnmarshalBatchedPrivateOPRFKey(data []byte) *oprf.PrivateKey { 162 | key := new(oprf.PrivateKey) 163 | err := key.UnmarshalBinary(oprf.SuiteRistretto255, data) 164 | if err != nil { 165 | panic(err) 166 | } 167 | return key 168 | } 169 | 170 | func MustMarshalPublicOPRFKey(key *oprf.PublicKey) []byte { 171 | encodedKey, err := key.MarshalBinary() 172 | if err != nil { 173 | panic(err) 174 | } 175 | return encodedKey 176 | } 177 | 178 | func MustUnmarshalPublicOPRFKey(data []byte) *oprf.PublicKey { 179 | key := new(oprf.PublicKey) 180 | err := key.UnmarshalBinary(oprf.SuiteP384, data) 181 | if err != nil { 182 | panic(err) 183 | } 184 | return key 185 | } 186 | 187 | func MustUnmarshalBatchedPublicOPRFKey(data []byte) *oprf.PublicKey { 188 | key := new(oprf.PublicKey) 189 | err := key.UnmarshalBinary(oprf.SuiteRistretto255, data) 190 | if err != nil { 191 | panic(err) 192 | } 193 | return key 194 | } 195 | -------------------------------------------------------------------------------- /util/x509util_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/x509" 6 | "encoding/pem" 7 | "testing" 8 | ) 9 | 10 | // 2048-bit RSA private key 11 | const testTokenPrivateKey = ` 12 | -----BEGIN RSA PRIVATE KEY----- 13 | MIIEowIBAAKCAQEAyxrta2qV9bHOATpM/KsluUsuZKIwNOQlCn6rQ8DfOowSmTrx 14 | KxEZCNS0cb7DHUtsmtnN2pBhKi7pA1I+beWiJNawLwnlw3TQz+Adj1KcUAp4ovZ5 15 | CPpoK1orQwyB6vGvcte155T8mKMTknaHl1fORTtSbvm/bOuZl5uEI7kPRGGiKvN6 16 | qwz1cz91l6vkTTHHMttooYHGy75gfYwOUuBlX9mZbcWE7KC+h6+814ozfRex26no 17 | KLvYHikTFxROf/ifVWGXCbCWy7nqR0zq0mTCBz/kl0DAHwDhCRBgZpg9IeX4Pwhu 18 | LoI8h5zUPO9wDSo1Kpur1hLQPK0C2xNLfiJaXwIDAQABAoIBAC8wm3c4tYz3efDJ 19 | Ffgi38n0kNvq3x5636xXj/1XA8a7otqdWklyWIm3uhEvjG/zBVHZRz4AC8NcUOFn 20 | q3+nOgwrIZZcS1klfBrAbL3PKOhj9nGOqMKQQ8HG2oRilJD9BJG/UtFyyVnBkhuW 21 | lJxyV0e4p8eHGZX6C56xEHuoVMbDKm9HR8XRwwTHRn1VsICqIzo6Uv/fJhFMu1Qf 22 | +mtpa3oJb43P9pygirWO+w+3U6pRhccwAWlrvOjAmeP0Ndy7/gXn26rSPbKmWcI6 23 | 3VIUB/FQsa8tkFTEFkIp1oQLejKk+EgUk66JWc8K6o3vDDyfdbmjTHVxi3ByyNur 24 | F87+ykkCgYEA73MLD1FLwPWdmV/V+ZiMTEwTXRBc1W1D7iigNclp9VDAzXFI6ofs 25 | 3v+5N8hcZIdEBd9W6utHi/dBiEogDuSjljPRCqPsQENm2itTHzmNRvvI8wV1KQbP 26 | eJOd0vPMl5iup8nYL+9ASfGYeX5FKlttKEm4ZIY0XUsx9pERoq4PlEsCgYEA2STJ 27 | 68thMWv9xKuz26LMQDzImJ5OSQD0hsts9Ge01G/rh0Dv/sTzO5wtLsiyDA/ZWkzB 28 | 8J+rO/y2xqBD9VkYKaGB/wdeJP0Z+n7sETetiKPbXPfgAi7VAe77Rmst/oEcGLUg 29 | tm+XnfJSInoLU5HmtIdLg0kcQLVbN5+ZMmtkPb0CgYBSbhczmbfrYGJ1p0FBIFvD 30 | 9DiCRBzBOFE3TnMAsSqx0a/dyY7hdhN8HSqE4ouz68DmCKGiU4aYz3CW23W3ysvp 31 | 7EKdWBr/cHSazGlcCXLyKcFer9VKX1bS2nZtZZJb6arOhjTPI5zNF8d2o5pp33lv 32 | chlxOaYTK8yyZfRdPXCNiwKBgQDV77oFV66dm7E9aJHerkmgbIKSYz3sDUXd3GSv 33 | c9Gkj9Q0wNTzZKXkMB4P/un0mlTh88gMQ7PYeUa28UWjX7E/qwFB+8dUmA1VUGFT 34 | IVEW06GXuhv46p0wt3zXx1dcbWX6LdJaDB4MHqevkiDAqHntmXLbmVd9pXCGn/a2 35 | xznO3QKBgHkPJPEiCzRugzgN9UxOT5tNQCSGMOwJUd7qP0TWgvsWHT1N07JLgC8c 36 | Yg0f1rCxEAQo5BVppiQFp0FA7W52DUnMEfBtiehZ6xArW7crO91gFRqKBWZ3Jjyz 37 | /JcS8m5UgQxC8mmb/2wLD5TDvWw+XCfjUgWmvqIi5dcJgmuTAn5X 38 | -----END RSA PRIVATE KEY-----` 39 | 40 | func loadPrivateKey(t *testing.T) *rsa.PrivateKey { 41 | block, _ := pem.Decode([]byte(testTokenPrivateKey)) 42 | if block == nil || block.Type != "RSA PRIVATE KEY" { 43 | t.Fatal("PEM private key decoding failed") 44 | } 45 | 46 | privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | return privateKey 52 | } 53 | 54 | func TestEncode(t *testing.T) { 55 | rsaPrivateKey := loadPrivateKey(t) 56 | publicKey := rsaPrivateKey.PublicKey 57 | 58 | publicKeyEnc, err := MarshalTokenKey(&publicKey, false) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | 63 | recoveredKey, err := UnmarshalTokenKey(publicKeyEnc) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | 68 | legacyEnc, err := MarshalTokenKey(&publicKey, true) 69 | if err != nil { 70 | t.Fatal(err) 71 | } 72 | t.Log(string(legacyEnc)) 73 | 74 | if publicKey.N.Cmp(recoveredKey.N) != 0 { 75 | t.Fatal("N mismatch") 76 | } 77 | if publicKey.E != recoveredKey.E { 78 | t.Fatal("E mismatch") 79 | } 80 | } 81 | --------------------------------------------------------------------------------