├── .github ├── actions-rs │ ├── grcov.yml │ └── toolchain.yml └── workflows │ └── rust.yml ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── aeonflux_benchmarks.rs └── src ├── amacs.rs ├── credential.rs ├── encoding.rs ├── errors.rs ├── issuer.rs ├── lib.rs ├── macros.rs ├── nizk ├── encryption.rs ├── issuance.rs ├── mod.rs └── presentation.rs ├── parameters.rs ├── prelude.rs ├── symmetric.rs └── user.rs /.github/actions-rs/grcov.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: Code Coverage 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: actions-rs/cargo@v1 11 | with: 12 | command: clean 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: nightly 16 | override: true 17 | - uses: actions-rs/cargo@v1 18 | with: 19 | command: test 20 | args: --all-features --no-fail-fast 21 | env: 22 | CARGO_INCREMENTAL: '0' 23 | RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' 24 | - uses: actions-rs/grcov@v0.1 25 | - name: Coveralls upload 26 | uses: coverallsapp/github-action@master 27 | with: 28 | github-token: ${{ secrets.GITHUB_TOKEN }} 29 | path-to-lcov: ${{ steps.coverage.outputs.report }} 30 | -------------------------------------------------------------------------------- /.github/actions-rs/toolchain.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: build 4 | 5 | jobs: 6 | check: 7 | name: Rust project 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Install latest nightly 12 | uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: nightly 15 | override: true 16 | components: rustfmt, clippy 17 | 18 | # `cargo check` command here will use installed `nightly` 19 | # as it is set as an "override" for current directory 20 | 21 | - name: Run cargo check 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: check 25 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Build 17 | run: cargo build --verbose 18 | - name: Run tests 19 | run: cargo test --verbose 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | *~ 6 | *.swp 7 | \#* 8 | .#* 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - nightly 5 | 6 | env: 7 | # Tests the u32 backend 8 | - TEST_COMMAND=test EXTRA_FLAGS='--no-default-features' FEATURES='std u32_backend' 9 | # Tests the u64 backend 10 | - TEST_COMMAND=test EXTRA_FLAGS='--no-default-features' FEATURES='std u64_backend' 11 | # Tests the simd backend 12 | - TEST_COMMAND=test EXTRA_FLAGS='--no-default-features' FEATURES='std simd_backend' 13 | # Tests serde support and default feature selection 14 | #- TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='serde' 15 | # Tests no_std+alloc usage using the most embedded-friendly backend 16 | - TEST_COMMAND=test EXTRA_FLAGS='--lib --no-default-features' FEATURES='alloc u32_backend' 17 | 18 | script: 19 | - cargo $TEST_COMMAND --features="$FEATURES" $EXTRA_FLAGS 20 | 21 | notifications: 22 | slack: 23 | rooms: 24 | - dalek-cryptography:Xxv9WotKYWdSoKlgKNqXiHoD#dalek-bots 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aeonflux" 3 | version = "0.2.0" 4 | authors = ["isis lovecruft "] 5 | edition = "2018" 6 | readme = "README.md" 7 | license = "BSD-3-Clause" 8 | categories = ["cryptography", "no-std"] 9 | keywords = ["MAC", "zero-knowledge", "anonymous", "credential", "algebraic-MAC"] 10 | description = "Composable, lightweight, fast attribute-based anonymous credentials with infinite (aeon) rerandomised (flux) presentations using algebraic message authentication codes (aMACs), symmetric verifiable encryption, and non-interactive zero-knowledge proofs." 11 | exclude = [ 12 | "**/.gitignore", 13 | ".gitignore", 14 | ".travis.yml", 15 | ] 16 | autobenches = false 17 | 18 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 19 | 20 | [lib] 21 | name = "aeonflux" 22 | #crate-type = ["staticlib", "rlib", "cdylib"] 23 | 24 | # Heck yeah, XSS As A Service. 25 | [package.metadata.docs.rs] 26 | rustdoc-args = ["--html-in-header", ".cargo/registry/src/github.com-1ecc6299db9ec823/curve25519-dalek-0.13.2/rustdoc-include-katex-header.html"] 27 | features = ["nightly"] 28 | 29 | [[bench]] 30 | name = "aeonflux_benchmarks" 31 | harness = false 32 | 33 | [dependencies] 34 | curve25519-dalek = { version = "2", default-features = false, features = ["serde"] } 35 | rand_core = { version = "0.5", default-features = false } 36 | serde = { version = "1" } 37 | sha2 = { version = "0.8", default-features = false } 38 | subtle = { version = "2" } 39 | zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] } 40 | zkp = { version = "0.7", default-features = false } 41 | 42 | [dev-dependencies] 43 | criterion = { version = "0.3" } 44 | curve25519-dalek = { version = "2", default-features = false } 45 | rand = { version = "0.7" } 46 | 47 | [features] 48 | default = [ "std", "nightly", "u64_backend" ] 49 | std = [ "curve25519-dalek/std", "sha2/std" ] 50 | nightly = [ "curve25519-dalek/nightly", "subtle/nightly", "zkp/nightly" ] 51 | alloc = [ "curve25519-dalek/alloc" ] 52 | debug-transcript = [ "zkp/debug-transcript" ] 53 | u32_backend = [ "curve25519-dalek/u32_backend", "zkp/u32_backend" ] 54 | u64_backend = [ "curve25519-dalek/u64_backend", "zkp/u64_backend" ] 55 | simd_backend = [ "curve25519-dalek/simd_backend", "zkp/simd_backend" ] 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 The Brave Authors. All rights reserved. 2 | Copyright (c) 2018-2020 isis agora lovecruft. All rights reserved. 3 | Copyright (c) 2018 Signal Foundation. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 23 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 26 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # aeonflux 3 | 4 | Composable, lightweight, fast attribute-based anonymous credentials with 5 | infinite (aeon) rerandomised (flux) presentations using algebraic message 6 | authentication codes (aMACs), symmetric verifiable encryption, and 7 | non-interactive zero-knowledge proofs. 8 | 9 | These are largely based on the credentials in 10 | [2019/1416](https://eprint.iacr.org/2019/1416). 11 | 12 | ## Features 13 | 14 | Currently, we only support revealed credential issuance; that is, a user reveals 15 | all the attributes on their credentials to the issuer when requesting a new 16 | credential. When presenting said credential afterwards, attributes may be 17 | either hidden or revealed. 18 | 19 | Credential attributes may be either scalars (integers modulo the group order, a large 20 | prime) or group elements. This library provides a way to encode arbitrary byte 21 | arrays to group elements---which may then be encrypted and decrypted---in an 22 | invertible manner, such that arbitrary strings can be stored as attributes. 23 | 24 | Group element attributes which are hidden upon credential presentation are 25 | symmetrically encrypted, such that the user can prove to the issuer their 26 | correctness in zero-knowledge, while sharing the symmetric decryption key with 27 | other third parties. This allows for uses such as the issuer performing some 28 | external verification of personally identifiable information, such as an email 29 | address or a phone number, when the user requests a new credential, without 30 | the issuer being able to track this data afterwards; however the user can still 31 | share the data with other users. Another example use case is storing a shared 32 | key, in a way that all users who have access to the key can prove knowledge of 33 | it in zero-knowledge later, thus allowing for arbitrary namespacing and/or 34 | access control lists. 35 | 36 | ## Warning 37 | 38 | While this library was created by a cryptographer, it hasn't yet been reviewed 39 | by any other cryptographers. Additionally, while I may be _a_ cryptographer, 40 | I'm likely not _your_ cryptographer. Use at your own risk. 41 | 42 | ## Usage 43 | 44 | ```rust 45 | extern crate aeonflux; 46 | extern crate curve25519_dalek; 47 | extern crate rand; 48 | 49 | use aeonflux::issuer::Issuer; 50 | use aeonflux::parameters::IssuerParameters; 51 | use aeonflux::parameters::SystemParameters; 52 | use aeonflux::symmetric::Plaintext; 53 | use aeonflux::symmetric::Keypair as SymmetricKeypair; 54 | use aeonflux::user::CredentialRequestConstructor; 55 | 56 | use curve25519_dalek::ristretto::RistrettoPoint; 57 | use curve25519_dalek::scalar::Scalar; 58 | 59 | use rand::thread_rng; 60 | 61 | // First we set up an anonymous credential issuer. We have to specify 62 | // the number of attributes the credentials will have (here, 4), 63 | // but not their type. 64 | let mut rng = thread_rng(); 65 | let system_parameters = SystemParameters::generate(&mut rng, 4).unwrap(); 66 | let issuer = Issuer::new(&system_parameters, &mut rng); 67 | 68 | // The issuer then publishes the `system_parameters` and the 69 | // `issuer.issuer_parameters` somewhere publicly where users may obtain them. 70 | let issuer_parameters = issuer.issuer_parameters.clone(); 71 | 72 | // A user creates a request for a new credential with some revealed 73 | // attributes and sends it to the issuer. 74 | let mut request = CredentialRequestConstructor::new(&system_parameters); 75 | 76 | // Revealed scalars and revealed points count for one attribute each. 77 | request.append_revealed_scalar(Scalar::random(&mut rng)); 78 | request.append_revealed_scalar(Scalar::random(&mut rng)); 79 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 80 | 81 | // Every 30 bytes of message uses one plaintext attribute. This plaintext 82 | // message is exactly 30 bytes, so it accounts for one attribute total on the 83 | // credential. If it were one byte longer, it would account for two attributes. 84 | let plaintexts = request.append_plaintext(&String::from("This is a tsunami alert test..").into_bytes()); 85 | 86 | // Hence we have 4 total attributes, as specified in the generation of the 87 | // `system_parameters` above. 88 | let credential_request = request.finish(); 89 | 90 | // The user now sends `credential_request` to the issuer, who may issue the 91 | // credential, if seen fit to do so. 92 | let issuance = issuer.issue(credential_request, &mut rng).unwrap(); 93 | 94 | // The issuer sends the `credential_issuance` to the user, who verifies the 95 | // contained proof of correct issuance. 96 | let mut credential = issuance.verify(&system_parameters, &issuer_parameters).unwrap(); 97 | 98 | // Optionally, upon showing the credential, the user can create a 99 | // keypair and encrypt some or all of the attributes. The master secret 100 | // can be stored to regenerate the full keypair later on. Encryption 101 | // keys can be rotated to rerandomise the encrypted attributes. 102 | let (keypair, master_secret) = SymmetricKeypair::generate(&system_parameters, &mut rng); 103 | 104 | // For this presentation, we're going to encrypt the plaintext (the fourth attribute) 105 | // and also mark the first attribute, a scalar, as being hidden. Remember that 106 | // indexing starts at 0. 107 | credential.hide_attribute(0); 108 | credential.hide_attribute(3); 109 | 110 | // The user now creates a presentation of the credential to give to the issuer. 111 | let presentation = credential.show(&system_parameters, &issuer_parameters, Some(&keypair), &mut rng).unwrap(); 112 | 113 | // The user then sends this presentation to the issuer, who verifies it. 114 | let verification = issuer.verify(&presentation); 115 | 116 | assert!(verification.is_ok()); 117 | ``` 118 | 119 | # TODO 120 | 121 | * [] Add DLEQ proofs between the C_y commitments to hidden group attributes and 122 | the corresponding proofs of encryption. 123 | -------------------------------------------------------------------------------- /benches/aeonflux_benchmarks.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::vec::Vec; 3 | 4 | extern crate rand; 5 | 6 | use rand::thread_rng; 7 | 8 | #[macro_use] 9 | extern crate criterion; 10 | 11 | use criterion::Criterion; 12 | 13 | extern crate aeonflux; 14 | 15 | use aeonflux::amacs::Attribute; 16 | use aeonflux::amacs::SecretKey; 17 | use aeonflux::issuer::Issuer; 18 | use aeonflux::nizk::ProofOfValidCredential; 19 | use aeonflux::parameters::IssuerParameters; 20 | use aeonflux::parameters::SystemParameters; 21 | use aeonflux::symmetric::Plaintext; 22 | use aeonflux::symmetric::Keypair as SymmetricKeypair; 23 | 24 | extern crate curve25519_dalek; 25 | 26 | use curve25519_dalek::ristretto::RistrettoPoint; 27 | use curve25519_dalek::scalar::Scalar; 28 | 29 | mod proof_of_credential_benches { 30 | use super::*; 31 | 32 | fn creation_1(c: &mut Criterion) { 33 | let mut rng = thread_rng(); 34 | let system_parameters = SystemParameters::generate(&mut rng, 1).unwrap(); 35 | let amacs_key = SecretKey::generate(&mut rng, &system_parameters); 36 | let issuer_parameters = IssuerParameters::generate(&system_parameters, &amacs_key); 37 | let issuer = Issuer::new(&system_parameters, &issuer_parameters, &amacs_key); 38 | let plaintext: Plaintext = b"This is a tsunami alert test..".into(); 39 | 40 | let mut attributes = Vec::new(); 41 | 42 | attributes.push(Attribute::SecretPoint(plaintext)); 43 | 44 | let credential = issuer.issue(attributes, &mut rng).unwrap(); 45 | let (keypair, _) = SymmetricKeypair::generate(&system_parameters, &mut rng); 46 | 47 | c.bench_function("Proof-of-Valid-Credential with 1 attribute Creation", |b| { 48 | b.iter(|| ProofOfValidCredential::prove(&system_parameters, &issuer_parameters, &credential, Some(&keypair), &mut rng)); 49 | }); 50 | } 51 | 52 | fn verification_1(c: &mut Criterion) { 53 | let mut rng = thread_rng(); 54 | let system_parameters = SystemParameters::generate(&mut rng, 1).unwrap(); 55 | let amacs_key = SecretKey::generate(&mut rng, &system_parameters); 56 | let issuer_parameters = IssuerParameters::generate(&system_parameters, &amacs_key); 57 | let issuer = Issuer::new(&system_parameters, &issuer_parameters, &amacs_key); 58 | let plaintext: Plaintext = b"This is a tsunami alert test..".into(); 59 | 60 | let mut attributes = Vec::new(); 61 | 62 | attributes.push(Attribute::SecretPoint(plaintext)); 63 | 64 | let credential = issuer.issue(attributes, &mut rng).unwrap(); 65 | let (keypair, _) = SymmetricKeypair::generate(&system_parameters, &mut rng); 66 | let proof = ProofOfValidCredential::prove(&system_parameters, &issuer_parameters, &credential, Some(&keypair), &mut rng).unwrap(); 67 | 68 | c.bench_function("Proof-of-Valid-Credential with 1 attribute Verification", |b| { 69 | b.iter(|| proof.verify(&issuer, &credential)); 70 | }); 71 | } 72 | 73 | fn creation_8(c: &mut Criterion) { 74 | let mut rng = thread_rng(); 75 | let system_parameters = SystemParameters::generate(&mut rng, 8).unwrap(); 76 | let amacs_key = SecretKey::generate(&mut rng, &system_parameters); 77 | let issuer_parameters = IssuerParameters::generate(&system_parameters, &amacs_key); 78 | let issuer = Issuer::new(&system_parameters, &issuer_parameters, &amacs_key); 79 | let plaintext: Plaintext = b"This is a tsunami alert test..".into(); 80 | 81 | let mut attributes = Vec::new(); 82 | 83 | attributes.push(Attribute::SecretPoint(plaintext)); 84 | attributes.push(Attribute::SecretScalar(Scalar::random(&mut rng))); 85 | attributes.push(Attribute::SecretScalar(Scalar::random(&mut rng))); 86 | attributes.push(Attribute::PublicScalar(Scalar::random(&mut rng))); 87 | attributes.push(Attribute::PublicPoint(RistrettoPoint::random(&mut rng))); 88 | attributes.push(Attribute::SecretScalar(Scalar::random(&mut rng))); 89 | attributes.push(Attribute::PublicScalar(Scalar::random(&mut rng))); 90 | attributes.push(Attribute::PublicPoint(RistrettoPoint::random(&mut rng))); 91 | 92 | let credential = issuer.issue(attributes, &mut rng).unwrap(); 93 | let (keypair, _) = SymmetricKeypair::generate(&system_parameters, &mut rng); 94 | 95 | c.bench_function("Proof-of-Valid-Credential with 8 attributes Creation", |b| { 96 | b.iter(|| ProofOfValidCredential::prove(&system_parameters, &issuer_parameters, &credential, Some(&keypair), &mut rng)); 97 | }); 98 | } 99 | 100 | fn verification_8(c: &mut Criterion) { 101 | let mut rng = thread_rng(); 102 | let system_parameters = SystemParameters::generate(&mut rng, 8).unwrap(); 103 | let amacs_key = SecretKey::generate(&mut rng, &system_parameters); 104 | let issuer_parameters = IssuerParameters::generate(&system_parameters, &amacs_key); 105 | let issuer = Issuer::new(&system_parameters, &issuer_parameters, &amacs_key); 106 | let plaintext: Plaintext = b"This is a tsunami alert test..".into(); 107 | 108 | let mut attributes = Vec::new(); 109 | 110 | attributes.push(Attribute::SecretPoint(plaintext)); 111 | attributes.push(Attribute::SecretScalar(Scalar::random(&mut rng))); 112 | attributes.push(Attribute::SecretScalar(Scalar::random(&mut rng))); 113 | attributes.push(Attribute::PublicScalar(Scalar::random(&mut rng))); 114 | attributes.push(Attribute::PublicPoint(RistrettoPoint::random(&mut rng))); 115 | attributes.push(Attribute::SecretScalar(Scalar::random(&mut rng))); 116 | attributes.push(Attribute::PublicScalar(Scalar::random(&mut rng))); 117 | attributes.push(Attribute::PublicPoint(RistrettoPoint::random(&mut rng))); 118 | 119 | let credential = issuer.issue(attributes, &mut rng).unwrap(); 120 | let (keypair, _) = SymmetricKeypair::generate(&system_parameters, &mut rng); 121 | let proof = ProofOfValidCredential::prove(&system_parameters, &issuer_parameters, &credential, Some(&keypair), &mut rng).unwrap(); 122 | 123 | c.bench_function("Proof-of-Valid-Credential with 8 attributes Verification", |b| { 124 | b.iter(|| proof.verify(&issuer, &credential)); 125 | }); 126 | } 127 | 128 | criterion_group! { 129 | name = proof_of_credential_benches; 130 | config = Criterion::default(); 131 | targets = 132 | creation_1, 133 | verification_1, 134 | creation_8, 135 | verification_8, 136 | } 137 | } 138 | 139 | criterion_main!( 140 | proof_of_credential_benches::proof_of_credential_benches, 141 | ); 142 | -------------------------------------------------------------------------------- /src/amacs.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - isis agora lovecruft 9 | 10 | //! Implementation of the MAC_GGM scheme in https://eprint.iacr.org/2019/1416.pdf. 11 | //! 12 | //! Algebraic Message Authentication Codes (or AMACs for short) are MACs with an 13 | //! algebraic polynomial structure. They are symmetrically keyed, meaning the 14 | //! keypair used to create an AMAC must also be the keypair used to verify its 15 | //! correctness. Due to the symmetric setting and the algebraic structure, a 16 | //! proof of correctness of the AMAC can be constructed which requires sending 17 | //! only a vector of commitments to the AMAC. This is the underlying primitive 18 | //! used for our anonymous credential scheme. 19 | 20 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 21 | use alloc::vec::Vec; 22 | #[cfg(all(not(feature = "alloc"), feature = "std"))] 23 | use std::vec::Vec; 24 | 25 | use curve25519_dalek::ristretto::CompressedRistretto; 26 | use curve25519_dalek::ristretto::RistrettoPoint; 27 | use curve25519_dalek::scalar::Scalar; 28 | use curve25519_dalek::traits::Identity; 29 | use curve25519_dalek::traits::MultiscalarMul; 30 | 31 | use rand_core::CryptoRng; 32 | use rand_core::RngCore; 33 | 34 | use serde::{self, Serialize, Deserialize, Serializer, Deserializer}; 35 | use serde::de::Visitor; 36 | 37 | use zeroize::Zeroize; 38 | 39 | use crate::errors::MacError; 40 | use crate::parameters::SystemParameters; 41 | use crate::symmetric::Plaintext; 42 | 43 | /// Determine the size of a [`SecretKey`], in bytes. 44 | pub(crate) fn sizeof_secret_key(number_of_attributes: u32) -> usize { 45 | 32 * (5 + number_of_attributes) as usize + 4 46 | } 47 | 48 | /// An AMAC secret key is \(( (w, w', x_0, x_1, \vec{y_{n}}, W ) \in \mathbb{Z}_q \)) 49 | /// where \(( W := G_w * w \)). (The \(( G_w \)) is one of the orthogonal generators 50 | /// from the [`SystemParameters`].) 51 | #[derive(Clone, Debug)] 52 | pub struct SecretKey { 53 | pub(crate) w: Scalar, 54 | pub(crate) w_prime: Scalar, 55 | pub(crate) x_0: Scalar, 56 | pub(crate) x_1: Scalar, 57 | pub(crate) y: Vec, 58 | pub(crate) W: RistrettoPoint, 59 | } 60 | 61 | // We can't derive this because generally in elliptic curve cryptography group 62 | // elements aren't used as secrets, thus curve25519-dalek doesn't impl Zeroize 63 | // for RistrettoPoint. 64 | impl Zeroize for SecretKey { 65 | fn zeroize(&mut self) { 66 | self.w.zeroize(); 67 | self.w_prime.zeroize(); 68 | self.x_0.zeroize(); 69 | self.x_1.zeroize(); 70 | self.y.zeroize(); 71 | 72 | self.W = RistrettoPoint::identity(); 73 | } 74 | } 75 | 76 | /// Overwrite the secret key material with zeroes (and the identity element) 77 | /// when it drops out of scope. 78 | impl Drop for SecretKey { 79 | fn drop(&mut self) { 80 | self.zeroize(); 81 | } 82 | } 83 | 84 | impl SecretKey { 85 | /// Given the [`SystemParameters`], generate a secret key. 86 | /// 87 | /// The size of the secret key is linear in the size of the desired number 88 | /// of attributes for the anonymous credential. 89 | pub fn generate(csprng: &mut R, system_parameters: &SystemParameters) -> SecretKey 90 | where 91 | R: RngCore + CryptoRng, 92 | { 93 | let w: Scalar = Scalar::random(csprng); 94 | let w_prime: Scalar = Scalar::random(csprng); 95 | let x_0: Scalar = Scalar::random(csprng); 96 | let x_1: Scalar = Scalar::random(csprng); 97 | 98 | let mut y: Vec = Vec::with_capacity(system_parameters.NUMBER_OF_ATTRIBUTES as usize); 99 | 100 | for _ in 0..system_parameters.NUMBER_OF_ATTRIBUTES { 101 | y.push(Scalar::random(csprng)); 102 | } 103 | 104 | let W: RistrettoPoint = &system_parameters.G_w * &w; 105 | 106 | SecretKey { w, w_prime, x_0, x_1, y, W } 107 | } 108 | 109 | /// Serialise this AMAC secret key to a vector of bytes. 110 | pub(crate) fn to_bytes(&self) -> Vec { 111 | let mut bytes: Vec = Vec::with_capacity(sizeof_secret_key(self.y.len() as u32)); 112 | 113 | bytes.extend(&(self.y.len() as u32).to_le_bytes()); 114 | bytes.extend(self.w.as_bytes()); 115 | bytes.extend(self.w_prime.as_bytes()); 116 | bytes.extend(self.x_0.as_bytes()); 117 | bytes.extend(self.x_1.as_bytes()); 118 | 119 | for y in self.y.iter() { 120 | bytes.extend(y.as_bytes()); 121 | } 122 | 123 | bytes.extend(self.W.compress().as_bytes()); 124 | bytes 125 | } 126 | 127 | /// Attempt to deserialise this AMAC secret key from bytes. 128 | pub(crate) fn from_bytes(bytes: &[u8]) -> Result { 129 | // We assume no one is going to create a key for less that one attributes. 130 | if bytes.len() < sizeof_secret_key(1) { 131 | return Err(MacError::KeypairDeserialisation); 132 | } 133 | 134 | let mut index: usize = 0; 135 | let mut chunk: [u8; 32] = [0u8; 32]; 136 | 137 | let mut tmp = [0u8; 4]; 138 | 139 | tmp.copy_from_slice(&bytes[index..index+4]); 140 | let number_of_attributes = u32::from_le_bytes(tmp); index += 4; chunk.copy_from_slice(&bytes[index..index+32]); 141 | let w = Scalar::from_canonical_bytes(chunk)?; index += 32; chunk.copy_from_slice(&bytes[index..index+32]); 142 | let w_prime = Scalar::from_canonical_bytes(chunk)?; index += 32; chunk.copy_from_slice(&bytes[index..index+32]); 143 | let x_0 = Scalar::from_canonical_bytes(chunk)?; index += 32; chunk.copy_from_slice(&bytes[index..index+32]); 144 | let x_1 = Scalar::from_canonical_bytes(chunk)?; index += 32; chunk.copy_from_slice(&bytes[index..index+32]); 145 | 146 | let mut y: Vec = Vec::with_capacity(number_of_attributes as usize); 147 | 148 | for _ in 0..number_of_attributes { 149 | y.push(Scalar::from_canonical_bytes(chunk)?); index += 32; 150 | } 151 | 152 | let W = CompressedRistretto::from_slice(&bytes[index..index+32]).decompress()?; 153 | 154 | Ok(SecretKey{ w, w_prime, x_0, x_1, y, W }) 155 | } 156 | } 157 | 158 | impl_serde_with_to_bytes_and_from_bytes!(SecretKey, "A valid byte sequence representing an amacs::SecretKey"); 159 | 160 | /// Attributes may be either group elements \(( M_i \in \mathbb{G} \)) or 161 | /// scalars \(( m_j \in \mathbb{Z}_q \)), written as \(( M_j = G_m_j * m_j \)) 162 | /// where \(( G_m_j \)) is taken from the [`SystemParameters`]. 163 | /// 164 | /// When a `Credential` is shown, its attributes may be either revealed or 165 | /// hidden from the credential issuer. These represent all the valid attribute 166 | /// types. 167 | #[derive(Clone, Debug)] 168 | pub enum Attribute { 169 | /// A scalar attribute which is revealed upon credential presentation. 170 | PublicScalar(Scalar), 171 | /// A scalar attribute which is hidden upon credential presentation. 172 | SecretScalar(Scalar), 173 | /// A group element attribute which is always revealed upon credential presentation. 174 | PublicPoint(RistrettoPoint), 175 | /// A group element attribute which can be hidden or revealed upon credential presentation. 176 | EitherPoint(Plaintext), 177 | /// A group element attribute which is hidden upon credential presentation. 178 | SecretPoint(Plaintext), 179 | } 180 | 181 | // We can't derive this because generally in elliptic curve cryptography group 182 | // elements aren't used as secrets, thus curve25519-dalek doesn't impl Zeroize 183 | // for RistrettoPoint. 184 | impl Zeroize for Attribute { 185 | fn zeroize(&mut self) { 186 | match self { 187 | Attribute::SecretScalar(x) => x.zeroize(), 188 | Attribute::SecretPoint(x) => x.zeroize(), 189 | _ => return, 190 | } 191 | } 192 | } 193 | 194 | /// Overwrite the secret attributes with zeroes (and the identity element) 195 | /// when it drops out of scope. 196 | impl Drop for Attribute { 197 | fn drop(&mut self) { 198 | self.zeroize(); 199 | } 200 | } 201 | 202 | /// These are the form of the attributes during credential presentation, when 203 | /// some may be be hidden either by commiting to them and proving them in 204 | /// zero-knowledge (as is the case for hidden scalar attributes) or by 205 | /// encrypting them and proving the ciphertext's validity in zero-knowledge (as 206 | /// is the case for the hidden group element attributes). 207 | #[derive(Clone, Debug)] 208 | pub enum EncryptedAttribute { 209 | /// A scalar attribute which is revealed upon credential presentation. 210 | PublicScalar(Scalar), 211 | /// A scalar attribute which is hidden upon credential presentation. 212 | SecretScalar, 213 | /// A group element attribute which is revealed upon credential presentation. 214 | PublicPoint(RistrettoPoint), 215 | /// A group element attribute which is hidden upon credential presentation. 216 | SecretPoint, 217 | } 218 | 219 | /// Messages are computed from `Attribute`s by scalar multiplying the scalar 220 | /// portions by their respective generator in `SystemParameters.G_m`. 221 | #[derive(Debug)] 222 | pub(crate) struct Messages(pub(crate) Vec); 223 | 224 | impl Messages { 225 | pub(crate) fn from_attributes( 226 | attributes: &Vec, 227 | system_parameters: &SystemParameters 228 | ) -> Messages 229 | { 230 | let mut messages: Vec = Vec::with_capacity(attributes.len()); 231 | 232 | for (i, attribute) in attributes.iter().enumerate() { 233 | let M_i: RistrettoPoint = match attribute { 234 | Attribute::PublicScalar(m) => m * system_parameters.G_m[i], 235 | Attribute::SecretScalar(m) => m * system_parameters.G_m[i], 236 | Attribute::PublicPoint(M) => *M, 237 | Attribute::EitherPoint(p) => p.M1, 238 | Attribute::SecretPoint(p) => p.M1, 239 | }; 240 | messages.push(M_i); 241 | } 242 | Messages(messages) 243 | } 244 | } 245 | 246 | /// An algebraic message authentication code, \(( (t,U,V) \in \mathbb{Z}_q \times \mathbb{G} \times \mathbb{G} \)). 247 | #[derive(Clone, Debug)] 248 | pub(crate) struct Amac { 249 | pub(crate) t: Scalar, 250 | pub(crate) U: RistrettoPoint, 251 | pub(crate) V: RistrettoPoint, 252 | } 253 | 254 | impl Amac { 255 | /// Compute \(( V = W + (U (x_0 + x_1 t)) + \sigma{i=1}{n} M_i y_i \)). 256 | fn compute_V( 257 | system_parameters: &SystemParameters, 258 | secret_key: &SecretKey, 259 | attributes: &Vec, 260 | t: &Scalar, 261 | U: &RistrettoPoint, 262 | ) -> RistrettoPoint 263 | { 264 | let messages: Messages = Messages::from_attributes(attributes, system_parameters); 265 | 266 | // V = W + U * x_0 + U * x_1 * t 267 | let mut V: RistrettoPoint = secret_key.W + (U * secret_key.x_0) + (U * (secret_key.x_1 * t)); 268 | 269 | // V = W + U * x_0 + U * x_1 + U * t + \sigma{i=1}{n} M_i y_i 270 | V += RistrettoPoint::multiscalar_mul(&secret_key.y[..], &messages.0[..]); 271 | V 272 | } 273 | 274 | /// Compute an algebraic message authentication code with a secret key for a 275 | /// vector of messages. 276 | pub(crate) fn tag( 277 | csprng: &mut R, 278 | system_parameters: &SystemParameters, 279 | secret_key: &SecretKey, 280 | attributes: &Vec, 281 | ) -> Result 282 | where 283 | R: RngCore + CryptoRng, 284 | { 285 | if attributes.len() != system_parameters.NUMBER_OF_ATTRIBUTES as usize { 286 | return Err(MacError::MessageLengthError{length: system_parameters.NUMBER_OF_ATTRIBUTES as usize}); 287 | } 288 | 289 | let t: Scalar = Scalar::random(csprng); 290 | let U: RistrettoPoint = RistrettoPoint::random(csprng); 291 | let V: RistrettoPoint = Amac::compute_V(system_parameters, secret_key, attributes, &t, &U); 292 | 293 | Ok(Amac { t, U, V }) 294 | } 295 | 296 | /// Verify this algebraic MAC w.r.t. a secret key and vector of messages. 297 | #[allow(unused)] // We never actually call this function as the AMAC is verified indirectly in a NIZK. 298 | pub(crate) fn verify( 299 | &self, 300 | system_parameters: &SystemParameters, 301 | secret_key: &SecretKey, 302 | attributes: &Vec, 303 | ) -> Result<(), MacError> { 304 | if attributes.len() != system_parameters.NUMBER_OF_ATTRIBUTES as usize { 305 | return Err(MacError::MessageLengthError{length: system_parameters.NUMBER_OF_ATTRIBUTES as usize}); 306 | } 307 | 308 | let V_prime = Amac::compute_V(system_parameters, secret_key, attributes, &self.t, &self.U); 309 | 310 | if self.V == V_prime { 311 | return Ok(()); 312 | } 313 | Err(MacError::AuthenticationError) 314 | } 315 | } 316 | 317 | #[cfg(test)] 318 | mod test { 319 | use super::*; 320 | 321 | use rand::thread_rng; 322 | 323 | #[test] 324 | fn secret_key_generate() { 325 | let mut rng = thread_rng(); 326 | let params = SystemParameters::generate(&mut rng, 2).unwrap(); 327 | let sk = SecretKey::generate(&mut rng, ¶ms); 328 | 329 | assert!(sk.w != Scalar::zero()); 330 | } 331 | 332 | #[test] 333 | fn secret_key_from_bytes_2_attributes() { 334 | let mut rng = thread_rng(); 335 | let params = SystemParameters::generate(&mut rng, 2).unwrap(); 336 | let sk = SecretKey::generate(&mut rng, ¶ms); 337 | let bytes = sk.to_bytes(); 338 | let sk_prime = SecretKey::from_bytes(&bytes); 339 | 340 | assert!(sk_prime.is_ok()); 341 | } 342 | 343 | #[test] 344 | fn secret_key_sizeof() { 345 | let mut rng = thread_rng(); 346 | let params = SystemParameters::generate(&mut rng, 2).unwrap(); 347 | let sk = SecretKey::generate(&mut rng, ¶ms); 348 | let sizeof = sizeof_secret_key(2); 349 | let serialised = sk.to_bytes(); 350 | 351 | // We use 4 bytes for storing the number of attributes. 352 | assert!(sizeof == serialised.len(), "{} != {}", sizeof, serialised.len()); 353 | } 354 | 355 | #[test] 356 | fn amac_verification_with_plaintext_attribute() { 357 | let mut rng = thread_rng(); 358 | let params = SystemParameters::generate(&mut rng, 2).unwrap(); 359 | let sk = SecretKey::generate(&mut rng, ¶ms); 360 | let mut attributes = Vec::new(); 361 | 362 | attributes.push(Attribute::SecretScalar(Scalar::random(&mut rng))); 363 | attributes.push(Attribute::PublicScalar(Scalar::random(&mut rng))); 364 | 365 | let amac = Amac::tag(&mut rng, ¶ms, &sk, &attributes).unwrap(); 366 | 367 | assert!(amac.verify(¶ms, &sk, &attributes).is_ok()); 368 | } 369 | 370 | #[test] 371 | fn amac_verification_with_plaintext_attributes() { 372 | let mut rng = thread_rng(); 373 | let params = SystemParameters::generate(&mut rng, 8).unwrap(); 374 | let sk = SecretKey::generate(&mut rng, ¶ms); 375 | let mut messages = Vec::new(); 376 | 377 | let P1: Plaintext = (&[0u8; 30]).into(); 378 | let P2: Plaintext = (&[1u8; 30]).into(); 379 | let P3: Plaintext = (&[2u8; 30]).into(); 380 | 381 | messages.push(Attribute::PublicScalar(Scalar::random(&mut rng))); 382 | messages.push(Attribute::SecretPoint(P1)); 383 | messages.push(Attribute::PublicScalar(Scalar::random(&mut rng))); 384 | messages.push(Attribute::SecretPoint(P2)); 385 | messages.push(Attribute::EitherPoint(P3)); 386 | messages.push(Attribute::SecretScalar(Scalar::random(&mut rng))); 387 | messages.push(Attribute::PublicPoint(RistrettoPoint::random(&mut rng))); 388 | messages.push(Attribute::PublicScalar(Scalar::random(&mut rng))); 389 | 390 | let amac = Amac::tag(&mut rng, ¶ms, &sk, &messages).unwrap(); 391 | 392 | assert!(amac.verify(¶ms, &sk, &messages).is_ok()); 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /src/credential.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - isis agora lovecruft 9 | 10 | //! Attribute-based anonymous credentials. 11 | 12 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 13 | use alloc::vec::Vec; 14 | #[cfg(all(not(feature = "alloc"), feature = "std"))] 15 | use std::vec::Vec; 16 | 17 | use rand_core::CryptoRng; 18 | use rand_core::RngCore; 19 | 20 | use crate::amacs::Amac; 21 | use crate::amacs::Attribute; 22 | use crate::errors::CredentialError; 23 | use crate::parameters::IssuerParameters; 24 | use crate::parameters::SystemParameters; 25 | use crate::nizk::presentation::ProofOfValidCredential; 26 | use crate::symmetric::Keypair as SymmetricKeypair; 27 | 28 | /// An anonymous credential. 29 | #[derive(Clone, Debug)] 30 | pub struct AnonymousCredential { 31 | pub(crate) amac: Amac, 32 | pub(crate) attributes: Vec, 33 | } 34 | 35 | impl AnonymousCredential { 36 | /// Present this credential to an issuer. 37 | pub fn show( 38 | &self, 39 | system_parameters: &SystemParameters, 40 | issuer_parameters: &IssuerParameters, 41 | keypair: Option<&SymmetricKeypair>, 42 | mut csprng: impl CryptoRng + RngCore, 43 | ) -> Result 44 | { 45 | ProofOfValidCredential::prove(&system_parameters, &issuer_parameters, &self, keypair, &mut csprng) 46 | } 47 | 48 | /// Change one of this credential's attributes to be revealed upon presentation. 49 | /// 50 | /// # Returns 51 | /// 52 | /// A `Result` whose `Ok` value is an empty tuple, or a string describing the error. 53 | pub fn reveal_attribute( 54 | &mut self, 55 | index: usize, 56 | ) -> Result<(), &'static str> 57 | { 58 | let attribute = match self.attributes.get(index) { 59 | Some(x) => x, 60 | None => return Err("Could not find attribute"), 61 | }; 62 | 63 | match attribute { 64 | Attribute::SecretScalar(x) => self.attributes[index] = Attribute::PublicScalar(*x), 65 | Attribute::SecretPoint(x) => self.attributes[index] = Attribute::EitherPoint(x.clone()), 66 | _ => return Ok(()), 67 | } 68 | 69 | Ok(()) 70 | } 71 | 72 | /// Change one of this credential's attributes to be hidden upon presentation. 73 | /// 74 | /// # Returns 75 | /// 76 | /// A `Result` whose `Ok` value is an empty tuple, or a string describing the error. 77 | pub fn hide_attribute( 78 | &mut self, 79 | index: usize, 80 | ) -> Result<(), &'static str> 81 | { 82 | let attribute = match self.attributes.get(index) { 83 | Some(x) => x, 84 | None => return Err("Could not find attribute"), 85 | }; 86 | 87 | match attribute { 88 | Attribute::PublicScalar(x) => self.attributes[index] = Attribute::SecretScalar(*x), 89 | Attribute::EitherPoint(p) => self.attributes[index] = Attribute::SecretPoint(p.clone()), 90 | Attribute::PublicPoint(_) => return Err("Public point attributes cannot be converted \ 91 | to secret point attributes because this changes \ 92 | the number of attributes on the credential."), 93 | _ => return Ok(()), 94 | } 95 | 96 | Ok(()) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/encoding.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - isis agora lovecruft 9 | // - Henry de Valence 10 | 11 | //! Encoding/decoding byte sequences to and from the ristretto255 group. 12 | 13 | use curve25519_dalek::ristretto::CompressedRistretto; 14 | use curve25519_dalek::ristretto::RistrettoPoint; 15 | 16 | /// Encodes up to 30 bytes as an element of the ristretto255 group. 17 | /// 18 | /// It's not possible to straightforwardly use Elligator for this 19 | /// purpose, because we want the encoding to be invertible: Elligator 20 | /// maps field elements to points on the Jacobi quartic, but to get a 21 | /// point on the ristretto255 group we then apply the quotient. While the 22 | /// Elligator map is invertible when considered as a map to the Jacobi 23 | /// quartic, it is not invertible once the quotient is applied: 24 | /// that two different internal representatives of the same point 25 | /// may be the images of different field elements. 26 | /// 27 | /// Instead, we encode 30 bytes at a time using increment-and-test. 28 | /// 29 | /// This function computes a sequence of 32-byte candidate encodings, of 30 | /// the form `i || data || 0`, where `i` is a counter running over even 31 | /// numbers `[0,2,4,...,254]`. The first candidate which is a valid encoding 32 | /// is the canonical representative of `data`. 33 | /// 34 | /// Each candidate has a 1/4 chance of being a valid encoding, so the 35 | /// probability of *not* finding a representative after k trials is 36 | /// (3/4)**k. The chance of not finding a representative after 128 37 | /// trials is 2**(lg(3/4)*128) < 2**(-53). 38 | /// 39 | /// The number of trials before success is a geometric distribution with 40 | /// probability p = 1/4, so the expected number of trials is 41 | /// (1-1/4)/(1/4) = 3. 42 | /// 43 | /// In the extremely unlikely event that no candidate is found after 128 44 | /// trials, we can begin incrementing the high byte from 0 (its initial 45 | /// value) by 1s up to 64, giving 128*64 trials in total and cutting the 46 | /// failure probability to 2**(lg(3/4)*128*64) ~= 2**(-3400) ~= 0. 47 | /// 48 | /// # Returns 49 | /// 50 | /// The encoded group element and the counter for which try succeeded. 51 | // 52 | // XXX TODO return a Vec<(RistrettoPoint, usize)> 53 | // XXX error handling 54 | // XXX shortcut if counter is known 55 | // XXX [0u8; 30] encodes to the identity element 56 | pub fn encode_to_group(data: &[u8]) -> (RistrettoPoint, usize) { 57 | assert!(data.len() <= 30); 58 | let mut bytes = [0u8; 32]; 59 | bytes[1..1 + data.len()].copy_from_slice(data); 60 | for j in 0..64 { 61 | bytes[31] = j as u8; 62 | for i in 0..128 { 63 | bytes[0] = 2 * i as u8; 64 | if let Some(point) = CompressedRistretto(bytes).decompress() { 65 | return (point, i + j * 128); 66 | } 67 | } 68 | } 69 | panic!("a very unlikely event occurred"); 70 | } 71 | 72 | /// Decode a group element into up to 30 bytes of data. 73 | // 74 | // XXX TODO return a Vec 75 | pub fn decode_from_group(point: &RistrettoPoint) -> ([u8; 30], usize) { 76 | let mut data: [u8; 30] = [0u8; 30]; 77 | let compressed = point.compress(); 78 | 79 | data.copy_from_slice(&compressed.as_bytes()[1..31]); 80 | 81 | (data, (compressed.as_bytes()[0] / 2) as usize + compressed.as_bytes()[31] as usize * 128usize) 82 | } 83 | 84 | #[cfg(test)] 85 | mod test { 86 | use super::*; 87 | 88 | use rand::thread_rng; 89 | use rand_core::RngCore; 90 | 91 | #[test] 92 | fn encoding_decoding_roundtrip() { 93 | let mut rng = thread_rng(); 94 | let mut data = [0u8; 30]; 95 | 96 | rng.fill_bytes(&mut data); 97 | 98 | let (encoded, counter_a) = encode_to_group(&data[..]); 99 | let (decoded, counter_b) = decode_from_group(&encoded); 100 | 101 | assert_eq!(counter_a, counter_b); 102 | assert_eq!(decoded, data); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // Copyright (c) 2018 Signal Foundation 6 | // See LICENSE for licensing information. 7 | // 8 | // Authors: 9 | // - isis agora lovecruft 10 | 11 | //! Errors which may occur during anonymous credential issuance and verification. 12 | 13 | #[cfg(feature = "std")] 14 | use std::convert::From; 15 | #[cfg(feature = "std")] 16 | use std::fmt; 17 | #[cfg(feature = "std")] 18 | use std::fmt::Display; 19 | #[cfg(feature = "std")] 20 | use std::option::NoneError; 21 | 22 | #[cfg(feature = "std")] 23 | use std::error::Error; 24 | 25 | #[cfg(not(feature = "std"))] 26 | use core::convert::From; 27 | #[cfg(not(feature = "std"))] 28 | use core::fmt; 29 | #[cfg(not(feature = "std"))] 30 | use core::fmt::Display; 31 | #[cfg(not(feature = "std"))] 32 | use core::option::NoneError; 33 | 34 | use zkp::ProofError; 35 | 36 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] 37 | pub(crate) enum MacError { 38 | KeypairDeserialisation, 39 | PointDecompressionError, 40 | /// An error in the length of bytes handed to a constructor. 41 | /// 42 | /// To use this, pass the `length` in bytes which its constructor expects. 43 | MessageLengthError{ length: usize }, 44 | /// The MAC could not be authenticated. 45 | AuthenticationError, 46 | } 47 | 48 | impl Display for MacError { 49 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 50 | match *self { 51 | MacError::KeypairDeserialisation 52 | => write!(f, "Cannot deserialise keypair"), 53 | MacError::PointDecompressionError 54 | => write!(f, "Cannot decompress Ristretto point"), 55 | MacError::MessageLengthError{ length: l } 56 | => write!(f, "Messages can only have up to {} attributes", l), 57 | MacError::AuthenticationError 58 | => write!(f, "MAC could not be authenticated"), 59 | } 60 | } 61 | } 62 | 63 | impl From for MacError { 64 | fn from(_source: NoneError) -> MacError { 65 | MacError::PointDecompressionError 66 | } 67 | } 68 | 69 | #[cfg(feature = "std")] 70 | impl Error for MacError { } 71 | 72 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] 73 | pub enum CredentialError { 74 | BadAttribute, 75 | CredentialIssuance, 76 | MacCreation, 77 | MacVerification, 78 | MissingData, 79 | NoSymmetricKey, 80 | NoIssuerKey, 81 | NoIssuerParameters, 82 | NoSystemParameters, 83 | PointDecompressionError, 84 | ScalarFormatError, 85 | UndecryptableAttribute, 86 | VerificationFailure, 87 | WrongNumberOfAttributes, 88 | WrongNumberOfBytes, 89 | } 90 | 91 | impl fmt::Display for CredentialError { 92 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 93 | match *self { 94 | CredentialError::BadAttribute 95 | => write!(f, "An attribute was unacceptable"), 96 | CredentialError::CredentialIssuance 97 | => write!(f, "Failed to get a credential issued"), 98 | CredentialError::MacCreation 99 | => write!(f, "Could not create a MAC"), 100 | CredentialError::MacVerification 101 | => write!(f, "Could not verify a MAC"), 102 | CredentialError::MissingData 103 | => write!(f, "Some data, such as a key or zkproof, was missing"), 104 | CredentialError::NoSymmetricKey 105 | => write!(f, "Encrypted group element attributes require a symmetric key"), 106 | CredentialError::NoIssuerKey 107 | => write!(f, "The issuer was not initialised properly and has no secret key"), 108 | CredentialError::NoIssuerParameters 109 | => write!(f, "The issuer was not initialised properly and has no parameters"), 110 | CredentialError::NoSystemParameters 111 | => write!(f, "The system parameters were not initialised"), 112 | CredentialError::PointDecompressionError 113 | => write!(f, "Cannot decompress Ristretto point"), 114 | CredentialError::ScalarFormatError 115 | => write!(f, "Cannot use scalar with high-bit set"), 116 | CredentialError::UndecryptableAttribute 117 | => write!(f, "A hidden group attribute could not be decrypted"), 118 | CredentialError::VerificationFailure 119 | => write!(f, "The proof could not be verified"), 120 | CredentialError::WrongNumberOfAttributes 121 | => write!(f, "The credential did not have the correct number of attributes"), 122 | CredentialError::WrongNumberOfBytes 123 | => write!(f, "The credential could not be deserialised because it was not a multiple of 32 bytes"), 124 | } 125 | } 126 | } 127 | 128 | impl From for CredentialError { 129 | fn from(_source: NoneError) -> CredentialError { 130 | CredentialError::MissingData 131 | } 132 | } 133 | 134 | impl From for CredentialError { 135 | fn from(source: MacError) -> CredentialError { 136 | match source { 137 | MacError::KeypairDeserialisation 138 | => CredentialError::NoIssuerKey, 139 | MacError::PointDecompressionError 140 | => CredentialError::NoIssuerParameters, 141 | MacError::MessageLengthError{ length: _ } 142 | => CredentialError::MacCreation, 143 | MacError::AuthenticationError 144 | => CredentialError::MacVerification, 145 | } 146 | } 147 | } 148 | 149 | #[cfg(feature = "std")] 150 | impl Error for CredentialError { } 151 | 152 | impl From for CredentialError { 153 | fn from(_source: ProofError) -> CredentialError { 154 | CredentialError::VerificationFailure 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/issuer.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - isis agora lovecruft 9 | 10 | //! Centralised credential issuer and honest verifier. 11 | 12 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 13 | use alloc::vec::Vec; 14 | #[cfg(all(not(feature = "alloc"), feature = "std"))] 15 | use std::vec::Vec; 16 | 17 | use rand_core::CryptoRng; 18 | use rand_core::RngCore; 19 | 20 | use serde::de::Deserialize; 21 | use serde::de::Deserializer; 22 | use serde::de::Visitor; 23 | use serde::ser::Serialize; 24 | use serde::ser::Serializer; 25 | 26 | use crate::amacs::sizeof_secret_key; 27 | use crate::amacs::Amac; 28 | use crate::amacs::SecretKey; 29 | use crate::credential::AnonymousCredential; 30 | use crate::errors::CredentialError; 31 | use crate::nizk::issuance::ProofOfIssuance; 32 | use crate::nizk::presentation::ProofOfValidCredential; 33 | use crate::parameters::sizeof_system_parameters; 34 | use crate::parameters::IssuerParameters; 35 | use crate::parameters::SystemParameters; 36 | use crate::user::CredentialRequest; 37 | 38 | /// An issued anonymous credential. 39 | /// 40 | /// The `User` who receives such a proof must check the included zero-knowledge 41 | /// proof that the contained credential was issued correctly. 42 | pub struct CredentialIssuance { 43 | pub(crate) proof: ProofOfIssuance, 44 | pub(crate) credential: AnonymousCredential, 45 | } 46 | 47 | impl CredentialIssuance { 48 | pub fn verify( 49 | self, 50 | system_parameters: &SystemParameters, 51 | issuer_parameters: &IssuerParameters, 52 | ) -> Result 53 | { 54 | self.proof 55 | .verify(system_parameters, issuer_parameters, &self.credential) 56 | .and(Ok(self.credential)) 57 | } 58 | } 59 | 60 | /// An anonymous credential issuer/verifier. 61 | pub struct Issuer { 62 | pub system_parameters: SystemParameters, 63 | pub issuer_parameters: IssuerParameters, 64 | pub(crate) amacs_key: SecretKey, 65 | } 66 | 67 | impl Issuer { 68 | /// Create a new anonymous credential issuer and verifier. 69 | /// 70 | /// # Inputs 71 | /// 72 | /// * Some previously generated [`SystemParameters`]. 73 | /// * A cryptographically secure PRNG. 74 | /// 75 | /// # Returns 76 | /// 77 | /// An new issuer. 78 | pub fn new( 79 | system_parameters: &SystemParameters, 80 | csprng: &mut C, 81 | ) -> Issuer 82 | where 83 | C: CryptoRng + RngCore, 84 | { 85 | let amacs_key = SecretKey::generate(csprng, &system_parameters); 86 | let issuer_parameters = IssuerParameters::generate(&system_parameters, &amacs_key); 87 | 88 | Issuer { 89 | system_parameters: system_parameters.clone(), 90 | issuer_parameters: issuer_parameters, 91 | amacs_key: amacs_key, 92 | } 93 | } 94 | 95 | /// Issue a new anonymous credential on a set of `attributes` in an 96 | /// unblinded manner. 97 | /// 98 | /// By "unblinded" we mean that all attributes are revealed (unencrypted) 99 | /// and the issuer is able to perform verification/validation on all of 100 | /// them. 101 | /// 102 | /// # Inputs 103 | /// 104 | /// * The set of `attributes` to include on the credential, 105 | /// * A `csprng`. 106 | /// 107 | /// # Returns 108 | /// 109 | /// A `Result` whose `Ok` value is an [`AnonymousCredential`], otherwise a 110 | /// [`CredentialError`]. 111 | pub fn issue( 112 | &self, 113 | request: CredentialRequest, 114 | csprng: &mut C, 115 | ) -> Result 116 | where 117 | C: CryptoRng + RngCore, 118 | { 119 | let amac = Amac::tag(csprng, &self.system_parameters, &self.amacs_key, &request.attributes)?; 120 | let cred = AnonymousCredential { amac, attributes: request.attributes }; 121 | let proof = ProofOfIssuance::prove(&self, &cred); 122 | 123 | Ok(CredentialIssuance { proof: proof, credential: cred }) 124 | } 125 | 126 | /// Verify a user's presentation of an anonymous credential. 127 | /// 128 | /// The user's presentation may reveal or hide any of the attributes, so 129 | /// long as the overall structure remains the same (e.g. a user cannot 130 | /// reorder a credential with attributes being a scalar then a group element 131 | /// to be instead a group element and then a scalar, nor can they add or 132 | /// remove attributes). 133 | /// 134 | /// # Inputs 135 | /// 136 | /// * A user's [`ProofOfValidCredential`]. 137 | /// 138 | /// # Returns 139 | /// 140 | /// A `Result` whose `Ok` value is empty, otherwise a `CredentialError`. 141 | pub fn verify( 142 | &self, 143 | presentation: &ProofOfValidCredential, 144 | ) -> Result<(), CredentialError> 145 | { 146 | presentation.verify(&self) 147 | } 148 | } 149 | 150 | impl Issuer { 151 | /// Create an [`Issuer`] from bytes. 152 | pub fn from_bytes(bytes: &[u8]) -> Result { 153 | let system_parameters = SystemParameters::from_bytes(&bytes)?; 154 | let offset = sizeof_system_parameters(system_parameters.NUMBER_OF_ATTRIBUTES); 155 | let issuer_parameters = IssuerParameters::from_bytes(&bytes[offset..offset+64])?; 156 | let amacs_key = SecretKey::from_bytes(&bytes[offset+64..])?; 157 | 158 | Ok(Issuer { system_parameters, issuer_parameters, amacs_key }) 159 | } 160 | 161 | /// Serialise this [`Issuer`] to a byte array. 162 | pub fn to_bytes(&self) -> Vec { 163 | let size = 64 + 164 | sizeof_system_parameters(self.system_parameters.NUMBER_OF_ATTRIBUTES) + 165 | sizeof_secret_key(self.system_parameters.NUMBER_OF_ATTRIBUTES); 166 | 167 | let mut bytes: Vec = Vec::with_capacity(size); 168 | 169 | bytes.extend(self.system_parameters.to_bytes()); 170 | bytes.extend(self.issuer_parameters.to_bytes()); 171 | bytes.extend(self.amacs_key.to_bytes()); 172 | 173 | bytes 174 | } 175 | } 176 | 177 | impl_serde_with_to_bytes_and_from_bytes!(Issuer, "A valid byte sequence representing an Issuer"); 178 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // Copyright (c) 2018 Signal Foundation 6 | // See LICENSE for licensing information. 7 | // 8 | // Authors: 9 | // - isis agora lovecruft 10 | 11 | #![no_std] 12 | 13 | #![cfg_attr(feature = "nightly", feature(external_doc))] 14 | #![cfg_attr(feature = "nightly", feature(doc_cfg))] 15 | 16 | // Refuse to compile if documentation is missing, but only on nightly. 17 | // 18 | // This means that missing docs will still fail CI, but means we can use 19 | // README.md as the crate documentation. 20 | //#![cfg_attr(feature = "nightly", deny(missing_docs))] 21 | 22 | #![cfg_attr(feature = "nightly", doc(include = "../README.md"))] 23 | 24 | // TODO Get rid of the syntax that uses the nightly-only try_trait. 25 | #![feature(try_trait)] 26 | // We denote group elements with capital and scalars with lowercased names. 27 | #![allow(non_snake_case)] 28 | 29 | #[cfg(feature = "std")] 30 | #[macro_use] 31 | extern crate std; 32 | #[cfg(any(not(feature = "std"), feature = "alloc"))] 33 | #[macro_use] 34 | extern crate alloc; 35 | 36 | extern crate curve25519_dalek; 37 | #[cfg(test)] 38 | extern crate rand; 39 | extern crate rand_core; 40 | extern crate serde; 41 | extern crate sha2; 42 | extern crate subtle; 43 | extern crate zeroize; 44 | extern crate zkp; 45 | 46 | // The macros have to come first. 47 | #[macro_use] 48 | mod macros; 49 | 50 | pub mod amacs; 51 | pub mod credential; 52 | pub mod encoding; 53 | pub mod errors; 54 | pub mod issuer; 55 | pub mod nizk; 56 | pub mod parameters; 57 | pub mod prelude; 58 | pub mod symmetric; 59 | pub mod user; 60 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // Copyright (c) 2018 Signal Foundation 6 | // See LICENSE for licensing information. 7 | // 8 | // Authors: 9 | // - isis agora lovecruft 10 | 11 | macro_rules! impl_serde_with_to_bytes_and_from_bytes { 12 | ($t:tt, $expecting:expr) => { 13 | impl Serialize for $t { 14 | fn serialize(&self, serializer: S) -> Result 15 | where S: Serializer 16 | { 17 | serializer.serialize_bytes(&self.to_bytes()[..]) 18 | } 19 | } 20 | 21 | impl<'de> Deserialize<'de> for $t { 22 | fn deserialize(deserializer: D) -> Result 23 | where D: Deserializer<'de> 24 | { 25 | struct AeonfluxVisitor; 26 | 27 | impl<'de> Visitor<'de> for AeonfluxVisitor { 28 | type Value = $t; 29 | 30 | fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 31 | formatter.write_str($expecting) 32 | } 33 | 34 | fn visit_bytes(self, v: &[u8]) -> Result<$t, E> 35 | where E: serde::de::Error 36 | { 37 | match $t::from_bytes(v) { 38 | Ok(x) => Ok(x), 39 | Err(_x) => { 40 | #[cfg(feature = "std")] 41 | println!("Error while deserialising {}: {:?}", stringify!($t), _x); 42 | Err(serde::de::Error::invalid_length(v.len(), &self)) 43 | }, 44 | } 45 | } 46 | } 47 | deserializer.deserialize_bytes(AeonfluxVisitor) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/nizk/encryption.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - isis agora lovecruft 9 | 10 | //! Non-interactive zero-knowledge proofs (NIZKs) of correct encryption under a given key. 11 | 12 | use curve25519_dalek::scalar::Scalar; 13 | use curve25519_dalek::ristretto::RistrettoPoint; 14 | 15 | use zkp::CompactProof; 16 | use zkp::Transcript; 17 | // XXX do we want/need batch proof verification? 18 | // use zkp::toolbox::batch_verifier::BatchVerifier; 19 | use zkp::toolbox::SchnorrCS; 20 | use zkp::toolbox::prover::Prover; 21 | use zkp::toolbox::verifier::Verifier; 22 | 23 | use crate::errors::CredentialError; 24 | use crate::parameters::SystemParameters; 25 | use crate::symmetric::Ciphertext; 26 | use crate::symmetric::Keypair as SymmetricKeypair; 27 | use crate::symmetric::Plaintext; 28 | use crate::symmetric::PublicKey as SymmetricPublicKey; // XXX rename this to something more sensical 29 | 30 | /// A proof-of-knowledge that a ciphertext encrypts a plaintext 31 | /// committed to in a list of commitments. 32 | pub struct ProofOfEncryption { 33 | proof: CompactProof, 34 | public_key: SymmetricPublicKey, 35 | pub(crate) ciphertext: Ciphertext, 36 | index: u16, 37 | C_y_1: RistrettoPoint, 38 | C_y_2: RistrettoPoint, 39 | C_y_3: RistrettoPoint, 40 | C_y_2_prime: RistrettoPoint, 41 | } 42 | 43 | impl ProofOfEncryption { 44 | /// Prove in zero-knowledge that a ciphertext is a verifiable encryption of 45 | /// a plaintext w.r.t. a valid commitment to a secret symmetric key. 46 | /// 47 | /// # Inputs 48 | /// 49 | /// * The [`SystemParameters`] for this anonymous credential instantiation, 50 | /// * A `plaintext` of up to thirty bytes. 51 | /// * The `index` of the attribute to be encrypted. 52 | /// * A symmetric "keypair", 53 | /// * The nonce, `z`, must be reused from the outer-lying [`ProofOfValidCredential`]. 54 | /// 55 | /// # Returns 56 | /// 57 | /// A `Result` whose `Ok` value is empty, otherwise a [`CredentialError`]. 58 | pub(crate) fn prove( 59 | system_parameters: &SystemParameters, 60 | plaintext: &Plaintext, 61 | index: u16, 62 | keypair: &SymmetricKeypair, 63 | z: &Scalar, 64 | ) -> ProofOfEncryption 65 | { 66 | // Encrypt the plaintext. 67 | let ciphertext_ = keypair.encrypt(&plaintext); 68 | 69 | // Compute the vector C of commitments to the plaintext. 70 | let C_y_1_ = (system_parameters.G_y[0] * z) + plaintext.M1; 71 | let C_y_2_ = (system_parameters.G_y[1] * z) + plaintext.M2; 72 | let C_y_3_ = (system_parameters.G_y[2] * z) + (system_parameters.G_m[index as usize] * plaintext.m3); 73 | 74 | // Compute C_y_2' = C_y_2 * a1. 75 | let C_y_2_prime_ = C_y_2_ * keypair.secret.a1; 76 | 77 | // Calculate z1 = -z(a0 + a1 * m3). 78 | let z1_ = -z * (keypair.secret.a0 + keypair.secret.a1 * plaintext.m3); 79 | 80 | // Construct a protocol transcript and prover. 81 | let mut transcript = Transcript::new(b"2019/1416 anonymous credentials"); 82 | let mut prover = Prover::new(b"2019/1416 proof of encryption", &mut transcript); 83 | 84 | // Commit the names of the Camenisch-Stadler secrets to the protocol transcript. 85 | let a = prover.allocate_scalar(b"a", keypair.secret.a); 86 | let a0 = prover.allocate_scalar(b"a0", keypair.secret.a0); 87 | let a1 = prover.allocate_scalar(b"a1", keypair.secret.a1); 88 | let m3 = prover.allocate_scalar(b"m3", plaintext.m3); 89 | let z = prover.allocate_scalar(b"z", *z); 90 | let z1 = prover.allocate_scalar(b"z1", z1_); 91 | 92 | // Commit to the values and names of the Camenisch-Stadler publics. 93 | let (pk, _) = prover.allocate_point(b"pk", keypair.public.pk); 94 | let (G_a, _) = prover.allocate_point(b"G_a", system_parameters.G_a); 95 | let (G_a_0, _) = prover.allocate_point(b"G_a_0", system_parameters.G_a0); 96 | let (G_a_1, _) = prover.allocate_point(b"G_a_1", system_parameters.G_a1); 97 | let (G_y_1, _) = prover.allocate_point(b"G_y_1", system_parameters.G_y[0]); 98 | let (G_y_2, _) = prover.allocate_point(b"G_y_2", system_parameters.G_y[1]); 99 | let (G_y_3, _) = prover.allocate_point(b"G_y_3", system_parameters.G_y[2]); 100 | let (G_m_3, _) = prover.allocate_point(b"G_m_3", system_parameters.G_m[index as usize]); 101 | let (C_y_2, _) = prover.allocate_point(b"C_y_2", C_y_2_); 102 | let (C_y_3, _) = prover.allocate_point(b"C_y_3", C_y_3_); 103 | let (C_y_2_prime, _) = prover.allocate_point(b"C_y_2'", C_y_2_prime_); 104 | let (C_y_1_minus_E2, _) = prover.allocate_point(b"C_y_1-E2", C_y_1_ - ciphertext_.E2); 105 | let (E1, _) = prover.allocate_point(b"E1", ciphertext_.E1); 106 | let (minus_E1, _) = prover.allocate_point(b"-E1", -ciphertext_.E1); 107 | 108 | // Constraint #1: Prove knowledge of the secret portions of the symmetric key. 109 | // pk = G_a * a + G_a0 * a0 + G_a1 * a1 110 | prover.constrain(pk, vec![(a, G_a), (a0, G_a_0), (a1, G_a_1)]); 111 | 112 | // Constraint #2: The plaintext of this encryption is the message. 113 | // C_y_1 - E2 = G_y_1 * z - E_1 * a 114 | prover.constrain(C_y_1_minus_E2, vec![(z, G_y_1), (a, minus_E1)]); 115 | 116 | // Constraint #3: The encryption C_y_2' of the commitment C_y_2 is formed correctly w.r.t. the secret key. 117 | // C_y_2' = C_y_2 * a1 118 | prover.constrain(C_y_2_prime, vec![(a1, C_y_2)]); 119 | 120 | // Constraint #4: The encryption E1 is well formed. 121 | // E1 = C_y_2 * a0 + C_y_2' * m3 + G_y_2 * z1 122 | // M2 * (a0 + a1 * m3) = (M2 + G_y_2 * z) * a0 + (M2 + G_y_2 * z) * a1 * m3 + G_y_2 * -z (a0 + a1 * m3) 123 | // M2(a0) + M2(a1)(m3) = M2(a0) + G_y_2(z)(a0) + M2(a1)(m3) + G_y_2(z)(a1)(m3) + G_y_2(-z)(a0) + G_y_2(-z)(a1)(m3) 124 | // M2(a0) + M2(a1)(m3) = M2(a0) + M2(a1)(m3) 125 | prover.constrain(E1, vec![(a0, C_y_2), (m3, C_y_2_prime), (z1, G_y_2)]); 126 | 127 | // Constraint #5: The commitment to the hash m3 is a correct hash of the message commited to. 128 | prover.constrain(C_y_3, vec![(z, G_y_3), (m3, G_m_3)]); 129 | 130 | let proof = prover.prove_compact(); 131 | 132 | ProofOfEncryption { 133 | proof: proof, 134 | public_key: keypair.public, 135 | ciphertext: ciphertext_, 136 | index: index, 137 | C_y_1: C_y_1_, 138 | C_y_2: C_y_2_, 139 | C_y_3: C_y_3_, 140 | C_y_2_prime: C_y_2_prime_, 141 | } 142 | } 143 | 144 | /// Verify that this [`ProofOfEncryption`] proves that a `ciphertext` is a 145 | /// correct encryption of a verifiably-encrypted plaintext. 146 | /// 147 | /// # Inputs 148 | /// 149 | /// * The [`SystemParameters`] for this anonymous credential instantiation, 150 | /// 151 | /// # Returns 152 | /// 153 | /// A `Result` whose `Ok` value is empty, otherwise a [`CredentialError`]. 154 | pub(crate) fn verify( 155 | &self, 156 | system_parameters: &SystemParameters, 157 | ) -> Result<(), CredentialError> 158 | { 159 | // Construct a protocol transcript and verifier. 160 | let mut transcript = Transcript::new(b"2019/1416 anonymous credentials"); 161 | let mut verifier = Verifier::new(b"2019/1416 proof of encryption", &mut transcript); 162 | 163 | // Commit the names of the Camenisch-Stadler secrets to the protocol transcript. 164 | let a = verifier.allocate_scalar(b"a"); 165 | let a0 = verifier.allocate_scalar(b"a0"); 166 | let a1 = verifier.allocate_scalar(b"a1"); 167 | let m3 = verifier.allocate_scalar(b"m3"); 168 | let z = verifier.allocate_scalar(b"z"); 169 | let z1 = verifier.allocate_scalar(b"z1"); 170 | 171 | // Commit to the values and names of the Camenisch-Stadler publics. 172 | let pk = verifier.allocate_point(b"pk", self.public_key.pk.compress())?; 173 | let G_a = verifier.allocate_point(b"G_a", system_parameters.G_a.compress())?; 174 | let G_a_0 = verifier.allocate_point(b"G_a_0", system_parameters.G_a0.compress())?; 175 | let G_a_1 = verifier.allocate_point(b"G_a_1", system_parameters.G_a1.compress())?; 176 | let G_y_1 = verifier.allocate_point(b"G_y_1", system_parameters.G_y[0].compress())?; 177 | let G_y_2 = verifier.allocate_point(b"G_y_2", system_parameters.G_y[1].compress())?; 178 | let G_y_3 = verifier.allocate_point(b"G_y_3", system_parameters.G_y[2].compress())?; 179 | let G_m_3 = verifier.allocate_point(b"G_m_3", system_parameters.G_m[self.index as usize].compress())?; 180 | let C_y_2 = verifier.allocate_point(b"C_y_2", self.C_y_2.compress())?; 181 | let C_y_3 = verifier.allocate_point(b"C_y_3", self.C_y_3.compress())?; 182 | let C_y_2_prime = verifier.allocate_point(b"C_y_2'", self.C_y_2_prime.compress())?; 183 | let C_y_1_minus_E2 = verifier.allocate_point(b"C_y_1-E2", (self.C_y_1 - self.ciphertext.E2).compress())?; 184 | let E1 = verifier.allocate_point(b"E1", self.ciphertext.E1.compress())?; 185 | let minus_E1 = verifier.allocate_point(b"-E1", (-self.ciphertext.E1).compress())?; 186 | 187 | // Constraint #1: Prove knowledge of the secret portions of the symmetric key. 188 | // pk = G_a * a + G_a0 * a0 + G_a1 * a1 189 | verifier.constrain(pk, vec![(a, G_a), (a0, G_a_0), (a1, G_a_1)]); 190 | 191 | // Constraint #2: The plaintext of this encryption is the message. 192 | // C_y_1 - E2 = G_y_1 * z - E_1 * a 193 | verifier.constrain(C_y_1_minus_E2, vec![(z, G_y_1), (a, minus_E1)]); 194 | 195 | // Constraint #3: The encryption C_y_2' of the commitment C_y_2 is formed correctly w.r.t. the secret key. 196 | // C_y_2' = C_y_2 * a1 197 | verifier.constrain(C_y_2_prime, vec![(a1, C_y_2)]); 198 | 199 | // Constraint #4: The encryption E1 is well formed. 200 | // E1 = C_y_2 * a0 + C_y_2' * m3 + G_y_2 * z1 201 | // M2 * (a0 + a1 * m3) = (M2 + G_y_2 * z) * a0 + (M2 + G_y_2 * z) * a1 * m3 + G_y_2 * -z (a0 + a1 * m3) 202 | // M2(a0) + M2(a1)(m3) = M2(a0) + G_y_2(z)(a0) + M2(a1)(m3) + G_y_2(z)(a1)(m3) + G_y_2(-z)(a0) + G_y_2(-z)(a1)(m3) 203 | // M2(a0) + M2(a1)(m3) = M2(a0) + M2(a1)(m3) 204 | verifier.constrain(E1, vec![(a0, C_y_2), (m3, C_y_2_prime), (z1, G_y_2)]); 205 | 206 | // Constraint #5: The commitment to the hash m3 is a correct hash of the message commited to. 207 | verifier.constrain(C_y_3, vec![(z, G_y_3), (m3, G_m_3)]); 208 | 209 | verifier.verify_compact(&self.proof).or(Err(CredentialError::VerificationFailure)) 210 | } 211 | } 212 | 213 | #[cfg(test)] 214 | mod test { 215 | use super::*; 216 | 217 | use crate::user::CredentialRequestConstructor; 218 | 219 | use rand::thread_rng; 220 | 221 | #[test] 222 | fn encryption_proof() { 223 | let mut rng = thread_rng(); 224 | let system_parameters = SystemParameters::generate(&mut rng, 5).unwrap(); 225 | let (keypair, _) = SymmetricKeypair::generate(&system_parameters, &mut rng); 226 | let z = Scalar::random(&mut rng); 227 | let message1: &[u8; 30] = b"This is a tsunami alert test.."; 228 | let plaintext: Plaintext = message1.into(); 229 | 230 | let proof = ProofOfEncryption::prove(&system_parameters, &plaintext, 1u16, &keypair, &z); 231 | let decryption = keypair.decrypt(&proof.ciphertext).unwrap(); 232 | 233 | assert!(decryption.M1 == plaintext.M1); 234 | assert!(decryption.M2 == plaintext.M2); 235 | assert!(decryption.m3 == plaintext.m3); 236 | 237 | let message2: [u8; 30] = (&decryption).into(); 238 | 239 | assert!(message1 == &message2); 240 | 241 | let verification = proof.verify(&system_parameters); 242 | 243 | assert!(verification.is_ok()); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/nizk/issuance.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - isis agora lovecruft 9 | 10 | //! Non-interactive zero-knowledge proofs (NIPKs). 11 | 12 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 13 | use alloc::vec::Vec; 14 | #[cfg(all(not(feature = "alloc"), feature = "std"))] 15 | use std::vec::Vec; 16 | 17 | use curve25519_dalek::scalar::Scalar; 18 | 19 | use zkp::CompactProof; 20 | use zkp::Transcript; 21 | // XXX do we want/need batch proof verification? 22 | // use zkp::toolbox::batch_verifier::BatchVerifier; 23 | use zkp::toolbox::SchnorrCS; 24 | use zkp::toolbox::prover::Prover; 25 | use zkp::toolbox::verifier::Verifier; 26 | 27 | use crate::amacs::Messages; 28 | use crate::credential::AnonymousCredential; 29 | use crate::errors::CredentialError; 30 | use crate::issuer::Issuer; 31 | use crate::parameters::{IssuerParameters, SystemParameters}; 32 | 33 | /// A non-interactive zero-knowledge proof demonstrating knowledge of the 34 | /// issuer's secret key, and that an [`AnonymousCredential`] was computed 35 | /// correctly w.r.t. the pubilshed system and issuer parameters. 36 | pub struct ProofOfIssuance(CompactProof); 37 | 38 | impl ProofOfIssuance { 39 | /// Create a [`ProofOfIssuance`]. 40 | pub(crate) fn prove( 41 | issuer: &Issuer, 42 | credential: &AnonymousCredential, 43 | ) -> ProofOfIssuance 44 | { 45 | use zkp::toolbox::prover::PointVar; 46 | use zkp::toolbox::prover::ScalarVar; 47 | 48 | let mut transcript = Transcript::new(b"2019/1416 anonymous credential"); 49 | let mut prover = Prover::new(b"2019/1416 issuance proof", &mut transcript); 50 | 51 | // Commit the names of the Camenisch-Stadler secrets to the protocol transcript. 52 | let w = prover.allocate_scalar(b"w", issuer.amacs_key.w); 53 | let w_prime = prover.allocate_scalar(b"w'", issuer.amacs_key.w_prime); 54 | let x_0 = prover.allocate_scalar(b"x_0", issuer.amacs_key.x_0); 55 | let x_1 = prover.allocate_scalar(b"x_1", issuer.amacs_key.x_1); 56 | 57 | let mut y: Vec = Vec::with_capacity(issuer.system_parameters.NUMBER_OF_ATTRIBUTES as usize); 58 | 59 | for (_i, y_i) in issuer.amacs_key.y.iter().enumerate() { 60 | // XXX fix the zkp crate to take Strings 61 | //y.push(prover.allocate_scalar(format!("y_{}", _i), y_i)); 62 | y.push(prover.allocate_scalar(b"y", *y_i)); 63 | } 64 | 65 | // We also have to commit to the multiplicative identity since one of the 66 | // zero-knowledge statements requires the inverse of the G_V generator 67 | // without multiplying by any scalar. 68 | let one = prover.allocate_scalar(b"1", Scalar::one()); 69 | 70 | // Commit to the values and names of the Camenisch-Stadler publics. 71 | let (G_V, _) = prover.allocate_point(b"G_V", issuer.system_parameters.G_V); 72 | let (G_w, _) = prover.allocate_point(b"G_w", issuer.system_parameters.G_w); 73 | let (G_w_prime, _) = prover.allocate_point(b"G_w_prime", issuer.system_parameters.G_w_prime); 74 | let (neg_G_x_0, _) = prover.allocate_point(b"-G_x_0", -issuer.system_parameters.G_x_0); 75 | let (neg_G_x_1, _) = prover.allocate_point(b"-G_x_1", -issuer.system_parameters.G_x_1); 76 | 77 | let mut neg_G_y: Vec = Vec::with_capacity(issuer.system_parameters.NUMBER_OF_ATTRIBUTES as usize); 78 | 79 | for (_i, G_y_i) in issuer.system_parameters.G_y.iter().enumerate() { 80 | // XXX fix the zkp crate to take Strings 81 | //let (G_y_x, _) = prover.allocate_point(format!("G_y_{}", _i), G_y_i); 82 | let (neg_G_y_x, _) = prover.allocate_point(b"-G_y", -G_y_i); 83 | 84 | neg_G_y.push(neg_G_y_x); 85 | } 86 | 87 | let (C_W, _) = prover.allocate_point(b"C_W", issuer.issuer_parameters.C_W); 88 | let (I, _) = prover.allocate_point(b"I", issuer.issuer_parameters.I); 89 | let (U, _) = prover.allocate_point(b"U", credential.amac.U); 90 | let (V, _) = prover.allocate_point(b"V", credential.amac.V); 91 | let (tU, _) = prover.allocate_point(b"tU", credential.amac.t * credential.amac.U); 92 | 93 | let mut M: Vec = Vec::with_capacity(issuer.system_parameters.NUMBER_OF_ATTRIBUTES as usize); 94 | 95 | let messages: Messages = Messages::from_attributes(&credential.attributes, &issuer.system_parameters); 96 | 97 | for (_i, M_i) in messages.0.iter().enumerate() { 98 | // XXX fix the zkp crate to take Strings 99 | //let (M_x, _) = prover.allocate_point(format!("M_{}", _i), M_i); 100 | let (M_x, _) = prover.allocate_point(b"M", *M_i); 101 | 102 | M.push(M_x); 103 | } 104 | 105 | // Constraint #1: C_W = G_w * w + G_w' * w' 106 | prover.constrain(C_W, vec![(w, G_w), (w_prime, G_w_prime)]); 107 | 108 | // Constraint #2: I = G_V - (G_x_0 * x_0 + G_x_1 * x_1 + G_y_1 * y_1 + ... + G_y_n * y_n) 109 | let mut rhs: Vec<(ScalarVar, PointVar)> = Vec::with_capacity(3 + issuer.system_parameters.NUMBER_OF_ATTRIBUTES as usize); 110 | 111 | rhs.push((one, G_V)); 112 | rhs.push((x_0, neg_G_x_0)); 113 | rhs.push((x_1, neg_G_x_1)); 114 | rhs.extend(y.iter().copied().zip(neg_G_y.iter().copied())); 115 | 116 | prover.constrain(I, rhs); 117 | 118 | // Constraint #3: V = G_w * w + U * x_0 + U * x_1 * t + \sigma{i=1}{n} M_i * y_i 119 | let mut rhs: Vec<(ScalarVar, PointVar)> = Vec::with_capacity(3 + issuer.system_parameters.NUMBER_OF_ATTRIBUTES as usize); 120 | 121 | rhs.push((w, G_w)); 122 | rhs.push((x_0, U)); 123 | rhs.push((x_1, tU)); 124 | rhs.extend(y.iter().copied().zip(M.iter().copied())); 125 | 126 | prover.constrain(V, rhs); 127 | 128 | ProofOfIssuance(prover.prove_compact()) 129 | } 130 | 131 | /// Verify a [`ProofOfIssuance`]. 132 | pub fn verify( 133 | &self, 134 | system_parameters: &SystemParameters, 135 | issuer_parameters: &IssuerParameters, 136 | credential: &AnonymousCredential, 137 | ) -> Result<(), CredentialError> 138 | { 139 | use zkp::toolbox::verifier::PointVar; 140 | use zkp::toolbox::verifier::ScalarVar; 141 | 142 | let mut transcript = Transcript::new(b"2019/1416 anonymous credential"); 143 | let mut verifier = Verifier::new(b"2019/1416 issuance proof", &mut transcript); 144 | 145 | // Commit the names of the Camenisch-Stadler secrets to the protocol transcript. 146 | let w = verifier.allocate_scalar(b"w"); 147 | let w_prime = verifier.allocate_scalar(b"w'"); 148 | let x_0 = verifier.allocate_scalar(b"x_0"); 149 | let x_1 = verifier.allocate_scalar(b"x_1"); 150 | 151 | let mut y: Vec = Vec::with_capacity(system_parameters.NUMBER_OF_ATTRIBUTES as usize); 152 | 153 | for _i in 0..system_parameters.NUMBER_OF_ATTRIBUTES as usize { 154 | // XXX fix the zkp crate to take Strings 155 | //y.push(verifier.allocate_scalar(format!("y_{}", _i))); 156 | y.push(verifier.allocate_scalar(b"y")); 157 | } 158 | 159 | let one = verifier.allocate_scalar(b"1"); 160 | 161 | // Commit to the values and names of the Camenisch-Stadler publics. 162 | let G_V = verifier.allocate_point(b"G_V", system_parameters.G_V.compress())?; 163 | let G_w = verifier.allocate_point(b"G_w", system_parameters.G_w.compress())?; 164 | let G_w_prime = verifier.allocate_point(b"G_w_prime", system_parameters.G_w_prime.compress())?; 165 | let neg_G_x_0 = verifier.allocate_point(b"-G_x_0", (-system_parameters.G_x_0).compress())?; 166 | let neg_G_x_1 = verifier.allocate_point(b"-G_x_1", (-system_parameters.G_x_1).compress())?; 167 | 168 | let mut neg_G_y: Vec = Vec::with_capacity(system_parameters.NUMBER_OF_ATTRIBUTES as usize); 169 | 170 | for (_i, G_y_i) in system_parameters.G_y.iter().enumerate() { 171 | // XXX fix the zkp crate to take Strings 172 | //G_y.push(verifier.allocate_point(format!("G_y_{}", _i), G_y_i)?); 173 | neg_G_y.push(verifier.allocate_point(b"-G_y", (-G_y_i).compress())?); 174 | } 175 | 176 | let C_W = verifier.allocate_point(b"C_W", issuer_parameters.C_W.compress())?; 177 | let I = verifier.allocate_point(b"I", issuer_parameters.I.compress())?; 178 | let U = verifier.allocate_point(b"U", credential.amac.U.compress())?; 179 | let V = verifier.allocate_point(b"V", credential.amac.V.compress())?; 180 | let tU = verifier.allocate_point(b"tU", (credential.amac.t * credential.amac.U).compress())?; 181 | 182 | let mut M: Vec = Vec::with_capacity(system_parameters.NUMBER_OF_ATTRIBUTES as usize); 183 | 184 | let messages: Messages = Messages::from_attributes(&credential.attributes, system_parameters); 185 | 186 | for (_i, M_i) in messages.0.iter().enumerate() { 187 | // XXX fix the zkp crate to take Strings 188 | //let (M_x, _) = verifier.allocate_point(format!("M_{}", _i), M_i); 189 | let M_x = verifier.allocate_point(b"M", M_i.compress())?; 190 | 191 | M.push(M_x); 192 | } 193 | 194 | // Constraint #1: C_W = G_w * w + G_w' * w' 195 | verifier.constrain(C_W, vec![(w, G_w), (w_prime, G_w_prime)]); 196 | 197 | // Constraint #2: I = G_V - (G_x_0 * x_0 + G_x_1 * x_1 + G_y_1 * y_1 + ... + G_y_n * y_n) 198 | let mut rhs: Vec<(ScalarVar, PointVar)> = Vec::with_capacity(3 + system_parameters.NUMBER_OF_ATTRIBUTES as usize); 199 | 200 | rhs.push((one, G_V)); 201 | rhs.push((x_0, neg_G_x_0)); 202 | rhs.push((x_1, neg_G_x_1)); 203 | rhs.extend(y.iter().copied().zip(neg_G_y.iter().copied())); 204 | 205 | verifier.constrain(I, rhs); 206 | 207 | // Constraint #3: V = G_w * w + U * x_0 + U * x_1 * t + \sigma{i=1}{n} M_i * y_i 208 | let mut rhs: Vec<(ScalarVar, PointVar)> = Vec::with_capacity(3 + system_parameters.NUMBER_OF_ATTRIBUTES as usize); 209 | 210 | rhs.push((w, G_w)); 211 | rhs.push((x_0, U)); 212 | rhs.push((x_1, tU)); 213 | rhs.extend(y.iter().copied().zip(M.iter().copied())); 214 | 215 | verifier.constrain(V, rhs); 216 | 217 | verifier.verify_compact(&self.0).or(Err(CredentialError::VerificationFailure)) 218 | } 219 | } 220 | 221 | #[cfg(test)] 222 | mod test { 223 | use super::*; 224 | 225 | use crate::user::CredentialRequestConstructor; 226 | 227 | use curve25519_dalek::ristretto::RistrettoPoint; 228 | use curve25519_dalek::traits::IsIdentity; 229 | 230 | use rand::thread_rng; 231 | 232 | #[test] 233 | fn issuance_proof() { 234 | let mut rng = thread_rng(); 235 | let system_parameters = SystemParameters::generate(&mut rng, 3).unwrap(); 236 | let issuer = Issuer::new(&system_parameters, &mut rng); 237 | let mut request = CredentialRequestConstructor::new(&system_parameters); 238 | 239 | request.append_revealed_scalar(Scalar::random(&mut rng)); 240 | request.append_revealed_scalar(Scalar::random(&mut rng)); 241 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 242 | 243 | let credential_request = request.finish(); 244 | let issuance = issuer.issue(credential_request, &mut rng).unwrap(); 245 | let credential = issuance.verify(&system_parameters, &issuer.issuer_parameters); 246 | 247 | assert!(credential.is_ok()); 248 | } 249 | 250 | #[test] 251 | fn issuance_proof_with_plaintext() { 252 | let mut rng = thread_rng(); 253 | let system_parameters = SystemParameters::generate(&mut rng, 5).unwrap(); 254 | let issuer = Issuer::new(&system_parameters, &mut rng); 255 | let message: Vec = vec![1u8]; 256 | let mut request = CredentialRequestConstructor::new(&system_parameters); 257 | 258 | request.append_revealed_scalar(Scalar::random(&mut rng)); 259 | request.append_revealed_scalar(Scalar::random(&mut rng)); 260 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 261 | let _plaintext = request.append_plaintext(&message); 262 | request.append_revealed_scalar(Scalar::random(&mut rng)); 263 | 264 | let credential_request = request.finish(); 265 | let issuance = issuer.issue(credential_request, &mut rng).unwrap(); 266 | let credential = issuance.verify(&system_parameters, &issuer.issuer_parameters); 267 | 268 | assert!(credential.is_ok()); 269 | } 270 | 271 | /// An issuance proof with a plaintext equal to the identity element will fail. 272 | #[test] 273 | #[should_panic(expected = "assertion failed: credential.is_ok()")] 274 | fn issuance_proof_identity_plaintext() { 275 | let mut rng = thread_rng(); 276 | let system_parameters = SystemParameters::generate(&mut rng, 6).unwrap(); 277 | let issuer = Issuer::new(&system_parameters, &mut rng); 278 | let message: Vec = vec![0u8; 30]; 279 | let mut request = CredentialRequestConstructor::new(&system_parameters); 280 | let plaintext = request.append_plaintext(&message); 281 | 282 | assert!(plaintext[0].M1.is_identity()); 283 | 284 | request.append_revealed_scalar(Scalar::random(&mut rng)); 285 | request.append_revealed_scalar(Scalar::random(&mut rng)); 286 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 287 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 288 | request.append_revealed_scalar(Scalar::random(&mut rng)); 289 | 290 | let credential_request = request.finish(); 291 | let issuance = issuer.issue(credential_request, &mut rng).unwrap(); 292 | let credential = issuance.verify(&system_parameters, &issuer.issuer_parameters); 293 | 294 | assert!(credential.is_ok()); 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/nizk/mod.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - isis agora lovecruft 9 | 10 | //! Non-interactive zero-knowledge proofs (NIZKs). 11 | 12 | pub mod encryption; 13 | pub mod issuance; 14 | pub mod presentation; 15 | -------------------------------------------------------------------------------- /src/nizk/presentation.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - isis agora lovecruft 9 | 10 | //! Non-interactive zero-knowledge proofs (NIZKs) of credential presentation. 11 | 12 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 13 | use alloc::vec::Vec; 14 | #[cfg(all(not(feature = "alloc"), feature = "std"))] 15 | use std::vec::Vec; 16 | 17 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 18 | use alloc::string::String; 19 | 20 | #[cfg(not(feature = "std"))] 21 | use core::ops::Index; 22 | #[cfg(feature = "std")] 23 | use std::ops::Index; 24 | 25 | use curve25519_dalek::scalar::Scalar; 26 | use curve25519_dalek::ristretto::RistrettoPoint; 27 | 28 | use rand_core::CryptoRng; 29 | use rand_core::RngCore; 30 | 31 | use zkp::CompactProof; 32 | use zkp::Transcript; 33 | // XXX do we want/need batch proof verification? 34 | // use zkp::toolbox::batch_verifier::BatchVerifier; 35 | use zkp::toolbox::SchnorrCS; 36 | use zkp::toolbox::prover::Prover; 37 | use zkp::toolbox::prover::PointVar as ProverPointVar; 38 | use zkp::toolbox::prover::ScalarVar as ProverScalarVar; 39 | use zkp::toolbox::verifier::Verifier; 40 | use zkp::toolbox::verifier::PointVar as VerifierPointVar; 41 | use zkp::toolbox::verifier::ScalarVar as VerifierScalarVar; 42 | 43 | use crate::amacs::Attribute; 44 | use crate::amacs::EncryptedAttribute; 45 | use crate::credential::AnonymousCredential; 46 | use crate::errors::CredentialError; 47 | use crate::issuer::Issuer; 48 | use crate::nizk::encryption::ProofOfEncryption; 49 | use crate::parameters::{IssuerParameters, SystemParameters}; 50 | use crate::symmetric::Keypair as SymmetricKeypair; 51 | 52 | /// An incredibly shitty and inelegant hashmap-like structure to store/"index" 53 | /// hidden scalar attributes during construction of a [`ProofOfValidCredential`]. 54 | struct ProverHiddenScalars(Vec<(usize, ProverScalarVar)>); 55 | 56 | /// An incredibly shitty and inelegant hashmap-like structure to store/"index" 57 | /// corresponding hidden scalar basepoints during construction of a 58 | /// [`ProofOfValidCredential`]. 59 | struct ProverHiddenScalarBasepoints(Vec<(usize, ProverPointVar)>); 60 | 61 | /// An incredibly shitty and inelegant hashmap-like structure to store/"index" 62 | /// hidden scalar attributes during verification of a [`ProofOfValidCredential`]. 63 | struct VerifierHiddenScalars(Vec<(usize, VerifierScalarVar)>); 64 | 65 | /// An incredibly shitty and inelegant hashmap-like structure to store/"index" 66 | /// corresponding hidden scalar basepoints during verification of a 67 | /// [`ProofOfValidCredential`]. 68 | struct VerifierHiddenScalarBasepoints(Vec<(usize, VerifierPointVar)>); 69 | 70 | macro_rules! construct_hidden_scalar_variant { 71 | ($scalar_type: ty, $basepoint_type: ty, $scalar_var: ty, $basepoint_var: ty) => { 72 | impl Index for $scalar_type { 73 | type Output = $scalar_var; 74 | 75 | fn index(&self, i: usize) -> &Self::Output { 76 | for (index, scalar) in self.0.iter() { 77 | if *index == i { 78 | return scalar; 79 | } 80 | } 81 | panic!() 82 | } 83 | } 84 | 85 | impl $scalar_type { 86 | pub(crate) fn push(&mut self, item: (usize, $scalar_var)) { 87 | self.0.push(item); 88 | } 89 | } 90 | 91 | impl Index for $basepoint_type { 92 | type Output = $basepoint_var; 93 | 94 | fn index(&self, i: usize) -> &Self::Output { 95 | for (index, scalar) in self.0.iter() { 96 | if *index == i { 97 | return scalar; 98 | } 99 | } 100 | panic!() 101 | } 102 | } 103 | 104 | impl $basepoint_type { 105 | pub(crate) fn push(&mut self, item: (usize, $basepoint_var)) { 106 | self.0.push(item); 107 | } 108 | } 109 | } 110 | } 111 | 112 | construct_hidden_scalar_variant!(ProverHiddenScalars, ProverHiddenScalarBasepoints, ProverScalarVar, ProverPointVar); 113 | construct_hidden_scalar_variant!(VerifierHiddenScalars, VerifierHiddenScalarBasepoints, VerifierScalarVar, VerifierPointVar); 114 | 115 | /// A proof-of-knowledge of a valid `Credential` and its attributes, 116 | /// which may be either hidden or revealed. 117 | // XXX the commitments should be compressed 118 | pub struct ProofOfValidCredential { 119 | proof: CompactProof, 120 | proofs_of_encryption: Vec<(u16, ProofOfEncryption)>, 121 | encrypted_attributes: Vec, 122 | hidden_scalar_indices: Vec, 123 | C_x_0: RistrettoPoint, 124 | C_x_1: RistrettoPoint, 125 | C_V: RistrettoPoint, 126 | C_y: Vec, 127 | } 128 | 129 | impl ProofOfValidCredential { 130 | /// Create a [`ProofOfValidCredential`]. 131 | /// 132 | /// # Warning 133 | /// 134 | /// If there are any [`EitherPoint`]s in the `credential`'s attributes, they 135 | /// will be treated as if they are meant to be publicly revealed rather than 136 | /// encrypted. If you need them to be encrypted for this credential 137 | /// presentation, you *must* call `credential.hide_attribute()` with their 138 | /// indices *before* creating this proof. 139 | pub(crate) fn prove( 140 | system_parameters: &SystemParameters, 141 | issuer_parameters: &IssuerParameters, 142 | credential: &AnonymousCredential, 143 | keypair: Option<&SymmetricKeypair>, 144 | csprng: &mut C, 145 | ) -> Result 146 | where 147 | C: RngCore + CryptoRng, 148 | { 149 | // If a keypair was not supplied and we have encrypted group element attributes, bail early. 150 | if keypair.is_none() { 151 | for attribute in credential.attributes.iter() { 152 | match attribute { 153 | Attribute::SecretPoint(_) => return Err(CredentialError::NoSymmetricKey), 154 | _ => continue, 155 | } 156 | } 157 | } 158 | 159 | let NUMBER_OF_ATTRIBUTES = system_parameters.NUMBER_OF_ATTRIBUTES as usize; 160 | 161 | // Choose a nonce for the commitments. 162 | let z_: Scalar = Scalar::random(csprng); 163 | let z_0_: Scalar = (-credential.amac.t * z_).reduce(); 164 | 165 | // Commit to the credential attributes, and store the hidden scalar attributes in H_s. 166 | let mut C_y_: Vec = Vec::with_capacity(NUMBER_OF_ATTRIBUTES); 167 | let mut H_s_: Vec<(usize, RistrettoPoint, Scalar)> = Vec::new(); 168 | 169 | for (i, attribute) in credential.attributes.iter().enumerate() { 170 | match attribute { 171 | Attribute::PublicPoint(_) => C_y_.push(system_parameters.G_y[i] * z_), 172 | Attribute::EitherPoint(_) => C_y_.push(system_parameters.G_y[i] * z_), 173 | Attribute::SecretPoint(p) => C_y_.push(system_parameters.G_y[i] * z_ + p.M1), 174 | Attribute::PublicScalar(_) => C_y_.push(system_parameters.G_y[i] * z_), 175 | Attribute::SecretScalar(m) => { 176 | C_y_.push(system_parameters.G_y[i] * z_ + system_parameters.G_m[i] * *m); 177 | H_s_.push((i, system_parameters.G_m[i], *m)); 178 | }, 179 | }; 180 | } 181 | let C_x_0_: RistrettoPoint = (system_parameters.G_x_0 * z_) + credential.amac.U; 182 | let C_x_1_: RistrettoPoint = (system_parameters.G_x_1 * z_) + (credential.amac.U * credential.amac.t); 183 | let C_V_: RistrettoPoint = (system_parameters.G_V * z_) + credential.amac.V; 184 | let Z_: RistrettoPoint = issuer_parameters.I * z_; 185 | 186 | // Create a transcript and prover. 187 | let mut transcript = Transcript::new(b"2019/1416 anonymous credential"); 188 | let mut prover = Prover::new(b"2019/1416 presentation proof", &mut transcript); 189 | 190 | // Feed the domain separators for the Camenisch-Stadler secrets into the protocol transcript. 191 | let z = prover.allocate_scalar(b"z", z_); 192 | let z_0 = prover.allocate_scalar(b"z_0", z_0_); 193 | let t = prover.allocate_scalar(b"t", credential.amac.t); 194 | 195 | let mut H_s = ProverHiddenScalars(Vec::with_capacity(H_s_.len())); 196 | let mut hidden_scalar_indices: Vec = Vec::new(); 197 | // XXX assert number of attributes is less than 2^16-1 198 | 199 | for (i, _basepoint, scalar) in H_s_.iter() { 200 | // XXX Fix zkp crate to take Strings 201 | //H_s.push(prover.allocate_scalar(format!(b"H_s_{}", i), scalar)); 202 | H_s.push((*i, prover.allocate_scalar(b"m", *scalar))); 203 | hidden_scalar_indices.push(*i as u16); 204 | } 205 | 206 | // Feed in the domain separators and values for the publics into the transcript. 207 | let (I, _) = prover.allocate_point(b"I", issuer_parameters.I); 208 | let (C_x_1, _) = prover.allocate_point(b"C_x_1", C_x_1_); 209 | let (C_x_0, _) = prover.allocate_point(b"C_x_0", C_x_0_); 210 | let (G_x_0, _) = prover.allocate_point(b"G_x_0", system_parameters.G_x_0); 211 | let (G_x_1, _) = prover.allocate_point(b"G_x_1", system_parameters.G_x_1); 212 | 213 | let mut C_y: Vec = Vec::with_capacity(NUMBER_OF_ATTRIBUTES); 214 | let mut G_y: Vec = Vec::with_capacity(NUMBER_OF_ATTRIBUTES); 215 | 216 | // We only prove knowledge of commitment openings for hidden scalar 217 | // attributes and all revealed attributes; for hidden group element 218 | // attributes we use proofs of encryption. 219 | for (i, commitment) in C_y_.iter().enumerate() { 220 | match credential.attributes[i] { 221 | Attribute::SecretPoint { .. } => continue, 222 | _ => { 223 | // XXX Fix zkp crate to take Strings 224 | //let (C_y_i, _) = prover.allocate_point(format!(b"C_y_{}", i), commitment); 225 | let (C_y_i, _) = prover.allocate_point(b"C_y", *commitment); 226 | 227 | C_y.push(C_y_i); 228 | }, 229 | }; 230 | } 231 | 232 | for (_i, basepoint) in system_parameters.G_y.iter().enumerate() { 233 | // XXX Fix zkp crate to take Strings 234 | // let (G_y_i, _) = prover.allocate_point(format!(b"G_y_{}", _i), basepoint); 235 | let (G_y_i, _) = prover.allocate_point(b"G_y", *basepoint); 236 | 237 | G_y.push(G_y_i); 238 | } 239 | 240 | let mut G_m = ProverHiddenScalarBasepoints(Vec::with_capacity(H_s_.len())); 241 | 242 | for (i, basepoint, _scalar) in H_s_.iter() { 243 | // XXX Fix zkp crate to take Strings 244 | // let (G_m_i, _) = prover.allocate_point(format!(b"G_m_{}", i), basepoint); 245 | let (G_m_i, _) = prover.allocate_point(b"G_m", *basepoint); 246 | 247 | G_m.push((*i, G_m_i)); 248 | } 249 | 250 | // Put the calculation of Z last so that we can use merlin's 251 | // "debug-transcript" feature to detect if anything else was different. 252 | let (Z, _) = prover.allocate_point(b"Z", Z_); 253 | 254 | // Constraint #1: Prove knowledge of the nonce, z, and the correctness of the AMAC with Z. 255 | // Z = I * z 256 | prover.constrain(Z, vec![(z, I)]); 257 | 258 | // Constraint #2: Prove correctness of t and U. 259 | // C_x_1 = C_x_0 * t + G_x_0 * z_0 + G_x_1 * z 260 | // G_x_1 * z + U * t = G_x_0 * zt + U * t + G_x_0 * -tz + G_x_1 * z 261 | // G_x_1 * z + U * t = U * t + G_x_1 * z 262 | prover.constrain(C_x_1, vec![(t, C_x_0), (z_0, G_x_0), (z, G_x_1)]); 263 | 264 | // Constraint #3: Prove correctness/validation of attributes. 265 | // C_y_i = { G_y_i * z + G_m_i * m_i if i is a hidden scalar attribute 266 | // { G_y_i * z if i is a revealed attribute 267 | for (i, C_y_i) in C_y.iter().enumerate() { 268 | match credential.attributes[i] { 269 | Attribute::SecretPoint(_) => continue, 270 | Attribute::SecretScalar(_) => prover.constrain(*C_y_i, vec![(z, G_y[i]), (H_s[i], G_m[i])]), 271 | _ => prover.constrain(*C_y_i, vec![(z, G_y[i])]), 272 | } 273 | } 274 | // Notes: 275 | // 276 | // 1. Prover recalculates Z', so it is not sent. 277 | // 2. C_V, the commitment to the actual AMAC (recall that the t and U 278 | // values in the AMAC are nonces), is sent, but V is kept private to 279 | // provide anonymity, so we do not prove anything about it. 280 | // 3; That z_0 actually equals -tz (mod \ell) is never proven, but this 281 | // should not matter as we prove knowledge of t and z, and constraint 282 | // #2 would never pass verification if either were other than the 283 | // values used to compute z_0. 284 | let proof = prover.prove_compact(); 285 | 286 | // Construct proofs of correct encryptions for the hidden group attributes. 287 | let mut proofs_of_encryption: Vec<(u16, ProofOfEncryption)> = Vec::new(); 288 | 289 | // Rebuild the attributes for our credential to send to the verifier. 290 | let mut encrypted_attributes: Vec = Vec::with_capacity(system_parameters.NUMBER_OF_ATTRIBUTES as usize); 291 | 292 | // XXX don't we also need DLEQ between the plaintext here and that in the commitments above? 293 | for (i, attribute) in credential.attributes.iter().enumerate() { 294 | match attribute { 295 | Attribute::PublicScalar(x) => encrypted_attributes.push(EncryptedAttribute::PublicScalar(*x)), 296 | Attribute::SecretScalar(_) => encrypted_attributes.push(EncryptedAttribute::SecretScalar), 297 | Attribute::PublicPoint(x) => encrypted_attributes.push(EncryptedAttribute::PublicPoint(*x)), 298 | Attribute::EitherPoint(x) => encrypted_attributes.push(EncryptedAttribute::PublicPoint(x.M1)), 299 | Attribute::SecretPoint(pt) => { 300 | // The .unwrap() here can never panic because we check above that the key isn't 301 | // None if we have encrypted group element attributes. 302 | let proof_of_encryption = ProofOfEncryption::prove(&system_parameters, &pt, 303 | i as u16, &keypair.unwrap(), &z_); 304 | 305 | proofs_of_encryption.push((i as u16, proof_of_encryption)); 306 | encrypted_attributes.push(EncryptedAttribute::SecretPoint); 307 | }, 308 | } 309 | } 310 | 311 | Ok(ProofOfValidCredential { 312 | proof: proof, 313 | proofs_of_encryption: proofs_of_encryption, 314 | encrypted_attributes: encrypted_attributes, 315 | hidden_scalar_indices: hidden_scalar_indices, 316 | C_x_0: C_x_0_, 317 | C_x_1: C_x_1_, 318 | C_V: C_V_, 319 | C_y: C_y_, 320 | }) 321 | } 322 | 323 | /// Verify a `ProofOfValidCredential`. 324 | pub(crate) fn verify( 325 | &self, 326 | issuer: &Issuer, 327 | ) -> Result<(), CredentialError> 328 | { 329 | let NUMBER_OF_ATTRIBUTES = issuer.system_parameters.NUMBER_OF_ATTRIBUTES as usize; 330 | 331 | // Recompute the prover's Z value. 332 | // 333 | // Let \mathcal{H} denote the set of hidden attributes, both those which are group elements 334 | // and those which are scalars. 335 | // 336 | // Let M_i be a revealed group element attribute, if so, and otherwise if a revealed scalar 337 | // attribute, m_i, then let M_i be G_m_i * m_i. 338 | // 339 | // Z = C_V - (W + C_x0 * x0 + C_x1 * x1 + 340 | // \sigma_{i \in \mathcal{H}}{C_y_i * y_i} + 341 | // \sigma_{i \notin \mathcal{H}}{(C_y_i + M_i) * y_i}) 342 | let mut Z_ = self.C_V - issuer.amacs_key.W - (self.C_x_0 * issuer.amacs_key.x_0) - (self.C_x_1 * issuer.amacs_key.x_1); 343 | 344 | for (i, attribute) in self.encrypted_attributes.iter().enumerate() { 345 | let x = match attribute { 346 | EncryptedAttribute::PublicScalar(m_i) => self.C_y[i] + (issuer.system_parameters.G_m[i] * m_i), 347 | EncryptedAttribute::SecretScalar => self.C_y[i], 348 | EncryptedAttribute::PublicPoint(M_i) => self.C_y[i] + M_i, 349 | EncryptedAttribute::SecretPoint => self.C_y[i], 350 | }; 351 | Z_ -= x * issuer.amacs_key.y[i]; 352 | } 353 | 354 | // Create a transcript and verifier. 355 | let mut transcript = Transcript::new(b"2019/1416 anonymous credential"); 356 | let mut verifier = Verifier::new(b"2019/1416 presentation proof", &mut transcript); 357 | 358 | // Feed the domain separators for the Camenisch-Stadler secrets into the protocol transcript. 359 | let z = verifier.allocate_scalar(b"z"); 360 | let z_0 = verifier.allocate_scalar(b"z_0"); 361 | let t = verifier.allocate_scalar(b"t"); 362 | 363 | let mut H_s = VerifierHiddenScalars(Vec::new()); 364 | 365 | // XXX fixme this struct won't work here 366 | for i in self.hidden_scalar_indices.iter() { 367 | // XXX Fix zkp crate to take Strings 368 | //H_s.push(*i, verifier.allocate_scalar(format!(b"H_s_{}", i))); 369 | H_s.push((*i as usize, verifier.allocate_scalar(b"m"))); 370 | } 371 | 372 | // Feed in the domain separators and values for the publics into the transcript. 373 | let I = verifier.allocate_point(b"I", issuer.issuer_parameters.I.compress())?; 374 | let C_x_1 = verifier.allocate_point(b"C_x_1", self.C_x_1.compress())?; 375 | let C_x_0 = verifier.allocate_point(b"C_x_0", self.C_x_0.compress())?; 376 | let G_x_0 = verifier.allocate_point(b"G_x_0", issuer.system_parameters.G_x_0.compress())?; 377 | let G_x_1 = verifier.allocate_point(b"G_x_1", issuer.system_parameters.G_x_1.compress())?; 378 | 379 | let mut C_y: Vec = Vec::with_capacity(NUMBER_OF_ATTRIBUTES); 380 | let mut G_y: Vec = Vec::with_capacity(NUMBER_OF_ATTRIBUTES); 381 | 382 | // We only prove knowledge of commitment openings for hidden scalar 383 | // attributes and all revealed attributes; for hidden group element 384 | // attributes we use proofs of encryption. 385 | for (i, commitment) in self.C_y.iter().enumerate() { 386 | match self.encrypted_attributes[i] { 387 | EncryptedAttribute::SecretPoint { .. } => continue, 388 | _ => { 389 | // XXX Fix zkp crate to take Strings 390 | // C_y.push(verifier.allocate_point(format!(b"C_y_{}", i), commitment.compress())?); 391 | C_y.push(verifier.allocate_point(b"C_y", commitment.compress())?); 392 | }, 393 | }; 394 | } 395 | 396 | for (_i, basepoint) in issuer.system_parameters.G_y.iter().enumerate() { 397 | // XXX Fix zkp crate to take Strings 398 | // G_y.push(verifier.allocate_point(format!(b"G_y_{}", _i), basepoint.compress())?); 399 | G_y.push(verifier.allocate_point(b"G_y", basepoint.compress())?); 400 | } 401 | 402 | let mut G_m = VerifierHiddenScalarBasepoints(Vec::with_capacity(H_s.0.len())); 403 | 404 | for (i, _) in H_s.0.iter() { 405 | // XXX Fix zkp crate to take Strings 406 | // G_m.push(verifier.allocate_point(format!(b"G_m_{}", i), issuer.system_parameters.G_m[i].compress())?); 407 | G_m.push((*i, verifier.allocate_point(b"G_m", issuer.system_parameters.G_m[*i].compress())?)); 408 | } 409 | 410 | // Put the recalculation of Z last so that we can use merlin's 411 | // "debug-transcript" feature to detect if anything else was different. 412 | let Z = verifier.allocate_point(b"Z", Z_.compress())?; 413 | 414 | // Constraint #1: Prove knowledge of the nonce, z, and the correctness of the AMAC with Z. 415 | // Z = I * z 416 | verifier.constrain(Z, vec![(z, I)]); 417 | 418 | // Constraint #2: Prove correctness of t and U. 419 | // C_x_1 = C_x_0 * t + G_x_0 * z_0 + G_x_1 * z 420 | // G_x_1 * z + U * t = G_x_0 * zt + U * t + G_x_0 * -tz + G_x_1 * z 421 | // G_x_1 * z + U * t = U * t + G_x_1 * z 422 | verifier.constrain(C_x_1, vec![(t, C_x_0), (z_0, G_x_0), (z, G_x_1)]); 423 | 424 | // Constraint #3: Prove correctness/validation of attributes. 425 | // C_y_i = { G_y_i * z + G_m_i * m_i if i is a hidden scalar attribute 426 | // { G_y_i * z if i is a revealed attribute 427 | for (i, C_y_i) in C_y.iter().enumerate() { 428 | match self.encrypted_attributes[i] { 429 | EncryptedAttribute::SecretPoint => continue, 430 | EncryptedAttribute::SecretScalar => verifier.constrain(*C_y_i, vec![(z, G_y[i]), (H_s[i], G_m[i])]), 431 | _ => verifier.constrain(*C_y_i, vec![(z, G_y[i])]), 432 | } 433 | } 434 | 435 | verifier.verify_compact(&self.proof).or(Err(CredentialError::VerificationFailure))?; 436 | 437 | // Check the proofs of correct encryptions and fail if any cannot be verified. 438 | for (_i, proof_of_encryption) in self.proofs_of_encryption.iter() { 439 | proof_of_encryption.verify(&issuer.system_parameters)?; 440 | } 441 | 442 | Ok(()) 443 | } 444 | } 445 | 446 | #[cfg(test)] 447 | mod test { 448 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 449 | use alloc::string::String; 450 | #[cfg(all(feature = "std", not(feature = "alloc")))] 451 | use std::string::String; 452 | 453 | use super::*; 454 | 455 | use crate::symmetric::Plaintext; 456 | use crate::user::CredentialRequestConstructor; 457 | 458 | use rand::thread_rng; 459 | 460 | #[test] 461 | fn credential_proof_10_attributes() { 462 | let mut rng = thread_rng(); 463 | let system_parameters = SystemParameters::generate(&mut rng, 10).unwrap(); 464 | let issuer = Issuer::new(&system_parameters, &mut rng); 465 | let mut request = CredentialRequestConstructor::new(&system_parameters); 466 | 467 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 468 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 469 | request.append_revealed_scalar(Scalar::random(&mut rng)); 470 | request.append_revealed_scalar(Scalar::random(&mut rng)); 471 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 472 | request.append_revealed_scalar(Scalar::random(&mut rng)); 473 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 474 | request.append_revealed_scalar(Scalar::random(&mut rng)); 475 | request.append_revealed_scalar(Scalar::random(&mut rng)); 476 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 477 | 478 | let credential_request = request.finish(); 479 | let issuance = issuer.issue(credential_request, &mut rng).unwrap(); 480 | let credential = issuance.verify(&system_parameters, &issuer.issuer_parameters).unwrap(); 481 | let (keypair, _) = SymmetricKeypair::generate(&system_parameters, &mut rng); 482 | let proof = ProofOfValidCredential::prove(&system_parameters, &issuer.issuer_parameters, &credential, Some(&keypair), &mut rng); 483 | 484 | assert!(proof.is_ok()); 485 | 486 | let verification = proof.unwrap().verify(&issuer); 487 | 488 | assert!(verification.is_ok()); 489 | } 490 | 491 | #[test] 492 | fn credential_proof_10_attributes_with_plaintext() { 493 | let mut rng = thread_rng(); 494 | let system_parameters = SystemParameters::generate(&mut rng, 10).unwrap(); 495 | let issuer = Issuer::new(&system_parameters, &mut rng); 496 | let mut request = CredentialRequestConstructor::new(&system_parameters); 497 | let message = String::from("This is a tsunami alert test..").into_bytes(); 498 | let _plaintext = request.append_plaintext(&message); 499 | 500 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 501 | request.append_revealed_scalar(Scalar::random(&mut rng)); 502 | request.append_revealed_scalar(Scalar::random(&mut rng)); 503 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 504 | request.append_revealed_scalar(Scalar::random(&mut rng)); 505 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 506 | request.append_revealed_scalar(Scalar::random(&mut rng)); 507 | request.append_revealed_scalar(Scalar::random(&mut rng)); 508 | request.append_revealed_point(RistrettoPoint::random(&mut rng)); 509 | 510 | let credential_request = request.finish(); 511 | let issuance = issuer.issue(credential_request, &mut rng).unwrap(); 512 | let mut credential = issuance.verify(&system_parameters, &issuer.issuer_parameters).unwrap(); 513 | 514 | //credential.hide_attribute(0).unwrap(); 515 | credential.hide_attribute(2).unwrap(); 516 | 517 | let (keypair, _) = SymmetricKeypair::generate(&system_parameters, &mut rng); 518 | let proof = credential.show(&system_parameters, &issuer.issuer_parameters, Some(&keypair), &mut rng); 519 | 520 | assert!(proof.is_ok()); 521 | 522 | let verification = proof.unwrap().verify(&issuer); 523 | 524 | assert!(verification.is_ok()); 525 | } 526 | 527 | #[test] 528 | fn credential_proof_1_plaintext() { 529 | let mut rng = thread_rng(); 530 | let system_parameters = SystemParameters::generate(&mut rng, 1).unwrap(); 531 | let issuer = Issuer::new(&system_parameters, &mut rng); 532 | let mut request = CredentialRequestConstructor::new(&system_parameters); 533 | let message = String::from("This is a tsunami alert test..").into_bytes(); 534 | let _plaintext = request.append_plaintext(&message); 535 | let credential_request = request.finish(); 536 | let issuance = issuer.issue(credential_request, &mut rng).unwrap(); 537 | let credential = issuance.verify(&system_parameters, &issuer.issuer_parameters).unwrap(); 538 | let (keypair, _) = SymmetricKeypair::generate(&system_parameters, &mut rng); 539 | let proof = credential.show(&system_parameters, &issuer.issuer_parameters, Some(&keypair), &mut rng); 540 | 541 | assert!(proof.is_ok()); 542 | } 543 | 544 | #[test] 545 | fn credential_proof_1_plaintext_hidden() { 546 | let mut rng = thread_rng(); 547 | let system_parameters = SystemParameters::generate(&mut rng, 1).unwrap(); 548 | let issuer = Issuer::new(&system_parameters, &mut rng); 549 | let mut request = CredentialRequestConstructor::new(&system_parameters); 550 | let message = String::from("This is a tsunami alert test..").into_bytes(); 551 | let _plaintext = request.append_plaintext(&message); 552 | let credential_request = request.finish(); 553 | let issuance = issuer.issue(credential_request, &mut rng).unwrap(); 554 | let mut credential = issuance.verify(&system_parameters, &issuer.issuer_parameters).unwrap(); 555 | 556 | credential.hide_attribute(0).unwrap(); 557 | 558 | let (keypair, _) = SymmetricKeypair::generate(&system_parameters, &mut rng); 559 | let proof = credential.show(&system_parameters, &issuer.issuer_parameters, Some(&keypair), &mut rng); 560 | 561 | assert!(proof.is_ok()); 562 | 563 | let verification = proof.unwrap().verify(&issuer); 564 | 565 | assert!(verification.is_ok()); 566 | } 567 | 568 | #[test] 569 | fn credential_proof_1_scalar_revealed() { 570 | let mut rng = thread_rng(); 571 | let system_parameters = SystemParameters::generate(&mut rng, 1).unwrap(); 572 | let issuer = Issuer::new(&system_parameters, &mut rng); 573 | let mut request = CredentialRequestConstructor::new(&system_parameters); 574 | 575 | request.append_revealed_scalar(Scalar::random(&mut rng)); 576 | 577 | let credential_request = request.finish(); 578 | let issuance = issuer.issue(credential_request, &mut rng).unwrap(); 579 | let credential = issuance.verify(&system_parameters, &issuer.issuer_parameters).unwrap(); 580 | let presentation = ProofOfValidCredential::prove(&system_parameters, &issuer.issuer_parameters, &credential, None, &mut rng).unwrap(); 581 | let verification = issuer.verify(&presentation); 582 | 583 | assert!(verification.is_ok()); 584 | } 585 | 586 | #[test] 587 | fn credential_proof_scalar_and_group_element_switch() { 588 | let mut rng = thread_rng(); 589 | let system_parameters = SystemParameters::generate(&mut rng, 2).unwrap(); 590 | let issuer = Issuer::new(&system_parameters, &mut rng); 591 | let mut request1 = CredentialRequestConstructor::new(&system_parameters); 592 | 593 | request1.append_revealed_scalar(Scalar::random(&mut rng)); 594 | request1.append_revealed_point(RistrettoPoint::random(&mut rng)); 595 | 596 | let credential_request1 = request1.finish(); 597 | let issuance1 = issuer.issue(credential_request1, &mut rng).unwrap(); 598 | let credential1 = issuance1.verify(&system_parameters, &issuer.issuer_parameters).unwrap(); 599 | let presentation1 = credential1.show(&system_parameters, &issuer.issuer_parameters, None, &mut rng).unwrap(); 600 | let verification1 = issuer.verify(&presentation1); 601 | 602 | assert!(verification1.is_ok()); 603 | 604 | let mut request2 = CredentialRequestConstructor::new(&system_parameters); 605 | 606 | request2.append_revealed_point(RistrettoPoint::random(&mut rng)); 607 | request2.append_revealed_scalar(Scalar::random(&mut rng)); 608 | 609 | let credential_request2 = request2.finish(); 610 | let issuance2 = issuer.issue(credential_request2, &mut rng).unwrap(); 611 | let credential2 = issuance2.verify(&system_parameters, &issuer.issuer_parameters).unwrap(); 612 | let presentation2 = credential2.show(&system_parameters, &issuer.issuer_parameters, None, &mut rng).unwrap(); 613 | let verification2 = issuer.verify(&presentation2); 614 | 615 | assert!(verification2.is_ok()); 616 | } 617 | 618 | #[test] 619 | #[should_panic(expected = "assertion failed: verification.is_ok()")] 620 | fn bad_credential_proof_1_scalar_revealed() { 621 | let mut rng = thread_rng(); 622 | let system_parameters = SystemParameters::generate(&mut rng, 1).unwrap(); 623 | let issuer = Issuer::new(&system_parameters, &mut rng); 624 | let mut request = CredentialRequestConstructor::new(&system_parameters); 625 | 626 | request.append_revealed_scalar(Scalar::random(&mut rng)); 627 | 628 | let credential_request = request.finish(); 629 | let issuance = issuer.issue(credential_request, &mut rng).unwrap(); 630 | let mut credential = issuance.verify(&system_parameters, &issuer.issuer_parameters).unwrap(); 631 | 632 | credential.attributes[0] = Attribute::PublicScalar(Scalar::random(&mut rng)); 633 | 634 | let presentation = ProofOfValidCredential::prove(&system_parameters, &issuer.issuer_parameters, &credential, None, &mut rng).unwrap(); 635 | let verification = issuer.verify(&presentation); 636 | 637 | assert!(verification.is_ok()); 638 | } 639 | } 640 | -------------------------------------------------------------------------------- /src/parameters.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // Copyright (c) 2018 Signal Foundation 6 | // See LICENSE for licensing information. 7 | // 8 | // Authors: 9 | // - isis agora lovecruft 10 | 11 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 12 | use alloc::vec::Vec; 13 | #[cfg(all(not(feature = "alloc"), feature = "std"))] 14 | use std::vec::Vec; 15 | 16 | use curve25519_dalek::constants::RISTRETTO_BASEPOINT_COMPRESSED; 17 | use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT; 18 | use curve25519_dalek::ristretto::CompressedRistretto; 19 | use curve25519_dalek::ristretto::RistrettoPoint; 20 | use curve25519_dalek::scalar::Scalar; 21 | use curve25519_dalek::traits::Identity; 22 | 23 | use serde::{self, Serialize, Deserialize, Serializer, Deserializer}; 24 | use serde::de::Visitor; 25 | 26 | use rand_core::CryptoRng; 27 | use rand_core::RngCore; 28 | 29 | use crate::amacs::SecretKey; 30 | use crate::errors::CredentialError; 31 | 32 | /// Given the `number_of_attributes`, calculate the size of a serialised 33 | /// [`SystemParameters`], in bytes. 34 | pub(crate) fn sizeof_system_parameters(number_of_attributes: u32) -> usize { 35 | // G_y is always at least three elements 36 | if number_of_attributes < 3 { 37 | return 32 * (5 + 3 + number_of_attributes as usize + 4) + 4 38 | } 39 | 32 * (5 + (2 * number_of_attributes as usize) + 4) + 4 40 | } 41 | 42 | /// The `SystemParameters` define the system-wide context in which the anonymous 43 | /// credentials scheme and its proofs are constructed within. 44 | /// 45 | /// They are defined as \\( \( \mathbb{G}, q, G, G_{w}, G_{w'}, G_{x_{0}}, G_{x_{1}}, 46 | /// G_{y_{0}}, \ldots G_{y_{n}}, G_{m_{0}}, \ldots, G_{m_{n}}, G_V \) \\) 47 | /// 48 | /// where: 49 | /// 50 | /// * \\( \mathbb{G} \\) is a group with order \\( q \\), where 51 | /// `q` is a `k`-bit prime (`k = 255` in the case of using the Ristretto255 52 | /// group), 53 | /// * `G*` generators of `\\( \mathbb{G} \\)`, 54 | /// * `\\( \log_G(G*) \\)` is unknown, that is, all generators `G*` are chosen 55 | /// as a distinguished basepoint which is orthogonal to `g`. 56 | /// * `n` is the [`NUMBER_OF_ATTRIBUTES`] in the message space. 57 | /// 58 | /// Additionally, for the [`symmetric`]-key verifiable encryption scheme, we 59 | /// require three more generators chosen orthogonally, 60 | /// \\( (G_a, G_a0, G_a1) \in \mathbb{G} \\), chosen as detailed above. 61 | #[derive(Clone, Debug, Eq, PartialEq)] 62 | pub struct SystemParameters { 63 | /// The number of credential attributes these system parameters support. 64 | pub NUMBER_OF_ATTRIBUTES: u32, 65 | pub(crate) G: RistrettoPoint, 66 | pub(crate) G_w: RistrettoPoint, 67 | pub(crate) G_w_prime: RistrettoPoint, 68 | pub(crate) G_x_0: RistrettoPoint, 69 | pub(crate) G_x_1: RistrettoPoint, 70 | pub(crate) G_y: Vec, 71 | pub(crate) G_m: Vec, 72 | pub(crate) G_V: RistrettoPoint, 73 | pub(crate) G_a: RistrettoPoint, 74 | pub(crate) G_a0: RistrettoPoint, 75 | pub(crate) G_a1: RistrettoPoint, 76 | } 77 | 78 | macro_rules! try_deserialise { 79 | ($name:expr, $bytes:expr) => { 80 | match CompressedRistretto($bytes).decompress() { 81 | Some(x) => x, 82 | None => { 83 | #[cfg(feature = "std")] 84 | println!("Could not decode {:?} from bytes: {:?}", $name, $bytes); 85 | return Err(CredentialError::PointDecompressionError); 86 | }, 87 | } 88 | } 89 | } 90 | 91 | impl SystemParameters { 92 | pub fn from_bytes(bytes: &[u8]) -> Result { 93 | let mut index: usize = 0; 94 | let mut chunk = [0u8; 32]; 95 | 96 | let mut tmp = [0u8; 4]; 97 | 98 | tmp.copy_from_slice(&bytes[index..index+4]); index += 4; 99 | let NUMBER_OF_ATTRIBUTES: u32 = u32::from_le_bytes(tmp); 100 | 101 | if bytes.len() != sizeof_system_parameters(NUMBER_OF_ATTRIBUTES) { 102 | return Err(CredentialError::NoSystemParameters); 103 | } 104 | 105 | chunk.copy_from_slice(&bytes[index..index+32]); index += 32; 106 | let G: RistrettoPoint = try_deserialise!("G", chunk); 107 | 108 | chunk.copy_from_slice(&bytes[index..index+32]); index += 32; 109 | let G_w: RistrettoPoint = try_deserialise!("G_w", chunk); 110 | 111 | chunk.copy_from_slice(&bytes[index..index+32]); index += 32; 112 | let G_w_prime: RistrettoPoint = try_deserialise!("G_w_prime", chunk); 113 | 114 | chunk.copy_from_slice(&bytes[index..index+32]); index += 32; 115 | let G_x_0: RistrettoPoint = try_deserialise!("G_x_0", chunk); 116 | 117 | chunk.copy_from_slice(&bytes[index..index+32]); index += 32; 118 | let G_x_1: RistrettoPoint = try_deserialise!("G_x_1", chunk); 119 | 120 | let mut G_y: Vec = Vec::with_capacity(NUMBER_OF_ATTRIBUTES as usize); 121 | 122 | let mut number_of_G_y = NUMBER_OF_ATTRIBUTES; 123 | 124 | if number_of_G_y < 3 { 125 | number_of_G_y = 3; 126 | } 127 | 128 | for i in 0..number_of_G_y { 129 | chunk.copy_from_slice(&bytes[index..index+32]); index += 32; 130 | G_y.push(try_deserialise!(format!("G_y_{}", i), chunk)); 131 | } 132 | 133 | let mut G_m: Vec = Vec::with_capacity(NUMBER_OF_ATTRIBUTES as usize); 134 | 135 | for i in 0..NUMBER_OF_ATTRIBUTES { 136 | chunk.copy_from_slice(&bytes[index..index+32]); index += 32; 137 | G_m.push(try_deserialise!(format!("G_m_{}", i), chunk)); 138 | } 139 | 140 | chunk.copy_from_slice(&bytes[index..index+32]); index += 32; 141 | let G_V: RistrettoPoint = try_deserialise!("G_V", chunk); 142 | 143 | chunk.copy_from_slice(&bytes[index..index+32]); index += 32; 144 | let G_a: RistrettoPoint = try_deserialise!("G_a", chunk); 145 | 146 | chunk.copy_from_slice(&bytes[index..index+32]); index += 32; 147 | let G_a0: RistrettoPoint = try_deserialise!("G_a0", chunk); 148 | 149 | chunk.copy_from_slice(&bytes[index..index+32]); 150 | let G_a1: RistrettoPoint = try_deserialise!("G_a1", chunk); 151 | 152 | Ok(SystemParameters { NUMBER_OF_ATTRIBUTES, G, G_w, G_w_prime, G_x_0, G_x_1, G_y, G_m, G_V, G_a, G_a0, G_a1 }) 153 | } 154 | 155 | pub fn to_bytes(&self) -> Vec { 156 | let mut v: Vec = Vec::with_capacity(sizeof_system_parameters(self.NUMBER_OF_ATTRIBUTES)); 157 | 158 | v.extend(&self.NUMBER_OF_ATTRIBUTES.to_le_bytes()); 159 | v.extend(self.G.compress().to_bytes().iter()); 160 | v.extend(self.G_w.compress().to_bytes().iter()); 161 | v.extend(self.G_w_prime.compress().to_bytes().iter()); 162 | v.extend(self.G_x_0.compress().to_bytes().iter()); 163 | v.extend(self.G_x_1.compress().to_bytes().iter()); 164 | 165 | let mut number_of_G_y = self.NUMBER_OF_ATTRIBUTES; 166 | 167 | if number_of_G_y < 3 { 168 | number_of_G_y = 3; 169 | } 170 | 171 | for i in 0..number_of_G_y as usize { 172 | v.extend(self.G_y[i].compress().to_bytes().iter()); 173 | } 174 | 175 | for i in 0..self.NUMBER_OF_ATTRIBUTES as usize { 176 | v.extend(self.G_m[i].compress().to_bytes().iter()); 177 | } 178 | 179 | v.extend(self.G_V.compress().to_bytes().iter()); 180 | v.extend(self.G_a.compress().to_bytes().iter()); 181 | v.extend(self.G_a0.compress().to_bytes().iter()); 182 | v.extend(self.G_a1.compress().to_bytes().iter()); 183 | v 184 | } 185 | } 186 | 187 | impl_serde_with_to_bytes_and_from_bytes!(SystemParameters, 188 | "A valid byte sequence representing a SystemParameters"); 189 | 190 | impl SystemParameters { 191 | /// Generate the [`SystemParameters`] randomly via an RNG. 192 | /// 193 | /// In order to never have a secret scalar in memory for generating the 194 | /// orthogonal basepoint, this method can be used to obtain bytes from the 195 | /// `csprng` and attempt to decompress them into a basepoint. 196 | pub fn hash_and_pray( 197 | csprng: &mut R, 198 | number_of_attributes: u32, 199 | ) -> Result 200 | where 201 | R: RngCore + CryptoRng, 202 | { 203 | let mut tmp: [u8; 32] = [0u8; 32]; 204 | let mut G_w: Option = None; 205 | let mut G_w_prime: Option = None; 206 | let mut G_x_0: Option = None; 207 | let mut G_x_1: Option = None; 208 | let mut G_y: Vec = Vec::with_capacity(number_of_attributes as usize); 209 | let mut G_m: Vec = Vec::with_capacity(number_of_attributes as usize); 210 | let mut G_V: Option = None; 211 | let mut G_a: Option = None; 212 | let mut G_a0: Option = None; 213 | let mut G_a1: Option = None; 214 | 215 | while G_w.is_none() { 216 | csprng.fill_bytes(&mut tmp); 217 | G_w = CompressedRistretto(tmp).decompress(); 218 | } 219 | 220 | while G_w_prime.is_none() { 221 | csprng.fill_bytes(&mut tmp); 222 | G_w_prime = CompressedRistretto(tmp).decompress(); 223 | } 224 | 225 | while G_x_0.is_none() { 226 | csprng.fill_bytes(&mut tmp); 227 | G_x_0 = CompressedRistretto(tmp).decompress(); 228 | } 229 | 230 | while G_x_1.is_none() { 231 | csprng.fill_bytes(&mut tmp); 232 | G_x_1 = CompressedRistretto(tmp).decompress(); 233 | } 234 | 235 | // The number of elements in G_y must always be at least three in order 236 | // to support encrypted group element attributes. 237 | let mut number_of_G_y: usize = number_of_attributes as usize; 238 | 239 | if number_of_G_y < 3 { 240 | number_of_G_y = 3; 241 | } 242 | 243 | for _ in 0..number_of_G_y { 244 | let mut G_y_i: Option = None; 245 | 246 | while G_y_i.is_none() { 247 | csprng.fill_bytes(&mut tmp); 248 | G_y_i = CompressedRistretto(tmp).decompress(); 249 | } 250 | G_y.push(G_y_i.unwrap()); 251 | } 252 | 253 | for _ in 0..number_of_attributes { 254 | let mut G_m_i: Option = None; 255 | 256 | while G_m_i.is_none() { 257 | csprng.fill_bytes(&mut tmp); 258 | G_m_i = CompressedRistretto(tmp).decompress(); 259 | } 260 | G_m.push(G_m_i.unwrap()); 261 | } 262 | 263 | while G_V.is_none() { 264 | csprng.fill_bytes(&mut tmp); 265 | G_V = CompressedRistretto(tmp).decompress(); 266 | } 267 | 268 | while G_a.is_none() { 269 | csprng.fill_bytes(&mut tmp); 270 | G_a = CompressedRistretto(tmp).decompress(); 271 | } 272 | 273 | while G_a0.is_none() { 274 | csprng.fill_bytes(&mut tmp); 275 | G_a0 = CompressedRistretto(tmp).decompress(); 276 | } 277 | 278 | while G_a1.is_none() { 279 | csprng.fill_bytes(&mut tmp); 280 | G_a1 = CompressedRistretto(tmp).decompress(); 281 | } 282 | 283 | let NUMBER_OF_ATTRIBUTES = number_of_attributes; 284 | let G = RISTRETTO_BASEPOINT_POINT; 285 | let G_w = G_w.unwrap(); 286 | let G_w_prime = G_w_prime.unwrap(); 287 | let G_x_0 = G_x_0.unwrap(); 288 | let G_x_1 = G_x_1.unwrap(); 289 | let G_V = G_V.unwrap(); 290 | let G_a = G_a.unwrap(); 291 | let G_a0 = G_a0.unwrap(); 292 | let G_a1 = G_a1.unwrap(); 293 | 294 | // Safety check: all generators should be generators (i.e. not the 295 | // identity element) and be unique. While the chances of this happening 296 | // with a CSPRNG are miniscule, we might have been handed a bad RNG. 297 | let mut generators: Vec = Vec::new(); 298 | 299 | generators.push(RistrettoPoint::identity().compress()); 300 | generators.push(RISTRETTO_BASEPOINT_COMPRESSED); 301 | generators.push(G_w.compress()); 302 | generators.push(G_w_prime.compress()); 303 | generators.push(G_x_0.compress()); 304 | generators.push(G_x_1.compress()); 305 | generators.push(G_V.compress()); 306 | generators.push(G_a.compress()); 307 | generators.push(G_a0.compress()); 308 | generators.push(G_a1.compress()); 309 | 310 | for i in 0..NUMBER_OF_ATTRIBUTES as usize { 311 | generators.push(G_y[i].compress()); 312 | generators.push(G_m[i].compress()); 313 | } 314 | 315 | while generators.len() >= 2 { 316 | let x = generators.pop().unwrap(); 317 | 318 | for i in 0 .. generators.len()-1 { 319 | if x == generators[i] { 320 | return Err(CredentialError::NoSystemParameters); 321 | } 322 | } 323 | } 324 | 325 | Ok(SystemParameters { NUMBER_OF_ATTRIBUTES, G, G_w, G_w_prime, G_x_0, G_x_1, G_y, G_m, G_V, G_a, G_a0, G_a1 }) 326 | } 327 | 328 | /// Generate new system parameters using the 329 | /// [`hash_and_pray`](SystemParameters::hash_and_pray) algorithm. 330 | pub fn generate(csprng: &mut R, number_of_attributes: u32) 331 | -> Result 332 | where 333 | R: RngCore + CryptoRng, 334 | { 335 | SystemParameters::hash_and_pray(csprng, number_of_attributes) 336 | } 337 | } 338 | 339 | /// DOCDOC 340 | #[derive(Clone, Debug, Eq, PartialEq)] 341 | pub struct IssuerParameters { 342 | pub C_W: RistrettoPoint, 343 | pub I: RistrettoPoint, 344 | } 345 | 346 | /// DOCDOC 347 | impl IssuerParameters { 348 | /// DOCDOC 349 | pub fn generate(system_parameters: &SystemParameters, secret_key: &SecretKey) -> IssuerParameters { 350 | let C_W: RistrettoPoint = (system_parameters.G_w * secret_key.w) + 351 | (system_parameters.G_w_prime * secret_key.w_prime); 352 | 353 | let mut I: RistrettoPoint = system_parameters.G_V - 354 | (system_parameters.G_x_0 * secret_key.x_0) - 355 | (system_parameters.G_x_1 * secret_key.x_1); 356 | 357 | for i in 0..system_parameters.NUMBER_OF_ATTRIBUTES as usize { 358 | I -= system_parameters.G_y[i] * secret_key.y[i]; 359 | } 360 | 361 | IssuerParameters { C_W, I } 362 | } 363 | 364 | /// DOCDOC 365 | pub fn from_bytes(bytes: &[u8]) -> Result { 366 | unimplemented!(); 367 | } 368 | 369 | /// DOCDOC 370 | pub fn to_bytes(&self) -> Vec { 371 | unimplemented!(); 372 | } 373 | } 374 | 375 | impl_serde_with_to_bytes_and_from_bytes!(IssuerParameters, "A valid byte sequence representing IssuerParameters"); 376 | 377 | #[cfg(test)] 378 | mod test { 379 | use super::*; 380 | 381 | use rand::thread_rng; 382 | 383 | #[test] 384 | fn system_parameters_serialize_deserialize() { 385 | let mut rng = thread_rng(); 386 | let system_parameters: SystemParameters = SystemParameters::hash_and_pray(&mut rng, 2).unwrap(); 387 | 388 | let serialized = system_parameters.to_bytes(); 389 | let deserialized = SystemParameters::from_bytes(&serialized).unwrap(); 390 | 391 | assert!(system_parameters == deserialized); 392 | } 393 | 394 | #[test] 395 | fn hash_and_pray() { 396 | let mut rng = thread_rng(); 397 | 398 | SystemParameters::hash_and_pray(&mut rng, 2).unwrap(); 399 | } 400 | 401 | #[test] 402 | fn issuer_parameters_generate() { 403 | let mut rng = thread_rng(); 404 | let system_parameters: SystemParameters = SystemParameters::hash_and_pray(&mut rng, 2).unwrap(); 405 | let sk: SecretKey = SecretKey::generate(&mut rng, &system_parameters); 406 | let issuer_params: IssuerParameters = IssuerParameters::generate(&system_parameters, &sk); 407 | 408 | assert!(issuer_params.C_W != RistrettoPoint::identity()); 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // Copyright (c) 2018 Signal Foundation 6 | // See LICENSE for licensing information. 7 | // 8 | // Authors: 9 | // - isis agora lovecruft 10 | 11 | //! Use this module to import commonly used structs automatically. 12 | 13 | pub use curve25519_dalek::ristretto::RistrettoPoint; 14 | pub use curve25519_dalek::scalar::Scalar; 15 | 16 | pub use crate::amacs::Attribute; 17 | pub use crate::issuer::Issuer; 18 | pub use crate::parameters::SystemParameters; 19 | pub use crate::symmetric::Plaintext; 20 | pub use crate::symmetric::Keypair; 21 | -------------------------------------------------------------------------------- /src/symmetric.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - isis agora lovecruft 9 | 10 | //! Symmetric-keyed verifiable encryption with unique ciphertexts. 11 | //! 12 | //! In order to facilitate credential presentation with hidden group element 13 | //! attributes, we require a symmetric-keyed encryption scheme that: 14 | //! 15 | //! * has public verifiability, meaning that we can prove that a ciphertext is 16 | //! an encryption of a certified plaintext with a key that is consistent with 17 | //! some public parameters, 18 | //! 19 | //! * has unique ciphertexts, meaning that for every plaintext there is at most 20 | //! one ciphertext that will decrypt correctly, and 21 | //! 22 | //! * is correct under adversarially chosen keys, meaning that it is hard to 23 | //! find a key and a message that cause decryption to fail. 24 | 25 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 26 | use alloc::vec::Vec; 27 | #[cfg(all(not(feature = "alloc"), feature = "std"))] 28 | use std::vec::Vec; 29 | 30 | use curve25519_dalek::ristretto::RistrettoPoint; 31 | use curve25519_dalek::scalar::Scalar; 32 | use curve25519_dalek::traits::Identity; 33 | 34 | use rand_core::CryptoRng; 35 | use rand_core::RngCore; 36 | 37 | use sha2::Sha512; 38 | 39 | use subtle::Choice; 40 | use subtle::ConstantTimeEq; 41 | 42 | use zeroize::Zeroize; 43 | 44 | use crate::encoding::decode_from_group; 45 | use crate::encoding::encode_to_group; 46 | use crate::errors::CredentialError; 47 | use crate::parameters::SystemParameters; 48 | 49 | /// A secret key, used for hidden group element attributes during credential 50 | /// presentation. 51 | #[derive(Clone, Zeroize)] 52 | pub(crate) struct SecretKey { 53 | pub(crate) a: Scalar, 54 | pub(crate) a0: Scalar, 55 | pub(crate) a1: Scalar, 56 | } 57 | 58 | /// Overwrite the secret key material with zeroes (and the identity element) 59 | /// when it drops out of scope. 60 | impl Drop for SecretKey { 61 | fn drop(&mut self) { 62 | self.zeroize(); 63 | } 64 | } 65 | 66 | /// A public key, used for verification of a symmetrica encryption. 67 | #[derive(Clone, Copy)] 68 | pub struct PublicKey { 69 | pub pk: RistrettoPoint, 70 | } 71 | 72 | /// A keypair for encryption of hidden group element attributes. 73 | #[derive(Clone)] 74 | pub struct Keypair { 75 | /// The secret portion of this keypair. 76 | pub(crate) secret: SecretKey, 77 | /// The public portion of this keypair. 78 | pub public: PublicKey, 79 | } 80 | 81 | /// A master secret, which can be used with the [`Keypair::derive`] method to 82 | /// produce a [`Keypair`]. 83 | pub type MasterSecret = [u8; 64]; 84 | 85 | // XXX impl Drop for MasterSecret 86 | 87 | /// A plaintext encodes up to thrity bytes of information into a group element. 88 | #[derive(Clone, Debug)] 89 | pub struct Plaintext { 90 | /// M1 = EncodeToG(m). 91 | pub(crate) M1: RistrettoPoint, 92 | /// M2 = HashToG(m). 93 | pub(crate) M2: RistrettoPoint, 94 | /// m3 = HashToZZq(m). 95 | pub(crate) m3: Scalar, 96 | } 97 | 98 | // We can't derive this because generally in elliptic curve cryptography group 99 | // elements aren't used as secrets, thus curve25519-dalek doesn't impl Zeroize 100 | // for RistrettoPoint. 101 | impl Zeroize for Plaintext { 102 | fn zeroize(&mut self) { 103 | self.M1 = RistrettoPoint::identity(); 104 | self.M2 = RistrettoPoint::identity(); 105 | self.m3.zeroize(); 106 | } 107 | } 108 | 109 | /// Overwrite the plaintext with zeroes (and the identity element) 110 | /// when it drops out of scope. 111 | impl Drop for Plaintext { 112 | fn drop(&mut self) { 113 | self.zeroize(); 114 | } 115 | } 116 | 117 | impl Plaintext { 118 | pub(crate) fn from_slice(slice: &[u8]) -> Vec { 119 | let mut plaintexts: Vec<Plaintext> = Vec::new(); 120 | 121 | for chunk in slice.chunks(30) { 122 | let mut bytes = [0u8; 30]; 123 | 124 | for i in 0..chunk.len() { 125 | bytes[i] = chunk[i]; 126 | } 127 | 128 | plaintexts.push(Plaintext::from(&bytes)); 129 | } 130 | 131 | plaintexts 132 | } 133 | } 134 | 135 | impl From<&[u8; 30]> for Plaintext { 136 | fn from(source: &[u8; 30]) -> Plaintext { 137 | let (M1, _) = encode_to_group(source); 138 | let M2: RistrettoPoint = RistrettoPoint::hash_from_bytes::<Sha512>(source); 139 | let m3: Scalar = Scalar::hash_from_bytes::<Sha512>(source); 140 | 141 | Plaintext { M1, M2, m3 } 142 | } 143 | } 144 | 145 | // XXX TODO return attempt counter 146 | impl From<&Plaintext> for [u8; 30] { 147 | fn from(source: &Plaintext) -> [u8; 30] { 148 | decode_from_group(&source.M1).0 149 | } 150 | } 151 | 152 | impl From<&RistrettoPoint> for Plaintext { 153 | fn from(source: &RistrettoPoint) -> Plaintext { 154 | let compressed = source.compress(); 155 | 156 | let M2 = RistrettoPoint::hash_from_bytes::<Sha512>(compressed.as_bytes()); 157 | let m3 = Scalar::hash_from_bytes::<Sha512>(compressed.as_bytes()); 158 | 159 | Plaintext { M1: source.clone(), M2, m3 } 160 | } 161 | } 162 | 163 | impl ConstantTimeEq for Plaintext { 164 | fn ct_eq(&self, other: &Plaintext) -> Choice { 165 | self.M1.compress().ct_eq(&other.M1.compress()) & 166 | self.M2.compress().ct_eq(&other.M2.compress()) & 167 | self.m3.ct_eq(&other.m3) 168 | } 169 | } 170 | 171 | impl PartialEq for Plaintext { 172 | fn eq(&self, other: &Plaintext) -> bool { 173 | self.ct_eq(other).into() 174 | } 175 | } 176 | 177 | impl Eq for Plaintext {} 178 | 179 | /// A symmetrically encrypted verifiable ciphertext, corresponding to one unique 180 | /// [`Plaintext`] and [`Keypair`]. 181 | pub struct Ciphertext { 182 | pub(crate) E1: RistrettoPoint, 183 | pub(crate) E2: RistrettoPoint, 184 | } 185 | 186 | impl Keypair { 187 | /// Derive this [`Keypair`] from a master secret. 188 | /// 189 | /// # Inputs 190 | /// 191 | /// * A [`MasterSecret`], and 192 | /// * some [`SystemParameters`] 193 | /// 194 | /// # Returns 195 | /// 196 | /// A `Keypair`. 197 | pub fn derive( 198 | master_secret: &MasterSecret, 199 | system_parameters: &SystemParameters 200 | ) -> Keypair 201 | { 202 | let a: Scalar = Scalar::hash_from_bytes::<Sha512>(&master_secret[..]); 203 | let a0: Scalar = Scalar::hash_from_bytes::<Sha512>(a.as_bytes()); 204 | let a1: Scalar = Scalar::hash_from_bytes::<Sha512>(a0.as_bytes()); 205 | 206 | let pk: RistrettoPoint = 207 | (system_parameters.G_a * a) + 208 | (system_parameters.G_a0 * a0) + 209 | (system_parameters.G_a1 * a1); 210 | 211 | Keypair { 212 | secret: SecretKey { a, a0, a1 }, 213 | public: PublicKey { pk }, 214 | } 215 | } 216 | 217 | /// Generate a new keypair. 218 | /// 219 | /// # Inputs 220 | /// 221 | /// * Some [`SystemParameters`], and 222 | /// * A cryptographically secure pseudo-random number generator. 223 | /// 224 | /// # Returns 225 | /// 226 | /// A newly generated [`Keypair`] and its associated [`MasterSecret]. 227 | pub fn generate<R>( 228 | system_parameters: &SystemParameters, 229 | csprng: &mut R, 230 | ) -> (Keypair, MasterSecret) 231 | where 232 | R: RngCore + CryptoRng, 233 | { 234 | let mut master_secret: MasterSecret = [0u8; 64]; 235 | 236 | csprng.fill_bytes(&mut master_secret); 237 | 238 | let keypair = Keypair::derive(&master_secret, system_parameters); 239 | 240 | (keypair, master_secret) 241 | } 242 | 243 | /// Use this key to encrypt a plaintext. 244 | /// 245 | /// # Inputs 246 | /// 247 | /// * The [`Plaintext`] to encrypt. 248 | /// 249 | /// # Returns 250 | /// 251 | /// The corresponding, unique ciphertext. 252 | pub fn encrypt( 253 | &self, 254 | plaintext: &Plaintext, 255 | ) -> Ciphertext 256 | { 257 | let E1: RistrettoPoint = plaintext.M2 * (self.secret.a0 + self.secret.a1 * plaintext.m3); 258 | let E2: RistrettoPoint = E1 * self.secret.a + plaintext.M1; 259 | 260 | Ciphertext { E1, E2 } 261 | } 262 | 263 | /// Use this key to decrypt a ciphertext. 264 | /// 265 | /// # Inputs 266 | /// 267 | /// * The [`Ciphertext`] to be decrypted. 268 | /// 269 | /// # Returns 270 | /// 271 | /// A `Result` whose `Ok` value is the [`Plaintext`], otherwise a [`CredentialError`]. 272 | // XXX TODO return the counter 273 | pub fn decrypt( 274 | &self, 275 | ciphertext: &Ciphertext, 276 | ) -> Result<Plaintext, CredentialError> 277 | { 278 | let M1_prime = ciphertext.E2 - (ciphertext.E1 * self.secret.a); 279 | let (m_prime, _) = decode_from_group(&M1_prime); 280 | let m3_prime = Scalar::hash_from_bytes::<Sha512>(&m_prime); 281 | 282 | let M2_prime = RistrettoPoint::hash_from_bytes::<Sha512>(&m_prime); 283 | let E1_prime = M2_prime * (self.secret.a0 + self.secret.a1 * m3_prime); 284 | 285 | match ciphertext.E1 == E1_prime { 286 | true => Ok(Plaintext { M1: M1_prime, M2: M2_prime, m3: m3_prime }), 287 | false => Err(CredentialError::UndecryptableAttribute), 288 | } 289 | } 290 | } 291 | 292 | #[cfg(test)] 293 | mod test { 294 | use super::*; 295 | 296 | use rand::thread_rng; 297 | 298 | #[test] 299 | fn encrypt_decrypt_roundtrip() { 300 | let mut csprng = thread_rng(); 301 | let system_parameters = SystemParameters::hash_and_pray(&mut csprng, 2).unwrap(); 302 | let (keypair, _) = Keypair::generate(&system_parameters, &mut csprng); 303 | let message = [0u8; 30]; 304 | let plaintext: Plaintext = (&message).into(); 305 | let ciphertext = keypair.encrypt(&plaintext); 306 | let decrypted = keypair.decrypt(&ciphertext); 307 | 308 | assert!(decrypted.is_ok()); 309 | assert_eq!(plaintext, decrypted.unwrap()); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/user.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of aeonflux. 4 | // Copyright (c) 2020 The Brave Authors 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - isis agora lovecruft <isis@patternsinthevoid.net> 9 | 10 | //! A user of an anonymous credential. 11 | 12 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 13 | use alloc::vec::Vec; 14 | #[cfg(all(not(feature = "alloc"), feature = "std"))] 15 | use std::vec::Vec; 16 | 17 | use curve25519_dalek::scalar::Scalar; 18 | use curve25519_dalek::ristretto::RistrettoPoint; 19 | 20 | use crate::symmetric; 21 | use crate::amacs::Attribute; 22 | use crate::parameters::SystemParameters; 23 | use crate::symmetric::Plaintext; 24 | 25 | /// A constructor for creating a request for a new credential. 26 | pub struct CredentialRequestConstructor { 27 | pub(crate) parameters: SystemParameters, 28 | pub(crate) attributes: Vec<Attribute>, 29 | } 30 | 31 | impl CredentialRequestConstructor { 32 | /// Begin a new [`CredentialRequest`]. 33 | pub fn new(system_parameters: &SystemParameters) -> CredentialRequestConstructor { 34 | let attributes: Vec<Attribute> = Vec::with_capacity(system_parameters.NUMBER_OF_ATTRIBUTES as usize); 35 | 36 | CredentialRequestConstructor { parameters: system_parameters.clone(), attributes } 37 | } 38 | 39 | /// Append a revealed-at-issuance scalar as an attribute in the eventual 40 | /// `AnonymousCredential`. 41 | // XXX check if we're overflowing the allotted attributes and return Result 42 | pub fn append_revealed_scalar(&mut self, scalar: Scalar) { 43 | self.attributes.push(Attribute::PublicScalar(scalar)); 44 | } 45 | 46 | /// Append a revealed-at-issuance group element as an attribute in the 47 | /// eventual `AnonymousCredential`. 48 | // XXX check if we're overflowing the allotted attributes and return Result 49 | pub fn append_revealed_point(&mut self, point: RistrettoPoint) { 50 | self.attributes.push(Attribute::PublicPoint(point)); 51 | } 52 | 53 | /// Append a message to be encoded as revealed plaintext into the 54 | /// `AnonymousCredential` attributes. 55 | /// 56 | /// This method is intended for appending arbitrary data to a 57 | /// credential, where the data is intended to be hidden upon credential 58 | /// presentation. The user should store the returned [`Plaintext`]s for 59 | /// faster computation of the [`Ciphertext`]s later. 60 | /// 61 | /// # Warning 62 | /// 63 | /// The message is split up into 31-byte segments which are mapped to group 64 | /// elements. Passing in a larger or smaller amount of data, resulting in 65 | /// more or fewer group elements, then results in a **different number of 66 | /// attributes** then may be allowed for by the configured 67 | /// [`SystemParameters`] for an instance of this protocol. 68 | // XXX check if we're overflowing the allotted attributes and return Result 69 | pub fn append_plaintext(&mut self, message: &Vec<u8>) -> Vec<symmetric::Plaintext> { 70 | let plaintexts = Plaintext::from_slice(&message[..]); 71 | 72 | for plaintext in plaintexts.iter() { 73 | self.attributes.push(Attribute::EitherPoint(plaintext.clone())); 74 | } 75 | 76 | plaintexts 77 | } 78 | 79 | /// Append a hidden-at-issuance scalar to the eventual `AnonymousCredential` 80 | /// attributes. 81 | /// 82 | /// # Returns 83 | /// 84 | /// The [`Ciphertext`] of the encrypted scalar. 85 | #[allow(unused_variables)] 86 | fn append_hidden_scalar( 87 | &mut self, 88 | scalar: Scalar, 89 | key: symmetric::Keypair, 90 | ) -> symmetric::Ciphertext 91 | { 92 | unimplemented!("Blinded issuance is not yet supported"); 93 | } 94 | 95 | /// Append a hidden-at-issuance group element to the eventual 96 | /// `AnonymousCredential` attributes. 97 | /// 98 | /// # Returns 99 | /// 100 | /// The [`Ciphertext`] of the encrypted group element. 101 | #[allow(unused_variables)] 102 | fn append_hidden_point( 103 | &mut self, 104 | scalar: Scalar, 105 | key: symmetric::Keypair, 106 | ) -> symmetric::Ciphertext 107 | { 108 | unimplemented!("Blinded issuance is not yet supported"); 109 | } 110 | 111 | /// Append a message to be encoded as ciphertext into the `AnonymousCredential` attributes. 112 | /// 113 | /// # Warning 114 | /// 115 | /// The message is split up into 31-byte segments which are mapped to group 116 | /// elements. Passing in a larger or smaller amount of data, resulting in 117 | /// more or fewer group elements, then results in a **different number of 118 | /// attributes** then may be allowed for by the configured 119 | /// [`SystemParameters`] for an instance of this protocol. 120 | #[allow(unused_variables)] 121 | fn append_ciphertext( 122 | &mut self, 123 | message: &Vec<u8>, 124 | key: symmetric::Keypair, 125 | ) -> Vec<(symmetric::Plaintext, symmetric::Ciphertext)> 126 | { 127 | unimplemented!("Blinded issuance is not yet supported"); 128 | } 129 | 130 | /// Finish creating this request for a [`Credential`]. 131 | pub fn finish(self) -> CredentialRequest { 132 | CredentialRequest { attributes: self.attributes } 133 | } 134 | } 135 | 136 | /// A request for a new credential. 137 | pub struct CredentialRequest { 138 | pub(crate) attributes: Vec<Attribute>, 139 | } 140 | --------------------------------------------------------------------------------