├── .github ├── dependabot.yml ├── settings.yml └── workflows │ ├── build.yml │ ├── security-audit.yml │ └── windows.yml ├── .gitignore ├── API.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── MAINTAINERS.md ├── NOTICE.md ├── README.md ├── samples ├── credentials │ ├── anoncreds_credential.json │ └── w3c_credential.json └── presentations │ ├── anoncreds_output.json │ ├── anoncreds_presentation_list.json │ └── w3c_presentation.json ├── src ├── blind.rs ├── blind │ ├── bundle.rs │ ├── credential.rs │ └── request.rs ├── claim.rs ├── claim │ ├── data.rs │ ├── enumeration.rs │ ├── hashed.rs │ ├── number.rs │ ├── revocation.rs │ ├── scalar.rs │ ├── type.rs │ └── validator.rs ├── credential.rs ├── credential │ ├── bundle.rs │ ├── membership.rs │ ├── offer.rs │ └── schema.rs ├── error.rs ├── issuer.rs ├── knox.rs ├── knox │ ├── accumulator.rs │ ├── accumulator │ │ ├── vb20.rs │ │ └── vb20 │ │ │ ├── accumulator.rs │ │ │ ├── error.rs │ │ │ ├── key.rs │ │ │ ├── proof.rs │ │ │ └── witness.rs │ ├── bbs.rs │ ├── bbs │ │ ├── blind_signature.rs │ │ ├── blind_signature_context.rs │ │ ├── msg_gens.rs │ │ ├── pok_signature.rs │ │ ├── pok_signature_proof.rs │ │ ├── public_key.rs │ │ ├── scheme.rs │ │ ├── secret_key.rs │ │ └── signature.rs │ ├── ecc_group.rs │ ├── ps.rs │ ├── ps │ │ ├── blind_signature.rs │ │ ├── blind_signature_context.rs │ │ ├── pok_signature.rs │ │ ├── pok_signature_proof.rs │ │ ├── public_key.rs │ │ ├── scheme.rs │ │ ├── secret_key.rs │ │ └── signature.rs │ ├── short_group_sig_core.rs │ └── short_group_sig_core │ │ ├── hidden_message.rs │ │ ├── proof_committed_builder.rs │ │ ├── proof_message.rs │ │ └── short_group_traits.rs ├── lib.rs ├── mapping │ ├── map_credential.rs │ └── map_presentation.rs ├── presentation.rs ├── presentation │ ├── commitment.rs │ ├── create.rs │ ├── credential.rs │ ├── equality.rs │ ├── membership.rs │ ├── proof.rs │ ├── range.rs │ ├── revocation.rs │ ├── schema.rs │ ├── signature.rs │ ├── verifiable_encryption.rs │ ├── verifiable_encryption_decryption.rs │ └── verify.rs ├── revocation_registry.rs ├── statement.rs ├── statement │ ├── commitment.rs │ ├── equality.rs │ ├── membership.rs │ ├── range.rs │ ├── revocation.rs │ ├── signature.rs │ ├── verifiable_encryption.rs │ └── verifiable_encryption_decryption.rs ├── utils.rs ├── verifier.rs └── verifier │ ├── commitment.rs │ ├── equality.rs │ ├── membership.rs │ ├── range.rs │ ├── revocation.rs │ ├── signature.rs │ ├── verifiable_encryption.rs │ └── verifiable_encryption_decryption.rs └── tests ├── equality-and-reveal.rs ├── flow.rs ├── out-of-range-panic.rs ├── range.rs ├── real-id.rs └── revocation.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # For details on how this file works refer to: 2 | # - https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 3 | version: 2 4 | updates: 5 | # Maintain dependencies for GitHub Actions 6 | # - Check for updates once a week 7 | # - Group all updates into a single PR 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | groups: 13 | all-actions: 14 | patterns: [ "*" ] 15 | 16 | # Maintain dependencies for Cargo Packages 17 | - package-ecosystem: "cargo" 18 | directory: "/" 19 | schedule: 20 | interval: "weekly" 21 | day: "monday" 22 | time: "04:00" 23 | timezone: "Canada/Pacific" 24 | ignore: 25 | - dependency-name: "*" 26 | update-types: ["version-update:semver-major"] 27 | 28 | 29 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: CC-BY-4.0 3 | # 4 | 5 | repository: 6 | name: anoncreds-v2-rs 7 | description: anoncreds-v2-rs 8 | homepage: https://wiki.hyperledger.org/display/anoncreds 9 | default_branch: main 10 | has_downloads: false 11 | has_issues: true 12 | has_projects: true 13 | has_wiki: true 14 | archived: false 15 | private: false 16 | allow_squash_merge: false 17 | allow_merge_commit: false 18 | allow_rebase_merge: true 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: anoncreds-v2-rs 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | env: 11 | CARGO_INCREMENTAL: 0 12 | CARGO_TERM_COLOR: always 13 | RUST_BACKTRACE: full 14 | RUST_LOG: debug 15 | RUST_LOG_STYLE: always 16 | 17 | defaults: 18 | run: 19 | shell: bash 20 | 21 | jobs: 22 | lint: 23 | runs-on: ubuntu-22.04 24 | timeout-minutes: 20 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Install latest stable 28 | uses: actions-rs/toolchain@v1 29 | with: 30 | toolchain: stable 31 | override: true 32 | components: rustfmt, clippy 33 | 34 | - name: Cache cargo resources 35 | uses: Swatinem/rust-cache@v2 36 | 37 | - name: Cargo check 38 | run: cargo check 39 | 40 | - name: Cargo format 41 | run: cargo fmt --all -- --check 42 | 43 | - name: Cargo clippy 44 | run: cargo clippy -- -Dwarnings 45 | 46 | test: 47 | strategy: 48 | matrix: 49 | os: [ubuntu-latest, macos-latest] 50 | 51 | runs-on: ${{ matrix.os }} 52 | 53 | steps: 54 | - name: Checkout 55 | uses: actions/checkout@v4 56 | 57 | - name: Install Stable 58 | uses: actions-rs/toolchain@v1 59 | with: 60 | toolchain: stable 61 | override: true 62 | components: rustfmt, clippy 63 | 64 | - name: Cache cargo resources 65 | uses: Swatinem/rust-cache@v2 66 | 67 | - name: Cargo test 68 | run: cargo test 69 | -------------------------------------------------------------------------------- /.github/workflows/security-audit.yml: -------------------------------------------------------------------------------- 1 | name: Security Audit 2 | on: 3 | pull_request: 4 | paths: 5 | - Cargo.lock 6 | - Cargo.toml 7 | push: 8 | branches: 9 | - main 10 | schedule: 11 | - cron: "0 0 * * *" 12 | 13 | jobs: 14 | security_audit: 15 | name: Security Audit 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: dtolnay/rust-toolchain@master 20 | with: 21 | toolchain: stable 22 | - uses: actions/cache@v4 23 | with: 24 | path: ~/.cargo/bin 25 | key: ${{ runner.os }}-cargo-audit-v0.15.2 26 | - uses: actions-rs/audit-check@v1 27 | with: 28 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: windows 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | env: 11 | CARGO_INCREMENTAL: 0 12 | CARGO_TERM_COLOR: always 13 | RUST_BACKTRACE: full 14 | RUST_LOG: debug 15 | RUST_LOG_STYLE: always 16 | 17 | jobs: 18 | test: 19 | runs-on: windows-latest 20 | name: (${{ matrix.target }}, ${{ matrix.cfg_release_channel }}) 21 | env: 22 | CFG_RELEASE_CHANNEL: ${{ matrix.cfg_release_channel }} 23 | strategy: 24 | max-parallel: 2 25 | fail-fast: false 26 | matrix: 27 | target: [i686-pc-windows-msvc, x86_64-pc-windows-msvc] 28 | cfg_release_channel: stable 29 | steps: 30 | - name: Disable git eol translation 31 | run: git config --global core.autocrlf false 32 | - name: Checkout 33 | run: actions/checkout@v3 34 | - name: Install Stable 35 | run: | 36 | $ProgressPreference = "SilentlyContinue" 37 | Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe 38 | .\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --default-toolchain=none 39 | del rustup-init.exe 40 | rustup target add ${{ matrix.target }} 41 | shell: powershell 42 | 43 | - name: Cargo test 44 | run: cargo test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | 8 | 9 | # Added by cargo 10 | 11 | /target 12 | .idea 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Hyperledger AnonCreds Developers"] 3 | categories = ["cryptography"] 4 | description = "Anonymous Credentials 2.0 implemention" 5 | documentation = "https://docs.rs/anoncreds-v2-rs" 6 | edition = "2021" 7 | exclude = ["samples/**"] 8 | homepage = "https://crates.io/crates/anoncreds-v2-rs" 9 | license = "Apache-2.0" 10 | name = "credx" 11 | repository = "https://github.com/hyperledger/anoncreds-v2-rs" 12 | version = "0.2.1" 13 | 14 | # NOTE: Please keep dependencies sorted alphabetically. 15 | [dependencies] 16 | aes-gcm = "0.10" 17 | base64 = "0.22" 18 | blsful = "3.0.0-pre8" 19 | bulletproofs = { version = "4.0.0", package = "bulletproofs-bls" } 20 | chrono = "0.4" 21 | elliptic-curve = { version = "0.13", features = ["hash2curve"] } 22 | elliptic-curve-tools = "0.1" 23 | hex = "0.4" 24 | indexmap = "2" 25 | log = "0.4" 26 | merlin = "3" 27 | maplit = "1" 28 | rand = "0.8" 29 | rand_chacha = "0.3" 30 | rand_core = "0.6" 31 | rayon = "1.10" 32 | regex = "1" 33 | rmp-serde = "1.3" 34 | serde = { version = "1", features = ["serde_derive"] } 35 | serde_bare = "0.5" 36 | serde_json = "1" 37 | serde_regex = "1" 38 | sha2 = "0.10" 39 | sha3 = "0.10" 40 | subtle = "2.6" 41 | uint-zigzag = { version = "0.2", features = ["std"] } 42 | uuid = {version = "1.11", features = ["v4"]} 43 | zeroize = "1" 44 | 45 | 46 | [dev-dependencies] 47 | lazy_static = "1.5.0" 48 | maplit = "1" 49 | serde_cbor = "0.11" 50 | sha2 = "0.10" 51 | env_logger = "0.11" 52 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | 4 | 5 | ## Active Maintainers 6 | 7 | 8 | 9 | | Name | Github | LFID | 10 | | ---------------- | ---------------- | ---------------- | 11 | | Michael Lodder | mikelodder7 | MikeLodder | 12 | | Andrew Whitehead | andrewwhitehead | cywolf | 13 | | Berend Sliedrecht| berendsliedrecht | beri14 | 14 | | Darko Kulic | dkulic | dkulic | 15 | | Stephen Curran | swcurran | swcurran | 16 | | Timo Glastra | TimoGlastra | TimoGlastra | 17 | | Wade Barnes | WadeBarnes | WadeBarnes | 18 | 19 | ## Becoming a Maintainer 20 | 21 | AnonCreds welcomes community contribution. 22 | Each community member may progress to become a maintainer. 23 | 24 | How to become a maintainer: 25 | 26 | - Contribute significantly to the code in this repository. 27 | 28 | ### Maintainers contribution requirement 29 | 30 | The requirement to be able to be proposed as a maintainer is: 31 | 32 | - 5 significant changes on code have been authored in this repos by the proposed maintainer and accepted (merged PRs). 33 | 34 | ### Maintainers approval process 35 | 36 | The following steps must occur for a contributor to be "upgraded" as a maintainer: 37 | 38 | - The proposed maintainer has the sponsorship of at least one other maintainer. 39 | - This sponsoring maintainer will create a proposal PR modifying the list of 40 | maintainers. (see [proposal PR template](#proposal-pr-template).) 41 | - The proposed maintainer accepts the nomination and expresses a willingness 42 | to be a long-term (more than 6 month) committer by adding a comment in the proposal PR. 43 | - The PR will be communicated in all appropriate communication channels 44 | including at least [anoncreds-maintainers channel on Hyperledger Discord](https://discord.gg/hyperledger), 45 | the [mailing list](https://lists.hyperledger.org/g/anoncreds) 46 | and any maintainer/community call. 47 | - Approval by at least 3 current maintainers within two weeks of the proposal or 48 | an absolute majority (half the total + 1) of current maintainers. 49 | - Maintainers will vote by approving the proposal PR. 50 | - No veto raised by another maintainer within the voting timeframe. 51 | - All vetoes must be accompanied by a public explanation as a comment in the 52 | proposal PR. 53 | - A veto can be retracted, in that case the voting timeframe is reset and all approvals are removed. 54 | - It is bad form to veto, retract, and veto again. 55 | 56 | The proposed maintainer becomes a maintainer either: 57 | 58 | - when two weeks have passed without veto since the third approval of the proposal PR, 59 | - or an absolute majority of maintainers approved the proposal PR. 60 | 61 | In either case, no maintainer raised and stood by a veto. 62 | 63 | ## Removing Maintainers 64 | 65 | Being a maintainer is not a status symbol or a title to be maintained indefinitely. 66 | 67 | It will occasionally be necessary and appropriate to move a maintainer to emeritus status. 68 | 69 | This can occur in the following situations: 70 | 71 | - Resignation of a maintainer. 72 | - Violation of the Code of Conduct warranting removal. 73 | - Inactivity. 74 | - A general measure of inactivity will be no commits or code review comments 75 | for two reporting quarters, although this will not be strictly enforced if 76 | the maintainer expresses a reasonable intent to continue contributing. 77 | - Reasonable exceptions to inactivity will be granted for known long term 78 | leave such as parental leave and medical leave. 79 | - Other unspecified circumstances. 80 | 81 | As for adding a maintainer, the record and governance process for moving a 82 | maintainer to emeritus status is recorded using review approval in the PR making that change. 83 | 84 | Returning to active status from emeritus status uses the same steps as adding a 85 | new maintainer. 86 | 87 | Note that the emeritus maintainer always already has the required significant contributions. 88 | There is no contribution prescription delay. 89 | 90 | ## Proposal PR template 91 | 92 | ```markdown 93 | I propose to add [maintainer github handle] as a AnonCreds project maintainer. 94 | 95 | [maintainer github handle] contributed with many high quality commits: 96 | 97 | - [list significant achievements] 98 | 99 | Here are [their past contributions on AnonCreds project](https://github.com/hyperledger/anoncreds-rs/commits?author=[user github handle]). 100 | 101 | Voting ends two weeks from today. 102 | 103 | For more information on this process see the Becoming a Maintainer section in the MAINTAINERS.md file. 104 | ``` 105 | -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | 2 | This project contains contributions subject to: 3 | 4 | Copyright © 2024, Oracle and/or its affiliates. 5 | -------------------------------------------------------------------------------- /samples/credentials/anoncreds_credential.json: -------------------------------------------------------------------------------- 1 | { 2 | "credential": { 3 | "claims": [ 4 | { 5 | "Hashed": { 6 | "print_friendly": true, 7 | "value": "P Sherman 42 Wallaby Way Sydney" 8 | } 9 | }, 10 | { 11 | "Number": { 12 | "value": 30303 13 | } 14 | }, 15 | { 16 | "Hashed": { 17 | "print_friendly": true, 18 | "value": "John Doe" 19 | } 20 | }, 21 | { 22 | "Hashed": { 23 | "value": "91742856-6eda-45fb-a709-d22ebb5ec8a5" 24 | } 25 | } 26 | ], 27 | "revocation_handle": null, 28 | "revocation_index": null, 29 | "signature": { 30 | "m_tick": "1056e25782ed14dbc564a19f36e33979931c0fa44ee89d3495b5a5a892911969", 31 | "sigma_1": "b52d724c8e7ab2bc11c4f363bdd12d25353e63a8047791aff1615bae9defff97382c48e80a8b0a8c8bb26956eadc86ed", 32 | "sigma_2": "b531c3b8cc92b94e7642293c6927f9a30c8f88573a0e6e25bb701940abf6d614b4961e804e34700b2b0046d6fe10cfbe" 33 | } 34 | }, 35 | "issuer": { 36 | "cred_def": "1c3a7a2abb59a43e499148acbfa48c4e", 37 | "id": "1c3a7a2abb59a43e499148acbfa48c4e", 38 | "schema": "d25004aa952e5ac30dc7a71562e09587" 39 | } 40 | } -------------------------------------------------------------------------------- /samples/credentials/w3c_credential.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | "https://www.w3.org/2018/credentials/v1", 4 | "https://w3id.org/security/data-integrity/v2", 5 | { 6 | "@vocab": "https://www.w3.org/ns/credentials/issuer-dependent#" 7 | } 8 | ], 9 | "credentialSubject": { 10 | "address": "P Sherman 42 Wallaby Way Sydney", 11 | "age": 30303, 12 | "name": "John Doe", 13 | "revocation_identifier": "91742856-6eda-45fb-a709-d22ebb5ec8a5" 14 | }, 15 | "issuanceDate": "2025-03-14T15:10:01Z", 16 | "issuer": { 17 | "cred_def": "did:key:7f596d64a4205133a9cad45c02ce44d6", 18 | "id": "did:key:7f596d64a4205133a9cad45c02ce44d6", 19 | "schema": "did:key:ac3fe7854b109146f57b881ef25533f0" 20 | }, 21 | "proof": { 22 | "proofPurpose": "assertionMethod", 23 | "proofValue": "ukoGpc2lnbmF0dXJlg6ZtX3RpY2vZQDEwNTZlMjU3ODJlZDE0ZGJjNTY0YTE5ZjM2ZTMzOTc5OTMxYzBmYTQ0ZWU4OWQzNDk1YjVhNWE4OTI5MTE5Njmnc2lnbWFfMdlgYjUyZDcyNGM4ZTdhYjJiYzExYzRmMzYzYmRkMTJkMjUzNTNlNjNhODA0Nzc5MWFmZjE2MTViYWU5ZGVmZmY5NzM4MmM0OGU4MGE4YjBhOGM4YmIyNjk1NmVhZGM4NmVkp3NpZ21hXzLZYDg5ZWM2MDE2ZWU2OWMyOGI4MGJlZTIxNjFiZmRhZjQwYjU3NjcwYjZkMDM4YmQ0YzFkNmQwYjJiOTRhODYyODQ3ZGFiNTE5OWFhODE2MzNiZjZlZDcyZWIwZjg5YTFmY4KxcmV2b2NhdGlvbl9oYW5kbGXZYGI5N2Y5N2YyNjkwZGQ2MGZiMzAyZTRhMWE3ZWJkZWJiNDY5ZDc1MDVhZWNmY2QxYTQxYWJiM2I4NDA0YTI2Y2EzODcwYWRlNWMxNTJmZmRkMDNkNDk3MTIzNWFiZWQ0NbByZXZvY2F0aW9uX2luZGV4AA", 24 | "type": "DataIntegrityProof", 25 | "verificationMethod": "did:key:z6MkwXG2WjeQnNxSoynSGYU8V9j3QzP3JSqhdmkHc6SaVWoT/credential-definition" 26 | }, 27 | "type": [ 28 | "VerifiableCredential" 29 | ] 30 | } -------------------------------------------------------------------------------- /src/blind.rs: -------------------------------------------------------------------------------- 1 | mod bundle; 2 | mod credential; 3 | mod request; 4 | 5 | pub use bundle::*; 6 | pub use credential::*; 7 | pub use request::*; 8 | -------------------------------------------------------------------------------- /src/blind/bundle.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::{ 3 | claim::ClaimData, credential::CredentialBundle, error::Error, issuer::IssuerPublic, CredxResult, 4 | }; 5 | 6 | use crate::credential::Credential; 7 | use crate::knox::short_group_sig_core::short_group_traits::{ 8 | BlindSignature, ShortGroupSignatureScheme, 9 | }; 10 | use blsful::inner_types::Scalar; 11 | use serde::{Deserialize, Serialize}; 12 | use std::collections::BTreeMap; 13 | 14 | /// A blind credential bundle returned by the issuer from a blind signing operation 15 | #[derive(Clone, Debug, Deserialize, Serialize)] 16 | pub struct BlindCredentialBundle { 17 | /// The issuer information that gave this credential 18 | #[serde(bound( 19 | serialize = "IssuerPublic: Serialize", 20 | deserialize = "IssuerPublic: Deserialize<'de>" 21 | ))] 22 | pub issuer: IssuerPublic, 23 | /// The blind credential 24 | #[serde(bound( 25 | serialize = "BlindCredential: Serialize", 26 | deserialize = "BlindCredential: Deserialize<'de>" 27 | ))] 28 | pub credential: BlindCredential, 29 | } 30 | 31 | impl BlindCredentialBundle { 32 | /// Create a unblinded credential 33 | pub fn to_unblinded( 34 | mut self, 35 | blind_claims: &BTreeMap, 36 | blinder: Scalar, 37 | ) -> CredxResult> { 38 | for label in blind_claims.keys() { 39 | if !self.issuer.schema.blind_claims.contains(label) { 40 | return Err(Error::InvalidClaimData("claim is not blindable")); 41 | } 42 | if self.credential.claims.contains_key(label) { 43 | return Err(Error::InvalidClaimData("duplicate claim detected")); 44 | } 45 | } 46 | self.credential.claims.append(&mut blind_claims.clone()); 47 | let mut ordering = vec![String::new(); self.credential.claims.len()]; 48 | 49 | for label in self.credential.claims.keys() { 50 | ordering[self 51 | .issuer 52 | .schema 53 | .claim_indices 54 | .get_index_of(label) 55 | .unwrap()] = label.clone(); 56 | } 57 | let mut claims = Vec::with_capacity(self.issuer.schema.claims.len()); 58 | for label in &ordering { 59 | claims.push( 60 | self.credential 61 | .claims 62 | .remove(label) 63 | .ok_or(Error::InvalidClaimData("claim missing"))?, 64 | ); 65 | } 66 | let revocation_index = self 67 | .issuer 68 | .schema 69 | .claim_indices 70 | .get_index_of(&self.credential.revocation_label) 71 | .ok_or(Error::InvalidClaimData( 72 | "revocation label not found in claims", 73 | ))?; 74 | Ok(CredentialBundle { 75 | issuer: self.issuer, 76 | credential: Credential { 77 | claims, 78 | signature: self.credential.signature.to_unblinded(blinder), 79 | revocation_handle: self.credential.revocation_handle, 80 | revocation_index, 81 | }, 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/blind/credential.rs: -------------------------------------------------------------------------------- 1 | use crate::knox::accumulator::vb20::MembershipWitness; 2 | use crate::knox::short_group_sig_core::short_group_traits::{ 3 | BlindSignature, ShortGroupSignatureScheme, 4 | }; 5 | use crate::{ 6 | claim::ClaimData, credential::Credential, error::Error, issuer::IssuerPublic, CredxResult, 7 | }; 8 | use blsful::inner_types::Scalar; 9 | use serde::{Deserialize, Serialize}; 10 | use std::collections::BTreeMap; 11 | 12 | /// A blind credential 13 | #[derive(Clone, Debug, Deserialize, Serialize)] 14 | pub struct BlindCredential { 15 | /// The known claims signed by the issuer 16 | pub claims: BTreeMap, 17 | /// The blind signature 18 | pub signature: S::BlindSignature, 19 | /// The revocation handle 20 | pub revocation_handle: MembershipWitness, 21 | /// The claim that is used for revocation 22 | pub revocation_label: String, 23 | } 24 | 25 | impl BlindCredential { 26 | /// Convert this blind credential into a regular credential 27 | pub fn to_credential( 28 | mut self, 29 | issuer: &IssuerPublic, 30 | blinder: Scalar, 31 | hidden_claims: &BTreeMap, 32 | ) -> CredxResult> { 33 | if issuer.schema.claims.len() != self.claims.len() + hidden_claims.len() { 34 | return Err(Error::InvalidClaimData( 35 | "hidden + known claims != schema claims", 36 | )); 37 | } 38 | let mut combined_claims = hidden_claims.clone(); 39 | combined_claims.append(&mut self.claims); 40 | let signature = self.signature.to_unblinded(blinder); 41 | 42 | let revocation_index = issuer 43 | .schema 44 | .claim_indices 45 | .get_index_of(&self.revocation_label) 46 | .ok_or(Error::InvalidClaimData("revocation index not found"))?; 47 | 48 | let mut claims = Vec::with_capacity(issuer.schema.claims.len()); 49 | for claim in &issuer.schema.claims { 50 | match combined_claims.remove(&claim.label) { 51 | None => return Err(Error::InvalidClaimData("claim not found list")), 52 | Some(claim) => claims.push(claim), 53 | } 54 | } 55 | 56 | Ok(Credential { 57 | claims, 58 | signature, 59 | revocation_handle: self.revocation_handle, 60 | revocation_index, 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/blind/request.rs: -------------------------------------------------------------------------------- 1 | use crate::knox::short_group_sig_core::short_group_traits::{ 2 | BlindSignatureContext as _, ShortGroupSignatureScheme, 3 | }; 4 | use crate::{ 5 | claim::ClaimData, 6 | error::Error, 7 | issuer::{Issuer, IssuerPublic}, 8 | CredxResult, 9 | }; 10 | use blsful::inner_types::*; 11 | use serde::{Deserialize, Serialize}; 12 | use std::collections::BTreeMap; 13 | 14 | /// A blind credential signing request 15 | #[derive(Clone, Debug, Deserialize, Serialize)] 16 | pub struct BlindCredentialRequest { 17 | /// The blind signing context 18 | pub blind_signature_context: S::BlindSignatureContext, 19 | /// The blind claim labels 20 | pub blind_claim_labels: Vec, 21 | /// The nonce for this context 22 | pub nonce: Scalar, 23 | } 24 | 25 | impl BlindCredentialRequest { 26 | /// Create a new request 27 | pub fn new( 28 | issuer: &IssuerPublic, 29 | claims: &BTreeMap, 30 | ) -> CredxResult<(Self, Scalar)> { 31 | let nonce = Scalar::random(rand::thread_rng()); 32 | let mut messages = Vec::with_capacity(claims.len()); 33 | for (label, claim) in claims { 34 | if !issuer.schema.blind_claims.contains(label) { 35 | return Err(Error::InvalidClaimData("claim is not blindable")); 36 | } 37 | messages.push(( 38 | issuer 39 | .schema 40 | .claim_indices 41 | .get_index_of(label) 42 | .ok_or(Error::InvalidClaimData("claim does not exist in schema"))?, 43 | claim.to_scalar(), 44 | )); 45 | } 46 | let (ctx, blinder) = S::new_blind_signature_context( 47 | &messages, 48 | &issuer.verifying_key, 49 | nonce, 50 | rand::thread_rng(), 51 | ) 52 | .map_err(|_| Error::InvalidClaimData("unable to create blind signature context"))?; 53 | Ok(( 54 | Self { 55 | blind_signature_context: ctx, 56 | blind_claim_labels: claims.iter().map(|(l, _)| l.clone()).collect(), 57 | nonce, 58 | }, 59 | blinder, 60 | )) 61 | } 62 | 63 | /// Verify the signing request is well-formed 64 | pub fn verify(&self, issuer: &Issuer) -> CredxResult<()> { 65 | let mut known_messages = 66 | Vec::with_capacity(issuer.schema.claims.len() - self.blind_claim_labels.len()); 67 | for label in &self.blind_claim_labels { 68 | if !issuer.schema.blind_claims.contains(label) { 69 | return Err(Error::InvalidClaimData("claim is not blindable")); 70 | } 71 | known_messages.push( 72 | issuer 73 | .schema 74 | .claim_indices 75 | .get_index_of(label) 76 | .ok_or(Error::InvalidClaimData("claim does not exist in schema"))?, 77 | ); 78 | } 79 | let res = self 80 | .blind_signature_context 81 | .verify(&known_messages, &issuer.signing_key, self.nonce) 82 | .map_err(|_| Error::InvalidSigningOperation)?; 83 | if !res { 84 | return Err(Error::InvalidSigningOperation); 85 | } 86 | Ok(()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/claim.rs: -------------------------------------------------------------------------------- 1 | mod data; 2 | mod enumeration; 3 | mod hashed; 4 | mod number; 5 | mod revocation; 6 | mod scalar; 7 | mod r#type; 8 | mod validator; 9 | 10 | pub use data::*; 11 | pub use enumeration::*; 12 | pub use hashed::*; 13 | pub use number::*; 14 | pub use r#type::*; 15 | pub use revocation::*; 16 | pub use scalar::*; 17 | pub use validator::*; 18 | 19 | use blsful::inner_types::*; 20 | 21 | /// Represents claims 22 | pub trait Claim { 23 | /// The inner type 24 | type Value; 25 | 26 | /// Get the claim type 27 | fn get_type(&self) -> ClaimType; 28 | /// Convert this claim to a scalar 29 | fn to_scalar(&self) -> Scalar; 30 | /// Get the claim data value 31 | fn get_value(&self) -> Self::Value; 32 | } 33 | -------------------------------------------------------------------------------- /src/claim/enumeration.rs: -------------------------------------------------------------------------------- 1 | use crate::claim::{Claim, ClaimType}; 2 | use crate::knox::Knox; 3 | use blsful::inner_types::Scalar; 4 | use core::{ 5 | fmt::{self, Display, Formatter}, 6 | hash::{Hash, Hasher}, 7 | }; 8 | use serde::{Deserialize, Serialize}; 9 | use sha3; 10 | 11 | /// A claim where there there is a list of values 12 | /// but can't use simple number like 0, 1, 2 13 | #[derive(Clone, Debug, Eq, Deserialize, Serialize)] 14 | pub struct EnumerationClaim { 15 | /// The domain separation tag for this enumeration 16 | pub dst: String, 17 | /// The index value to be hashed 18 | pub value: u8, 19 | /// The size of the enumeration 20 | pub total_values: usize, 21 | } 22 | 23 | impl PartialEq for EnumerationClaim { 24 | fn eq(&self, other: &Self) -> bool { 25 | self.dst == other.dst 26 | && self.value == other.value 27 | && self.total_values == other.total_values 28 | } 29 | } 30 | 31 | impl Hash for EnumerationClaim { 32 | fn hash(&self, state: &mut H) { 33 | self.dst.hash(state); 34 | self.value.hash(state); 35 | self.total_values.hash(state); 36 | } 37 | } 38 | 39 | impl Display for EnumerationClaim { 40 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 41 | write!(f, "EnumerationClaim {{ {} }}", self.value) 42 | } 43 | } 44 | 45 | impl Claim for EnumerationClaim { 46 | type Value = u8; 47 | 48 | fn get_type(&self) -> ClaimType { 49 | ClaimType::Enumeration 50 | } 51 | 52 | fn to_scalar(&self) -> Scalar { 53 | let mut data = self.dst.as_bytes().to_vec(); 54 | data.push(self.dst.len() as u8); 55 | data.extend_from_slice((self.total_values as u16).to_le_bytes().as_ref()); 56 | data.push(self.value); 57 | let mut buffer = [0u8; 64]; 58 | Knox::xof_digest::(data.as_slice(), &mut buffer); 59 | Scalar::from_bytes_wide(&buffer) 60 | } 61 | 62 | fn get_value(&self) -> Self::Value { 63 | self.value 64 | } 65 | } 66 | 67 | #[test] 68 | fn serialize() { 69 | let e = EnumerationClaim { 70 | dst: String::from("phone_number_type"), 71 | total_values: 3, 72 | value: 0, 73 | }; 74 | let res = serde_bare::to_vec(&e); 75 | assert!(res.is_ok()); 76 | let bytes = res.unwrap(); 77 | let res = serde_bare::from_slice::(bytes.as_slice()); 78 | assert!(res.is_ok()); 79 | let ee = res.unwrap(); 80 | 81 | assert_eq!(e, ee); 82 | } 83 | -------------------------------------------------------------------------------- /src/claim/hashed.rs: -------------------------------------------------------------------------------- 1 | use super::{Claim, ClaimType}; 2 | use crate::knox::Knox; 3 | use blsful::inner_types::Scalar; 4 | use core::{ 5 | fmt::{self, Display, Formatter}, 6 | hash::{Hash, Hasher}, 7 | }; 8 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 9 | use sha3::Shake256; 10 | 11 | /// Claims that are hashed to a scalar 12 | #[derive(Clone, Debug, Eq)] 13 | pub struct HashedClaim { 14 | /// The value to be hashed 15 | pub value: Vec, 16 | /// Whether the claim can be printed 17 | pub print_friendly: bool, 18 | } 19 | 20 | #[derive(Deserialize, Serialize)] 21 | struct HashedClaimSerdesFriendly { 22 | pub value: String, 23 | pub print_friendly: bool, 24 | } 25 | 26 | #[derive(Deserialize, Serialize)] 27 | struct HashedClaimSerdes { 28 | pub value: Vec, 29 | pub print_friendly: bool, 30 | } 31 | 32 | impl Serialize for HashedClaim { 33 | fn serialize(&self, s: S) -> Result 34 | where 35 | S: Serializer, 36 | { 37 | if s.is_human_readable() { 38 | let ss = if self.print_friendly { 39 | String::from_utf8(self.value.clone()).unwrap() 40 | } else { 41 | hex::encode(&self.value) 42 | }; 43 | HashedClaimSerdesFriendly { 44 | value: ss, 45 | print_friendly: self.print_friendly, 46 | } 47 | .serialize(s) 48 | } else { 49 | HashedClaimSerdes { 50 | value: self.value.clone(), 51 | print_friendly: self.print_friendly, 52 | } 53 | .serialize(s) 54 | } 55 | } 56 | } 57 | 58 | impl<'de> Deserialize<'de> for HashedClaim { 59 | fn deserialize(d: D) -> Result 60 | where 61 | D: Deserializer<'de>, 62 | { 63 | if d.is_human_readable() { 64 | let hc = HashedClaimSerdesFriendly::deserialize(d)?; 65 | let value = if hc.print_friendly { 66 | hc.value.as_bytes().to_vec() 67 | } else { 68 | hex::decode(hc.value).map_err(|e| serde::de::Error::custom(e.to_string()))? 69 | }; 70 | Ok(HashedClaim { 71 | value, 72 | print_friendly: hc.print_friendly, 73 | }) 74 | } else { 75 | let hc = HashedClaimSerdes::deserialize(d)?; 76 | Ok(HashedClaim { 77 | value: hc.value, 78 | print_friendly: hc.print_friendly, 79 | }) 80 | } 81 | } 82 | } 83 | 84 | impl PartialEq for HashedClaim { 85 | fn eq(&self, other: &Self) -> bool { 86 | self.value == other.value && self.print_friendly == other.print_friendly 87 | } 88 | } 89 | 90 | impl Hash for HashedClaim { 91 | fn hash(&self, state: &mut H) { 92 | self.value.hash(state); 93 | self.print_friendly.hash(state); 94 | } 95 | } 96 | 97 | impl Display for HashedClaim { 98 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 99 | if self.print_friendly { 100 | write!(f, "HashedClaim {{ ")?; 101 | write!(f, "{}", String::from_utf8(self.value.clone()).unwrap())?; 102 | write!(f, "}}") 103 | } else { 104 | write!(f, "HashedClaim {{ [")?; 105 | let mut sep = ""; 106 | for b in &self.value { 107 | write!(f, "{}{}", sep, b)?; 108 | sep = ", "; 109 | } 110 | write!(f, "] }}") 111 | } 112 | } 113 | } 114 | 115 | impl<'a> From<&'a [u8]> for HashedClaim { 116 | fn from(value: &'a [u8]) -> Self { 117 | Self { 118 | value: value.to_vec(), 119 | print_friendly: false, 120 | } 121 | } 122 | } 123 | 124 | impl From<&Vec> for HashedClaim { 125 | fn from(v: &Vec) -> Self { 126 | Self { 127 | value: v.clone(), 128 | print_friendly: false, 129 | } 130 | } 131 | } 132 | 133 | impl From> for HashedClaim { 134 | fn from(value: Vec) -> Self { 135 | Self { 136 | value, 137 | print_friendly: false, 138 | } 139 | } 140 | } 141 | 142 | impl From<&str> for HashedClaim { 143 | fn from(v: &str) -> Self { 144 | Self { 145 | value: v.to_string().into_bytes(), 146 | print_friendly: true, 147 | } 148 | } 149 | } 150 | 151 | impl From<&String> for HashedClaim { 152 | fn from(v: &String) -> Self { 153 | Self { 154 | value: v.to_string().into_bytes(), 155 | print_friendly: true, 156 | } 157 | } 158 | } 159 | 160 | impl From for HashedClaim { 161 | fn from(v: String) -> Self { 162 | Self { 163 | value: v.into_bytes(), 164 | print_friendly: true, 165 | } 166 | } 167 | } 168 | 169 | impl AsRef<[u8]> for HashedClaim { 170 | fn as_ref(&self) -> &[u8] { 171 | self.value.as_ref() 172 | } 173 | } 174 | 175 | impl Claim for HashedClaim { 176 | type Value = Vec; 177 | 178 | fn get_type(&self) -> ClaimType { 179 | ClaimType::Hashed 180 | } 181 | 182 | fn to_scalar(&self) -> Scalar { 183 | let mut buffer = [0u8; 64]; 184 | Knox::xof_digest::(&self.value, &mut buffer); 185 | Scalar::from_bytes_wide(&buffer) 186 | } 187 | 188 | fn get_value(&self) -> Self::Value { 189 | self.value.clone() 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/claim/number.rs: -------------------------------------------------------------------------------- 1 | use super::{Claim, ClaimType}; 2 | use crate::utils::zero_center; 3 | use crate::{error::Error, utils::get_num_scalar}; 4 | use blsful::inner_types::Scalar; 5 | use chrono::Datelike; 6 | use core::{ 7 | fmt::{self, Display, Formatter}, 8 | hash::{Hash, Hasher}, 9 | }; 10 | use serde::{Deserialize, Serialize}; 11 | 12 | /// A claim that is a 64-bit signed number 13 | #[derive(Copy, Clone, Eq, Debug, Deserialize, Serialize)] 14 | pub struct NumberClaim { 15 | /// The claim value 16 | pub value: isize, 17 | } 18 | 19 | impl PartialEq for NumberClaim { 20 | fn eq(&self, other: &Self) -> bool { 21 | self.value == other.value 22 | } 23 | } 24 | 25 | impl Hash for NumberClaim { 26 | fn hash(&self, state: &mut H) { 27 | self.value.hash(state); 28 | } 29 | } 30 | 31 | impl Display for NumberClaim { 32 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 33 | write!(f, "NumberClaim {{ {} }}", self.value) 34 | } 35 | } 36 | 37 | macro_rules! impl_from { 38 | ($name:ident, $($ty:ty),*) => { 39 | $( 40 | impl From<$ty> for $name { 41 | fn from(value: $ty) -> Self { 42 | Self::from(value as isize) 43 | } 44 | } 45 | )* 46 | }; 47 | } 48 | 49 | impl From for NumberClaim { 50 | fn from(value: Scalar) -> Self { 51 | let limb = <[u8; 8]>::try_from(&value.to_le_bytes()[..8]) 52 | .expect("Scalar is 32 bytes, so 8 bytes should exist"); 53 | Self::from(zero_center(u64::from_le_bytes(limb) as isize)) 54 | } 55 | } 56 | 57 | impl From for NumberClaim { 58 | fn from(value: isize) -> Self { 59 | Self { value } 60 | } 61 | } 62 | 63 | impl_from!(NumberClaim, i64, i32, i16, i8, usize, u64, u32, u16, u8); 64 | 65 | impl Claim for NumberClaim { 66 | type Value = isize; 67 | 68 | fn get_type(&self) -> ClaimType { 69 | ClaimType::Number 70 | } 71 | 72 | fn to_scalar(&self) -> Scalar { 73 | get_num_scalar(self.value) 74 | } 75 | 76 | fn get_value(&self) -> Self::Value { 77 | self.value 78 | } 79 | } 80 | 81 | impl NumberClaim { 82 | /// RFC3339 dates are in the format of `YYYY-MM-DD` and are treated 83 | /// as a number claim with the value of `YYYYMMDD`. 84 | pub fn parse_rfc3339_date>(date: S) -> Result { 85 | // Use chrono to check whether the date is valid. 86 | // Invalid dates include days greater than 31, months greater than 12, etc. 87 | // and checks whether the month even supports the number of days specified 88 | // like February 30th is never valid. 89 | let dt = chrono::NaiveDate::parse_from_str(date.as_ref(), "%Y-%m-%d") 90 | .map_err(|_| Error::InvalidClaimData("Invalid RFC3339 date"))?; 91 | // There's no need to zero center unless we're dealing with a dates BC 92 | let mut value = dt.year().to_string(); 93 | if dt.month() < 10 { 94 | value.push('0'); 95 | } 96 | value.push_str(&dt.month().to_string()); 97 | if dt.day() < 10 { 98 | value.push('0'); 99 | } 100 | value.push_str(&dt.day().to_string()); 101 | Ok(Self::from(value.parse::().map_err(|_| { 102 | Error::InvalidClaimData("Invalid RFC3339 date") 103 | })?)) 104 | } 105 | 106 | /// RFC3339 DateTimes are in the format of `YYYY-MM-DDTHH:MM:SSZ` and are treated 107 | /// as a number claim representing the number of seconds since the Unix epoch. 108 | pub fn parse_rfc3339_datetime>(datetime: S) -> Result { 109 | let dt = chrono::DateTime::parse_from_rfc3339(datetime.as_ref()) 110 | .map_err(|_| Error::InvalidClaimData("Invalid RFC3339 datetime"))?; 111 | // There's no need to zero center unless we're dealing with a dates BC 112 | Ok(Self::from(dt.timestamp() as isize)) 113 | } 114 | } 115 | 116 | #[cfg(test)] 117 | mod tests { 118 | use super::*; 119 | 120 | #[test] 121 | fn test_rfc3339_date() { 122 | let claim = NumberClaim::parse_rfc3339_date("2021-01-01").unwrap(); 123 | assert_eq!(claim.value, 20210101); 124 | 125 | let claim = NumberClaim::parse_rfc3339_date("1982-12-31").unwrap(); 126 | assert_eq!(claim.value, 19821231); 127 | } 128 | 129 | #[test] 130 | fn test_rfc3339_datetime() { 131 | let claim = NumberClaim::parse_rfc3339_datetime("2021-01-01T00:00:00Z").unwrap(); 132 | assert_eq!(claim.value, 1609459200); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/claim/revocation.rs: -------------------------------------------------------------------------------- 1 | use super::{Claim, ClaimType}; 2 | use crate::knox::accumulator::vb20::Element; 3 | use blsful::inner_types::Scalar; 4 | use core::{ 5 | fmt::{self, Display, Formatter}, 6 | hash::{Hash, Hasher}, 7 | }; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | /// A claim used for revocation 11 | #[derive(Clone, Debug, Eq, Deserialize, Serialize)] 12 | pub struct RevocationClaim { 13 | /// The revocation id 14 | pub value: String, 15 | } 16 | 17 | impl PartialEq for RevocationClaim { 18 | fn eq(&self, other: &Self) -> bool { 19 | self.value == other.value 20 | } 21 | } 22 | 23 | impl Hash for RevocationClaim { 24 | fn hash(&self, state: &mut H) { 25 | self.value.hash(state); 26 | } 27 | } 28 | 29 | impl Display for RevocationClaim { 30 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 31 | write!(f, "RevocationClaim {{ {} }}", self.value) 32 | } 33 | } 34 | 35 | impl From for RevocationClaim { 36 | fn from(s: String) -> Self { 37 | Self { value: s } 38 | } 39 | } 40 | 41 | impl From<&String> for RevocationClaim { 42 | fn from(s: &String) -> Self { 43 | Self { value: s.clone() } 44 | } 45 | } 46 | 47 | impl From<&str> for RevocationClaim { 48 | fn from(s: &str) -> Self { 49 | Self { 50 | value: s.to_string(), 51 | } 52 | } 53 | } 54 | 55 | impl Claim for RevocationClaim { 56 | type Value = String; 57 | 58 | fn get_type(&self) -> ClaimType { 59 | ClaimType::Revocation 60 | } 61 | 62 | fn to_scalar(&self) -> Scalar { 63 | Element::hash(self.value.as_bytes()).0 64 | } 65 | 66 | fn get_value(&self) -> Self::Value { 67 | self.value.clone() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/claim/scalar.rs: -------------------------------------------------------------------------------- 1 | use super::{Claim, ClaimType}; 2 | use crate::error::Error; 3 | use crate::CredxResult; 4 | use blsful::inner_types::Scalar; 5 | use core::{ 6 | fmt::{self, Display, Formatter}, 7 | hash::{Hash, Hasher}, 8 | }; 9 | use serde::{Deserialize, Serialize}; 10 | 11 | /// A claim that is already a scalar 12 | #[derive(Copy, Clone, Debug, Eq, Deserialize, Serialize)] 13 | pub struct ScalarClaim { 14 | /// The scalar value 15 | pub value: Scalar, 16 | } 17 | 18 | impl PartialEq for ScalarClaim { 19 | fn eq(&self, other: &Self) -> bool { 20 | self.value == other.value 21 | } 22 | } 23 | 24 | impl Hash for ScalarClaim { 25 | fn hash(&self, state: &mut H) { 26 | self.value.to_be_bytes().hash(state) 27 | } 28 | } 29 | 30 | impl Display for ScalarClaim { 31 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 32 | write!(f, "ScalarClaim {{ {} }}", self.value) 33 | } 34 | } 35 | 36 | impl From for ScalarClaim { 37 | fn from(value: Scalar) -> Self { 38 | Self { value } 39 | } 40 | } 41 | 42 | impl ScalarClaim { 43 | pub fn encode_str(value: &str) -> CredxResult { 44 | if value.len() > 31 { 45 | return Err(Error::InvalidClaimData( 46 | "scalar claim can only store 31 bytes or less", 47 | )); 48 | } 49 | let mut bytes = [0u8; 32]; 50 | if !value.is_empty() { 51 | bytes[0] = value.len() as u8; 52 | bytes[32 - value.len()..].copy_from_slice(value.as_bytes()); 53 | } 54 | let s = Option::::from(Scalar::from_be_bytes(&bytes)) 55 | .ok_or(Error::InvalidClaimData("scalar claim is not valid UTF-8"))?; 56 | Ok(Self::from(s)) 57 | } 58 | 59 | pub fn decode_to_str(&self) -> CredxResult { 60 | let data = self.value.to_be_bytes(); 61 | let len = data[0] as usize; 62 | String::from_utf8(data[32 - len..].to_vec()) 63 | .map_err(|_| Error::InvalidClaimData("scalar claim is not valid UTF-8")) 64 | } 65 | 66 | pub fn encode_bytes(value: &[u8]) -> CredxResult { 67 | if value.len() > 31 { 68 | return Err(Error::InvalidClaimData( 69 | "scalar claim can only store 31 bytes or less", 70 | )); 71 | } 72 | let mut bytes = [0u8; 32]; 73 | if !value.is_empty() { 74 | bytes[0] = value.len() as u8; 75 | bytes[32 - value.len()..].copy_from_slice(value); 76 | } 77 | let s = Option::::from(Scalar::from_be_bytes(&bytes)) 78 | .ok_or(Error::InvalidClaimData("scalar claim is not byte data"))?; 79 | Ok(Self::from(s)) 80 | } 81 | 82 | pub fn decode_to_bytes(&self) -> CredxResult> { 83 | let data = self.value.to_be_bytes(); 84 | let len = data[1] as usize; 85 | Ok(data[32 - len..].to_vec()) 86 | } 87 | } 88 | 89 | impl Claim for ScalarClaim { 90 | type Value = Scalar; 91 | 92 | fn get_type(&self) -> ClaimType { 93 | ClaimType::Scalar 94 | } 95 | 96 | fn to_scalar(&self) -> Scalar { 97 | self.value 98 | } 99 | 100 | fn get_value(&self) -> Self::Value { 101 | self.value 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/claim/type.rs: -------------------------------------------------------------------------------- 1 | use serde::{ 2 | de::{Error as DError, Unexpected, Visitor}, 3 | Deserialize, Deserializer, Serialize, Serializer, 4 | }; 5 | use std::fmt::{Display, Error as FmtError, Formatter}; 6 | use std::str::FromStr; 7 | 8 | /// The claim type 9 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 10 | #[repr(u8)] 11 | pub enum ClaimType { 12 | /// The case where its none of the others 13 | Unknown = 0, 14 | /// Hashed claims 15 | Hashed = 1, 16 | /// Numeric claims 17 | Number = 2, 18 | /// Scalar based claims 19 | Scalar = 3, 20 | /// Revocation based claims 21 | Revocation = 4, 22 | /// Enumeration based claims 23 | Enumeration = 5, 24 | } 25 | 26 | impl FromStr for ClaimType { 27 | type Err = String; 28 | 29 | fn from_str(s: &str) -> Result { 30 | match s.to_lowercase().as_str() { 31 | "enumeration" => Ok(Self::Enumeration), 32 | "hashed" => Ok(Self::Hashed), 33 | "number" => Ok(Self::Number), 34 | "scalar" => Ok(Self::Scalar), 35 | "revocation" => Ok(Self::Revocation), 36 | _ => Err("invalid type".to_string()), 37 | } 38 | } 39 | } 40 | 41 | impl Display for ClaimType { 42 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 43 | match self { 44 | Self::Enumeration => write!(f, "Enumeration"), 45 | Self::Hashed => write!(f, "Hashed"), 46 | Self::Number => write!(f, "Number"), 47 | Self::Revocation => write!(f, "Revocation"), 48 | Self::Scalar => write!(f, "Scalar"), 49 | _ => Err(FmtError), 50 | } 51 | } 52 | } 53 | 54 | impl From for ClaimType { 55 | fn from(v: u8) -> Self { 56 | match v { 57 | 1 => Self::Hashed, 58 | 2 => Self::Number, 59 | 3 => Self::Scalar, 60 | 4 => Self::Revocation, 61 | 5 => Self::Enumeration, 62 | _ => Self::Unknown, 63 | } 64 | } 65 | } 66 | 67 | impl Serialize for ClaimType { 68 | fn serialize(&self, serializer: S) -> Result 69 | where 70 | S: Serializer, 71 | { 72 | if serializer.is_human_readable() { 73 | let s = self.to_string(); 74 | serializer.serialize_str(&s) 75 | } else { 76 | let u = *self as u8; 77 | serializer.serialize_u8(u) 78 | } 79 | } 80 | } 81 | 82 | impl<'de> Deserialize<'de> for ClaimType { 83 | fn deserialize(deserializer: D) -> Result 84 | where 85 | D: Deserializer<'de>, 86 | { 87 | struct ClaimTypeVisitor; 88 | 89 | impl Visitor<'_> for ClaimTypeVisitor { 90 | type Value = ClaimType; 91 | 92 | fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { 93 | write!(formatter, "a string or byte") 94 | } 95 | 96 | fn visit_u8(self, v: u8) -> Result 97 | where 98 | E: DError, 99 | { 100 | Ok(ClaimType::from(v)) 101 | } 102 | 103 | fn visit_str(self, v: &str) -> Result 104 | where 105 | E: DError, 106 | { 107 | ClaimType::from_str(v) 108 | .map_err(|_e| DError::invalid_type(Unexpected::Other(v), &self)) 109 | } 110 | } 111 | 112 | if deserializer.is_human_readable() { 113 | deserializer.deserialize_str(ClaimTypeVisitor) 114 | } else { 115 | deserializer.deserialize_u8(ClaimTypeVisitor) 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/credential.rs: -------------------------------------------------------------------------------- 1 | mod bundle; 2 | mod membership; 3 | mod offer; 4 | mod schema; 5 | 6 | pub use bundle::*; 7 | pub use membership::*; 8 | pub use offer::*; 9 | pub use schema::*; 10 | 11 | use super::claim::*; 12 | use crate::knox::accumulator::vb20::MembershipWitness; 13 | use crate::knox::short_group_sig_core::short_group_traits::ShortGroupSignatureScheme; 14 | use serde::{Deserialize, Serialize}; 15 | 16 | /// A credential 17 | #[derive(Clone, Debug, Deserialize, Serialize)] 18 | pub struct Credential { 19 | /// The signed claims 20 | pub claims: Vec, 21 | /// The signature 22 | pub signature: S::Signature, 23 | /// The revocation handle 24 | pub revocation_handle: MembershipCredential, 25 | /// The claim that is used for revocation 26 | pub revocation_index: usize, 27 | } 28 | -------------------------------------------------------------------------------- /src/credential/bundle.rs: -------------------------------------------------------------------------------- 1 | use crate::knox::accumulator::vb20::Accumulator; 2 | use crate::knox::short_group_sig_core::short_group_traits::ShortGroupSignatureScheme; 3 | use crate::{credential::*, issuer::*}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// A credential and the issuer's information 7 | #[derive(Clone, Debug, Deserialize, Serialize)] 8 | pub struct CredentialBundle { 9 | /// The issuer information that gave this credential 10 | #[serde(bound( 11 | serialize = "IssuerPublic: Serialize", 12 | deserialize = "IssuerPublic: Deserialize<'de>" 13 | ))] 14 | pub issuer: IssuerPublic, 15 | /// The signed credential 16 | #[serde(bound( 17 | serialize = "Credential: Serialize", 18 | deserialize = "Credential: Deserialize<'de>" 19 | ))] 20 | pub credential: Credential, 21 | } 22 | 23 | impl CredentialBundle { 24 | /// Update the bundles revocation handle 25 | pub fn update_revocation_handle( 26 | &mut self, 27 | revocation_handle: MembershipWitness, 28 | revocation_registry: Accumulator, 29 | ) { 30 | self.credential.revocation_handle = revocation_handle; 31 | self.issuer.revocation_registry = revocation_registry; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/credential/membership.rs: -------------------------------------------------------------------------------- 1 | use crate::knox::accumulator::vb20; 2 | use crate::prelude::*; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// A membership signing key 6 | pub type MembershipSigningKey = vb20::SecretKey; 7 | 8 | /// A membership verification key 9 | pub type MembershipVerificationKey = vb20::PublicKey; 10 | 11 | /// A membership registry 12 | pub type MembershipRegistry = vb20::Accumulator; 13 | 14 | /// A membership credential 15 | pub type MembershipCredential = vb20::MembershipWitness; 16 | 17 | /// A membership claim in the registry 18 | #[derive(Clone, Debug, Deserialize, Serialize)] 19 | pub struct MembershipClaim(pub vb20::Element); 20 | 21 | impl From for MembershipClaim { 22 | fn from(value: ClaimData) -> Self { 23 | Self::from(&value) 24 | } 25 | } 26 | 27 | impl From<&ClaimData> for MembershipClaim { 28 | fn from(value: &ClaimData) -> Self { 29 | Self(vb20::Element(value.to_scalar())) 30 | } 31 | } 32 | 33 | impl From for MembershipClaim { 34 | fn from(value: NumberClaim) -> Self { 35 | Self::from(&value) 36 | } 37 | } 38 | 39 | impl From<&NumberClaim> for MembershipClaim { 40 | fn from(value: &NumberClaim) -> Self { 41 | Self(vb20::Element(value.to_scalar())) 42 | } 43 | } 44 | 45 | impl From for MembershipClaim { 46 | fn from(value: HashedClaim) -> Self { 47 | Self::from(&value) 48 | } 49 | } 50 | 51 | impl From<&HashedClaim> for MembershipClaim { 52 | fn from(value: &HashedClaim) -> Self { 53 | Self(vb20::Element(value.to_scalar())) 54 | } 55 | } 56 | 57 | impl From for MembershipClaim { 58 | fn from(value: ScalarClaim) -> Self { 59 | Self::from(&value) 60 | } 61 | } 62 | 63 | impl From<&ScalarClaim> for MembershipClaim { 64 | fn from(value: &ScalarClaim) -> Self { 65 | Self(vb20::Element(value.to_scalar())) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/credential/offer.rs: -------------------------------------------------------------------------------- 1 | use crate::knox::short_group_sig_core::short_group_traits::ShortGroupSignatureScheme; 2 | use crate::{claim::ClaimData, issuer::IssuerPublic}; 3 | use rand_core::RngCore; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// A credential offer from the issuer to the holder 7 | #[derive(Clone, Debug, Deserialize, Serialize)] 8 | pub struct CredentialOffer { 9 | /// The claims to be signed 10 | pub claims: Vec, 11 | /// The issuer's id 12 | #[serde(bound( 13 | serialize = "IssuerPublic: Serialize", 14 | deserialize = "IssuerPublic: Deserialize<'de>" 15 | ))] 16 | pub issuer: IssuerPublic, 17 | /// The credential offer id 18 | pub offer_id: [u8; 16], 19 | } 20 | 21 | impl CredentialOffer { 22 | /// Create a new offer 23 | pub fn new(claims: &[ClaimData], issuer: IssuerPublic) -> Self { 24 | let mut offer_id = [0u8; 16]; 25 | rand::thread_rng().fill_bytes(&mut offer_id); 26 | Self { 27 | claims: claims.to_vec(), 28 | issuer, 29 | offer_id, 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | /// Errors created by this library 2 | #[derive(Clone, Debug, Eq, PartialEq)] 3 | pub enum Error { 4 | /// Bad Credential Schema 5 | InvalidCredentialSchema, 6 | /// Bad Blinding Indices 7 | InvalidBlindingIndices, 8 | /// Invalid revocation registry elements 9 | InvalidRevocationRegistryRevokeOperation, 10 | /// Attempted to update a handle for a value that's already revoked or not included 11 | InvalidRevocationHandleUpdate, 12 | /// Invalid signing operation 13 | InvalidSigningOperation, 14 | /// Invalid claim data 15 | InvalidClaimData(&'static str), 16 | /// Invalid public key 17 | InvalidPublicKey, 18 | /// Invalid data for creating a signature proof 19 | InvalidSignatureProofData, 20 | /// Invalid data for creating a presentation 21 | InvalidPresentationData(String), 22 | /// Invalid bulletproof range 23 | InvalidBulletproofRange, 24 | /// Invalid binary or text data 25 | DeserializationError, 26 | /// A generic error message 27 | General(&'static str), 28 | } 29 | -------------------------------------------------------------------------------- /src/knox.rs: -------------------------------------------------------------------------------- 1 | /// Accumulator methods 2 | pub mod accumulator; 3 | pub mod bbs; 4 | /// ECC group operations 5 | pub mod ecc_group; 6 | /// Pointcheval Sanders signatures 7 | pub mod ps; 8 | /// Operations for short group signatures 9 | pub mod short_group_sig_core; 10 | 11 | use blsful::*; 12 | use rand_core::{CryptoRng, RngCore}; 13 | use sha3::digest::{ExtendableOutput, Update, XofReader}; 14 | 15 | /// General purpose crypto operations 16 | pub struct Knox {} 17 | 18 | impl Knox { 19 | /// Compute a variable length hash 20 | pub fn xof_digest(input: &[u8], output: &mut [u8]) { 21 | let mut r = X::default().chain(input.as_ref()).finalize_xof(); 22 | r.read(output); 23 | } 24 | 25 | /// New BLS Keys w/G1 public keys 26 | pub fn new_bls381g1_keys( 27 | rng: impl RngCore + CryptoRng, 28 | ) -> (PublicKey, SecretKey) { 29 | let sk = Bls12381G1::random_secret_key(rng); 30 | (sk.public_key(), sk) 31 | } 32 | 33 | /// New BLS Keys w/G2 public keys 34 | pub fn new_bls381g2_keys( 35 | rng: impl RngCore + CryptoRng, 36 | ) -> (PublicKey, SecretKey) { 37 | let sk = Bls12381G2::random_secret_key(rng); 38 | (sk.public_key(), sk) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/knox/accumulator.rs: -------------------------------------------------------------------------------- 1 | /// See https://eprint.iacr.org/2020/777 2 | pub mod vb20; 3 | -------------------------------------------------------------------------------- /src/knox/accumulator/vb20.rs: -------------------------------------------------------------------------------- 1 | /// Accumulator elements 2 | mod accumulator; 3 | /// Accumulator errors 4 | mod error; 5 | /// Signing and Verification keys 6 | mod key; 7 | /// Proofs of inclusion or exclusion 8 | mod proof; 9 | /// Witnesses 10 | mod witness; 11 | 12 | pub use accumulator::*; 13 | pub use error::*; 14 | pub use key::*; 15 | pub use proof::*; 16 | pub use witness::*; 17 | 18 | use blsful::inner_types::*; 19 | use rand_core::{CryptoRng, RngCore}; 20 | use sha3::{ 21 | digest::{ExtendableOutput, Update, XofReader}, 22 | Shake256, 23 | }; 24 | 25 | /// Similar to https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-2.3 26 | /// info is left blank 27 | fn generate_fr(salt: &[u8], ikm: Option<&[u8]>, mut rng: impl RngCore + CryptoRng) -> Scalar { 28 | let mut hasher = Shake256::default(); 29 | match ikm { 30 | Some(v) => { 31 | hasher.update(salt); 32 | hasher.update(v); 33 | } 34 | None => { 35 | hasher.update(salt); 36 | let mut arr = [0u8; 32]; 37 | rng.fill_bytes(&mut arr); 38 | hasher.update(&arr); 39 | } 40 | }; 41 | let mut okm = [0u8; 64]; 42 | let mut xof = hasher.finalize_xof(); 43 | xof.read(&mut okm); 44 | Scalar::from_bytes_wide(&okm) 45 | } 46 | 47 | fn hash_to_g1>(data: I) -> G1Projective { 48 | const DST: &[u8] = b"BLS12381G1_XMD:SHA-256_SSWU_RO_VB_ACCUMULATOR:1_0_0"; 49 | G1Projective::hash::>(data.as_ref(), DST) 50 | } 51 | 52 | /// dA(x) and dD(x) 53 | fn dad(values: &[Element], y: Scalar) -> Scalar { 54 | if values.len() == 1 { 55 | values[0].0 - y 56 | } else { 57 | values 58 | .iter() 59 | .map(|v| v.0 - y) 60 | .fold(Scalar::ONE, |a, y| a * y) 61 | } 62 | } 63 | 64 | /// Salt used for hashing values into the accumulator 65 | /// Giuseppe Vitto, Alex Biryukov = VB 66 | /// Accumulator = ACC 67 | const SALT: &[u8] = b"VB-ACC-HASH-SALT-"; 68 | 69 | /// A Polynomial for Points 70 | pub struct PolynomialG1(pub Vec); 71 | 72 | impl PolynomialG1 { 73 | /// Initialize this polynomial with the expected capacity 74 | pub fn with_capacity(size: usize) -> Self { 75 | Self(Vec::with_capacity(size)) 76 | } 77 | 78 | /// Return the result of evaluating the polynomial with the specified point 79 | pub fn evaluate(&self, x: Scalar) -> Option { 80 | if self.0.is_empty() { 81 | return None; 82 | } 83 | 84 | let mut p = x; 85 | let mut res = self.0[0]; 86 | 87 | for i in 1..self.0.len() { 88 | res += self.0[i] * p; 89 | p *= x; 90 | } 91 | Some(res) 92 | } 93 | } 94 | 95 | impl core::ops::AddAssign for PolynomialG1 { 96 | fn add_assign(&mut self, rhs: Self) { 97 | let min_len = core::cmp::min(self.0.len(), rhs.0.len()); 98 | 99 | if self.0.len() == min_len { 100 | for i in min_len..rhs.0.len() { 101 | self.0.push(rhs.0[i]); 102 | } 103 | } 104 | for i in 0..min_len { 105 | self.0[i] += rhs.0[i]; 106 | } 107 | } 108 | } 109 | 110 | impl core::ops::MulAssign for PolynomialG1 { 111 | fn mul_assign(&mut self, rhs: Scalar) { 112 | for i in 0..self.0.len() { 113 | self.0[i] *= rhs; 114 | } 115 | } 116 | } 117 | 118 | /// A Polynomial for scalars 119 | #[derive(Default)] 120 | pub struct Polynomial(pub Vec); 121 | 122 | impl Polynomial { 123 | /// Initialize this polynomial with the expected capacity 124 | pub fn with_capacity(size: usize) -> Self { 125 | Self(Vec::with_capacity(size)) 126 | } 127 | 128 | /// Add the scalar to the end of the polynomial 129 | pub fn push(&mut self, value: Scalar) { 130 | self.0.push(value); 131 | } 132 | } 133 | 134 | impl From> for Polynomial { 135 | fn from(scalars: Vec) -> Self { 136 | Self(scalars) 137 | } 138 | } 139 | 140 | impl core::ops::AddAssign for Polynomial { 141 | fn add_assign(&mut self, rhs: Self) { 142 | *self += rhs.0.as_slice(); 143 | } 144 | } 145 | 146 | impl core::ops::AddAssign<&[Scalar]> for Polynomial { 147 | fn add_assign(&mut self, rhs: &[Scalar]) { 148 | let min_len = core::cmp::min(self.0.len(), rhs.len()); 149 | 150 | if self.0.len() == min_len { 151 | for i in rhs.iter().skip(min_len) { 152 | self.0.push(*i); 153 | } 154 | } 155 | for (i, item) in rhs.iter().enumerate().take(min_len) { 156 | self.0[i] += item; 157 | } 158 | } 159 | } 160 | 161 | impl core::ops::SubAssign for Polynomial { 162 | fn sub_assign(&mut self, rhs: Self) { 163 | *self -= rhs.0.as_slice(); 164 | } 165 | } 166 | 167 | impl core::ops::SubAssign<&[Scalar]> for Polynomial { 168 | fn sub_assign(&mut self, rhs: &[Scalar]) { 169 | let min_len = core::cmp::min(self.0.len(), rhs.len()); 170 | if self.0.len() == min_len { 171 | for item in rhs.iter().skip(min_len) { 172 | self.0.push(-item); 173 | } 174 | } 175 | for (i, item) in rhs.iter().enumerate().take(min_len) { 176 | self.0[i] -= item; 177 | } 178 | } 179 | } 180 | 181 | impl core::ops::MulAssign for Polynomial { 182 | fn mul_assign(&mut self, rhs: Self) { 183 | *self *= rhs.0.as_slice(); 184 | } 185 | } 186 | 187 | impl core::ops::MulAssign<&[Scalar; 2]> for Polynomial { 188 | fn mul_assign(&mut self, rhs: &[Scalar; 2]) { 189 | *self *= &rhs[..]; 190 | } 191 | } 192 | 193 | impl core::ops::MulAssign<&[Scalar]> for Polynomial { 194 | fn mul_assign(&mut self, rhs: &[Scalar]) { 195 | let orig = self.0.clone(); 196 | 197 | // Both vectors can't be empty 198 | if !self.0.is_empty() || !rhs.is_empty() { 199 | for i in 0..self.0.len() { 200 | self.0[i] = Scalar::ZERO; 201 | } 202 | // M + N - 1 203 | self.0 204 | .resize_with(self.0.len() + rhs.len() - 1, || Scalar::ZERO); 205 | 206 | // Calculate product 207 | for (i, item) in orig.iter().enumerate() { 208 | for (j, jitem) in rhs.iter().enumerate() { 209 | self.0[i + j] += jitem * item; 210 | } 211 | } 212 | } 213 | } 214 | } 215 | 216 | impl core::ops::MulAssign for Polynomial { 217 | fn mul_assign(&mut self, rhs: Scalar) { 218 | for i in 0..self.0.len() { 219 | self.0[i] *= rhs; 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/knox/accumulator/vb20/error.rs: -------------------------------------------------------------------------------- 1 | /// An accumulator error 2 | #[derive(Clone, Debug)] 3 | pub struct Error { 4 | /// The string message 5 | pub message: String, 6 | /// The code number of the error 7 | pub code: usize, 8 | } 9 | 10 | impl Error { 11 | /// Create a message from a number and string 12 | pub fn from_msg(code: usize, message: &str) -> Self { 13 | Self { 14 | code, 15 | message: String::from(message), 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/knox/bbs.rs: -------------------------------------------------------------------------------- 1 | mod blind_signature; 2 | mod blind_signature_context; 3 | mod msg_gens; 4 | mod pok_signature; 5 | mod pok_signature_proof; 6 | mod public_key; 7 | mod scheme; 8 | mod secret_key; 9 | mod signature; 10 | 11 | pub use blind_signature::*; 12 | pub use blind_signature_context::*; 13 | pub use msg_gens::*; 14 | pub use pok_signature::*; 15 | pub use pok_signature_proof::*; 16 | pub use public_key::*; 17 | pub use scheme::*; 18 | pub use secret_key::*; 19 | pub use signature::*; 20 | -------------------------------------------------------------------------------- /src/knox/bbs/blind_signature.rs: -------------------------------------------------------------------------------- 1 | use super::{PublicKey, SecretKey, Signature}; 2 | use crate::error::Error; 3 | use crate::knox::short_group_sig_core::short_group_traits::BlindSignature as BlindSignatureTrait; 4 | use crate::CredxResult; 5 | use blsful::inner_types::{Field, G1Projective, Scalar}; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Deserialize, Serialize)] 9 | pub struct BlindSignature(pub(crate) Signature); 10 | 11 | impl BlindSignatureTrait for BlindSignature { 12 | type SecretKey = SecretKey; 13 | type PublicKey = PublicKey; 14 | type Signature = Signature; 15 | 16 | fn new( 17 | commitment: G1Projective, 18 | sk: &SecretKey, 19 | msgs: &[(usize, Scalar)], 20 | ) -> CredxResult { 21 | if sk.is_invalid() { 22 | return Err(Error::InvalidSigningOperation); 23 | } 24 | let max_idx = msgs 25 | .iter() 26 | .map(|(idx, _)| *idx) 27 | .max() 28 | .ok_or(Error::General("No messages"))?; 29 | if max_idx >= sk.max_messages { 30 | return Err(Error::General("Invalid message index")); 31 | } 32 | let expanded_pub_key = PublicKey::from(sk); 33 | let domain = super::signature::domain_calculation(&expanded_pub_key); 34 | let (points, scalars): (Vec, Vec) = msgs 35 | .iter() 36 | .map(|(i, m)| (expanded_pub_key.y[*i], *m)) 37 | .unzip(); 38 | 39 | let e = super::signature::compute_e(sk, &scalars, domain); 40 | 41 | let ske = (sk.x + e).invert(); 42 | if ske.is_none().into() { 43 | // only fails if sk + e is zero 44 | return Err(Error::General("Invalid signature")); 45 | } 46 | 47 | let b = 48 | G1Projective::GENERATOR + commitment + G1Projective::sum_of_products(&points, &scalars); 49 | 50 | let a = b * ske.expect("a valid scalar"); 51 | 52 | Ok(Self(Signature { a, e })) 53 | } 54 | 55 | fn to_unblinded(self, _blinding: Scalar) -> Signature { 56 | self.0 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/knox/bbs/blind_signature_context.rs: -------------------------------------------------------------------------------- 1 | use super::PublicKey; 2 | use crate::error::Error; 3 | use crate::knox::bbs::SecretKey; 4 | use crate::knox::short_group_sig_core::short_group_traits::BlindSignatureContext as BlindSignatureContextTrait; 5 | use crate::CredxResult; 6 | use blsful::inner_types::{Curve, G1Projective, Scalar}; 7 | use merlin::Transcript; 8 | use serde::{Deserialize, Serialize}; 9 | use std::collections::BTreeSet; 10 | use subtle::ConstantTimeEq; 11 | 12 | /// Contains the data used for computing a blind signature and verifying 13 | /// proof of hidden messages from a prover 14 | #[derive(Debug, Clone, Serialize, Deserialize)] 15 | pub struct BlindSignatureContext { 16 | /// The blinded signature commitment 17 | pub commitment: G1Projective, 18 | /// The challenge hash for the Fiat-Shamir heuristic 19 | pub challenge: Scalar, 20 | /// The proofs for the hidden messages 21 | pub proofs: Vec, 22 | } 23 | 24 | impl BlindSignatureContextTrait for BlindSignatureContext { 25 | type SecretKey = SecretKey; 26 | 27 | /// Assumes the proof of hidden messages 28 | /// If other proofs were included, those will need to be verified another way 29 | fn verify( 30 | &self, 31 | known_messages: &[usize], 32 | sk: &Self::SecretKey, 33 | nonce: Scalar, 34 | ) -> CredxResult { 35 | let pk = PublicKey::from(sk); 36 | let mut known = BTreeSet::new(); 37 | let mut points = Vec::with_capacity(pk.y.len()); 38 | for idx in known_messages { 39 | if *idx >= pk.y.len() { 40 | return Err(Error::InvalidSignatureProofData); 41 | } 42 | known.insert(*idx); 43 | } 44 | for i in 0..pk.y.len() { 45 | if !known.contains(&i) { 46 | points.push(pk.y[i]); 47 | } 48 | } 49 | points.push(self.commitment); 50 | 51 | let mut scalars = self.proofs.clone(); 52 | scalars.push(-self.challenge); 53 | 54 | let mut transcript = Transcript::new(b"new blind signature"); 55 | transcript.append_message(b"public key", pk.to_bytes().as_ref()); 56 | transcript.append_message(b"generator", &G1Projective::GENERATOR.to_compressed()); 57 | let mut res = [0u8; 64]; 58 | 59 | let commitment = G1Projective::sum_of_products(points.as_ref(), scalars.as_ref()); 60 | transcript.append_message( 61 | b"random commitment", 62 | &commitment.to_affine().to_compressed(), 63 | ); 64 | transcript.append_message( 65 | b"blind commitment", 66 | &self.commitment.to_affine().to_compressed(), 67 | ); 68 | transcript.append_message(b"nonce", &nonce.to_be_bytes()); 69 | transcript.challenge_bytes(b"blind signature context challenge", &mut res); 70 | let challenge = Scalar::from_bytes_wide(&res); 71 | 72 | Ok(self.challenge.ct_eq(&challenge).unwrap_u8() == 1) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/knox/bbs/msg_gens.rs: -------------------------------------------------------------------------------- 1 | use blsful::inner_types::G1Projective; 2 | use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander}; 3 | use sha2::Sha256; 4 | use std::num::NonZeroUsize; 5 | 6 | /// The message generators used for signing and proofs 7 | #[derive(Debug, Clone)] 8 | pub struct MessageGenerators(pub(crate) Vec); 9 | 10 | impl MessageGenerators { 11 | /// Create a new set of message generators 12 | pub fn new(count: NonZeroUsize) -> Self { 13 | Self::with_api_id(count, None) 14 | } 15 | 16 | /// Create a new set of message generators using a specific API ID 17 | pub fn with_api_id(count: NonZeroUsize, api_id: Option<&[u8]>) -> Self { 18 | const SEED_DST: &[u8] = b"SIG_GENERATOR_SEED_"; 19 | const GENERATOR_DST: &[u8] = b"SIG_GENERATOR_DST_"; 20 | const GENERATOR_SEED: &[u8] = b"SIG_GENERATOR_SEED_"; 21 | 22 | let seed_dst = api_id 23 | .unwrap_or(&[]) 24 | .iter() 25 | .chain(SEED_DST) 26 | .copied() 27 | .collect::>(); 28 | let generator_seed = api_id 29 | .unwrap_or(&[]) 30 | .iter() 31 | .chain(GENERATOR_SEED) 32 | .copied() 33 | .collect::>(); 34 | let generator_dst = api_id 35 | .unwrap_or(&[]) 36 | .iter() 37 | .chain(GENERATOR_DST) 38 | .copied() 39 | .collect::>(); 40 | 41 | let count = count.get(); 42 | let mut generators = Vec::with_capacity(count); 43 | 44 | let binding = [seed_dst.as_slice()]; 45 | let mut v = [0u8; 40]; 46 | let mut v_expander = 47 | ExpandMsgXmd::::expand_message(&[&generator_seed], &binding, 32) 48 | .expect("Failed to expand message"); 49 | v_expander.fill_bytes(&mut v[..32]); 50 | 51 | let mut inner_v = [0u8; 32]; 52 | for i in 0..count { 53 | v[32..].copy_from_slice(&(i as u64).to_be_bytes()); 54 | let mut inner_v_expander = ExpandMsgXmd::::expand_message(&[&v], &binding, 32) 55 | .expect("Failed to expand message"); 56 | inner_v_expander.fill_bytes(&mut inner_v); 57 | let g_i = G1Projective::hash::>(&inner_v, &generator_dst); 58 | generators.push(g_i); 59 | } 60 | 61 | Self(generators) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/knox/bbs/pok_signature.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::knox::bbs::{PokSignatureProof, PublicKey, Signature}; 3 | use crate::knox::short_group_sig_core::{ 4 | short_group_traits::ProofOfSignatureKnowledgeContribution, *, 5 | }; 6 | use crate::CredxResult; 7 | use blsful::inner_types::{G1Affine, G1Projective, Scalar}; 8 | use elliptic_curve::Field; 9 | use merlin::Transcript; 10 | use rand_core::*; 11 | 12 | /// Proof of Knowledge of a Signature that is used by the prover 13 | /// to construct `PoKOfSignatureProof`. 14 | pub struct PokSignature { 15 | proof: ProofCommittedBuilder, 16 | a_bar: G1Projective, 17 | b_bar: G1Projective, 18 | hidden_messages: Vec, 19 | } 20 | 21 | impl ProofOfSignatureKnowledgeContribution for PokSignature { 22 | type Signature = Signature; 23 | type PublicKey = PublicKey; 24 | type ProofOfKnowledge = PokSignatureProof; 25 | 26 | fn commit( 27 | signature: &Self::Signature, 28 | public_key: &Self::PublicKey, 29 | messages: &[ProofMessage], 30 | mut rng: impl RngCore + CryptoRng, 31 | ) -> CredxResult { 32 | let msgs = messages.iter().map(|m| m.get_message()).collect::>(); 33 | 34 | let r = Scalar::random(&mut rng); 35 | let r_inv = Option::from((-r).invert()).ok_or(Error::InvalidPresentationData("an error occurred when creating a signature proof of knowledge, the random value `r` was zero".to_string()))?; 36 | let r_inv_e = r_inv * signature.e; 37 | 38 | let b = G1Projective::GENERATOR + G1Projective::sum_of_products(&public_key.y, &msgs); 39 | 40 | let a_bar = signature.a * r; 41 | let b_bar = b * r - a_bar * signature.e; 42 | 43 | let mut proof = ProofCommittedBuilder::new(G1Projective::sum_of_products); 44 | let mut hidden_messages = Vec::with_capacity(msgs.len() + 2); 45 | 46 | for (i, m) in messages.iter().enumerate() { 47 | match m { 48 | ProofMessage::Hidden(HiddenMessage::ProofSpecificBlinding(msg)) => { 49 | proof.commit_random(public_key.y[i], &mut rng); 50 | hidden_messages.push(*msg); 51 | } 52 | ProofMessage::Hidden(HiddenMessage::ExternalBlinding(msg, n)) => { 53 | proof.commit(public_key.y[i], *n); 54 | hidden_messages.push(*msg); 55 | } 56 | ProofMessage::Revealed(_) => {} 57 | } 58 | } 59 | proof.commit_random(a_bar, &mut rng); 60 | hidden_messages.push(r_inv_e); 61 | proof.commit_random(b_bar, &mut rng); 62 | hidden_messages.push(r_inv); 63 | Ok(Self { 64 | proof, 65 | a_bar, 66 | b_bar, 67 | hidden_messages, 68 | }) 69 | } 70 | 71 | fn add_proof_contribution(&self, transcript: &mut Transcript) { 72 | transcript.append_message(b"a_bar", self.a_bar.to_compressed().as_ref()); 73 | transcript.append_message(b"b_bar", self.b_bar.to_compressed().as_ref()); 74 | self.proof 75 | .add_challenge_contribution(b"commitment", transcript); 76 | } 77 | 78 | fn generate_proof(self, challenge: Scalar) -> CredxResult { 79 | let proof = self 80 | .proof 81 | .generate_proof(challenge, &self.hidden_messages)?; 82 | Ok(PokSignatureProof { 83 | a_bar: self.a_bar, 84 | b_bar: self.b_bar, 85 | t: self.proof.commitment(), 86 | proof, 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/knox/bbs/public_key.rs: -------------------------------------------------------------------------------- 1 | use super::{MessageGenerators, SecretKey}; 2 | use crate::{ 3 | error::Error, knox::short_group_sig_core::short_group_traits::PublicKey as PublicKeyTrait, 4 | }; 5 | use blsful::inner_types::*; 6 | use serde::{Deserialize, Serialize}; 7 | use std::num::NonZeroUsize; 8 | use subtle::Choice; 9 | 10 | /// BBS compressed public key 11 | /// 12 | /// See 13 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] 14 | pub struct CompressedPublicKey { 15 | pub(crate) x: G2Projective, 16 | pub(crate) max_messages: usize, 17 | } 18 | 19 | impl From<&SecretKey> for CompressedPublicKey { 20 | fn from(sk: &SecretKey) -> Self { 21 | Self { 22 | x: G2Projective::GENERATOR * sk.x, 23 | max_messages: sk.max_messages, 24 | } 25 | } 26 | } 27 | 28 | impl PublicKeyTrait for CompressedPublicKey { 29 | type MessageGenerator = G1Projective; 30 | type BlindMessageGenerator = G1Projective; 31 | } 32 | 33 | impl From for Vec { 34 | fn from(pk: CompressedPublicKey) -> Self { 35 | pk.to_bytes() 36 | } 37 | } 38 | 39 | impl TryFrom> for CompressedPublicKey { 40 | type Error = Error; 41 | 42 | fn try_from(bytes: Vec) -> Result { 43 | Self::try_from(bytes.as_slice()) 44 | } 45 | } 46 | 47 | impl TryFrom<&Vec> for CompressedPublicKey { 48 | type Error = Error; 49 | 50 | fn try_from(bytes: &Vec) -> Result { 51 | Self::try_from(bytes.as_slice()) 52 | } 53 | } 54 | 55 | impl TryFrom<&[u8]> for CompressedPublicKey { 56 | type Error = Error; 57 | 58 | fn try_from(bytes: &[u8]) -> Result { 59 | Self::from_bytes(bytes).ok_or(Error::General("Invalid public key")) 60 | } 61 | } 62 | 63 | impl TryFrom> for CompressedPublicKey { 64 | type Error = Error; 65 | 66 | fn try_from(bytes: Box<[u8]>) -> Result { 67 | Self::try_from(bytes.as_ref()) 68 | } 69 | } 70 | 71 | impl CompressedPublicKey { 72 | /// Check if this public key is valid 73 | pub fn is_valid(&self) -> Choice { 74 | !self.x.is_identity() 75 | } 76 | 77 | /// Check if this public key is invalid 78 | pub fn is_invalid(&self) -> Choice { 79 | self.x.is_identity() 80 | } 81 | 82 | /// Convert a byte sequence into the public key 83 | pub fn from_bytes>(bytes: B) -> Option { 84 | let bytes = bytes.as_ref(); 85 | serde_bare::from_slice(bytes).ok() 86 | } 87 | 88 | /// Decompress into the public key 89 | pub fn decompress(&self) -> PublicKey { 90 | PublicKey::new(*self) 91 | } 92 | } 93 | 94 | /// Public key which includes the generators for each message 95 | #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] 96 | pub struct PublicKey { 97 | pub(crate) y: Vec, 98 | pub(crate) w: G2Projective, 99 | } 100 | 101 | impl PublicKeyTrait for PublicKey { 102 | type MessageGenerator = G1Projective; 103 | type BlindMessageGenerator = G1Projective; 104 | } 105 | 106 | impl From<&PublicKey> for CompressedPublicKey { 107 | fn from(pk: &PublicKey) -> Self { 108 | pk.compress() 109 | } 110 | } 111 | 112 | impl From<&SecretKey> for PublicKey { 113 | fn from(sk: &SecretKey) -> Self { 114 | Self::with_secret_key(sk) 115 | } 116 | } 117 | 118 | impl PublicKey { 119 | /// Create a new public key 120 | pub fn new(public_key: CompressedPublicKey) -> Self { 121 | let count = NonZeroUsize::new(public_key.max_messages).expect("non-zero"); 122 | let y = MessageGenerators::with_api_id(count, Some(&public_key.x.to_compressed())).0; 123 | Self { y, w: public_key.x } 124 | } 125 | 126 | /// Create a new public key from a secret key 127 | pub fn with_secret_key(secret_key: &SecretKey) -> Self { 128 | let public_key = CompressedPublicKey::from(secret_key); 129 | Self::new(public_key) 130 | } 131 | 132 | /// Compress this public key 133 | pub fn compress(&self) -> CompressedPublicKey { 134 | CompressedPublicKey { 135 | x: self.w, 136 | max_messages: self.y.len(), 137 | } 138 | } 139 | 140 | /// Check if this public key is invalid 141 | pub fn is_invalid(&self) -> Choice { 142 | self.w.is_identity() 143 | | self 144 | .y 145 | .iter() 146 | .map(|y| y.is_identity()) 147 | .fold(Choice::from(0u8), |acc, x| acc | x) 148 | } 149 | 150 | /// The raw bytes of this public key 151 | pub fn to_bytes(&self) -> Vec { 152 | serde_bare::to_vec(&self).expect("to serialize public key") 153 | } 154 | 155 | /// Convert a byte sequence into the public key 156 | pub fn from_bytes(bytes: &[u8]) -> Option { 157 | serde_bare::from_slice(bytes).ok() 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/knox/bbs/scheme.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | BlindSignature, BlindSignatureContext, PokSignature, PokSignatureProof, PublicKey, SecretKey, 3 | Signature, 4 | }; 5 | use crate::error::Error; 6 | use crate::knox::short_group_sig_core::short_group_traits::{ 7 | BlindSignature as _, BlindSignatureContext as _, ProofOfSignatureKnowledge, 8 | ProofOfSignatureKnowledgeContribution, ShortGroupSignatureScheme, 9 | }; 10 | use crate::knox::short_group_sig_core::{ProofCommittedBuilder, ProofMessage}; 11 | use crate::CredxResult; 12 | use blsful::inner_types::{Curve, G1Affine, G1Projective, Scalar}; 13 | use merlin::Transcript; 14 | use rand_core::{CryptoRng, RngCore}; 15 | use serde::{Deserialize, Serialize}; 16 | use std::num::NonZeroUsize; 17 | 18 | #[derive(Debug, Copy, Clone, Deserialize, Serialize)] 19 | pub struct BbsScheme; 20 | 21 | impl ShortGroupSignatureScheme for BbsScheme { 22 | type PublicKey = PublicKey; 23 | type SecretKey = SecretKey; 24 | type Signature = Signature; 25 | type BlindSignatureContext = BlindSignatureContext; 26 | type BlindSignature = BlindSignature; 27 | type ProofOfSignatureKnowledge = PokSignatureProof; 28 | type ProofOfSignatureKnowledgeContribution = PokSignature; 29 | 30 | fn new_keys( 31 | count: NonZeroUsize, 32 | rng: impl RngCore + CryptoRng, 33 | ) -> CredxResult<(Self::PublicKey, Self::SecretKey)> { 34 | if count.get() > 128 { 35 | return Err(Error::General("Invalid key generation")); 36 | } 37 | let sk = SecretKey::random(count, rng); 38 | let pk = PublicKey::from(&sk); 39 | Ok((pk, sk)) 40 | } 41 | 42 | fn sign(sk: &Self::SecretKey, msgs: M) -> CredxResult 43 | where 44 | M: AsRef<[Scalar]>, 45 | { 46 | Signature::new(sk, msgs) 47 | } 48 | 49 | fn blind_sign( 50 | ctx: &Self::BlindSignatureContext, 51 | sk: &Self::SecretKey, 52 | msgs: &[(usize, Scalar)], 53 | nonce: Scalar, 54 | ) -> CredxResult { 55 | let tv1 = msgs.iter().map(|(i, _)| *i).collect::>(); 56 | if ctx.verify(tv1.as_ref(), sk, nonce)? { 57 | BlindSignature::new(ctx.commitment, sk, msgs) 58 | } else { 59 | Err(Error::General("BlindSignatureError")) 60 | } 61 | } 62 | 63 | fn new_blind_signature_context( 64 | messages: &[(usize, Scalar)], 65 | public_key: &Self::PublicKey, 66 | nonce: Scalar, 67 | mut rng: impl RngCore + CryptoRng, 68 | ) -> CredxResult<(Self::BlindSignatureContext, Scalar)> { 69 | let mut points = Vec::with_capacity(messages.len()); 70 | let mut secrets = Vec::with_capacity(messages.len()); 71 | let mut committing = ProofCommittedBuilder::::new( 72 | G1Projective::sum_of_products, 73 | ); 74 | for (i, m) in messages { 75 | if *i > public_key.y.len() { 76 | return Err(Error::General("invalid blind signing")); 77 | } 78 | secrets.push(*m); 79 | points.push(public_key.y[*i]); 80 | committing.commit_random(public_key.y[*i], &mut rng); 81 | } 82 | let mut transcript = Transcript::new(b"new blind signature"); 83 | transcript.append_message(b"public key", public_key.to_bytes().as_ref()); 84 | transcript.append_message(b"generator", &G1Projective::GENERATOR.to_compressed()); 85 | let commitment = G1Projective::sum_of_products(points.as_ref(), secrets.as_ref()); 86 | committing.add_challenge_contribution(b"random commitment", &mut transcript); 87 | transcript.append_message( 88 | b"blind commitment", 89 | commitment.to_affine().to_compressed().as_ref(), 90 | ); 91 | transcript.append_message(b"nonce", nonce.to_be_bytes().as_ref()); 92 | let mut res = [0u8; 64]; 93 | transcript.challenge_bytes(b"blind signature context challenge", &mut res); 94 | let challenge = Scalar::from_bytes_wide(&res); 95 | let proofs = committing.generate_proof(challenge, secrets.as_slice())?; 96 | Ok(( 97 | BlindSignatureContext { 98 | commitment, 99 | challenge, 100 | proofs, 101 | }, 102 | Scalar::ZERO, 103 | )) 104 | } 105 | 106 | fn commit_signature_pok( 107 | signature: Self::Signature, 108 | public_key: &Self::PublicKey, 109 | messages: &[ProofMessage], 110 | rng: impl RngCore + CryptoRng, 111 | ) -> CredxResult { 112 | PokSignature::commit(&signature, public_key, messages, rng) 113 | } 114 | 115 | fn verify_signature_pok( 116 | revealed_msgs: &[(usize, Scalar)], 117 | public_key: &Self::PublicKey, 118 | proof: &Self::ProofOfSignatureKnowledge, 119 | nonce: Scalar, 120 | challenge: Scalar, 121 | ) -> bool { 122 | let mut transcript = Transcript::new(b"signature proof of knowledge"); 123 | proof.add_proof_contribution(public_key, revealed_msgs, challenge, &mut transcript); 124 | transcript.append_message(b"nonce", nonce.to_be_bytes().as_ref()); 125 | let mut res = [0u8; 64]; 126 | transcript.challenge_bytes(b"signature proof of knowledge", &mut res); 127 | let v_challenge = Scalar::from_bytes_wide(&res); 128 | 129 | proof.verify(public_key, revealed_msgs, challenge).is_ok() && challenge == v_challenge 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/knox/bbs/secret_key.rs: -------------------------------------------------------------------------------- 1 | use super::PublicKey; 2 | use crate::knox::short_group_sig_core::short_group_traits::SecretKey as SecretKeyTrait; 3 | use std::num::NonZeroUsize; 4 | 5 | use blsful::inner_types::{Field, Scalar}; 6 | use rand_chacha::ChaChaRng; 7 | use rand_core::{CryptoRng, RngCore, SeedableRng}; 8 | use serde::{Deserialize, Serialize}; 9 | use sha3::{ 10 | digest::{ExtendableOutput, Update}, 11 | Shake128, 12 | }; 13 | use zeroize::Zeroize; 14 | 15 | /// The secret key for BBS signatures 16 | /// 17 | /// See 18 | #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Zeroize)] 19 | pub struct SecretKey { 20 | pub(crate) x: Scalar, 21 | pub(crate) max_messages: usize, 22 | } 23 | 24 | impl SecretKeyTrait for SecretKey { 25 | type PublicKey = PublicKey; 26 | 27 | fn public_key(&self) -> PublicKey { 28 | PublicKey::from(self) 29 | } 30 | } 31 | 32 | impl SecretKey { 33 | /// Compute a secret key from a hash 34 | pub fn hash>(data: B, max_messages: NonZeroUsize) -> Self { 35 | const SALT: &[u8] = b"BBS-SIG-KEYGEN-SALT-"; 36 | let mut okm = [0u8; 32]; 37 | Shake128::default() 38 | .chain(SALT) 39 | .chain(data.as_ref()) 40 | .finalize_xof_into(&mut okm); 41 | let rng = ChaChaRng::from_seed(okm); 42 | Self { 43 | x: Scalar::random(rng), 44 | max_messages: max_messages.get(), 45 | } 46 | } 47 | 48 | /// Compute a secret key from a CS-PRNG 49 | pub fn random(max_messages: NonZeroUsize, rng: impl RngCore + CryptoRng) -> Self { 50 | Self { 51 | x: Scalar::random(rng), 52 | max_messages: max_messages.get(), 53 | } 54 | } 55 | 56 | /// Convert the secret key to bytes 57 | pub fn to_bytes(&self) -> Vec { 58 | serde_bare::to_vec(self).expect("to serialize SecretKey") 59 | } 60 | 61 | /// Convert a byte sequence into the secret key 62 | pub fn from_bytes>(bytes: B) -> Option { 63 | serde_bare::from_slice(bytes.as_ref()).ok() 64 | } 65 | 66 | /// Check if the secret key is valid 67 | pub fn is_valid(&self) -> bool { 68 | self.x.is_zero().unwrap_u8() == 0 69 | } 70 | 71 | /// Check if the secret key is invalid 72 | pub fn is_invalid(&self) -> bool { 73 | self.x.is_zero().unwrap_u8() == 1 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/knox/bbs/signature.rs: -------------------------------------------------------------------------------- 1 | use super::{PublicKey, SecretKey}; 2 | use crate::error::Error; 3 | use crate::knox::short_group_sig_core::short_group_traits::Signature as SignatureTrait; 4 | use crate::CredxResult; 5 | use blsful::inner_types::{ 6 | multi_miller_loop, Curve, Field, G1Projective, G2Affine, G2Prepared, G2Projective, Group, 7 | MillerLoopResult, Scalar, 8 | }; 9 | use elliptic_curve::{group::prime::PrimeCurveAffine, hash2curve::ExpandMsgXmd}; 10 | use serde::{Deserialize, Serialize}; 11 | use sha2::Sha256; 12 | use subtle::{Choice, ConditionallySelectable}; 13 | 14 | const DST: &[u8] = b"H2S_"; 15 | 16 | /// A BBS signature 17 | #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] 18 | pub struct Signature { 19 | pub(crate) a: G1Projective, 20 | pub(crate) e: Scalar, 21 | } 22 | 23 | impl ConditionallySelectable for Signature { 24 | fn conditional_select(lhs: &Self, rhs: &Self, choice: Choice) -> Self { 25 | let a = G1Projective::conditional_select(&lhs.a, &rhs.a, choice); 26 | let e = Scalar::conditional_select(&lhs.e, &rhs.e, choice); 27 | Self { a, e } 28 | } 29 | } 30 | 31 | impl SignatureTrait for Signature { 32 | type SecretKey = SecretKey; 33 | type PublicKey = PublicKey; 34 | 35 | fn create(sk: &Self::SecretKey, msgs: &[Scalar]) -> CredxResult { 36 | Self::new(sk, msgs) 37 | } 38 | 39 | fn verify(&self, pk: &Self::PublicKey, msgs: &[Scalar]) -> CredxResult<()> { 40 | if self.verify(pk, msgs).into() { 41 | Ok(()) 42 | } else { 43 | Err(Error::General("Invalid signature")) 44 | } 45 | } 46 | } 47 | 48 | impl Signature { 49 | /// The size in bytes of the signature 50 | pub const BYTES: usize = 80; 51 | 52 | /// Generate a new signature where all messages are known to the signer 53 | pub fn new(sk: &SecretKey, msgs: M) -> CredxResult 54 | where 55 | M: AsRef<[Scalar]>, 56 | { 57 | if sk.is_invalid() { 58 | return Err(Error::General("Invalid secret key")); 59 | } 60 | 61 | let msgs = msgs.as_ref(); 62 | if msgs.len() > sk.max_messages { 63 | return Err(Error::General("Too many messages")); 64 | } 65 | 66 | let pub_key = PublicKey::from(sk); 67 | let domain = domain_calculation(&pub_key); 68 | let e = compute_e(sk, msgs, domain); 69 | 70 | let ske = (sk.x + e).invert(); 71 | if ske.is_none().into() { 72 | // only fails if sk + e is zero 73 | return Err(Error::General("Invalid signature")); 74 | } 75 | 76 | let b = G1Projective::GENERATOR + G1Projective::sum_of_products(&pub_key.y, msgs); 77 | 78 | let a = b * ske.expect("a valid scalar"); 79 | 80 | Ok(Self { a, e }) 81 | } 82 | 83 | /// Verify a signature 84 | pub fn verify(&self, pk: &PublicKey, msgs: M) -> Choice 85 | where 86 | M: AsRef<[Scalar]>, 87 | { 88 | if (pk.is_invalid() | self.is_invalid()).into() { 89 | return Choice::from(0); 90 | } 91 | let msgs = msgs.as_ref(); 92 | if msgs.is_empty() || msgs.len() > pk.y.len() { 93 | return Choice::from(0); 94 | } 95 | 96 | let b = G1Projective::GENERATOR + G1Projective::sum_of_products(&pk.y, msgs); 97 | let lhs_pk = G2Projective::GENERATOR * self.e + pk.w; 98 | 99 | multi_miller_loop(&[ 100 | (&self.a.to_affine(), &G2Prepared::from(lhs_pk.to_affine())), 101 | (&b.to_affine(), &G2Prepared::from(-G2Affine::generator())), 102 | ]) 103 | .final_exponentiation() 104 | .is_identity() 105 | } 106 | 107 | /// Check if the signature is invalid 108 | pub fn is_invalid(&self) -> Choice { 109 | self.a.is_identity() | self.e.is_zero() 110 | } 111 | 112 | /// Convert the signature to bytes 113 | pub fn to_bytes(&self) -> Vec { 114 | serde_bare::to_vec(self).expect("to serialize Signature") 115 | } 116 | 117 | /// Convert bytes to a signature 118 | pub fn from_bytes(bytes: &[u8]) -> Option { 119 | serde_bare::from_slice(bytes).ok() 120 | } 121 | } 122 | 123 | pub(crate) fn compute_e(sk: &SecretKey, msgs: &[Scalar], domain: Scalar) -> Scalar { 124 | let mut bytes = Vec::with_capacity(32 * msgs.len() + 64); 125 | bytes.extend_from_slice(&sk.to_bytes()); 126 | for msg in msgs { 127 | bytes.extend_from_slice(&msg.to_be_bytes()); 128 | } 129 | bytes.extend_from_slice(&domain.to_be_bytes()); 130 | Scalar::hash::>(&bytes, DST) 131 | } 132 | 133 | pub(crate) fn domain_calculation(pk: &PublicKey) -> Scalar { 134 | let mut bytes = Vec::with_capacity(8 + 96 + 48 * pk.y.len()); 135 | bytes.extend_from_slice(&pk.w.to_compressed()); 136 | bytes.extend_from_slice(&((pk.y.len() + 1) as u64).to_be_bytes()); 137 | bytes.extend_from_slice(&G1Projective::GENERATOR.to_compressed()); 138 | for gen in &pk.y { 139 | bytes.extend_from_slice(&gen.to_compressed()); 140 | } 141 | bytes.extend_from_slice(&[0u8; 8]); 142 | Scalar::hash::>(&bytes, DST) 143 | } 144 | -------------------------------------------------------------------------------- /src/knox/ecc_group.rs: -------------------------------------------------------------------------------- 1 | use core::{fmt, ops}; 2 | use sha3::digest::Digest; 3 | 4 | use blsful::inner_types::Scalar; 5 | use zeroize::Zeroize; 6 | 7 | /// Adds `from_bytes_wide` for Generic Scalars 8 | pub trait ScalarOps { 9 | /// The scalar value to be returned 10 | type Scalar: Copy 11 | + Default 12 | + From 13 | + From 14 | + ops::Neg 15 | + ops::Add 16 | + ops::Sub 17 | + ops::Mul 18 | + Zeroize 19 | + fmt::Debug; 20 | 21 | /// Convert 64 bytes into a scalar element 22 | fn from_bytes_wide(input: &[u8; 64]) -> Self::Scalar; 23 | 24 | /// Perform a cryptographic hashing operation to produce a scalar element 25 | fn from_hash(input: &[u8]) -> Self::Scalar { 26 | let mut bytes = [0u8; 64]; 27 | bytes.copy_from_slice(&sha2::Sha512::digest(input)); 28 | Self::from_bytes_wide(&bytes) 29 | } 30 | } 31 | 32 | impl ScalarOps for Scalar { 33 | type Scalar = Scalar; 34 | 35 | fn from_bytes_wide(input: &[u8; 64]) -> Self::Scalar { 36 | Scalar::from_bytes_wide(input) 37 | } 38 | } 39 | 40 | /// Adds necessary methods for frost signing 41 | pub trait ElementOps: ScalarOps { 42 | /// The inner element to operate on 43 | type Element: Copy 44 | + ops::Add 45 | + ops::Sub 46 | + ops::Neg 47 | + for<'a> ops::Mul<&'a Self::Scalar, Output = Self::Element> 48 | + fmt::Debug; 49 | 50 | /// Return if this Element is negative or odd 51 | fn is_negative(&self) -> bool; 52 | 53 | /// Return the bytes use for computing signatures 54 | fn to_sig_bytes(&self) -> [u8; 32]; 55 | } 56 | -------------------------------------------------------------------------------- /src/knox/ps.rs: -------------------------------------------------------------------------------- 1 | mod blind_signature; 2 | mod blind_signature_context; 3 | mod pok_signature; 4 | mod pok_signature_proof; 5 | mod public_key; 6 | mod scheme; 7 | mod secret_key; 8 | mod signature; 9 | 10 | pub use blind_signature::*; 11 | pub use blind_signature_context::*; 12 | pub use pok_signature::*; 13 | pub use pok_signature_proof::*; 14 | pub use public_key::*; 15 | pub use scheme::*; 16 | pub use secret_key::*; 17 | pub use signature::*; 18 | -------------------------------------------------------------------------------- /src/knox/ps/blind_signature.rs: -------------------------------------------------------------------------------- 1 | use super::{PublicKey, SecretKey, Signature}; 2 | use crate::knox::short_group_sig_core::short_group_traits::BlindSignature as BlindSignatureTrait; 3 | use crate::CredxResult; 4 | use blsful::inner_types::{G1Projective, Scalar}; 5 | use serde::{Deserialize, Serialize}; 6 | use sha3::digest::{ExtendableOutput, Update, XofReader}; 7 | use subtle::CtOption; 8 | 9 | /// A PS blind signature 10 | /// structurally identical to `Signature` but is used to 11 | /// help with misuse and confusion. 12 | /// 13 | /// 1 or more messages have been hidden by the signature recipient 14 | /// so the signer only knows a subset of the messages to be signed 15 | #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Deserialize, Serialize)] 16 | pub struct BlindSignature(pub(crate) Signature); 17 | 18 | impl BlindSignatureTrait for BlindSignature { 19 | type SecretKey = SecretKey; 20 | type PublicKey = PublicKey; 21 | type Signature = Signature; 22 | 23 | fn new( 24 | commitment: G1Projective, 25 | sk: &SecretKey, 26 | msgs: &[(usize, Scalar)], 27 | ) -> CredxResult { 28 | Self::new(commitment, sk, msgs) 29 | } 30 | 31 | fn to_unblinded(self, blinding: Scalar) -> Signature { 32 | self.to_unblinded(blinding) 33 | } 34 | } 35 | 36 | impl BlindSignature { 37 | /// The size of the signature in bytes 38 | pub const BYTES: usize = 128; 39 | 40 | /// Generate a new signature where all messages are known to the signer 41 | pub fn new( 42 | commitment: G1Projective, 43 | sk: &SecretKey, 44 | msgs: &[(usize, Scalar)], 45 | ) -> CredxResult { 46 | if sk.y.len() < msgs.len() { 47 | return Err(crate::error::Error::InvalidSigningOperation); 48 | } 49 | if sk.is_invalid() { 50 | return Err(crate::error::Error::InvalidSigningOperation); 51 | } 52 | 53 | let t_msgs = msgs.iter().map(|(_, m)| *m).collect::>(); 54 | let m_tick = Signature::compute_m_tick(t_msgs.as_slice()); 55 | 56 | let mut hasher = sha3::Shake256::default(); 57 | hasher.update(&sk.to_bytes()); 58 | t_msgs.iter().for_each(|m| hasher.update(&m.to_be_bytes())); 59 | 60 | let mut reader = hasher.finalize_xof(); 61 | let mut okm = [0u8; 64]; 62 | reader.read(&mut okm); 63 | 64 | // Should yield non-zero values for `u` and m', very small likelihood of it being zero 65 | let u = Scalar::from_bytes_wide(&okm); 66 | let sigma_1 = G1Projective::GENERATOR * u; 67 | 68 | let mut exp = sk.x + m_tick * sk.w; 69 | for (i, msg) in msgs { 70 | exp += sk.y[*i] * msg; 71 | } 72 | let mut sigma_2 = (G1Projective::GENERATOR * exp) + commitment; 73 | sigma_2 *= u; 74 | Ok(Self(Signature { 75 | sigma_1, 76 | sigma_2, 77 | m_tick, 78 | })) 79 | } 80 | 81 | /// Once signature on committed attributes (blind signature) is received, the signature needs to be unblinded. 82 | /// Takes the blinding factor used in the commitment. 83 | pub fn to_unblinded(self, blinding: Scalar) -> Signature { 84 | Signature { 85 | sigma_1: self.0.sigma_1, 86 | sigma_2: self.0.sigma_2 - (self.0.sigma_1 * blinding), 87 | m_tick: self.0.m_tick, 88 | } 89 | } 90 | 91 | /// Get the byte representation of this signature 92 | pub fn to_bytes(&self) -> [u8; Self::BYTES] { 93 | self.0.to_bytes() 94 | } 95 | 96 | /// Convert a byte sequence into a signature 97 | pub fn from_bytes(data: &[u8; Self::BYTES]) -> CtOption { 98 | Signature::from_bytes(data).map(Self) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/knox/ps/blind_signature_context.rs: -------------------------------------------------------------------------------- 1 | use super::{PublicKey, SecretKey}; 2 | use crate::knox::short_group_sig_core::short_group_traits::BlindSignatureContext as BlindSignatureContextTrait; 3 | use crate::CredxResult; 4 | use blsful::inner_types::{G1Affine, G1Projective, Scalar}; 5 | use core::convert::TryFrom; 6 | use elliptic_curve::group::Curve; 7 | use merlin::Transcript; 8 | use serde::{Deserialize, Serialize}; 9 | use std::collections::BTreeSet; 10 | use subtle::ConstantTimeEq; 11 | 12 | /// Contains the data used for computing a blind signature and verifying 13 | /// proof of hidden messages from a prover 14 | #[derive(Debug, Clone, Serialize, Deserialize)] 15 | pub struct BlindSignatureContext { 16 | /// The blinded signature commitment 17 | pub commitment: G1Projective, 18 | /// The challenge hash for the Fiat-Shamir heuristic 19 | pub challenge: Scalar, 20 | /// The proofs for the hidden messages 21 | pub proofs: Vec, 22 | } 23 | 24 | impl BlindSignatureContext { 25 | /// Store the generators as a sequence of bytes 26 | /// Each point is compressed to big-endian format 27 | /// Needs (N + 1) * 32 + 48 * 2 space otherwise it will panic 28 | pub fn to_bytes(&self) -> Vec { 29 | let mut buffer = Vec::new(); 30 | buffer.extend_from_slice(self.commitment.to_affine().to_compressed().as_ref()); 31 | buffer.extend_from_slice(&self.challenge.to_be_bytes()); 32 | 33 | for i in 0..self.proofs.len() { 34 | buffer.extend_from_slice(&self.proofs[i].to_be_bytes()); 35 | } 36 | buffer 37 | } 38 | 39 | /// Convert a byte sequence into the blind signature context 40 | /// Expected size is (N + 1) * 32 + 48 bytes 41 | pub fn from_bytes>(bytes: B) -> Option { 42 | let size = 32 * 2 + 48; 43 | let buffer = bytes.as_ref(); 44 | if buffer.len() < size { 45 | return None; 46 | } 47 | if buffer.len() - 48 % 32 != 0 { 48 | return None; 49 | } 50 | 51 | let commitment = G1Affine::from_compressed(&<[u8; 48]>::try_from(&buffer[..48]).unwrap()) 52 | .map(G1Projective::from); 53 | if commitment.is_none().unwrap_u8() == 1 { 54 | return None; 55 | } 56 | let mut offset = 48; 57 | let mut end = 80; 58 | 59 | let challenge = Scalar::from_be_bytes(&<[u8; 32]>::try_from(&buffer[offset..end]).unwrap()); 60 | if challenge.is_none().unwrap_u8() == 1 { 61 | return None; 62 | } 63 | 64 | let times = (buffer.len() - 48 - 32) / 32; 65 | 66 | offset = end; 67 | end += 32; 68 | 69 | let mut proofs = Vec::new(); 70 | for _ in 0..times { 71 | let p = Scalar::from_be_bytes(&<[u8; 32]>::try_from(&buffer[offset..end]).unwrap()); 72 | if p.is_none().unwrap_u8() == 1 { 73 | return None; 74 | } 75 | proofs.push(p.unwrap()); 76 | offset = end; 77 | end += 32; 78 | } 79 | 80 | Some(Self { 81 | commitment: commitment.unwrap(), 82 | challenge: challenge.unwrap(), 83 | proofs, 84 | }) 85 | } 86 | } 87 | 88 | impl BlindSignatureContextTrait for BlindSignatureContext { 89 | type SecretKey = SecretKey; 90 | 91 | /// Assumes the proof of hidden messages 92 | /// If other proofs were included, those will need to be verified another way 93 | fn verify( 94 | &self, 95 | known_messages: &[usize], 96 | sk: &Self::SecretKey, 97 | nonce: Scalar, 98 | ) -> CredxResult { 99 | let mut known = BTreeSet::new(); 100 | let mut points = Vec::new(); 101 | for idx in known_messages { 102 | if *idx >= sk.y.len() { 103 | return Err(crate::error::Error::InvalidSignatureProofData); 104 | } 105 | known.insert(*idx); 106 | } 107 | for i in 0..sk.y.len() { 108 | if !known.contains(&i) { 109 | points.push(G1Projective::GENERATOR * sk.y[i]); 110 | } 111 | } 112 | points.push(G1Projective::GENERATOR); 113 | points.push(self.commitment); 114 | 115 | let mut scalars = self.proofs.clone(); 116 | scalars.push(-self.challenge); 117 | 118 | let mut transcript = Transcript::new(b"new blind signature"); 119 | let pk = PublicKey::from(sk); 120 | transcript.append_message(b"public key", pk.to_bytes().as_ref()); 121 | let mut res = [0u8; 64]; 122 | 123 | let commitment = G1Projective::sum_of_products(points.as_ref(), scalars.as_ref()); 124 | transcript.append_message( 125 | b"random commitment", 126 | &commitment.to_affine().to_compressed(), 127 | ); 128 | transcript.append_message( 129 | b"blind commitment", 130 | &self.commitment.to_affine().to_compressed(), 131 | ); 132 | transcript.append_message(b"nonce", &nonce.to_be_bytes()); 133 | transcript.challenge_bytes(b"blind signature context challenge", &mut res); 134 | let challenge = Scalar::from_bytes_wide(&res); 135 | 136 | Ok(self.challenge.ct_eq(&challenge).unwrap_u8() == 1) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/knox/ps/pok_signature.rs: -------------------------------------------------------------------------------- 1 | use super::{PokSignatureProof, PublicKey, Signature}; 2 | use crate::knox::short_group_sig_core::{ 3 | short_group_traits::ProofOfSignatureKnowledgeContribution, *, 4 | }; 5 | use crate::CredxResult; 6 | use blsful::inner_types::{G1Projective, G2Affine, G2Projective, Scalar}; 7 | use elliptic_curve::{group::Curve, Field}; 8 | use merlin::Transcript; 9 | use rand_core::*; 10 | 11 | /// Proof of Knowledge of a Signature that is used by the prover 12 | /// to construct `PoKOfSignatureProof`. 13 | pub struct PokSignature { 14 | secrets: Vec, 15 | proof: ProofCommittedBuilder, 16 | commitment: G2Projective, 17 | sigma_1: G1Projective, 18 | sigma_2: G1Projective, 19 | } 20 | 21 | impl ProofOfSignatureKnowledgeContribution for PokSignature { 22 | type Signature = Signature; 23 | type PublicKey = PublicKey; 24 | type ProofOfKnowledge = PokSignatureProof; 25 | 26 | /// Creates the initial proof data before a Fiat-Shamir calculation 27 | fn commit( 28 | signature: &Signature, 29 | public_key: &PublicKey, 30 | messages: &[ProofMessage], 31 | mut rng: impl RngCore + CryptoRng, 32 | ) -> CredxResult { 33 | if public_key.y.len() < messages.len() { 34 | return Err(crate::error::Error::General("ProofCommitmentError")); 35 | } 36 | 37 | let r = Scalar::random(&mut rng); 38 | let t = Scalar::random(&mut rng); 39 | 40 | // ZKP for signature 41 | let sigma_1 = signature.sigma_1 * r; 42 | let sigma_2 = (signature.sigma_2 + (signature.sigma_1 * t)) * r; 43 | 44 | // Prove knowledge of m_tick, m_1, m_2, ... for all hidden m_i and t in J = Y_tilde_1^m_1 * Y_tilde_2^m_2 * ..... * g_tilde^t 45 | let mut proof = ProofCommittedBuilder::new(G2Projective::sum_of_products); 46 | let mut points = Vec::new(); 47 | let mut secrets = Vec::new(); 48 | 49 | proof.commit_random(G2Projective::GENERATOR, &mut rng); 50 | points.push(G2Projective::GENERATOR); 51 | secrets.push(t); 52 | 53 | proof.commit_random(public_key.w, &mut rng); 54 | points.push(public_key.w); 55 | secrets.push(signature.m_tick); 56 | 57 | for (i, m) in messages.iter().enumerate() { 58 | match m { 59 | ProofMessage::Hidden(HiddenMessage::ProofSpecificBlinding(msg)) => { 60 | proof.commit_random(public_key.y[i], &mut rng); 61 | points.push(public_key.y[i]); 62 | secrets.push(*msg); 63 | } 64 | ProofMessage::Hidden(HiddenMessage::ExternalBlinding(msg, n)) => { 65 | proof.commit(public_key.y[i], *n); 66 | points.push(public_key.y[i]); 67 | secrets.push(*msg); 68 | } 69 | ProofMessage::Revealed(_) => {} 70 | } 71 | } 72 | let commitment = G2Projective::sum_of_products(points.as_ref(), secrets.as_ref()); 73 | Ok(Self { 74 | secrets, 75 | proof, 76 | commitment, 77 | sigma_1, 78 | sigma_2, 79 | }) 80 | } 81 | 82 | /// Convert the committed values to bytes for the fiat-shamir challenge 83 | fn add_proof_contribution(&self, transcript: &mut Transcript) { 84 | transcript.append_message( 85 | b"sigma_1", 86 | self.sigma_1.to_affine().to_compressed().as_ref(), 87 | ); 88 | transcript.append_message( 89 | b"sigma_2", 90 | self.sigma_2.to_affine().to_compressed().as_ref(), 91 | ); 92 | transcript.append_message( 93 | b"random commitment", 94 | self.commitment.to_affine().to_compressed().as_ref(), 95 | ); 96 | self.proof 97 | .add_challenge_contribution(b"blind commitment", transcript); 98 | } 99 | 100 | /// Generate the Schnorr challenges for the selective disclosure proofs 101 | fn generate_proof(self, challenge: Scalar) -> CredxResult { 102 | let proof = self 103 | .proof 104 | .generate_proof(challenge, self.secrets.as_ref())?; 105 | Ok(PokSignatureProof { 106 | sigma_1: self.sigma_1, 107 | sigma_2: self.sigma_2, 108 | commitment: self.commitment, 109 | proof, 110 | }) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/knox/ps/public_key.rs: -------------------------------------------------------------------------------- 1 | use super::SecretKey; 2 | use crate::{ 3 | error::Error, knox::short_group_sig_core::short_group_traits::PublicKey as PublicKeyTrait, 4 | }; 5 | use blsful::inner_types::*; 6 | use serde::{Deserialize, Serialize}; 7 | use subtle::Choice; 8 | 9 | /// The public key contains a generator point for each 10 | /// message that is signed and two extra. 11 | /// See section 4.2 in 12 | /// and 13 | /// 14 | /// 15 | /// `w` corresponds to m' in the paper to achieve 16 | /// EUF-CMA security level. 17 | #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] 18 | pub struct PublicKey { 19 | /// The secret for m' 20 | pub w: G2Projective, 21 | /// The blinding secret 22 | pub x: G2Projective, 23 | /// The secrets for each signed message 24 | pub y: Vec, 25 | /// The secrets for each signed message for blinding purposes 26 | pub y_blinds: Vec, 27 | } 28 | 29 | impl Default for PublicKey { 30 | fn default() -> Self { 31 | Self { 32 | w: G2Projective::IDENTITY, 33 | x: G2Projective::IDENTITY, 34 | y: Vec::new(), 35 | y_blinds: Vec::new(), 36 | } 37 | } 38 | } 39 | 40 | impl From<&SecretKey> for PublicKey { 41 | fn from(sk: &SecretKey) -> Self { 42 | let w = G2Projective::GENERATOR * sk.w; 43 | let x = G2Projective::GENERATOR * sk.x; 44 | let mut y = Vec::new(); 45 | let mut y_blinds = Vec::new(); 46 | for s_y in &sk.y { 47 | y.push(G2Projective::GENERATOR * s_y); 48 | y_blinds.push(G1Projective::GENERATOR * s_y); 49 | } 50 | Self { w, x, y, y_blinds } 51 | } 52 | } 53 | 54 | impl PublicKeyTrait for PublicKey { 55 | type MessageGenerator = G2Projective; 56 | type BlindMessageGenerator = G1Projective; 57 | } 58 | 59 | impl From for Vec { 60 | fn from(pk: PublicKey) -> Self { 61 | Self::from(&pk) 62 | } 63 | } 64 | 65 | impl From<&PublicKey> for Vec { 66 | fn from(pk: &PublicKey) -> Self { 67 | pk.to_bytes() 68 | } 69 | } 70 | 71 | impl TryFrom> for PublicKey { 72 | type Error = Error; 73 | 74 | fn try_from(bytes: Vec) -> Result { 75 | Self::try_from(bytes.as_slice()) 76 | } 77 | } 78 | 79 | impl TryFrom<&Vec> for PublicKey { 80 | type Error = Error; 81 | 82 | fn try_from(bytes: &Vec) -> Result { 83 | Self::try_from(bytes.as_slice()) 84 | } 85 | } 86 | 87 | impl TryFrom<&[u8]> for PublicKey { 88 | type Error = Error; 89 | 90 | fn try_from(bytes: &[u8]) -> Result { 91 | Self::from_bytes(bytes).ok_or(Error::General("Invalid public key")) 92 | } 93 | } 94 | 95 | impl TryFrom> for PublicKey { 96 | type Error = Error; 97 | 98 | fn try_from(bytes: Box<[u8]>) -> Result { 99 | Self::try_from(bytes.as_ref()) 100 | } 101 | } 102 | 103 | impl PublicKey { 104 | /// Check if this public key is valid 105 | pub fn is_valid(&self) -> Choice { 106 | let mut res = !self.w.is_identity(); 107 | res &= !self.x.is_identity(); 108 | for y in &self.y { 109 | res &= !y.is_identity(); 110 | } 111 | for y in &self.y_blinds { 112 | res &= !y.is_identity(); 113 | } 114 | res 115 | } 116 | 117 | /// Check if this public key is invalid 118 | pub fn is_invalid(&self) -> Choice { 119 | let mut res = self.w.is_identity(); 120 | res |= self.x.is_identity(); 121 | for y in &self.y { 122 | res |= y.is_identity(); 123 | } 124 | for y in &self.y_blinds { 125 | res |= y.is_identity(); 126 | } 127 | res 128 | } 129 | 130 | /// Store the public key as a sequence of bytes 131 | /// Each scalar is compressed to big-endian format 132 | /// Needs (N + 2) * P space otherwise it will panic 133 | /// where N is the number of messages that can be signed 134 | pub fn to_bytes(&self) -> Vec { 135 | let mut buffer = Vec::new(); 136 | buffer.extend_from_slice(&self.w.to_affine().to_compressed()[..]); 137 | buffer.extend_from_slice(&self.x.to_affine().to_compressed()[..]); 138 | buffer.extend_from_slice(&(self.y.len() as u32).to_be_bytes()[..]); 139 | for y in &self.y { 140 | buffer.extend_from_slice(&y.to_affine().to_compressed()[..]); 141 | } 142 | buffer.extend_from_slice(&(self.y_blinds.len() as u32).to_be_bytes()[..]); 143 | for y in &self.y_blinds { 144 | buffer.extend_from_slice(&y.to_affine().to_compressed()[..]); 145 | } 146 | buffer 147 | } 148 | 149 | /// Convert a byte sequence into the public key 150 | pub fn from_bytes>(bytes: B) -> Option { 151 | const SIZE: usize = 48; 152 | // Length for w, x, and 1 y in g1 and 1 y in g2 153 | const MIN_SIZE: usize = SIZE * 5 + 8; 154 | 155 | let buffer = bytes.as_ref(); 156 | if buffer.len() < MIN_SIZE { 157 | return None; 158 | } 159 | 160 | fn from_be_bytes(d: &[u8]) -> G2Projective { 161 | let mut tv = ::Repr::default(); 162 | tv.as_mut().copy_from_slice(d); 163 | G2Projective::from_bytes(&tv).unwrap() 164 | } 165 | 166 | let mut offset = 0; 167 | let mut end = SIZE; 168 | let w = from_be_bytes(&buffer[offset..end]); 169 | offset = end; 170 | end += SIZE; 171 | 172 | let x = from_be_bytes(&buffer[offset..end]); 173 | offset = end; 174 | end += 4; 175 | 176 | let y_cnt = u32::from_be_bytes(<[u8; 4]>::try_from(&buffer[offset..end]).unwrap()) as usize; 177 | offset = end; 178 | end += SIZE * 2; 179 | 180 | let mut y = Vec::new(); 181 | 182 | for _ in 0..y_cnt { 183 | y.push(from_be_bytes(&buffer[offset..end])); 184 | offset = end; 185 | end += SIZE * 2; 186 | } 187 | 188 | offset = end; 189 | end += 4; 190 | 191 | let mut y_blinds = Vec::new(); 192 | let y_blind_cnt = 193 | u32::from_be_bytes(<[u8; 4]>::try_from(&buffer[offset..end]).unwrap()) as usize; 194 | 195 | offset = end; 196 | end += SIZE; 197 | 198 | for _ in 0..y_blind_cnt { 199 | let mut tv = ::Repr::default(); 200 | tv.as_mut().copy_from_slice(&buffer[offset..end]); 201 | y_blinds.push(G1Projective::from_bytes(&tv).unwrap()); 202 | offset = end; 203 | end += SIZE; 204 | } 205 | Some(Self { w, x, y, y_blinds }) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/knox/ps/scheme.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | BlindSignature, BlindSignatureContext, PokSignature, PokSignatureProof, PublicKey, SecretKey, 3 | Signature, 4 | }; 5 | use crate::error::Error; 6 | use crate::knox::short_group_sig_core::short_group_traits::{ 7 | BlindSignatureContext as _, ProofOfSignatureKnowledge, ProofOfSignatureKnowledgeContribution, 8 | ShortGroupSignatureScheme, 9 | }; 10 | use crate::knox::short_group_sig_core::{ProofCommittedBuilder, ProofMessage}; 11 | use crate::CredxResult; 12 | use blsful::inner_types::{G1Affine, G1Projective, Scalar}; 13 | use elliptic_curve::group::Curve; 14 | use elliptic_curve::Field; 15 | use merlin::Transcript; 16 | use rand_core::{CryptoRng, RngCore}; 17 | use serde::{Deserialize, Serialize}; 18 | use std::num::NonZeroUsize; 19 | 20 | /// Pointcheval-Sanders scheme 21 | #[derive(Debug, Copy, Clone, Deserialize, Serialize)] 22 | pub struct PsScheme; 23 | 24 | impl ShortGroupSignatureScheme for PsScheme { 25 | type PublicKey = PublicKey; 26 | type SecretKey = SecretKey; 27 | type Signature = Signature; 28 | type BlindSignatureContext = BlindSignatureContext; 29 | type BlindSignature = BlindSignature; 30 | type ProofOfSignatureKnowledge = PokSignatureProof; 31 | type ProofOfSignatureKnowledgeContribution = PokSignature; 32 | 33 | fn new_keys( 34 | count: NonZeroUsize, 35 | rng: impl RngCore + CryptoRng, 36 | ) -> CredxResult<(Self::PublicKey, Self::SecretKey)> { 37 | SecretKey::random(count, rng) 38 | .map(|sk| { 39 | let pk = PublicKey::from(&sk); 40 | (pk, sk) 41 | }) 42 | .ok_or(Error::General("invalid key generation")) 43 | } 44 | 45 | fn sign(sk: &Self::SecretKey, msgs: M) -> CredxResult 46 | where 47 | M: AsRef<[Scalar]>, 48 | { 49 | Signature::new(sk, msgs) 50 | } 51 | 52 | fn blind_sign( 53 | ctx: &Self::BlindSignatureContext, 54 | sk: &Self::SecretKey, 55 | msgs: &[(usize, Scalar)], 56 | nonce: Scalar, 57 | ) -> CredxResult { 58 | let tv1 = msgs.iter().map(|(i, _)| *i).collect::>(); 59 | if ctx.verify(tv1.as_ref(), sk, nonce)? { 60 | BlindSignature::new(ctx.commitment, sk, msgs) 61 | } else { 62 | Err(Error::General("BlindSignatureError")) 63 | } 64 | } 65 | 66 | fn new_blind_signature_context( 67 | messages: &[(usize, Scalar)], 68 | public_key: &Self::PublicKey, 69 | nonce: Scalar, 70 | mut rng: impl RngCore + CryptoRng, 71 | ) -> CredxResult<(Self::BlindSignatureContext, Scalar)> { 72 | let mut points = Vec::new(); 73 | let mut secrets = Vec::new(); 74 | let mut committing = ProofCommittedBuilder::::new( 75 | G1Projective::sum_of_products, 76 | ); 77 | 78 | for (i, m) in messages { 79 | if *i > public_key.y.len() { 80 | return Err(crate::error::Error::General("invalid blind signing")); 81 | } 82 | secrets.push(*m); 83 | points.push(public_key.y_blinds[*i]); 84 | committing.commit_random(public_key.y_blinds[*i], &mut rng); 85 | } 86 | 87 | let blinding = Scalar::random(&mut rng); 88 | secrets.push(blinding); 89 | points.push(G1Projective::GENERATOR); 90 | committing.commit_random(G1Projective::GENERATOR, &mut rng); 91 | 92 | let mut transcript = Transcript::new(b"new blind signature"); 93 | transcript.append_message(b"public key", public_key.to_bytes().as_ref()); 94 | let commitment = G1Projective::sum_of_products(points.as_ref(), secrets.as_ref()); 95 | committing.add_challenge_contribution(b"random commitment", &mut transcript); 96 | transcript.append_message( 97 | b"blind commitment", 98 | commitment.to_affine().to_compressed().as_ref(), 99 | ); 100 | transcript.append_message(b"nonce", nonce.to_be_bytes().as_ref()); 101 | let mut res = [0u8; 64]; 102 | transcript.challenge_bytes(b"blind signature context challenge", &mut res); 103 | let challenge = Scalar::from_bytes_wide(&res); 104 | let proofs = committing.generate_proof(challenge, secrets.as_slice())?; 105 | Ok(( 106 | BlindSignatureContext { 107 | commitment, 108 | challenge, 109 | proofs, 110 | }, 111 | blinding, 112 | )) 113 | } 114 | 115 | fn commit_signature_pok( 116 | signature: Self::Signature, 117 | public_key: &Self::PublicKey, 118 | messages: &[ProofMessage], 119 | rng: impl RngCore + CryptoRng, 120 | ) -> CredxResult { 121 | PokSignature::commit(&signature, public_key, messages, rng) 122 | } 123 | 124 | fn verify_signature_pok( 125 | revealed_msgs: &[(usize, Scalar)], 126 | public_key: &Self::PublicKey, 127 | proof: &Self::ProofOfSignatureKnowledge, 128 | nonce: Scalar, 129 | challenge: Scalar, 130 | ) -> bool { 131 | let mut transcript = Transcript::new(b"signature proof of knowledge"); 132 | proof.add_proof_contribution(public_key, revealed_msgs, challenge, &mut transcript); 133 | transcript.append_message(b"nonce", nonce.to_be_bytes().as_ref()); 134 | let mut res = [0u8; 64]; 135 | transcript.challenge_bytes(b"signature proof of knowledge", &mut res); 136 | let v_challenge = Scalar::from_bytes_wide(&res); 137 | 138 | proof.verify(public_key, revealed_msgs, challenge).is_ok() && challenge == v_challenge 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/knox/ps/secret_key.rs: -------------------------------------------------------------------------------- 1 | use crate::knox::{ 2 | ps::PublicKey, short_group_sig_core::short_group_traits::SecretKey as SecretKeyTrait, 3 | }; 4 | use blsful::inner_types::{G1Projective, G2Projective, Scalar}; 5 | use elliptic_curve::{Field, PrimeField}; 6 | use rand_chacha::ChaChaRng; 7 | use rand_core::{CryptoRng, RngCore, SeedableRng}; 8 | use serde::{Deserialize, Serialize}; 9 | use sha3::digest::{ExtendableOutput, Update, XofReader}; 10 | use std::num::NonZeroUsize; 11 | use zeroize::Zeroize; 12 | 13 | /// The secret key contains a field element for each 14 | /// message that is signed and two extra. 15 | /// See section 4.2 in 16 | /// and 17 | /// 18 | /// 19 | /// `w` corresponds to m' in the paper to achieve 20 | /// EUF-CMA security level. 21 | #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Zeroize)] 22 | #[zeroize(drop)] 23 | pub struct SecretKey { 24 | pub(crate) w: Scalar, 25 | pub(crate) x: Scalar, 26 | pub(crate) y: Vec, 27 | } 28 | 29 | impl Default for SecretKey { 30 | fn default() -> Self { 31 | Self { 32 | w: Scalar::ZERO, 33 | x: Scalar::ZERO, 34 | y: Vec::new(), 35 | } 36 | } 37 | } 38 | 39 | impl SecretKeyTrait for SecretKey { 40 | type PublicKey = PublicKey; 41 | 42 | fn public_key(&self) -> PublicKey { 43 | let w = G2Projective::GENERATOR * self.w; 44 | let x = G2Projective::GENERATOR * self.x; 45 | let mut y = Vec::with_capacity(self.y.len()); 46 | let mut y_blinds = Vec::with_capacity(self.y.len()); 47 | for s_y in &self.y { 48 | y.push(G2Projective::GENERATOR * s_y); 49 | y_blinds.push(G1Projective::GENERATOR * s_y); 50 | } 51 | PublicKey { w, x, y, y_blinds } 52 | } 53 | } 54 | 55 | impl SecretKey { 56 | const SCALAR_SIZE: usize = 32; 57 | 58 | /// Compute a secret key from a hash 59 | pub fn hash>(count: NonZeroUsize, data: B) -> Option { 60 | const SALT: &[u8] = b"PS-SIG-KEYGEN-SALT-"; 61 | let mut reader = sha3::Shake256::default() 62 | .chain(SALT) 63 | .chain(data.as_ref()) 64 | .finalize_xof(); 65 | let mut okm = [0u8; Self::SCALAR_SIZE]; 66 | reader.read(&mut okm); 67 | let rng = ChaChaRng::from_seed(okm); 68 | 69 | generate_secret_key(count, rng) 70 | } 71 | 72 | /// Compute a secret key from a CS-PRNG 73 | pub fn random(count: NonZeroUsize, rng: impl RngCore + CryptoRng) -> Option { 74 | generate_secret_key(count, rng) 75 | } 76 | 77 | /// Store the secret key as a sequence of bytes 78 | /// Each scalar is compressed to big-endian format 79 | /// Needs (N + 2) * 32 space otherwise it will panic 80 | /// where N is the number of messages that can be signed 81 | pub fn to_bytes(&self) -> Vec { 82 | let mut buffer = Vec::new(); 83 | buffer.extend_from_slice(self.w.to_repr().as_ref()); 84 | buffer.extend_from_slice(self.x.to_repr().as_ref()); 85 | 86 | for y in &self.y { 87 | buffer.extend_from_slice(y.to_repr().as_ref()); 88 | } 89 | buffer 90 | } 91 | 92 | /// Convert a byte sequence into the secret key 93 | /// Expected size is (N + 2) * 32 bytes 94 | /// where N is the number of messages that can be signed 95 | pub fn from_bytes>(bytes: B) -> Option { 96 | // Length for w, x, and 1 y 97 | const MIN_SIZE: usize = SecretKey::SCALAR_SIZE * 3; 98 | 99 | let buffer = bytes.as_ref(); 100 | if buffer.len() % Self::SCALAR_SIZE != 0 { 101 | return None; 102 | } 103 | if buffer.len() < MIN_SIZE { 104 | return None; 105 | } 106 | 107 | fn from_le_bytes(d: &[u8]) -> Option { 108 | let mut s = ::Repr::default(); 109 | s.as_mut().copy_from_slice(d); 110 | let res = Scalar::from_repr(s); 111 | if res.is_some().unwrap_u8() == 1 { 112 | Some(res.unwrap()) 113 | } else { 114 | None 115 | } 116 | } 117 | 118 | let y_cnt = (buffer.len() / Self::SCALAR_SIZE) - 2; 119 | let mut offset = 0; 120 | let mut end = Self::SCALAR_SIZE; 121 | let w = from_le_bytes(&buffer[offset..end])?; 122 | offset = end; 123 | end += Self::SCALAR_SIZE; 124 | 125 | let x = from_le_bytes(&buffer[offset..end])?; 126 | offset = end; 127 | end += Self::SCALAR_SIZE; 128 | 129 | let mut y = Vec::new(); 130 | 131 | for _ in 0..y_cnt { 132 | let s = from_le_bytes(&buffer[offset..end])?; 133 | y.push(s); 134 | offset = end; 135 | end += Self::SCALAR_SIZE; 136 | } 137 | Some(Self { w, x, y }) 138 | } 139 | 140 | /// Check if this secret key is valid 141 | pub fn is_valid(&self) -> bool { 142 | let mut res = !self.w.is_zero(); 143 | res &= !self.x.is_zero(); 144 | for y in &self.y { 145 | res &= !y.is_zero(); 146 | } 147 | res.unwrap_u8() == 1u8 148 | } 149 | 150 | /// Check if this public key is invalid 151 | pub fn is_invalid(&self) -> bool { 152 | let mut res = self.w.is_zero(); 153 | res |= self.x.is_zero(); 154 | for y in &self.y { 155 | res |= y.is_zero(); 156 | } 157 | res.unwrap_u8() == 1u8 158 | } 159 | } 160 | 161 | fn generate_secret_key( 162 | count: NonZeroUsize, 163 | mut rng: impl RngCore + CryptoRng, 164 | ) -> Option { 165 | let count = count.get(); 166 | if count > 128 { 167 | return None; 168 | } 169 | let w = Scalar::random(&mut rng); 170 | let x = Scalar::random(&mut rng); 171 | let mut y = Vec::new(); 172 | for _ in 0..count { 173 | y.push(Scalar::random(&mut rng)); 174 | } 175 | 176 | Some(SecretKey { w, x, y }) 177 | } 178 | -------------------------------------------------------------------------------- /src/knox/ps/signature.rs: -------------------------------------------------------------------------------- 1 | use super::{PublicKey, SecretKey}; 2 | use crate::error::Error; 3 | use crate::knox::short_group_sig_core::short_group_traits::Signature as SignatureTrait; 4 | use crate::CredxResult; 5 | use blsful::inner_types::*; 6 | use elliptic_curve::{ 7 | group::{prime::PrimeCurveAffine, Curve, Group}, 8 | PrimeField, 9 | }; 10 | use serde::{Deserialize, Serialize}; 11 | use sha3::digest::{ExtendableOutput, Update, XofReader}; 12 | use subtle::{Choice, ConditionallySelectable, CtOption}; 13 | 14 | /// A Pointcheval Saunders signature 15 | #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] 16 | pub struct Signature { 17 | pub(crate) sigma_1: G1Projective, 18 | pub(crate) sigma_2: G1Projective, 19 | pub(crate) m_tick: Scalar, 20 | } 21 | 22 | impl ConditionallySelectable for Signature { 23 | fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { 24 | let sigma_1 = G1Projective::conditional_select(&a.sigma_1, &b.sigma_1, choice); 25 | let sigma_2 = G1Projective::conditional_select(&a.sigma_2, &b.sigma_2, choice); 26 | let m_tick = Scalar::conditional_select(&a.m_tick, &b.m_tick, choice); 27 | Self { 28 | sigma_1, 29 | sigma_2, 30 | m_tick, 31 | } 32 | } 33 | } 34 | 35 | impl SignatureTrait for Signature { 36 | type SecretKey = SecretKey; 37 | type PublicKey = PublicKey; 38 | 39 | fn create(sk: &Self::SecretKey, msgs: &[Scalar]) -> CredxResult { 40 | Self::new(sk, msgs) 41 | } 42 | 43 | fn verify(&self, pk: &Self::PublicKey, msgs: &[Scalar]) -> CredxResult<()> { 44 | if self.verify(pk, msgs).into() { 45 | Ok(()) 46 | } else { 47 | Err(Error::General("Invalid signature")) 48 | } 49 | } 50 | } 51 | 52 | impl Signature { 53 | /// The size in bytes of the signature 54 | pub const BYTES: usize = 128; 55 | 56 | const DST: &'static [u8] = b"PS_SIG_BLS12381G1_XMD:BLAKE2B_SSWU_RO_"; 57 | 58 | /// Generate a new signature where all messages are known to the signer 59 | pub fn new(sk: &SecretKey, msgs: M) -> CredxResult 60 | where 61 | M: AsRef<[Scalar]>, 62 | { 63 | let msgs = msgs.as_ref(); 64 | if sk.is_invalid() { 65 | return Err(Error::General("Key generation error")); 66 | } 67 | if sk.y.len() < msgs.len() { 68 | return Err(Error::General("Key generation error")); 69 | } 70 | 71 | let m_tick = Self::compute_m_tick(msgs); 72 | let sigma_1 = 73 | G1Projective::hash::>(&m_tick.to_be_bytes()[..], Self::DST); 74 | let mut exp = sk.x + sk.w * m_tick; 75 | 76 | for (ski, m) in msgs.iter().zip(sk.y.iter()) { 77 | exp += *ski * *m; 78 | } 79 | let sigma_2 = sigma_1 * exp; 80 | Ok(Self { 81 | sigma_1, 82 | sigma_2, 83 | m_tick, 84 | }) 85 | } 86 | 87 | /// Verify a signature 88 | pub fn verify(&self, pk: &PublicKey, msgs: M) -> Choice 89 | where 90 | M: AsRef<[Scalar]>, 91 | { 92 | let msgs = msgs.as_ref(); 93 | if pk.y.len() < msgs.len() { 94 | return Choice::from(0); 95 | } 96 | if pk.is_invalid().unwrap_u8() == 1 { 97 | return Choice::from(0); 98 | } 99 | if (self.sigma_1.is_identity() | self.sigma_2.is_identity()).unwrap_u8() == 1u8 { 100 | return Choice::from(0); 101 | } 102 | 103 | let mut points = Vec::new(); 104 | let mut scalars = Vec::new(); 105 | points.push(pk.x); 106 | scalars.push(Scalar::ONE); 107 | 108 | points.push(pk.w); 109 | scalars.push(self.m_tick); 110 | 111 | for (i, m) in msgs.iter().enumerate() { 112 | points.push(pk.y[i]); 113 | scalars.push(*m); 114 | } 115 | 116 | // Y_m = X_tilde * W_tilde^m' * Y_tilde[1]^m_1 * Y_tilde[2]^m_2 * ...Y_tilde[i]^m_i 117 | let y_m = G2Projective::sum_of_products(points.as_ref(), scalars.as_ref()); 118 | 119 | // e(sigma_1, Y_m) == e(sigma_2, G2) or 120 | // e(sigma_1 + sigma_2, Y_m - G2) == GT_1 121 | multi_miller_loop(&[ 122 | ( 123 | &self.sigma_1.to_affine(), 124 | &G2Prepared::from(y_m.to_affine()), 125 | ), 126 | ( 127 | &self.sigma_2.to_affine(), 128 | &G2Prepared::from(-G2Affine::generator()), 129 | ), 130 | ]) 131 | .final_exponentiation() 132 | .is_identity() 133 | } 134 | 135 | /// Get the byte representation of this signature 136 | pub fn to_bytes(&self) -> [u8; Self::BYTES] { 137 | let mut bytes = [0u8; Self::BYTES]; 138 | bytes[..48].copy_from_slice(&self.sigma_1.to_affine().to_compressed()); 139 | bytes[48..96].copy_from_slice(&self.sigma_2.to_affine().to_compressed()); 140 | bytes[96..].copy_from_slice(&self.m_tick.to_be_bytes()); 141 | bytes 142 | } 143 | 144 | /// Convert a byte sequence into a signature 145 | pub fn from_bytes(data: &[u8; Self::BYTES]) -> CtOption { 146 | let s1 = G1Affine::from_compressed(&<[u8; 48]>::try_from(&data[..48]).unwrap()) 147 | .map(G1Projective::from); 148 | let s2 = G1Affine::from_compressed(&<[u8; 48]>::try_from(&data[48..96]).unwrap()) 149 | .map(G1Projective::from); 150 | let m = Scalar::from_be_bytes(&<[u8; 32]>::try_from(&data[96..]).unwrap()); 151 | 152 | s1.and_then(|sigma_1| { 153 | s2.and_then(|sigma_2| { 154 | m.and_then(|m_tick| { 155 | CtOption::new( 156 | Signature { 157 | sigma_1, 158 | sigma_2, 159 | m_tick, 160 | }, 161 | Choice::from(1), 162 | ) 163 | }) 164 | }) 165 | }) 166 | } 167 | 168 | pub(crate) fn compute_m_tick(msgs: &[Scalar]) -> Scalar { 169 | let mut hasher = sha3::Shake256::default(); 170 | for m in msgs { 171 | hasher.update(m.to_repr().as_ref()); 172 | } 173 | 174 | let mut reader = hasher.finalize_xof(); 175 | let mut out = [0u8; 64]; 176 | reader.read(&mut out); 177 | let a = Scalar::from_bytes_wide(&out); 178 | reader.read(&mut out); 179 | Scalar::from_bytes_wide(&out) + a 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/knox/short_group_sig_core.rs: -------------------------------------------------------------------------------- 1 | /// Hidden message types 2 | pub mod hidden_message; 3 | /// Commitment builder 4 | pub mod proof_committed_builder; 5 | /// Proof message types 6 | pub mod proof_message; 7 | pub mod short_group_traits; 8 | 9 | pub use hidden_message::*; 10 | pub use proof_committed_builder::*; 11 | pub use proof_message::*; 12 | -------------------------------------------------------------------------------- /src/knox/short_group_sig_core/hidden_message.rs: -------------------------------------------------------------------------------- 1 | use super::super::ecc_group::ScalarOps; 2 | use elliptic_curve::PrimeField; 3 | 4 | /// Two types of hidden messages 5 | #[derive(Copy, Clone, Debug)] 6 | pub enum HiddenMessage 7 | where 8 | S: PrimeField + ScalarOps, 9 | { 10 | /// Indicates the message is hidden and no other work is involved 11 | /// so a blinding factor will be generated specific to this proof 12 | ProofSpecificBlinding(S), 13 | /// Indicates the message is hidden but it is involved with other proofs 14 | /// like boundchecks, set memberships or inequalities, so the blinding factor 15 | /// is provided from an external source. 16 | ExternalBlinding(S, S), 17 | } 18 | -------------------------------------------------------------------------------- /src/knox/short_group_sig_core/proof_committed_builder.rs: -------------------------------------------------------------------------------- 1 | use super::super::ecc_group::*; 2 | use crate::CredxResult; 3 | use core::fmt::Debug; 4 | use elliptic_curve::{ 5 | ff::PrimeField, 6 | group::{Curve, GroupEncoding}, 7 | }; 8 | use merlin::Transcript; 9 | use rand_core::RngCore; 10 | use subtle::ConstantTimeEq; 11 | 12 | /// A builder struct for creating a proof of knowledge 13 | /// of messages in a vector commitment 14 | /// each message has a blinding factor 15 | pub struct ProofCommittedBuilder 16 | where 17 | B: Clone + Copy + Debug + Default + ConstantTimeEq + PartialEq + Eq + Curve, 18 | C: GroupEncoding + Debug, 19 | S: PrimeField + ScalarOps, 20 | { 21 | points: Vec, 22 | scalars: Vec, 23 | sum_of_products: fn(&[B], &[S]) -> B, 24 | } 25 | 26 | impl Default for ProofCommittedBuilder 27 | where 28 | B: Clone + Copy + Debug + Default + ConstantTimeEq + PartialEq + Eq + Curve, 29 | C: GroupEncoding + Debug, 30 | S: PrimeField + ScalarOps, 31 | { 32 | fn default() -> Self { 33 | Self::new(|_, _| B::default()) 34 | } 35 | } 36 | 37 | impl ProofCommittedBuilder 38 | where 39 | B: Clone + Copy + Debug + Default + ConstantTimeEq + PartialEq + Eq + Curve, 40 | C: GroupEncoding + Debug, 41 | S: PrimeField + ScalarOps, 42 | { 43 | /// Create a new builder 44 | pub fn new(sum_of_products: fn(&[B], &[S]) -> B) -> Self { 45 | Self { 46 | points: Vec::new(), 47 | scalars: Vec::new(), 48 | sum_of_products, 49 | } 50 | } 51 | 52 | /// Add a specified point and generate a random blinding factor 53 | pub fn commit_random(&mut self, point: B, rng: impl RngCore) { 54 | self.points.push(point); 55 | self.scalars.push(S::random(rng)); 56 | } 57 | 58 | /// Commit a specified point with the specified scalar 59 | pub fn commit(&mut self, point: B, scalar: S) { 60 | self.points.push(point); 61 | self.scalars.push(scalar); 62 | } 63 | 64 | /// Convert the committed values to bytes for the fiat-shamir challenge 65 | pub fn add_challenge_contribution(&self, label: &'static [u8], transcript: &mut Transcript) { 66 | let commitment = self.commitment(); 67 | transcript.append_message(label, commitment.to_affine().to_bytes().as_ref()); 68 | } 69 | 70 | pub fn commitment(&self) -> B { 71 | let mut scalars = self.scalars.clone(); 72 | (self.sum_of_products)(self.points.as_ref(), scalars.as_mut()) 73 | } 74 | 75 | /// Generate the Schnorr challenges given the specified secrets 76 | /// by computing p = r + c * s 77 | pub fn generate_proof(&self, challenge: S, secrets: &[S]) -> CredxResult> { 78 | Ok(self 79 | .scalars 80 | .iter() 81 | .enumerate() 82 | .map(|(i, s)| *s + secrets[i] * challenge) 83 | .collect()) 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | mod test { 89 | 90 | use super::*; 91 | use blsful::inner_types::*; 92 | 93 | #[test] 94 | fn test_proof_committed_builder() { 95 | let mut pb = ProofCommittedBuilder::::new( 96 | G1Projective::sum_of_products, 97 | ); 98 | 99 | let mut transcript = Transcript::new(b"test_proof_committed_builder"); 100 | let challenge = Scalar::from(3u64); 101 | 102 | pb.commit(G1Projective::IDENTITY, Scalar::from(2u64)); 103 | 104 | pb.add_challenge_contribution(b"test", &mut transcript); 105 | let proof = pb 106 | .generate_proof(challenge, &[Scalar::from(1337u64)]) 107 | .unwrap(); 108 | assert!(!proof.is_empty()); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/knox/short_group_sig_core/proof_message.rs: -------------------------------------------------------------------------------- 1 | use super::super::ecc_group::ScalarOps; 2 | use super::hidden_message::HiddenMessage; 3 | use elliptic_curve::PrimeField; 4 | use rand_core::{CryptoRng, RngCore}; 5 | 6 | /// A message classification by the prover 7 | #[derive(Copy, Clone, Debug)] 8 | pub enum ProofMessage 9 | where 10 | S: PrimeField + ScalarOps, 11 | { 12 | /// Message will be revealed to a verifier 13 | Revealed(S), 14 | /// Message will be hidden from a verifier 15 | Hidden(HiddenMessage), 16 | } 17 | 18 | impl ProofMessage 19 | where 20 | S: PrimeField + ScalarOps, 21 | { 22 | /// Extract the internal message 23 | pub fn get_message(&self) -> S { 24 | match *self { 25 | ProofMessage::Revealed(r) => r, 26 | ProofMessage::Hidden(HiddenMessage::ProofSpecificBlinding(p)) => p, 27 | ProofMessage::Hidden(HiddenMessage::ExternalBlinding(p, _)) => p, 28 | } 29 | } 30 | 31 | /// Get the blinding factor 32 | pub fn get_blinder(&self, rng: impl RngCore + CryptoRng) -> Option { 33 | match *self { 34 | ProofMessage::Revealed(_) => None, 35 | ProofMessage::Hidden(HiddenMessage::ProofSpecificBlinding(_)) => Some(S::random(rng)), 36 | ProofMessage::Hidden(HiddenMessage::ExternalBlinding(_, s)) => Some(s), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A map implementation 2 | 3 | use blsful::inner_types::G1Projective; 4 | use blsful::{Bls12381G2Impl, PublicKey, SecretKey}; 5 | use elliptic_curve::hash2curve::ExpandMsgXmd; 6 | use rand_core::{CryptoRng, RngCore}; 7 | 8 | /// The result type for this crate 9 | pub type CredxResult = Result; 10 | 11 | /// Generate a hex random string with `length` bytes. 12 | pub fn random_string(length: usize, mut rng: impl RngCore + CryptoRng) -> String { 13 | let mut buffer = vec![0u8; length]; 14 | rng.fill_bytes(&mut buffer); 15 | hex::encode(&buffer) 16 | } 17 | 18 | /// Anyone can generate a pair of verifiable encryption keys. 19 | pub fn generate_verifiable_encryption_keys( 20 | rng: impl RngCore + CryptoRng, 21 | ) -> (PublicKey, SecretKey) { 22 | Knox::new_bls381g2_keys(rng) 23 | } 24 | 25 | /// Create a domain proof generator 26 | pub fn create_domain_proof_generator(domain_string: &[u8]) -> G1Projective { 27 | G1Projective::hash::>( 28 | domain_string, 29 | b"BLS12381G1_XMD:SHA-256_SSWU_RO_", 30 | ) 31 | } 32 | 33 | use crate::knox::Knox; 34 | pub use indexmap; 35 | pub use regex; 36 | 37 | /// The blind credential operations 38 | pub mod blind; 39 | /// Claim related methods 40 | pub mod claim; 41 | /// Credential related methods 42 | pub mod credential; 43 | /// Errors produced by this library 44 | pub mod error; 45 | /// Issuer related methods 46 | pub mod issuer; 47 | /// Internal crypto primitives 48 | pub mod knox; 49 | /// Presentation related methods 50 | pub mod presentation; 51 | /// Revocation registry methods 52 | pub mod revocation_registry; 53 | /// Presentation statements 54 | pub mod statement; 55 | mod utils; 56 | /// Presentation verifiers 57 | mod verifier; 58 | 59 | /// One import to rule them all 60 | pub mod prelude { 61 | use super::*; 62 | 63 | pub use super::CredxResult; 64 | pub use blind::*; 65 | pub use claim::*; 66 | pub use credential::*; 67 | pub use error::*; 68 | pub use issuer::*; 69 | pub use knox::{accumulator::vb20, bbs, ps, Knox}; 70 | pub use presentation::*; 71 | pub use revocation_registry::*; 72 | pub use statement::*; 73 | 74 | pub use blsful; 75 | } 76 | 77 | mod mapping { 78 | #![allow(dead_code)] 79 | #![allow(unused_assignments)] 80 | pub mod map_credential; 81 | pub mod map_presentation; 82 | } 83 | -------------------------------------------------------------------------------- /src/presentation/commitment.rs: -------------------------------------------------------------------------------- 1 | use crate::knox::short_group_sig_core::short_group_traits::ShortGroupSignatureScheme; 2 | use crate::presentation::{PresentationBuilder, PresentationProofs}; 3 | use crate::statement::CommitmentStatement; 4 | use crate::CredxResult; 5 | use blsful::inner_types::{G1Projective, Scalar}; 6 | use elliptic_curve::{group::Curve, Field}; 7 | use merlin::Transcript; 8 | use rand_core::{CryptoRng, RngCore}; 9 | use serde::{Deserialize, Serialize}; 10 | 11 | /// A commitment builder 12 | #[derive(Debug)] 13 | pub(crate) struct CommitmentBuilder<'a> { 14 | pub(crate) commitment: G1Projective, 15 | pub(crate) statement: &'a CommitmentStatement, 16 | pub(crate) b: Scalar, 17 | pub(crate) r: Scalar, 18 | } 19 | 20 | impl PresentationBuilder for CommitmentBuilder<'_> { 21 | fn gen_proof(self, challenge: Scalar) -> PresentationProofs { 22 | let blinder_proof = self.r + challenge * self.b; 23 | CommitmentProof { 24 | id: self.statement.id.clone(), 25 | commitment: self.commitment, 26 | blinder_proof, 27 | } 28 | .into() 29 | } 30 | } 31 | 32 | impl<'a> CommitmentBuilder<'a> { 33 | /// Creates a commitment builder 34 | pub fn commit( 35 | statement: &'a CommitmentStatement, 36 | message: Scalar, 37 | b: Scalar, 38 | mut rng: impl RngCore + CryptoRng, 39 | transcript: &mut Transcript, 40 | ) -> CredxResult { 41 | let r = Scalar::random(&mut rng); 42 | let commitment = statement.message_generator * message + statement.blinder_generator * b; 43 | let blind_commitment = statement.message_generator * b + statement.blinder_generator * r; 44 | 45 | transcript.append_message(b"", statement.id.as_bytes()); 46 | transcript.append_message( 47 | b"commitment", 48 | commitment.to_affine().to_compressed().as_slice(), 49 | ); 50 | transcript.append_message( 51 | b"blind commitment", 52 | blind_commitment.to_affine().to_compressed().as_slice(), 53 | ); 54 | Ok(Self { 55 | commitment, 56 | statement, 57 | b, 58 | r, 59 | }) 60 | } 61 | } 62 | 63 | /// A commitment proof 64 | #[derive(Clone, Debug, Deserialize, Serialize)] 65 | pub struct CommitmentProof { 66 | /// The statement identifier 67 | pub id: String, 68 | /// The commitment 69 | pub commitment: G1Projective, 70 | /// The schnorr blinder proof 71 | pub blinder_proof: Scalar, 72 | } 73 | -------------------------------------------------------------------------------- /src/presentation/credential.rs: -------------------------------------------------------------------------------- 1 | use crate::knox::short_group_sig_core::short_group_traits::ShortGroupSignatureScheme; 2 | use crate::prelude::*; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// The types of credentials to pass in 6 | #[derive(Clone, Debug, Serialize, Deserialize)] 7 | pub enum PresentationCredential { 8 | /// A signature credential 9 | #[serde(bound( 10 | serialize = "Credential: Serialize", 11 | deserialize = "Credential: Deserialize<'de>" 12 | ))] 13 | Signature(Box>), 14 | /// A membership check credential 15 | Membership(Box), 16 | } 17 | 18 | impl From> for PresentationCredential { 19 | fn from(value: Credential) -> Self { 20 | Self::Signature(Box::new(value)) 21 | } 22 | } 23 | 24 | impl From for PresentationCredential { 25 | fn from(value: MembershipCredential) -> Self { 26 | Self::Membership(Box::new(value)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/presentation/equality.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::knox::short_group_sig_core::short_group_traits::ShortGroupSignatureScheme; 3 | use crate::statement::{EqualityStatement, Statement}; 4 | use crate::{error::Error, CredxResult}; 5 | use indexmap::IndexMap; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | pub(crate) struct EqualityBuilder<'a> { 9 | reference_statement: &'a EqualityStatement, 10 | } 11 | 12 | impl PresentationBuilder for EqualityBuilder<'_> { 13 | fn gen_proof(self, _challenge: Scalar) -> PresentationProofs { 14 | EqualityProof { 15 | id: self.reference_statement.id(), 16 | } 17 | .into() 18 | } 19 | } 20 | 21 | impl<'a> EqualityBuilder<'a> { 22 | pub fn commit( 23 | reference_statement: &'a EqualityStatement, 24 | reference_id_credential: &IndexMap>, 25 | ) -> CredxResult { 26 | let mut scalars = Vec::new(); 27 | for (id, claim_index) in &reference_statement.ref_id_claim_index { 28 | match reference_id_credential.get(id) { 29 | None => { 30 | return Err(Error::InvalidPresentationData(format!( 31 | "equality statement with id '{}' references a non-existent credential '{}'", 32 | reference_statement.id, id 33 | ))) 34 | } 35 | Some(cred) => { 36 | if let PresentationCredential::Signature(c) = cred { 37 | let sc = c.claims[*claim_index].to_scalar(); 38 | scalars.push(sc); 39 | } 40 | } 41 | } 42 | } 43 | let mut res = true; 44 | for i in 0..scalars.len() - 1 { 45 | res &= scalars[i] == scalars[i + 1]; 46 | } 47 | if !res { 48 | return Err(Error::InvalidClaimData( 49 | "equality statement - claims are not all the same", 50 | )); 51 | } 52 | Ok(Self { 53 | reference_statement, 54 | }) 55 | } 56 | } 57 | 58 | /// An equality proof for checking message equality 59 | #[derive(Clone, Debug, Serialize, Deserialize)] 60 | pub struct EqualityProof { 61 | /// The statement identifier 62 | pub id: String, 63 | } 64 | -------------------------------------------------------------------------------- /src/presentation/membership.rs: -------------------------------------------------------------------------------- 1 | use crate::knox::accumulator::vb20::{ 2 | Element, MembershipProof as Vb20MembershipProof, MembershipProofCommitting, ProofParams, 3 | }; 4 | use crate::knox::short_group_sig_core::short_group_traits::ShortGroupSignatureScheme; 5 | use crate::knox::short_group_sig_core::ProofMessage; 6 | use crate::prelude::MembershipCredential; 7 | use crate::presentation::{PresentationBuilder, PresentationProofs}; 8 | use crate::statement::MembershipStatement; 9 | use crate::CredxResult; 10 | use blsful::inner_types::Scalar; 11 | use merlin::Transcript; 12 | use serde::{Deserialize, Serialize}; 13 | 14 | pub(crate) struct MembershipProofBuilder<'a> { 15 | id: &'a String, 16 | committing: MembershipProofCommitting, 17 | } 18 | 19 | impl PresentationBuilder for MembershipProofBuilder<'_> { 20 | fn gen_proof(self, challenge: Scalar) -> PresentationProofs { 21 | let proof = self.committing.gen_proof(Element(challenge)); 22 | MembershipProof { 23 | id: self.id.clone(), 24 | proof, 25 | } 26 | .into() 27 | } 28 | } 29 | 30 | impl<'a> MembershipProofBuilder<'a> { 31 | /// Create a new accumulator set membership proof builder 32 | pub fn commit( 33 | statement: &'a MembershipStatement, 34 | credential: &MembershipCredential, 35 | message: ProofMessage, 36 | nonce: &[u8], 37 | transcript: &mut Transcript, 38 | ) -> CredxResult { 39 | let params = ProofParams::new(statement.verification_key, Some(nonce)); 40 | let committing = MembershipProofCommitting::new( 41 | message, 42 | *credential, 43 | params, 44 | statement.verification_key, 45 | ); 46 | params.add_to_transcript(transcript); 47 | committing.get_bytes_for_challenge(transcript); 48 | Ok(Self { 49 | id: &statement.id, 50 | committing, 51 | }) 52 | } 53 | } 54 | 55 | /// A membership proof based on accumulators 56 | #[derive(Clone, Debug, Deserialize, Serialize)] 57 | pub struct MembershipProof { 58 | /// The statement identifier 59 | pub id: String, 60 | /// The membership proof 61 | pub proof: Vb20MembershipProof, 62 | } 63 | -------------------------------------------------------------------------------- /src/presentation/proof.rs: -------------------------------------------------------------------------------- 1 | use super::SignatureProof; 2 | use crate::knox::short_group_sig_core::short_group_traits::ShortGroupSignatureScheme; 3 | use crate::presentation::verifiable_encryption_decryption::VerifiableEncryptionDecryptionProof; 4 | use crate::presentation::{ 5 | CommitmentProof, EqualityProof, MembershipProof, RangeProof, RevocationProof, 6 | VerifiableEncryptionProof, 7 | }; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | /// The types of presentation proofs 11 | #[derive(Clone, Debug, Deserialize, Serialize)] 12 | pub enum PresentationProofs { 13 | /// Signature proofs of knowledge 14 | #[serde(bound( 15 | serialize = "SignatureProof: Serialize", 16 | deserialize = "SignatureProof: Deserialize<'de>" 17 | ))] 18 | Signature(Box>), 19 | /// Accumulator set membership proof 20 | Revocation(Box), 21 | /// Equality proof 22 | Equality(Box), 23 | /// Commitment proof 24 | Commitment(Box), 25 | /// Verifiable Encryption proof 26 | VerifiableEncryption(Box), 27 | /// Range proof 28 | Range(Box), 29 | /// Membership Proofs 30 | Membership(Box), 31 | /// Verifiable Encryption Decryption Proofs 32 | VerifiableEncryptionDecryption(Box), 33 | } 34 | 35 | impl From> for PresentationProofs { 36 | fn from(p: SignatureProof) -> Self { 37 | Self::Signature(Box::new(p)) 38 | } 39 | } 40 | 41 | impl From for PresentationProofs { 42 | fn from(p: RevocationProof) -> Self { 43 | Self::Revocation(Box::new(p)) 44 | } 45 | } 46 | 47 | impl From for PresentationProofs { 48 | fn from(p: EqualityProof) -> Self { 49 | Self::Equality(Box::new(p)) 50 | } 51 | } 52 | 53 | impl From for PresentationProofs { 54 | fn from(p: CommitmentProof) -> Self { 55 | Self::Commitment(Box::new(p)) 56 | } 57 | } 58 | 59 | impl From for PresentationProofs { 60 | fn from(p: VerifiableEncryptionProof) -> Self { 61 | Self::VerifiableEncryption(Box::new(p)) 62 | } 63 | } 64 | 65 | impl From for PresentationProofs { 66 | fn from(p: RangeProof) -> Self { 67 | Self::Range(Box::new(p)) 68 | } 69 | } 70 | 71 | impl From for PresentationProofs { 72 | fn from(value: MembershipProof) -> Self { 73 | Self::Membership(Box::new(value)) 74 | } 75 | } 76 | 77 | impl From 78 | for PresentationProofs 79 | { 80 | fn from(value: VerifiableEncryptionDecryptionProof) -> Self { 81 | Self::VerifiableEncryptionDecryption(Box::new(value)) 82 | } 83 | } 84 | 85 | impl PresentationProofs { 86 | /// Get the underlying statement identifier 87 | pub fn id(&self) -> &String { 88 | match self { 89 | Self::Signature(s) => &s.id, 90 | Self::Revocation(a) => &a.id, 91 | Self::Equality(e) => &e.id, 92 | Self::Commitment(c) => &c.id, 93 | Self::VerifiableEncryption(v) => &v.id, 94 | Self::Range(r) => &r.id, 95 | Self::Membership(m) => &m.id, 96 | Self::VerifiableEncryptionDecryption(v) => &v.id, 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/presentation/revocation.rs: -------------------------------------------------------------------------------- 1 | use crate::credential::Credential; 2 | use crate::knox::accumulator::vb20::{ 3 | Element, MembershipProof, MembershipProofCommitting, ProofParams, 4 | }; 5 | use crate::knox::short_group_sig_core::short_group_traits::ShortGroupSignatureScheme; 6 | use crate::knox::short_group_sig_core::ProofMessage; 7 | use crate::presentation::{PresentationBuilder, PresentationProofs}; 8 | use crate::statement::RevocationStatement; 9 | use crate::CredxResult; 10 | use blsful::inner_types::Scalar; 11 | use merlin::Transcript; 12 | use serde::{Deserialize, Serialize}; 13 | 14 | pub(crate) struct RevocationProofBuilder<'a> { 15 | id: &'a String, 16 | committing: MembershipProofCommitting, 17 | } 18 | 19 | impl PresentationBuilder for RevocationProofBuilder<'_> { 20 | fn gen_proof(self, challenge: Scalar) -> PresentationProofs { 21 | let proof = self.committing.gen_proof(Element(challenge)); 22 | RevocationProof { 23 | id: self.id.clone(), 24 | proof, 25 | } 26 | .into() 27 | } 28 | } 29 | 30 | impl<'a> RevocationProofBuilder<'a> { 31 | /// Create a new accumulator set membership proof builder 32 | pub fn commit( 33 | statement: &'a RevocationStatement, 34 | credential: &Credential, 35 | message: ProofMessage, 36 | nonce: &[u8], 37 | transcript: &mut Transcript, 38 | ) -> CredxResult { 39 | let params = ProofParams::new(statement.verification_key, Some(nonce)); 40 | let committing = MembershipProofCommitting::new( 41 | message, 42 | credential.revocation_handle, 43 | params, 44 | statement.verification_key, 45 | ); 46 | params.add_to_transcript(transcript); 47 | committing.get_bytes_for_challenge(transcript); 48 | Ok(Self { 49 | id: &statement.id, 50 | committing, 51 | }) 52 | } 53 | } 54 | 55 | /// A membership proof based on accumulators 56 | #[derive(Clone, Debug, Deserialize, Serialize)] 57 | pub struct RevocationProof { 58 | /// The statement identifier 59 | pub id: String, 60 | /// The membership proof 61 | pub proof: MembershipProof, 62 | } 63 | -------------------------------------------------------------------------------- /src/presentation/schema.rs: -------------------------------------------------------------------------------- 1 | use crate::knox::short_group_sig_core::short_group_traits::ShortGroupSignatureScheme; 2 | use crate::random_string; 3 | use crate::{statement::Statements, utils::*}; 4 | use indexmap::IndexMap; 5 | use log::debug; 6 | use serde::{Deserialize, Serialize}; 7 | use uint_zigzag::Uint; 8 | 9 | /// A description of the proofs to be created by the verifier 10 | #[derive(Clone, Debug, Deserialize, Serialize)] 11 | pub struct PresentationSchema { 12 | /// The unique presentation context id 13 | pub id: String, 14 | /// The statements associated with this presentation 15 | #[serde( 16 | serialize_with = "serialize_indexmap", 17 | deserialize_with = "deserialize_indexmap", 18 | bound(serialize = "Statements: Serialize"), 19 | bound(deserialize = "Statements: Deserialize<'de>") 20 | )] 21 | pub statements: IndexMap>, 22 | } 23 | 24 | impl PresentationSchema { 25 | /// Create a new presentation schema with random id 26 | pub fn new(statements: &[Statements]) -> Self { 27 | let id = random_string(16, rand::thread_rng()); 28 | Self::new_with_id(statements, &id) 29 | } 30 | 31 | /// Create a new presentation schema with given id 32 | pub fn new_with_id(statements: &[Statements], pres_schema_id: &str) -> Self { 33 | let id = pres_schema_id.into(); 34 | let statements = statements.iter().map(|s| (s.id(), (*s).clone())).collect(); 35 | let presentation_schema = Self { id, statements }; 36 | debug!( 37 | "Presentation Schema: {}", 38 | serde_json::to_string_pretty(&presentation_schema).unwrap() 39 | ); 40 | presentation_schema 41 | } 42 | 43 | /// Add challenge contribution 44 | pub fn add_challenge_contribution(&self, transcript: &mut merlin::Transcript) { 45 | transcript.append_message(b"presentation schema id", self.id.as_bytes()); 46 | transcript.append_message( 47 | b"presentation statement length", 48 | &Uint::from(self.statements.len()).to_vec(), 49 | ); 50 | for (id, statement) in &self.statements { 51 | transcript.append_message(b"presentation statement id", id.as_bytes()); 52 | statement.add_challenge_contribution(transcript); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/presentation/signature.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::knox::short_group_sig_core::short_group_traits::{ 3 | ProofOfSignatureKnowledgeContribution, ShortGroupSignatureScheme, 4 | }; 5 | use crate::knox::short_group_sig_core::ProofMessage; 6 | use crate::statement::SignatureStatement; 7 | use crate::{error::Error, utils::*, CredxResult}; 8 | use blsful::inner_types::Scalar; 9 | use indexmap::IndexMap; 10 | use rand::{CryptoRng, RngCore}; 11 | use serde::{Deserialize, Serialize}; 12 | 13 | /// A builder for creating signature presentations 14 | pub(crate) struct SignatureBuilder<'a, S: ShortGroupSignatureScheme> { 15 | /// The statement identifier 16 | id: &'a String, 17 | /// The messages that belong to this signature 18 | disclosed_messages: IndexMap, 19 | /// The signature proof of knowledge builder 20 | pok_sig: S::ProofOfSignatureKnowledgeContribution, 21 | } 22 | 23 | impl PresentationBuilder for SignatureBuilder<'_, S> { 24 | /// Finalize proofs 25 | fn gen_proof(self, challenge: Scalar) -> PresentationProofs { 26 | // PS signature generate_proof can't fail, okay to unwrap 27 | SignatureProof { 28 | id: self.id.clone(), 29 | disclosed_messages: self.disclosed_messages, 30 | pok: self.pok_sig.generate_proof(challenge).unwrap(), 31 | } 32 | .into() 33 | } 34 | } 35 | 36 | impl<'a, S: ShortGroupSignatureScheme> SignatureBuilder<'a, S> { 37 | /// Create a new signature builder 38 | pub fn commit( 39 | statement: &'a SignatureStatement, 40 | signature: &S::Signature, 41 | messages: &[ProofMessage], 42 | rng: impl RngCore + CryptoRng, 43 | transcript: &mut Transcript, 44 | ) -> CredxResult { 45 | match S::ProofOfSignatureKnowledgeContribution::commit( 46 | signature, 47 | &statement.issuer.verifying_key, 48 | messages, 49 | rng, 50 | ) { 51 | Err(_) => Err(Error::InvalidSignatureProofData), 52 | Ok(poksig) => { 53 | let disclosed_messages = messages 54 | .iter() 55 | .enumerate() 56 | .filter(|(_i, m)| matches!(m, ProofMessage::Revealed(_))) 57 | .map(|(i, m)| (i, m.get_message())) 58 | .collect::>(); 59 | 60 | poksig.add_proof_contribution(transcript); 61 | 62 | Ok(Self { 63 | id: &statement.id, 64 | disclosed_messages, 65 | pok_sig: poksig, 66 | }) 67 | } 68 | } 69 | } 70 | } 71 | 72 | /// A signature proof that can be presented 73 | #[derive(Clone, Debug, Serialize, Deserialize)] 74 | pub struct SignatureProof { 75 | /// The statement identifier 76 | pub id: String, 77 | /// The disclosed message scalars 78 | #[serde( 79 | serialize_with = "serialize_indexmap", 80 | deserialize_with = "deserialize_indexmap" 81 | )] 82 | pub disclosed_messages: IndexMap, 83 | /// The proof 84 | pub pok: S::ProofOfSignatureKnowledge, 85 | } 86 | -------------------------------------------------------------------------------- /src/revocation_registry.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::knox::accumulator::vb20::{Accumulator, Element, SecretKey}; 3 | use crate::{utils::*, CredxResult}; 4 | use indexmap::IndexSet; 5 | use rand_core::{CryptoRng, RngCore}; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | /// A revocation registry for credentials 9 | #[derive(Clone, Debug, Serialize, Deserialize)] 10 | pub struct RevocationRegistry { 11 | /// The registry value 12 | pub value: Accumulator, 13 | /// All the elements ever included in the revocation registry 14 | /// Includes the index to help with ordering 15 | #[serde( 16 | serialize_with = "serialize_indexset", 17 | deserialize_with = "deserialize_indexset" 18 | )] 19 | pub elements: IndexSet, 20 | /// The current active set 21 | #[serde( 22 | serialize_with = "serialize_indexset", 23 | deserialize_with = "deserialize_indexset" 24 | )] 25 | pub active: IndexSet, 26 | } 27 | 28 | impl RevocationRegistry { 29 | /// Create a new revocation registry with an initial set of elements to `count` 30 | pub fn new(rng: impl RngCore + CryptoRng) -> Self { 31 | let value = Accumulator::random(rng); 32 | Self { 33 | active: IndexSet::new(), 34 | elements: IndexSet::new(), 35 | value, 36 | } 37 | } 38 | 39 | /// Remove the specified elements from the registry 40 | pub fn revoke(&mut self, sk: &SecretKey, elements: &[String]) -> CredxResult<()> { 41 | let mut removals = Vec::new(); 42 | for e in elements { 43 | if !self.active.shift_remove(e) { 44 | return Err(Error::InvalidRevocationRegistryRevokeOperation); 45 | } 46 | removals.push(Element::hash(e.as_bytes())); 47 | } 48 | 49 | self.value.remove_elements_assign(sk, removals.as_slice()); 50 | 51 | Ok(()) 52 | } 53 | 54 | /// Add the elements to the registry 55 | pub fn add(&mut self, elements: &[String]) { 56 | for e in elements { 57 | if self.elements.insert(e.clone()) { 58 | self.active.insert(e.clone()); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/statement/commitment.rs: -------------------------------------------------------------------------------- 1 | use crate::statement::Statement; 2 | use elliptic_curve::group::{Group, GroupEncoding}; 3 | use elliptic_curve_tools::group; 4 | use merlin::Transcript; 5 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 6 | use uint_zigzag::Uint; 7 | 8 | /// A commitment statement 9 | #[derive(Clone, Debug, Serialize, Deserialize)] 10 | pub struct CommitmentStatement { 11 | /// The generator for the message element 12 | #[serde(with = "group")] 13 | pub message_generator: P, 14 | /// The generator for the random element 15 | #[serde(with = "group")] 16 | pub blinder_generator: P, 17 | /// The statement id 18 | pub id: String, 19 | /// The other statement id 20 | pub reference_id: String, 21 | /// The claim index in the other statement 22 | pub claim: usize, 23 | } 24 | 25 | impl Statement for CommitmentStatement

{ 26 | fn id(&self) -> String { 27 | self.id.clone() 28 | } 29 | 30 | fn reference_ids(&self) -> Vec { 31 | vec![self.reference_id.clone()] 32 | } 33 | 34 | fn add_challenge_contribution(&self, transcript: &mut Transcript) { 35 | transcript.append_message(b"statement type", b"commitment"); 36 | transcript.append_message(b"statement id", self.id.as_bytes()); 37 | transcript.append_message(b"reference statement id", self.reference_id.as_bytes()); 38 | transcript.append_message(b"claim index", &Uint::from(self.claim).to_vec()); 39 | transcript.append_message( 40 | b"message generator", 41 | self.message_generator.to_bytes().as_ref(), 42 | ); 43 | transcript.append_message( 44 | b"blinder generator", 45 | self.blinder_generator.to_bytes().as_ref(), 46 | ); 47 | } 48 | 49 | fn get_claim_index(&self, _reference_id: &str) -> usize { 50 | self.claim 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/statement/equality.rs: -------------------------------------------------------------------------------- 1 | use crate::{statement::Statement, utils::*}; 2 | use indexmap::IndexMap; 3 | use merlin::Transcript; 4 | use serde::{Deserialize, Serialize}; 5 | use uint_zigzag::Uint; 6 | 7 | /// An equality statement 8 | #[derive(Clone, Debug, Deserialize, Serialize)] 9 | pub struct EqualityStatement { 10 | /// The statement id 11 | pub id: String, 12 | /// The reference statement id to claim index 13 | #[serde( 14 | serialize_with = "serialize_indexmap", 15 | deserialize_with = "deserialize_indexmap" 16 | )] 17 | pub ref_id_claim_index: IndexMap, 18 | } 19 | 20 | impl Statement for EqualityStatement { 21 | fn id(&self) -> String { 22 | self.id.clone() 23 | } 24 | 25 | fn reference_ids(&self) -> Vec { 26 | self.ref_id_claim_index.keys().cloned().collect() 27 | } 28 | 29 | fn add_challenge_contribution(&self, transcript: &mut Transcript) { 30 | transcript.append_message(b"statement type", b"equality"); 31 | transcript.append_message(b"statement id", self.id.as_bytes()); 32 | transcript.append_message( 33 | b"reference statement ids to claim index length", 34 | &Uint::from(self.ref_id_claim_index.len()).to_vec(), 35 | ); 36 | for (id, idx) in &self.ref_id_claim_index { 37 | transcript.append_message(b"reference statement id", id.as_bytes()); 38 | transcript.append_message( 39 | b"reference statement claim index", 40 | &Uint::from(*idx).to_vec(), 41 | ); 42 | } 43 | } 44 | 45 | fn get_claim_index(&self, reference_id: &str) -> usize { 46 | self.ref_id_claim_index[reference_id] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/statement/membership.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::{MembershipRegistry, MembershipVerificationKey}; 2 | use crate::statement::Statement; 3 | use merlin::Transcript; 4 | use serde::{Deserialize, Serialize}; 5 | use uint_zigzag::Uint; 6 | 7 | /// Accumulator set membership statement for revocation 8 | #[derive(Clone, Debug, Serialize, Deserialize)] 9 | pub struct MembershipStatement { 10 | /// The statement id 11 | pub id: String, 12 | /// The other statement id 13 | pub reference_id: String, 14 | /// The accumulator value 15 | pub accumulator: MembershipRegistry, 16 | /// The accumulator verification key 17 | pub verification_key: MembershipVerificationKey, 18 | /// The claim index in the other statement 19 | pub claim: usize, 20 | } 21 | 22 | impl Statement for MembershipStatement { 23 | fn id(&self) -> String { 24 | self.id.clone() 25 | } 26 | 27 | fn reference_ids(&self) -> Vec { 28 | vec![self.reference_id.clone()] 29 | } 30 | 31 | fn add_challenge_contribution(&self, transcript: &mut Transcript) { 32 | transcript.append_message(b"statement type", b"vb20 set membership"); 33 | transcript.append_message(b"statement id", self.id.as_bytes()); 34 | transcript.append_message(b"reference statement id", self.reference_id.as_bytes()); 35 | transcript.append_message(b"claim index", &Uint::from(self.claim).to_vec()); 36 | transcript.append_message( 37 | b"verification key", 38 | self.verification_key.to_bytes().as_ref(), 39 | ); 40 | transcript.append_message(b"accumulator", self.accumulator.to_bytes().as_ref()); 41 | } 42 | 43 | fn get_claim_index(&self, _reference_id: &str) -> usize { 44 | self.claim 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/statement/range.rs: -------------------------------------------------------------------------------- 1 | use crate::statement::*; 2 | use merlin::Transcript; 3 | use serde::{Deserialize, Serialize}; 4 | use uint_zigzag::Uint; 5 | 6 | /// A Range proof statement 7 | #[derive(Clone, Debug, Serialize, Deserialize)] 8 | pub struct RangeStatement { 9 | /// The statement id 10 | pub id: String, 11 | /// The reference id to the commitment statement 12 | pub reference_id: String, 13 | /// The reference id to the signature statement 14 | pub signature_id: String, 15 | /// The claim index in the other statement 16 | pub claim: usize, 17 | /// The lower bound to test against if set 18 | pub lower: Option, 19 | /// The upper bound to test against if set 20 | pub upper: Option, 21 | } 22 | 23 | impl Statement for RangeStatement { 24 | fn id(&self) -> String { 25 | self.id.clone() 26 | } 27 | 28 | fn reference_ids(&self) -> Vec { 29 | vec![self.reference_id.clone()] 30 | } 31 | 32 | fn add_challenge_contribution(&self, transcript: &mut Transcript) { 33 | transcript.append_message(b"statement type", b"range proof"); 34 | transcript.append_message(b"statement id", self.id.as_bytes()); 35 | transcript.append_message( 36 | b"reference commitment statement id", 37 | self.reference_id.as_bytes(), 38 | ); 39 | transcript.append_message( 40 | b"reference signature statement id", 41 | self.signature_id.as_bytes(), 42 | ); 43 | transcript.append_message(b"claim index", &Uint::from(self.claim).to_vec()); 44 | transcript.append_message(b"lower version", &[self.lower.map_or(0u8, |_| 1u8)]); 45 | if let Some(lower) = self.lower.as_ref() { 46 | transcript.append_message(b"lower", &Uint::from(*lower).to_vec()); 47 | } 48 | transcript.append_message(b"upper version", &[self.upper.map_or(0u8, |_| 1u8)]); 49 | if let Some(upper) = self.upper.as_ref() { 50 | transcript.append_message(b"upper", &Uint::from(*upper).to_vec()); 51 | } 52 | } 53 | 54 | fn get_claim_index(&self, _reference_id: &str) -> usize { 55 | self.claim 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/statement/revocation.rs: -------------------------------------------------------------------------------- 1 | use crate::knox::accumulator::vb20; 2 | use crate::statement::Statement; 3 | use merlin::Transcript; 4 | use serde::{Deserialize, Serialize}; 5 | use uint_zigzag::Uint; 6 | 7 | /// Accumulator set membership statement for revocation 8 | #[derive(Clone, Debug, Serialize, Deserialize)] 9 | pub struct RevocationStatement { 10 | /// The statement id 11 | pub id: String, 12 | /// The other statement id 13 | pub reference_id: String, 14 | /// The accumulator value 15 | pub accumulator: vb20::Accumulator, 16 | /// The accumulator verification key 17 | pub verification_key: vb20::PublicKey, 18 | /// The claim index in the other statement 19 | pub claim: usize, 20 | } 21 | 22 | impl Statement for RevocationStatement { 23 | fn id(&self) -> String { 24 | self.id.clone() 25 | } 26 | 27 | fn reference_ids(&self) -> Vec { 28 | vec![self.reference_id.clone()] 29 | } 30 | 31 | fn add_challenge_contribution(&self, transcript: &mut Transcript) { 32 | transcript.append_message(b"statement type", b"vb20 set membership revocation"); 33 | transcript.append_message(b"statement id", self.id.as_bytes()); 34 | transcript.append_message(b"reference statement id", self.reference_id.as_bytes()); 35 | transcript.append_message(b"claim index", &Uint::from(self.claim).to_vec()); 36 | transcript.append_message( 37 | b"verification key", 38 | self.verification_key.to_bytes().as_ref(), 39 | ); 40 | transcript.append_message(b"accumulator", self.accumulator.to_bytes().as_ref()); 41 | } 42 | 43 | fn get_claim_index(&self, _reference_id: &str) -> usize { 44 | self.claim 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/statement/signature.rs: -------------------------------------------------------------------------------- 1 | use super::Statement; 2 | use crate::issuer::IssuerPublic; 3 | use crate::knox::short_group_sig_core::short_group_traits::ShortGroupSignatureScheme; 4 | use merlin::Transcript; 5 | use serde::{Deserialize, Serialize}; 6 | use std::collections::BTreeSet; 7 | use uint_zigzag::Uint; 8 | 9 | /// A PS signature statement 10 | #[derive(Clone, Debug, Serialize, Deserialize)] 11 | pub struct SignatureStatement { 12 | /// The labels for the disclosed claims 13 | pub disclosed: BTreeSet, 14 | /// The statement id 15 | pub id: String, 16 | /// The issuer information 17 | #[serde(bound( 18 | serialize = "IssuerPublic: Serialize", 19 | deserialize = "IssuerPublic: Deserialize<'de>" 20 | ))] 21 | pub issuer: IssuerPublic, 22 | } 23 | 24 | impl Statement for SignatureStatement { 25 | /// Return this statement unique identifier 26 | fn id(&self) -> String { 27 | self.id.clone() 28 | } 29 | 30 | /// Any statements that this statement references 31 | fn reference_ids(&self) -> Vec { 32 | Vec::with_capacity(0) 33 | } 34 | 35 | fn add_challenge_contribution(&self, transcript: &mut Transcript) { 36 | transcript.append_message(b"statement type", b"ps signature"); 37 | transcript.append_message(b"statement id", self.id.as_bytes()); 38 | transcript.append_message( 39 | b"disclosed message length", 40 | &Uint::from(self.disclosed.len()).to_vec(), 41 | ); 42 | for (index, d) in self.disclosed.iter().enumerate() { 43 | transcript.append_message( 44 | b"disclosed message label index", 45 | &Uint::from(index).to_vec(), 46 | ); 47 | transcript.append_message(b"disclosed message label", d.as_bytes()); 48 | } 49 | self.issuer.add_challenge_contribution(transcript); 50 | } 51 | 52 | fn get_claim_index(&self, _reference_id: &str) -> usize { 53 | unimplemented!() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/statement/verifiable_encryption.rs: -------------------------------------------------------------------------------- 1 | use crate::statement::Statement; 2 | use blsful::*; 3 | use elliptic_curve::group::{Group, GroupEncoding}; 4 | use elliptic_curve_tools::group; 5 | use merlin::Transcript; 6 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 7 | use uint_zigzag::Uint; 8 | 9 | /// Verifiable encryption 10 | /// 11 | /// Use this statement when you know all the possible encrypted values 12 | /// and you want to prove that a specific value is one of them. 13 | /// To decrypt, you need to know the secret key and try all possible 14 | /// values until you find the one that matches the ciphertext. 15 | #[derive(Clone, Debug, Serialize, Deserialize)] 16 | pub struct VerifiableEncryptionStatement { 17 | /// The generator for the message element 18 | #[serde(with = "group")] 19 | pub message_generator: P, 20 | /// The encryption key for this ciphertext 21 | pub encryption_key: PublicKey, 22 | /// The statement id 23 | pub id: String, 24 | /// The other statement id 25 | pub reference_id: String, 26 | /// The claim index in the other statement 27 | pub claim: usize, 28 | /// Whether to allow message decryption 29 | pub allow_message_decryption: bool, 30 | } 31 | 32 | impl Statement 33 | for VerifiableEncryptionStatement

34 | { 35 | fn id(&self) -> String { 36 | self.id.clone() 37 | } 38 | 39 | fn reference_ids(&self) -> Vec { 40 | vec![self.reference_id.clone()] 41 | } 42 | 43 | fn add_challenge_contribution(&self, transcript: &mut Transcript) { 44 | transcript.append_message(b"statement type", b"el-gamal verifiable encryption"); 45 | transcript.append_message(b"statement id", self.id.as_bytes()); 46 | transcript.append_u64( 47 | b"allow message decryption", 48 | self.allow_message_decryption as u64, 49 | ); 50 | transcript.append_message(b"reference statement id", self.reference_id.as_bytes()); 51 | transcript.append_message(b"claim index", &Uint::from(self.claim).to_vec()); 52 | transcript.append_message( 53 | b"message generator", 54 | self.message_generator.to_bytes().as_ref(), 55 | ); 56 | transcript.append_message(b"encryption key", self.encryption_key.0.to_bytes().as_ref()); 57 | } 58 | 59 | fn get_claim_index(&self, _reference_id: &str) -> usize { 60 | self.claim 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/statement/verifiable_encryption_decryption.rs: -------------------------------------------------------------------------------- 1 | use crate::statement::Statement; 2 | use blsful::*; 3 | use elliptic_curve::group::GroupEncoding; 4 | use elliptic_curve::Group; 5 | use elliptic_curve_tools::group; 6 | use merlin::Transcript; 7 | use serde::de::DeserializeOwned; 8 | use serde::{Deserialize, Serialize}; 9 | use uint_zigzag::Uint; 10 | 11 | /// Verifiable encryption that also allows decryption. 12 | /// 13 | /// Use this statement when complete decryption is desired. 14 | /// The resulting proof is much larger than the one from [`VerifiableEncryptionStatement`] 15 | /// and slightly slower to generate. 16 | /// 17 | /// The value encrypted and decrypted is a scalar value and not 18 | /// the original value. This is not meant to encrypt arbitrary data since 19 | /// arbitrary data can be of any length and cannot be proven in zero-knowledge. 20 | /// Instead, this is meant to encrypt a scalar value that can be proven in zero-knowledge 21 | /// and that's the best that can be achieved. 22 | /// 23 | /// When arbitrary data is allowed, 24 | /// we use the proof to generate an AES key to encrypt 25 | /// arbitrary data then prove the encryption key is correct. But, again, this 26 | /// doesn't prove the encrypted data is correct. For that, verifiable decryption 27 | /// is needed. So the ciphertext when decrypted is proven to be correct but 28 | /// verifiers don't usually decrypt the data. 29 | #[derive(Clone, Debug, Serialize, Deserialize)] 30 | pub struct VerifiableEncryptionDecryptionStatement< 31 | P: Group + GroupEncoding + Serialize + DeserializeOwned, 32 | > { 33 | /// The generator for the message element 34 | #[serde(with = "group")] 35 | pub message_generator: P, 36 | /// The encryption key for this ciphertext 37 | pub encryption_key: PublicKey, 38 | /// The statement id 39 | pub id: String, 40 | /// The other statement id 41 | pub reference_id: String, 42 | /// The claim index in the other statement 43 | pub claim: usize, 44 | } 45 | 46 | impl Statement 47 | for VerifiableEncryptionDecryptionStatement

48 | { 49 | fn id(&self) -> String { 50 | self.id.clone() 51 | } 52 | 53 | fn reference_ids(&self) -> Vec { 54 | vec![self.reference_id.clone()] 55 | } 56 | 57 | fn add_challenge_contribution(&self, transcript: &mut Transcript) { 58 | transcript.append_message( 59 | b"statement type", 60 | b"el-gamal verifiable encryption w/decryption", 61 | ); 62 | transcript.append_message(b"statement id", self.id.as_bytes()); 63 | transcript.append_message(b"reference statement id", self.reference_id.as_bytes()); 64 | transcript.append_message(b"claim index", &Uint::from(self.claim).to_vec()); 65 | transcript.append_message( 66 | b"message generator", 67 | self.message_generator.to_bytes().as_ref(), 68 | ); 69 | transcript.append_message(b"encryption key", self.encryption_key.0.to_bytes().as_ref()); 70 | } 71 | 72 | fn get_claim_index(&self, _reference_id: &str) -> usize { 73 | self.claim 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use blsful::inner_types::Scalar; 2 | use indexmap::{IndexMap, IndexSet}; 3 | use serde::{ 4 | de::{DeserializeOwned, MapAccess, SeqAccess, Visitor}, 5 | ser::{SerializeMap, SerializeSeq}, 6 | Deserialize, Deserializer, Serialize, Serializer, 7 | }; 8 | use std::{ 9 | fmt::{self, Formatter}, 10 | hash::Hash, 11 | marker::PhantomData, 12 | }; 13 | 14 | pub const TOP_BIT: u64 = i64::MIN as u64; 15 | 16 | pub fn get_num_scalar(num: isize) -> Scalar { 17 | Scalar::from(zero_center(num)) 18 | } 19 | 20 | pub fn zero_center(num: isize) -> u64 { 21 | num as u64 ^ TOP_BIT 22 | } 23 | pub fn serialize_indexset( 24 | set: &IndexSet, 25 | s: S, 26 | ) -> Result { 27 | let mut i = s.serialize_seq(Some(set.len()))?; 28 | for e in set { 29 | i.serialize_element(e)?; 30 | } 31 | i.end() 32 | } 33 | 34 | pub fn deserialize_indexset<'de, T: Eq + Hash + DeserializeOwned, D: Deserializer<'de>>( 35 | d: D, 36 | ) -> Result, D::Error> { 37 | struct IndexSetVisitor { 38 | _marker: PhantomData, 39 | } 40 | 41 | impl<'de, TT: Eq + Hash + DeserializeOwned> Visitor<'de> for IndexSetVisitor { 42 | type Value = IndexSet; 43 | 44 | fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { 45 | write!(formatter, "a sequence") 46 | } 47 | 48 | fn visit_seq(self, mut seq: A) -> Result 49 | where 50 | A: SeqAccess<'de>, 51 | { 52 | let mut set = IndexSet::new(); 53 | while let Some(e) = seq.next_element()? { 54 | set.insert(e); 55 | } 56 | Ok(set) 57 | } 58 | } 59 | 60 | d.deserialize_seq(IndexSetVisitor:: { 61 | _marker: PhantomData, 62 | }) 63 | } 64 | 65 | pub fn serialize_indexmap( 66 | map: &IndexMap, 67 | s: S, 68 | ) -> Result { 69 | let mut i = s.serialize_map(Some(map.len()))?; 70 | for (k, v) in map { 71 | i.serialize_entry(k, v)?; 72 | } 73 | i.end() 74 | } 75 | 76 | pub fn deserialize_indexmap< 77 | 'de, 78 | K: Eq + Hash + DeserializeOwned, 79 | V: DeserializeOwned, 80 | D: Deserializer<'de>, 81 | >( 82 | d: D, 83 | ) -> Result, D::Error> { 84 | struct IndexMapVisitor { 85 | _key_marker: PhantomData, 86 | _value_marker: PhantomData, 87 | } 88 | 89 | impl<'de, KK: Eq + Hash + DeserializeOwned, VV: DeserializeOwned> Visitor<'de> 90 | for IndexMapVisitor 91 | { 92 | type Value = IndexMap; 93 | 94 | fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { 95 | write!(formatter, "a map") 96 | } 97 | 98 | fn visit_map(self, mut map: A) -> Result 99 | where 100 | A: MapAccess<'de>, 101 | { 102 | let mut m = IndexMap::new(); 103 | while let Some((k, v)) = map.next_entry()? { 104 | m.insert(k, v); 105 | } 106 | Ok(m) 107 | } 108 | } 109 | 110 | d.deserialize_map(IndexMapVisitor:: { 111 | _key_marker: PhantomData, 112 | _value_marker: PhantomData, 113 | }) 114 | } 115 | 116 | pub fn serialize_indexmap_nested( 117 | map: &IndexMap>, 118 | s: S, 119 | ) -> Result { 120 | let result: Vec<(&K1, Vec<(&K2, &V)>)> = map 121 | .iter() 122 | .map(|(k1, v1)| { 123 | let values = v1.iter().collect::>(); 124 | (k1, values) 125 | }) 126 | .collect(); 127 | result.serialize(s) 128 | } 129 | 130 | pub fn deserialize_indexmap_nested< 131 | 'de, 132 | K1: Deserialize<'de> + Hash + Eq, 133 | K2: Deserialize<'de> + Hash + Eq, 134 | V: Deserialize<'de>, 135 | D: Deserializer<'de>, 136 | >( 137 | deserialize: D, 138 | ) -> Result>, D::Error> { 139 | let inner = Vec::<(K1, Vec<(K2, V)>)>::deserialize(deserialize)?; 140 | let mut result = IndexMap::new(); 141 | for (k1, v) in inner.into_iter() { 142 | let value: IndexMap = v.into_iter().collect(); 143 | result.insert(k1, value); 144 | } 145 | Ok(result) 146 | } 147 | -------------------------------------------------------------------------------- /src/verifier.rs: -------------------------------------------------------------------------------- 1 | mod commitment; 2 | mod equality; 3 | mod membership; 4 | mod range; 5 | mod revocation; 6 | mod signature; 7 | mod verifiable_encryption; 8 | mod verifiable_encryption_decryption; 9 | 10 | pub use commitment::*; 11 | pub use equality::*; 12 | pub use membership::*; 13 | pub use range::*; 14 | pub use revocation::*; 15 | pub use signature::*; 16 | pub use verifiable_encryption::*; 17 | pub use verifiable_encryption_decryption::*; 18 | 19 | use crate::knox::short_group_sig_core::short_group_traits::ShortGroupSignatureScheme; 20 | use crate::CredxResult; 21 | use blsful::inner_types::Scalar; 22 | use merlin::Transcript; 23 | 24 | /// A trait for indication of proof verifier logic 25 | pub(crate) trait ProofVerifier { 26 | /// Recompute the challenge contribution 27 | fn add_challenge_contribution( 28 | &self, 29 | challenge: Scalar, 30 | transcript: &mut Transcript, 31 | ) -> CredxResult<()>; 32 | /// Verify any additional proof material 33 | fn verify(&self, challenge: Scalar) -> CredxResult<()>; 34 | } 35 | 36 | pub(crate) enum ProofVerifiers<'a, 'b, 'c, S: ShortGroupSignatureScheme> { 37 | Signature(Box>), 38 | Revocation(Box>), 39 | Equality(Box>), 40 | Commitment(Box>), 41 | VerifiableEncryption(Box>), 42 | Range(Box>), 43 | Membership(Box>), 44 | VerifiableEncryptionDecryption(Box>), 45 | } 46 | 47 | impl<'a, 'b, S: ShortGroupSignatureScheme> From> 48 | for ProofVerifiers<'a, 'b, '_, S> 49 | { 50 | fn from(s: SignatureVerifier<'a, 'b, S>) -> Self { 51 | Self::Signature(Box::new(s)) 52 | } 53 | } 54 | 55 | impl<'a, 'b, S: ShortGroupSignatureScheme> From> 56 | for ProofVerifiers<'a, 'b, '_, S> 57 | { 58 | fn from(a: RevocationVerifier<'a, 'b>) -> Self { 59 | Self::Revocation(Box::new(a)) 60 | } 61 | } 62 | 63 | impl<'a, 'b, 'c, S: ShortGroupSignatureScheme> From> 64 | for ProofVerifiers<'a, 'b, 'c, S> 65 | { 66 | fn from(e: EqualityVerifier<'a, 'b, 'c, S>) -> Self { 67 | Self::Equality(Box::new(e)) 68 | } 69 | } 70 | 71 | impl<'a, 'b, S: ShortGroupSignatureScheme> From> 72 | for ProofVerifiers<'a, 'b, '_, S> 73 | { 74 | fn from(a: CommitmentVerifier<'a, 'b>) -> Self { 75 | Self::Commitment(Box::new(a)) 76 | } 77 | } 78 | 79 | impl<'a, 'b, S: ShortGroupSignatureScheme> From> 80 | for ProofVerifiers<'a, 'b, '_, S> 81 | { 82 | fn from(a: VerifiableEncryptionVerifier<'a, 'b>) -> Self { 83 | Self::VerifiableEncryption(Box::new(a)) 84 | } 85 | } 86 | 87 | impl<'a, 'b, 'c, S: ShortGroupSignatureScheme> From> 88 | for ProofVerifiers<'a, 'b, 'c, S> 89 | { 90 | fn from(a: RangeProofVerifier<'a, 'b, 'c>) -> Self { 91 | Self::Range(Box::new(a)) 92 | } 93 | } 94 | 95 | impl<'a, 'b, S: ShortGroupSignatureScheme> From> 96 | for ProofVerifiers<'a, 'b, '_, S> 97 | { 98 | fn from(a: MembershipVerifier<'a, 'b>) -> Self { 99 | Self::Membership(Box::new(a)) 100 | } 101 | } 102 | 103 | impl<'a, 'b, S: ShortGroupSignatureScheme> From> 104 | for ProofVerifiers<'a, 'b, '_, S> 105 | { 106 | fn from(a: VerifiableEncryptionDecryptionVerifier<'a, 'b>) -> Self { 107 | Self::VerifiableEncryptionDecryption(Box::new(a)) 108 | } 109 | } 110 | 111 | impl ProofVerifiers<'_, '_, '_, S> { 112 | /// Verify any additional proof material 113 | pub fn verify(&self, challenge: Scalar) -> CredxResult<()> { 114 | match self { 115 | Self::Signature(s) => s.verify(challenge), 116 | Self::Revocation(a) => a.verify(challenge), 117 | Self::Equality(e) => e.verify(challenge), 118 | Self::Commitment(c) => c.verify(challenge), 119 | Self::VerifiableEncryption(v) => v.verify(challenge), 120 | Self::Range(r) => r.verify(challenge), 121 | Self::Membership(m) => m.verify(challenge), 122 | Self::VerifiableEncryptionDecryption(v) => v.verify(challenge), 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/verifier/commitment.rs: -------------------------------------------------------------------------------- 1 | use crate::presentation::CommitmentProof; 2 | use crate::statement::CommitmentStatement; 3 | use crate::verifier::ProofVerifier; 4 | use crate::CredxResult; 5 | use blsful::inner_types::{G1Projective, Scalar}; 6 | use elliptic_curve::group::Curve; 7 | use merlin::Transcript; 8 | 9 | pub struct CommitmentVerifier<'a, 'b> { 10 | pub statement: &'a CommitmentStatement, 11 | pub proof: &'b CommitmentProof, 12 | pub message_proof: Scalar, 13 | } 14 | 15 | impl ProofVerifier for CommitmentVerifier<'_, '_> { 16 | fn add_challenge_contribution( 17 | &self, 18 | challenge: Scalar, 19 | transcript: &mut Transcript, 20 | ) -> CredxResult<()> { 21 | let blind_commitment = self.proof.commitment * -challenge 22 | + self.statement.message_generator * self.message_proof 23 | + self.statement.blinder_generator * self.proof.blinder_proof; 24 | 25 | transcript.append_message(b"", self.statement.id.as_bytes()); 26 | transcript.append_message( 27 | b"commitment", 28 | self.proof.commitment.to_affine().to_compressed().as_slice(), 29 | ); 30 | transcript.append_message( 31 | b"blind commitment", 32 | blind_commitment.to_affine().to_compressed().as_slice(), 33 | ); 34 | Ok(()) 35 | } 36 | 37 | fn verify(&self, _challenge: Scalar) -> CredxResult<()> { 38 | Ok(()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/verifier/equality.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::knox::short_group_sig_core::short_group_traits::{ 3 | ProofOfSignatureKnowledge, ShortGroupSignatureScheme, 4 | }; 5 | use crate::presentation::{PresentationProofs, PresentationSchema}; 6 | use crate::statement::{EqualityStatement, Statements}; 7 | use crate::verifier::ProofVerifier; 8 | use crate::CredxResult; 9 | use blsful::inner_types::Scalar; 10 | use indexmap::IndexMap; 11 | use merlin::Transcript; 12 | 13 | #[derive(Debug)] 14 | pub struct EqualityVerifier<'a, 'b, 'c, S: ShortGroupSignatureScheme> { 15 | pub statement: &'a EqualityStatement, 16 | pub schema: &'b PresentationSchema, 17 | pub proofs: &'c IndexMap>, 18 | } 19 | 20 | impl ProofVerifier for EqualityVerifier<'_, '_, '_, S> { 21 | fn add_challenge_contribution( 22 | &self, 23 | _challenge: Scalar, 24 | _transcript: &mut Transcript, 25 | ) -> CredxResult<()> { 26 | Ok(()) 27 | } 28 | 29 | fn verify(&self, _challenge: Scalar) -> CredxResult<()> { 30 | let mut messages = Vec::with_capacity(self.statement.ref_id_claim_index.len()); 31 | for (id, claim_idx) in &self.statement.ref_id_claim_index { 32 | let proof = self 33 | .proofs 34 | .get(id) 35 | .ok_or(Error::InvalidPresentationData(format!( 36 | "no presentation proof exists with id '{}': equality_verifier: {:?}", 37 | id, self 38 | )))?; 39 | match proof { 40 | PresentationProofs::Signature(s) => { 41 | match self 42 | .schema 43 | .statements 44 | .get(&s.id) 45 | .ok_or(Error::InvalidPresentationData(format!("no statement with id '{}' is found in the presentation schema: equality_verifier: {:?}", s.id, self)))? 46 | { 47 | Statements::Signature(sig_st) => { 48 | let disclosed_messages: Vec<(usize, Scalar)> = s 49 | .disclosed_messages 50 | .iter() 51 | .map(|(idx, scalar)| (*idx, *scalar)) 52 | .collect(); 53 | let hidden_messages = s 54 | .pok 55 | .get_hidden_message_proofs( 56 | &sig_st.issuer.verifying_key, 57 | disclosed_messages.as_slice(), 58 | )?; 59 | let hidden_message = hidden_messages 60 | .get(claim_idx) 61 | .ok_or(Error::InvalidPresentationData(format!("the referenced claim_idx '{}' from in the equality proof statement '{}' does not exist in the signature statement: equality_verifier: {:?}", claim_idx, id, self)))?; 62 | messages.push(*hidden_message); 63 | } 64 | _ => return Err(Error::InvalidPresentationData(format!("tried to use a non-signature statement reference in an equality proof: equality_verifier: {:?}", self))), 65 | }; 66 | } 67 | _ => return Err(Error::InvalidPresentationData(format!("tried to use a non-signature proof in an equality proof using reference statement id '{}': equality_verifier: {:?}", id, self))), 68 | } 69 | } 70 | let first = messages 71 | .first() 72 | .ok_or(Error::InvalidPresentationData(format!( 73 | "must have at least one claim in an equality proof: equality_verifier: {:?}", 74 | self 75 | )))?; 76 | for (i, m) in messages.iter().enumerate().skip(1) { 77 | if first != m { 78 | return Err(Error::InvalidPresentationData(format!("not all of the claims in the equality proof are equal, the first claim at index 0 is {} and the current claim at index {} is {}: equality_verifier: {:?}", hex::encode(first.to_be_bytes()), i, hex::encode(m.to_be_bytes()), self))); 79 | } 80 | } 81 | Ok(()) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/verifier/membership.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::knox::accumulator::vb20::{Element, ProofParams}; 3 | use crate::presentation::MembershipProof; 4 | use crate::statement::MembershipStatement; 5 | use crate::verifier::ProofVerifier; 6 | use crate::CredxResult; 7 | use blsful::inner_types::Scalar; 8 | use merlin::Transcript; 9 | 10 | pub struct MembershipVerifier<'a, 'b> { 11 | statement: &'a MembershipStatement, 12 | accumulator_proof: &'b MembershipProof, 13 | params: ProofParams, 14 | message_proof: Scalar, 15 | } 16 | 17 | impl<'a, 'b> MembershipVerifier<'a, 'b> { 18 | pub fn new( 19 | statement: &'a MembershipStatement, 20 | accumulator_proof: &'b MembershipProof, 21 | nonce: &[u8], 22 | message_proof: Scalar, 23 | ) -> Self { 24 | let params = ProofParams::new(statement.verification_key, Some(nonce)); 25 | Self { 26 | statement, 27 | accumulator_proof, 28 | params, 29 | message_proof, 30 | } 31 | } 32 | } 33 | 34 | impl ProofVerifier for MembershipVerifier<'_, '_> { 35 | fn add_challenge_contribution( 36 | &self, 37 | challenge: Scalar, 38 | transcript: &mut Transcript, 39 | ) -> CredxResult<()> { 40 | self.params.add_to_transcript(transcript); 41 | let finalized = self.accumulator_proof.proof.finalize( 42 | self.statement.accumulator, 43 | self.params, 44 | self.statement.verification_key, 45 | Element(challenge), 46 | ); 47 | finalized.get_bytes_for_challenge(transcript); 48 | Ok(()) 49 | } 50 | 51 | fn verify(&self, _challenge: Scalar) -> CredxResult<()> { 52 | if self.accumulator_proof.proof.s_y != self.message_proof { 53 | return Err(Error::InvalidPresentationData(format!( 54 | "membership claim proof '{}' does not match the signature's same claim proof '{}'", 55 | hex::encode(self.accumulator_proof.proof.s_y.to_be_bytes()), 56 | hex::encode(self.message_proof.to_be_bytes()) 57 | ))); 58 | } 59 | Ok(()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/verifier/revocation.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::knox::accumulator::vb20::{Element, ProofParams}; 3 | use crate::presentation::RevocationProof; 4 | use crate::statement::RevocationStatement; 5 | use crate::verifier::ProofVerifier; 6 | use crate::CredxResult; 7 | use blsful::inner_types::Scalar; 8 | use merlin::Transcript; 9 | 10 | pub struct RevocationVerifier<'a, 'b> { 11 | statement: &'a RevocationStatement, 12 | accumulator_proof: &'b RevocationProof, 13 | params: ProofParams, 14 | message_proof: Scalar, 15 | } 16 | 17 | impl<'a, 'b> RevocationVerifier<'a, 'b> { 18 | pub fn new( 19 | statement: &'a RevocationStatement, 20 | accumulator_proof: &'b RevocationProof, 21 | nonce: &[u8], 22 | message_proof: Scalar, 23 | ) -> Self { 24 | let params = ProofParams::new(statement.verification_key, Some(nonce)); 25 | Self { 26 | statement, 27 | accumulator_proof, 28 | params, 29 | message_proof, 30 | } 31 | } 32 | } 33 | 34 | impl ProofVerifier for RevocationVerifier<'_, '_> { 35 | fn add_challenge_contribution( 36 | &self, 37 | challenge: Scalar, 38 | transcript: &mut Transcript, 39 | ) -> CredxResult<()> { 40 | self.params.add_to_transcript(transcript); 41 | let finalized = self.accumulator_proof.proof.finalize( 42 | self.statement.accumulator, 43 | self.params, 44 | self.statement.verification_key, 45 | Element(challenge), 46 | ); 47 | finalized.get_bytes_for_challenge(transcript); 48 | Ok(()) 49 | } 50 | 51 | fn verify(&self, _challenge: Scalar) -> CredxResult<()> { 52 | if self.accumulator_proof.proof.s_y != self.message_proof { 53 | return Err(Error::InvalidPresentationData(format!( 54 | "revocation claim proof '{}' does not match the signature's same claim proof '{}'", 55 | hex::encode(self.accumulator_proof.proof.s_y.to_be_bytes()), 56 | hex::encode(self.message_proof.to_be_bytes()) 57 | ))); 58 | } 59 | Ok(()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/verifier/signature.rs: -------------------------------------------------------------------------------- 1 | use crate::knox::short_group_sig_core::short_group_traits::{ 2 | ProofOfSignatureKnowledge, ShortGroupSignatureScheme, 3 | }; 4 | use crate::presentation::SignatureProof; 5 | use crate::statement::SignatureStatement; 6 | use crate::verifier::ProofVerifier; 7 | use crate::CredxResult; 8 | use blsful::inner_types::Scalar; 9 | use merlin::Transcript; 10 | 11 | pub struct SignatureVerifier<'a, 'b, S: ShortGroupSignatureScheme> { 12 | statement: &'a SignatureStatement, 13 | signature_proof: &'b SignatureProof, 14 | disclosed_messages: Vec<(usize, Scalar)>, 15 | } 16 | 17 | impl<'a, 'b, S: ShortGroupSignatureScheme> SignatureVerifier<'a, 'b, S> { 18 | pub fn new( 19 | statement: &'a SignatureStatement, 20 | signature_proof: &'b SignatureProof, 21 | ) -> Self { 22 | let disclosed_messages: Vec<(usize, Scalar)> = signature_proof 23 | .disclosed_messages 24 | .iter() 25 | .map(|(idx, sc)| (*idx, *sc)) 26 | .collect(); 27 | Self { 28 | statement, 29 | signature_proof, 30 | disclosed_messages, 31 | } 32 | } 33 | } 34 | 35 | impl ProofVerifier for SignatureVerifier<'_, '_, S> { 36 | fn add_challenge_contribution( 37 | &self, 38 | challenge: Scalar, 39 | transcript: &mut Transcript, 40 | ) -> CredxResult<()> { 41 | self.signature_proof.pok.add_proof_contribution( 42 | &self.statement.issuer.verifying_key, 43 | &self.disclosed_messages, 44 | challenge, 45 | transcript, 46 | ); 47 | Ok(()) 48 | } 49 | 50 | fn verify(&self, challenge: Scalar) -> CredxResult<()> { 51 | self.signature_proof.pok.verify( 52 | &self.statement.issuer.verifying_key, 53 | &self.disclosed_messages, 54 | challenge, 55 | ) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/verifier/verifiable_encryption.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::presentation::VerifiableEncryptionProof; 3 | use crate::statement::VerifiableEncryptionStatement; 4 | use crate::verifier::ProofVerifier; 5 | use crate::CredxResult; 6 | use blsful::inner_types::{G1Projective, Scalar}; 7 | use bulletproofs::{BulletproofGens, PedersenGens}; 8 | use elliptic_curve::group::Curve; 9 | use merlin::Transcript; 10 | 11 | pub struct VerifiableEncryptionVerifier<'a, 'b> { 12 | pub statement: &'a VerifiableEncryptionStatement, 13 | pub proof: &'b VerifiableEncryptionProof, 14 | pub message_proof: Scalar, 15 | } 16 | 17 | impl ProofVerifier for VerifiableEncryptionVerifier<'_, '_> { 18 | fn add_challenge_contribution( 19 | &self, 20 | challenge: Scalar, 21 | transcript: &mut Transcript, 22 | ) -> CredxResult<()> { 23 | let challenge = -challenge; 24 | let r1 = self.proof.c1 * challenge + G1Projective::GENERATOR * self.proof.blinder_proof; 25 | let r2 = self.proof.c2 * challenge 26 | + self.statement.message_generator * self.message_proof 27 | + self.statement.encryption_key.0 * self.proof.blinder_proof; 28 | 29 | transcript.append_message(b"", self.statement.id.as_bytes()); 30 | transcript.append_message(b"c1", self.proof.c1.to_affine().to_compressed().as_slice()); 31 | transcript.append_message(b"c2", self.proof.c2.to_affine().to_compressed().as_slice()); 32 | transcript.append_message(b"r1", r1.to_affine().to_compressed().as_slice()); 33 | transcript.append_message(b"r2", r2.to_affine().to_compressed().as_slice()); 34 | 35 | if let Some(decryptable_proof) = self.proof.decryptable_scalar_proof.as_ref() { 36 | for i in 0..decryptable_proof.byte_proofs.len() { 37 | transcript.append_u64( 38 | b"verifiable_encryption_decryptable_message_byte_index", 39 | i as u64, 40 | ); 41 | 42 | transcript.append_message( 43 | b"byte_proof_c1", 44 | decryptable_proof.byte_ciphertext.c1[i] 45 | .to_compressed() 46 | .as_slice(), 47 | ); 48 | transcript.append_message( 49 | b"byte_proof_c2", 50 | decryptable_proof.byte_ciphertext.c2[i] 51 | .to_compressed() 52 | .as_slice(), 53 | ); 54 | let inner_r1 = decryptable_proof.byte_ciphertext.c1[i] * challenge 55 | + G1Projective::GENERATOR * decryptable_proof.byte_proofs[i].blinder; 56 | let inner_r2 = decryptable_proof.byte_ciphertext.c2[i] * challenge 57 | + self.statement.encryption_key.0 * decryptable_proof.byte_proofs[i].blinder 58 | + self.statement.message_generator * decryptable_proof.byte_proofs[i].message; 59 | 60 | transcript.append_message(b"byte_proof_r1", inner_r1.to_compressed().as_slice()); 61 | transcript.append_message(b"byte_proof_r2", inner_r2.to_compressed().as_slice()); 62 | } 63 | } 64 | 65 | Ok(()) 66 | } 67 | 68 | fn verify(&self, challenge: Scalar) -> CredxResult<()> { 69 | if let Some(decryptable_proof) = self.proof.decryptable_scalar_proof.as_ref() { 70 | let bp_gens = BulletproofGens::new(8, decryptable_proof.byte_proofs.len()); 71 | let pedersen_gen = PedersenGens { 72 | B: self.statement.message_generator, 73 | B_blinding: self.statement.encryption_key.0, 74 | }; 75 | 76 | let mut transcript = 77 | Transcript::new(b"PresentationEncryptionDecryption byte range proof"); 78 | transcript.append_message(b"challenge", &challenge.to_be_bytes()); 79 | // Prove each byte is in the range [0, 255] 80 | decryptable_proof 81 | .range_proof 82 | .verify_multiple( 83 | &bp_gens, 84 | &pedersen_gen, 85 | &mut transcript, 86 | &decryptable_proof.byte_ciphertext.c2, 87 | 8, 88 | ) 89 | .map_err(|_| Error::General("Range proof verification failed"))?; 90 | 91 | let shift = Scalar::from(256u16); 92 | let mut sum = G1Projective::IDENTITY; 93 | 94 | for c2 in &decryptable_proof.byte_ciphertext.c2 { 95 | sum *= shift; 96 | sum += c2; 97 | } 98 | 99 | if sum != self.proof.c2 { 100 | return Err(Error::General( 101 | "Sum of byte ciphertexts does not match the ciphertext", 102 | )); 103 | } 104 | } 105 | Ok(()) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/verifier/verifiable_encryption_decryption.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::prelude::VerifiableEncryptionDecryptionStatement; 3 | use crate::presentation::VerifiableEncryptionDecryptionProof; 4 | use crate::verifier::ProofVerifier; 5 | use crate::CredxResult; 6 | use blsful::inner_types::{G1Projective, Scalar}; 7 | use bulletproofs::{BulletproofGens, PedersenGens}; 8 | use merlin::Transcript; 9 | 10 | pub struct VerifiableEncryptionDecryptionVerifier<'a, 'b> { 11 | pub statement: &'a VerifiableEncryptionDecryptionStatement, 12 | pub proof: &'b VerifiableEncryptionDecryptionProof, 13 | pub message_proof: Scalar, 14 | } 15 | 16 | impl ProofVerifier for VerifiableEncryptionDecryptionVerifier<'_, '_> { 17 | fn add_challenge_contribution( 18 | &self, 19 | challenge: Scalar, 20 | transcript: &mut Transcript, 21 | ) -> CredxResult<()> { 22 | let challenge = -challenge; 23 | let r1 = self.proof.c1 * challenge + G1Projective::GENERATOR * self.proof.blinder_proof; 24 | let r2 = self.proof.c2 * challenge 25 | + self.statement.message_generator * self.message_proof 26 | + self.statement.encryption_key.0 * self.proof.blinder_proof; 27 | 28 | transcript.append_message(b"", self.statement.id.as_bytes()); 29 | transcript.append_message(b"c1", self.proof.c1.to_compressed().as_slice()); 30 | transcript.append_message(b"c2", self.proof.c2.to_compressed().as_slice()); 31 | transcript.append_message(b"r1", r1.to_compressed().as_slice()); 32 | transcript.append_message(b"r2", r2.to_compressed().as_slice()); 33 | 34 | for i in 0..self.proof.byte_proofs.len() { 35 | transcript.append_u64( 36 | b"verifiable_encryption_decryption_message_byte_index", 37 | i as u64, 38 | ); 39 | 40 | transcript.append_message( 41 | b"byte_proof_c1", 42 | self.proof.byte_ciphertext.c1[i].to_compressed().as_slice(), 43 | ); 44 | transcript.append_message( 45 | b"byte_proof_c2", 46 | self.proof.byte_ciphertext.c2[i].to_compressed().as_slice(), 47 | ); 48 | let inner_r1 = self.proof.byte_ciphertext.c1[i] * challenge 49 | + G1Projective::GENERATOR * self.proof.byte_proofs[i].blinder; 50 | let inner_r2 = self.proof.byte_ciphertext.c2[i] * challenge 51 | + self.statement.encryption_key.0 * self.proof.byte_proofs[i].blinder 52 | + self.statement.message_generator * self.proof.byte_proofs[i].message; 53 | 54 | transcript.append_message(b"byte_proof_r1", inner_r1.to_compressed().as_slice()); 55 | transcript.append_message(b"byte_proof_r2", inner_r2.to_compressed().as_slice()); 56 | } 57 | transcript.append_message(b"arbitrary_data_ciphertext", &self.proof.ciphertext); 58 | 59 | Ok(()) 60 | } 61 | 62 | fn verify(&self, challenge: Scalar) -> CredxResult<()> { 63 | let bp_gens = BulletproofGens::new(8, self.proof.byte_proofs.len()); 64 | let pedersen_gen = PedersenGens { 65 | B: self.statement.message_generator, 66 | B_blinding: self.statement.encryption_key.0, 67 | }; 68 | 69 | let mut transcript = Transcript::new(b"PresentationEncryptionDecryption byte range proof"); 70 | transcript.append_message(b"challenge", &challenge.to_be_bytes()); 71 | // Prove each byte is in the range [0, 255] 72 | self.proof 73 | .range_proof 74 | .verify_multiple( 75 | &bp_gens, 76 | &pedersen_gen, 77 | &mut transcript, 78 | &self.proof.byte_ciphertext.c2, 79 | 8, 80 | ) 81 | .map_err(|_| Error::General("Range proof verification failed"))?; 82 | 83 | let shift = Scalar::from(256u16); 84 | let mut sum = G1Projective::IDENTITY; 85 | 86 | for c2 in &self.proof.byte_ciphertext.c2 { 87 | sum *= shift; 88 | sum += c2; 89 | } 90 | 91 | if sum != self.proof.c2 { 92 | return Err(Error::General( 93 | "Sum of byte ciphertexts does not match the ciphertext", 94 | )); 95 | } 96 | Ok(()) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tests/out-of-range-panic.rs: -------------------------------------------------------------------------------- 1 | use blsful::inner_types::*; 2 | use credx::claim::{ClaimType, NumberClaim, RevocationClaim}; 3 | use credx::credential::{ClaimSchema, CredentialSchema}; 4 | use credx::knox::bbs::BbsScheme; 5 | use credx::prelude::Issuer; 6 | use credx::presentation::{Presentation, PresentationSchema}; 7 | use credx::statement::{ 8 | CommitmentStatement, RangeStatement, RevocationStatement, SignatureStatement, 9 | }; 10 | use credx::{random_string, CredxResult}; 11 | use indexmap::indexmap; 12 | use maplit::btreeset; 13 | use rand::thread_rng; 14 | use rand_core::RngCore; 15 | 16 | fn setup() { 17 | let _ = env_logger::builder().is_test(true).try_init(); 18 | } 19 | 20 | #[test] 21 | fn out_of_range_panic() { 22 | setup(); 23 | let res = test_out_of_range_panic(); 24 | assert!(res.is_err()); 25 | } 26 | 27 | #[allow(unused_variables)] 28 | fn test_out_of_range_panic() -> CredxResult<()> { 29 | const LABEL: &str = "Test Schema"; 30 | const DESCRIPTION: &str = "This is a test presentation schema"; 31 | const CRED_ID: &str = "91742856-6eda-45fb-a709-d22ebb5ec8a5"; 32 | let schema_claims = [ 33 | ClaimSchema { 34 | claim_type: ClaimType::Revocation, 35 | label: "identifier".to_string(), 36 | print_friendly: false, 37 | validators: vec![], 38 | }, 39 | ClaimSchema { 40 | claim_type: ClaimType::Number, 41 | label: "age".to_string(), 42 | print_friendly: true, 43 | validators: vec![], 44 | }, 45 | ]; 46 | let cred_schema = CredentialSchema::new(Some(LABEL), Some(DESCRIPTION), &[], &schema_claims)?; 47 | let (issuer_public, mut issuer) = Issuer::::new(&cred_schema); 48 | let credential = issuer.sign_credential(&[ 49 | RevocationClaim::from(CRED_ID).into(), 50 | NumberClaim::from(5).into(), 51 | ])?; 52 | 53 | // presentation/proof creation 54 | 55 | let sig_st = SignatureStatement { 56 | disclosed: btreeset! {}, 57 | id: random_string(16, rand::thread_rng()), 58 | issuer: issuer_public.clone(), 59 | }; 60 | let acc_st = RevocationStatement { 61 | id: random_string(16, rand::thread_rng()), 62 | reference_id: sig_st.id.clone(), 63 | accumulator: issuer_public.revocation_registry, 64 | verification_key: issuer_public.revocation_verifying_key, 65 | claim: 0, 66 | }; 67 | let comm_st = CommitmentStatement { 68 | id: random_string(16, rand::thread_rng()), 69 | reference_id: sig_st.id.clone(), 70 | message_generator: G1Projective::hash::>( 71 | b"message generator", 72 | b"BLS12381G1_XMD:SHA-256_SSWU_RO_", 73 | ), 74 | blinder_generator: G1Projective::hash::>( 75 | b"blinder generator", 76 | b"BLS12381G1_XMD:SHA-256_SSWU_RO_", 77 | ), 78 | claim: 1, 79 | }; 80 | let range_st = RangeStatement { 81 | id: random_string(16, thread_rng()), 82 | reference_id: comm_st.id.clone(), 83 | signature_id: sig_st.id.clone(), 84 | claim: 1, 85 | lower: Some(0), 86 | upper: Some(3), // SIGNED VALUE OF 5 IS OUT OF THE REQUESTED RANGE 87 | }; 88 | 89 | let credentials = indexmap! { sig_st.id.clone() => credential.credential.into() }; 90 | let presentation_schema = PresentationSchema::new(&[ 91 | sig_st.into(), 92 | acc_st.into(), 93 | comm_st.into(), 94 | range_st.into(), 95 | ]); 96 | let mut nonce = [0u8; 16]; 97 | thread_rng().fill_bytes(&mut nonce); 98 | Presentation::create(&credentials, &presentation_schema, &nonce)?; 99 | 100 | Ok(()) 101 | } 102 | -------------------------------------------------------------------------------- /tests/range.rs: -------------------------------------------------------------------------------- 1 | use blsful::inner_types::*; 2 | use credx::claim::{ClaimType, HashedClaim, NumberClaim, RevocationClaim}; 3 | use credx::credential::{ClaimSchema, CredentialSchema}; 4 | use credx::issuer::Issuer; 5 | use credx::knox::bbs::BbsScheme; 6 | use credx::presentation::{Presentation, PresentationSchema}; 7 | use credx::statement::{CommitmentStatement, RangeStatement, SignatureStatement}; 8 | use credx::{random_string, CredxResult}; 9 | use indexmap::indexmap; 10 | use maplit::btreeset; 11 | use rand::{thread_rng, RngCore}; 12 | 13 | macro_rules! range_test_with { 14 | ($name: ident, $val:expr, $lower:expr, $upper:expr, $expected_to_fail:expr) => { 15 | #[test] 16 | fn $name() -> CredxResult<()> { 17 | let _ = env_logger::builder().is_test(true).try_init(); 18 | let res = test_range_proof_works($val, $lower, $upper, $expected_to_fail); 19 | assert!(res.is_ok(), "{:?}", res); 20 | Ok(()) 21 | } 22 | }; 23 | } 24 | 25 | // These tests are expected to pass (expected_to_fail argument is false) 26 | range_test_with!(in_range_from_flow_test, 30303, Some(0), Some(44829), false); 27 | range_test_with!(in_range_min, 0, Some(0), Some(isize::MAX), false); 28 | range_test_with!( 29 | in_range_max_explicit, 30 | isize::MAX, 31 | Some(0), 32 | Some(isize::MAX), 33 | false 34 | ); 35 | range_test_with!(in_range_max_implicit, isize::MAX, Some(0), None, false); 36 | 37 | // These tests are expected to fail (expected_to_fail argument is true) 38 | range_test_with!(out_of_range_below, 0, Some(1), Some(isize::MAX), true); 39 | range_test_with!(out_of_range_above, 1001, Some(0), Some(1000), true); 40 | 41 | #[test] 42 | fn test_out_of_range_above() { 43 | assert!(test_range_proof_works(1000, Some(0), Some(1000), false).is_ok()); 44 | } 45 | 46 | fn test_range_proof_works( 47 | val: isize, 48 | lower: Option, 49 | upper: Option, 50 | expected_to_fail: bool, 51 | ) -> Result<(), String> { 52 | const LABEL: &str = "Test Schema"; 53 | const DESCRIPTION: &str = "This is a test presentation schema"; 54 | const CRED_ID: &str = "91742856-6eda-45fb-a709-d22ebb5ec8a5"; 55 | let schema_claims = [ 56 | ClaimSchema { 57 | claim_type: ClaimType::Revocation, 58 | label: "identifier".to_string(), 59 | print_friendly: false, 60 | validators: vec![], 61 | }, 62 | ClaimSchema { 63 | claim_type: ClaimType::Hashed, 64 | label: "name".to_string(), 65 | print_friendly: true, 66 | validators: vec![], 67 | }, 68 | ClaimSchema { 69 | claim_type: ClaimType::Hashed, 70 | label: "address".to_string(), 71 | print_friendly: true, 72 | validators: vec![], 73 | }, 74 | ClaimSchema { 75 | claim_type: ClaimType::Number, 76 | label: "age".to_string(), 77 | print_friendly: true, 78 | validators: vec![], 79 | }, 80 | ]; 81 | let cred_schema = CredentialSchema::new(Some(LABEL), Some(DESCRIPTION), &[], &schema_claims) 82 | .map_err(|e| format!("unexpected, CredentialSchema::new failed {e:?}"))?; 83 | 84 | let (issuer_public, mut issuer) = Issuer::::new(&cred_schema); 85 | 86 | let credential = issuer 87 | .sign_credential(&[ 88 | RevocationClaim::from(CRED_ID).into(), 89 | HashedClaim::from("John Doe").into(), 90 | HashedClaim::from("P Sherman 42 Wallaby Way Sydney").into(), 91 | NumberClaim::from(val).into(), 92 | ]) 93 | .map_err(|e| format!("unexpected, sign credential failed {e:?}"))?; 94 | 95 | let sig_st = SignatureStatement { 96 | disclosed: btreeset! {"name".to_string()}, 97 | id: random_string(16, rand::thread_rng()), 98 | issuer: issuer_public.clone(), 99 | }; 100 | let comm_st = CommitmentStatement { 101 | id: random_string(16, rand::thread_rng()), 102 | reference_id: sig_st.id.clone(), 103 | message_generator: G1Projective::hash::>( 104 | b"message generator", 105 | b"BLS12381G1_XMD:SHA-256_SSWU_RO_", 106 | ), 107 | blinder_generator: G1Projective::hash::>( 108 | b"blinder generator", 109 | b"BLS12381G1_XMD:SHA-256_SSWU_RO_", 110 | ), 111 | claim: 3, 112 | }; 113 | let range_st = RangeStatement { 114 | id: random_string(16, thread_rng()), 115 | reference_id: comm_st.id.clone(), 116 | signature_id: sig_st.id.clone(), 117 | claim: 3, 118 | lower, 119 | upper, 120 | }; 121 | 122 | let mut nonce = [0u8; 16]; 123 | thread_rng().fill_bytes(&mut nonce); 124 | 125 | let credentials = indexmap! { sig_st.id.clone() => credential.credential.into() }; 126 | let presentation_schema = 127 | PresentationSchema::new(&[sig_st.into(), comm_st.into(), range_st.into()]); 128 | match Presentation::create(&credentials, &presentation_schema, &nonce) { 129 | Err(e) => { 130 | if expected_to_fail { 131 | Ok(()) 132 | } else { 133 | Err(format!("create presentation failed: {e:?}")) 134 | } 135 | } 136 | Ok(presentation) => match presentation.verify(&presentation_schema, &nonce) { 137 | Err(e) => { 138 | if expected_to_fail { 139 | Ok(()) 140 | } else { 141 | Err(format!("verify presentation failed: {e:?}")) 142 | } 143 | } 144 | Ok(_) => { 145 | if expected_to_fail { 146 | Err("verification passed, but was expected to fail".to_string()) 147 | } else { 148 | Ok(()) 149 | } 150 | } 151 | }, 152 | } 153 | } 154 | --------------------------------------------------------------------------------