├── .github ├── settings.yml └── workflows │ ├── build.yml │ └── scorcard.yml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── SECURITY.md ├── build-universal.sh ├── build-xcframework.sh ├── docs └── design │ └── w3c │ └── w3c-representation.md ├── include ├── README.md ├── cbindgen.toml └── libanoncreds.h ├── src ├── data_types │ ├── cred_def.rs │ ├── cred_offer.rs │ ├── cred_request.rs │ ├── credential.rs │ ├── issuer_id.rs │ ├── link_secret.rs │ ├── macros.rs │ ├── mod.rs │ ├── nonce.rs │ ├── pres_request.rs │ ├── presentation.rs │ ├── rev_reg.rs │ ├── rev_reg_def.rs │ ├── rev_status_list.rs │ ├── schema.rs │ └── w3c │ │ ├── constants.rs │ │ ├── context.rs │ │ ├── credential.rs │ │ ├── credential_attributes.rs │ │ ├── format.rs │ │ ├── mod.rs │ │ ├── one_or_many.rs │ │ ├── presentation.rs │ │ ├── proof.rs │ │ ├── sample_credential.json │ │ ├── sample_presentation.json │ │ └── uri.rs ├── error.rs ├── ffi │ ├── cred_def.rs │ ├── cred_offer.rs │ ├── cred_req.rs │ ├── credential.rs │ ├── error.rs │ ├── link_secret.rs │ ├── macros.rs │ ├── mod.rs │ ├── object.rs │ ├── pres_req.rs │ ├── presentation.rs │ ├── revocation.rs │ ├── schema.rs │ ├── util.rs │ └── w3c │ │ ├── credential.rs │ │ ├── mod.rs │ │ └── presentation.rs ├── lib.rs ├── macros.rs ├── services │ ├── helpers.rs │ ├── issuer.rs │ ├── mod.rs │ ├── prover.rs │ ├── tails.rs │ ├── types.rs │ ├── verifier.rs │ └── w3c │ │ ├── credential_conversion.rs │ │ ├── helpers.rs │ │ ├── issuer.rs │ │ ├── mod.rs │ │ ├── prover.rs │ │ ├── types.rs │ │ └── verifier.rs └── utils │ ├── base58.rs │ ├── base64.rs │ ├── error.rs │ ├── hash.rs │ ├── macros.rs │ ├── mod.rs │ ├── msg_pack.rs │ ├── query.rs │ └── validation.rs ├── tests ├── anoncreds_demos.rs ├── multiple-credentials.rs └── utils │ ├── fixtures.rs │ ├── mock.rs │ ├── mod.rs │ └── storage.rs └── wrappers └── python ├── .gitignore ├── README.md ├── anoncreds ├── __init__.py ├── bindings.py ├── error.py ├── types.py └── version.py ├── demo ├── test.py └── w3c_test.py ├── pyproject.toml ├── setup.cfg └── setup.py /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: CC-BY-4.0 3 | # 4 | 5 | repository: 6 | name: anoncreds-rs 7 | description: anoncreds-rs 8 | homepage: https://wiki.hyperledger.org/display/anoncreds 9 | default_branch: main 10 | has_downloads: false 11 | has_issues: true 12 | has_projects: false 13 | has_wiki: false 14 | archived: false 15 | private: false 16 | allow_squash_merge: true 17 | allow_merge_commit: true 18 | allow_rebase_merge: true 19 | -------------------------------------------------------------------------------- /.github/workflows/scorcard.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | # This workflow uses actions that are not certified by GitHub. They are provided 4 | # by a third-party and are governed by separate terms of service, privacy 5 | # policy, and support documentation. 6 | 7 | name: OpenSSF Scorecard supply-chain security 8 | on: 9 | workflow_dispatch: 10 | # For Branch-Protection check. Only the default branch is supported. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 12 | branch_protection_rule: 13 | # To guarantee Maintained check is occasionally updated. See 14 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 15 | schedule: 16 | - cron: '17 21 * * 3' 17 | push: 18 | branches: [ "main" ] 19 | 20 | # Declare default permissions as read only. 21 | permissions: read-all 22 | 23 | jobs: 24 | analysis: 25 | name: Scorecard analysis 26 | runs-on: ubuntu-latest 27 | permissions: 28 | # Needed to upload the results to code-scanning dashboard. 29 | security-events: write 30 | # Needed to publish results and get a badge (see publish_results below). 31 | id-token: write 32 | # Uncomment the permissions below if installing in a private repository. 33 | # contents: read 34 | # actions: read 35 | 36 | steps: 37 | - name: "Checkout code" 38 | uses: actions/checkout@v4 # was v4.1.1 - b4ffde65f46336ab88eb53be808477a3936bae11 39 | with: 40 | persist-credentials: false 41 | 42 | - name: "Run analysis" 43 | uses: ossf/scorecard-action@v2.4.0 # was v2.3.1 - 0864cf19026789058feabb7e87baa5f140aac736 44 | with: 45 | results_file: results.sarif 46 | results_format: sarif 47 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 48 | # - you want to enable the Branch-Protection check on a *public* repository, or 49 | # - you are installing Scorecard on a *private* repository 50 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. 51 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 52 | 53 | # Public repositories: 54 | # - Publish results to OpenSSF REST API for easy access by consumers 55 | # - Allows the repository to include the Scorecard badge. 56 | # - See https://github.com/ossf/scorecard-action#publishing-results. 57 | # For private repositories: 58 | # - `publish_results` will always be set to `false`, regardless 59 | # of the value entered here. 60 | publish_results: true 61 | 62 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 63 | # format to the repository Actions tab. 64 | - name: "Upload artifact" 65 | uses: actions/upload-artifact@v4 # was v3.pre.node20 97a0fba1372883ab732affbe8f94b823f91727db 66 | with: 67 | name: SARIF file 68 | path: results.sarif 69 | retention-days: 5 70 | 71 | # Upload the results to GitHub's code scanning dashboard (optional). 72 | # Commenting out will disable upload of results to your repo's Code Scanning dashboard 73 | - name: "Upload to code-scanning" 74 | uses: github/codeql-action/upload-sarif@v3 # was v3.24.9 - 1b1aada464948af03b950897e5eb522f92603cc2 75 | with: 76 | sarif_file: results.sarif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | target 3 | out 4 | Cargo.lock 5 | .DS_Store 6 | .tmp 7 | *.so 8 | *.tgz 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute 2 | 3 | You are encouraged to contribute to the repository by **forking and submitting a pull request**. 4 | 5 | For significant changes, please open an issue first to discuss the proposed changes to avoid re-work. 6 | 7 | (If you are new to GitHub, you might start with a [basic tutorial](https://help.github.com/articles/set-up-git) and check out a more detailed guide to [pull requests](https://help.github.com/articles/using-pull-requests/).) 8 | 9 | Pull requests will be evaluated by the repository guardians on a schedule and if deemed beneficial will be committed to the master. Pull requests should have a descriptive name and include an summary of all changes made in the pull request description. 10 | 11 | If you would like to propose a significant change, please open an issue first to discuss the work with the community. 12 | 13 | All contributors retain the original copyright to their stuff, but by contributing to this project, you grant a world-wide, royalty-free, perpetual, irrevocable, non-exclusive, transferable license to all users **under the terms of the license under which this project is distributed.** 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anoncreds" 3 | version = "0.2.0" 4 | authors = [ 5 | "Hyperledger AnonCreds Contributors ", 6 | ] 7 | description = "Verifiable credential issuance and presentation for Hyperledger AnonCreds (https://www.hyperledger.org/projects), which provides a foundation for self-sovereign identity." 8 | edition = "2021" 9 | license = "Apache-2.0" 10 | readme = "../README.md" 11 | repository = "https://github.com/hyperledger/anoncreds-rs/" 12 | categories = ["authentication", "cryptography"] 13 | keywords = ["hyperledger", "ssi", "verifiable", "credentials"] 14 | rust-version = "1.58" 15 | 16 | [lib] 17 | name = "anoncreds" 18 | path = "src/lib.rs" 19 | crate-type = ["staticlib", "rlib", "cdylib"] 20 | 21 | [features] 22 | default = ["ffi", "logger", "zeroize", "w3c"] 23 | ffi = ["dep:ffi-support"] 24 | logger = ["dep:env_logger"] 25 | vendored = ["anoncreds-clsignatures/openssl_vendored"] 26 | w3c = ["dep:base64", "dep:chrono", "dep:rmp-serde"] 27 | zeroize = ["dep:zeroize"] 28 | 29 | [dependencies] 30 | anoncreds-clsignatures = "0.3.2" 31 | base64 = { version = "0.21.5", optional = true } 32 | bitvec = { version = "1.0.1", features = ["serde"] } 33 | bs58 = "0.5.0" 34 | chrono = { version = "0.4.31", optional = true, features = ["serde"] } 35 | env_logger = { version = "0.9.3", optional = true } 36 | ffi-support = { version = "0.4.0", optional = true } 37 | log = "0.4.17" 38 | once_cell = "1.17.1" 39 | rand = "0.8.5" 40 | regex = "1.7.1" 41 | rmp-serde = { version = "1.1.2", optional = true } 42 | serde = { version = "1.0.155", features = ["derive"] } 43 | serde_json = { version = "1.0.94", features = ["raw_value"] } 44 | sha2 = "0.10.6" 45 | thiserror = "1.0.39" 46 | zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] } 47 | 48 | [dev-dependencies] 49 | rstest = "0.18.2" 50 | 51 | [profile.release] 52 | codegen-units = 1 53 | lto = true 54 | strip = "debuginfo" 55 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | 4 | 5 | ## Active Maintainers 6 | 7 | 8 | 9 | | Name | Github | LFID | 10 | | ---------------- | ---------------- | ---------------- | 11 | | Andrew Whitehead | andrewwhitehead | cywolf | 12 | | Berend Sliedrecht| berendsliedrecht | beri14 | 13 | | Darko Kulic | dkulic | dkulic | 14 | | Stephen Curran | swcurran | swcurran | 15 | | Timo Glastra | TimoGlastra | TimoGlastra | 16 | | Wade Barnes | WadeBarnes | WadeBarnes | 17 | 18 | ## Becoming a Maintainer 19 | 20 | AnonCreds welcomes community contribution. 21 | Each community member may progress to become a maintainer. 22 | 23 | How to become a maintainer: 24 | 25 | - Contribute significantly to the code in this repository. 26 | 27 | ### Maintainers contribution requirement 28 | 29 | The requirement to be able to be proposed as a maintainer is: 30 | 31 | - 5 significant changes on code have been authored in this repos by the proposed maintainer and accepted (merged PRs). 32 | 33 | ### Maintainers approval process 34 | 35 | The following steps must occur for a contributor to be "upgraded" as a maintainer: 36 | 37 | - The proposed maintainer has the sponsorship of at least one other maintainer. 38 | - This sponsoring maintainer will create a proposal PR modifying the list of 39 | maintainers. (see [proposal PR template](#proposal-pr-template).) 40 | - The proposed maintainer accepts the nomination and expresses a willingness 41 | to be a long-term (more than 6 month) committer by adding a comment in the proposal PR. 42 | - The PR will be communicated in all appropriate communication channels 43 | including at least [anoncreds-maintainers channel on Hyperledger Discord](https://discord.gg/hyperledger), 44 | the [mailing list](https://lists.hyperledger.org/g/anoncreds) 45 | and any maintainer/community call. 46 | - Approval by at least 3 current maintainers within two weeks of the proposal or 47 | an absolute majority (half the total + 1) of current maintainers. 48 | - Maintainers will vote by approving the proposal PR. 49 | - No veto raised by another maintainer within the voting timeframe. 50 | - All vetoes must be accompanied by a public explanation as a comment in the 51 | proposal PR. 52 | - A veto can be retracted, in that case the voting timeframe is reset and all approvals are removed. 53 | - It is bad form to veto, retract, and veto again. 54 | 55 | The proposed maintainer becomes a maintainer either: 56 | 57 | - when two weeks have passed without veto since the third approval of the proposal PR, 58 | - or an absolute majority of maintainers approved the proposal PR. 59 | 60 | In either case, no maintainer raised and stood by a veto. 61 | 62 | ## Removing Maintainers 63 | 64 | Being a maintainer is not a status symbol or a title to be maintained indefinitely. 65 | 66 | It will occasionally be necessary and appropriate to move a maintainer to emeritus status. 67 | 68 | This can occur in the following situations: 69 | 70 | - Resignation of a maintainer. 71 | - Violation of the Code of Conduct warranting removal. 72 | - Inactivity. 73 | - A general measure of inactivity will be no commits or code review comments 74 | for two reporting quarters, although this will not be strictly enforced if 75 | the maintainer expresses a reasonable intent to continue contributing. 76 | - Reasonable exceptions to inactivity will be granted for known long term 77 | leave such as parental leave and medical leave. 78 | - Other unspecified circumstances. 79 | 80 | As for adding a maintainer, the record and governance process for moving a 81 | maintainer to emeritus status is recorded using review approval in the PR making that change. 82 | 83 | Returning to active status from emeritus status uses the same steps as adding a 84 | new maintainer. 85 | 86 | Note that the emeritus maintainer always already has the required significant contributions. 87 | There is no contribution prescription delay. 88 | 89 | ## Proposal PR template 90 | 91 | ```markdown 92 | I propose to add [maintainer github handle] as a AnonCreds project maintainer. 93 | 94 | [maintainer github handle] contributed with many high quality commits: 95 | 96 | - [list significant achievements] 97 | 98 | Here are [their past contributions on AnonCreds project](https://github.com/hyperledger/anoncreds-rs/commits?author=[user github handle]). 99 | 100 | Voting ends two weeks from today. 101 | 102 | For more information on this process see the Becoming a Maintainer section in the MAINTAINERS.md file. 103 | ``` 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # anoncreds-rs 2 | 3 | Rust library and reference implementation of the [Anoncreds V1.0 4 | specification](https://hyperledger.github.io/anoncreds-spec/). 5 | 6 |

7 | 8 | The AnonCreds (Anonymous Credentials) specification is based on the open source 9 | verifiable credential implementation of AnonCreds that has been in use since 10 | 2017, initially as part of the Hyperledger Indy open source project and now in 11 | the Hyperledger AnonCreds project. The extensive use of AnonCreds around the 12 | world has made it a de facto standard for ZKP-based verifiable credentials, and 13 | this specification is the formalization of that implementation. 14 | 15 | ## Library 16 | 17 | Anoncreds-rs exposes three main parts: [`issuer`](./src/services/issuer.rs), 18 | [`prover`](./src/services/prover.rs) and 19 | [`verifier`](./src/services/verifier.rs). 20 | 21 | ### Issuer 22 | 23 | - Create a [schema](https://hyperledger.github.io/anoncreds-spec/#schema-publisher-publish-schema-object) 24 | - Create a [credential definition](https://hyperledger.github.io/anoncreds-spec/#issuer-create-and-publish-credential-definition-object) 25 | - Create a [revocation registry definition](https://hyperledger.github.io/anoncreds-spec/#issuer-create-and-publish-revocation-registry-objects) 26 | - Create a [revocation status list](https://hyperledger.github.io/anoncreds-spec/#publishing-the-initial-initial-revocation-status-list-object) 27 | - Update a [revocation status list](https://hyperledger.github.io/anoncreds-spec/#publishing-the-initial-initial-revocation-status-list-object) 28 | - Update a [revocation status list](https://hyperledger.github.io/anoncreds-spec/#publishing-the-initial-initial-revocation-status-list-object)'s timestamp 29 | - Create a [credential offer](https://hyperledger.github.io/anoncreds-spec/#credential-offer) 30 | - Create a [credential](https://hyperledger.github.io/anoncreds-spec/#issue-credential) 31 | 32 | ### Prover / Holder 33 | 34 | - Create a [credential request](https://hyperledger.github.io/anoncreds-spec/#credential-request) 35 | - Process an incoming [credential](https://hyperledger.github.io/anoncreds-spec/#receiving-a-credential) 36 | - Create a [presentation](https://hyperledger.github.io/anoncreds-spec/#generate-presentation) 37 | - Create, and update, a revocation state 38 | - Create, and update, a revocation state with a witness 39 | 40 | ### Verifier 41 | 42 | - [Verify a presentation](https://hyperledger.github.io/anoncreds-spec/#verify-presentation) 43 | - generate a nonce 44 | 45 | ## Wrappers 46 | 47 | Anoncreds is, soon, available as a standalone library in Rust, but also via wrappers. 48 | 49 | | Language | Location | Status | 50 | | ------------ | ------------------------------------------------------------------------------------------------------------------- | ------ | 51 | | Node.js | [javascript](https://github.com/hyperledger/anoncreds-wrapper-javascript/tree/main/packages/anoncreds-nodejs) | ✅ | 52 | | React Native | [javascript](https://github.com/hyperledger/anoncreds-wrapper-javascript/tree/main/packages/anoncreds-react-native) | ✅ | 53 | | Python | [python](https://github.com/hyperledger/anoncreds-rs/tree/main/wrappers/python) | ✅ | 54 | 55 | ## Credit 56 | 57 | The initial implementation of `anoncreds-rs` is derived from `indy-shared-rs` 58 | that was developed by the Verifiable Organizations Network (VON) team based at 59 | the Province of British Columbia, and derives largely from the implementations 60 | within [Hyperledger Indy-SDK](https://github.com/hyperledger/indy-sdk). To 61 | learn more about VON and what's happening with decentralized identity in 62 | British Columbia, please go to [https://vonx.io](https://vonx.io). 63 | 64 | ## Contributing 65 | 66 | Pull requests are welcome! Please read our [contributions 67 | guide](https://github.com/hyperledger/anoncreds-rs/blob/main/CONTRIBUTING.md) 68 | and submit your PRs. We enforce [developer certificate of 69 | origin](https://developercertificate.org/) (DCO) commit signing. See guidance 70 | [here](https://github.com/apps/dco). 71 | 72 | We also welcome issues submitted about problems you encounter in using 73 | `anoncreds-rs` or any of the wrappers. 74 | 75 | ## License 76 | 77 | [Apache License Version 78 | 2.0](https://github.com/hyperledger/anoncreds-rs/blob/main/LICENSE) 79 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Hyperledger Security Policy 2 | 3 | ## Reporting a Security Bug 4 | 5 | If you think you have discovered a security issue in any of the Hyperledger projects, we'd love to hear from you. We will take all security bugs seriously and if confirmed upon investigation we will patch it within a reasonable amount of time and release a public security bulletin discussing the impact and credit the discoverer. 6 | 7 | There are two ways to report a security bug. The easiest is to email a description of the flaw and any related information (e.g. reproduction steps, version) to [security at hyperledger dot org](mailto:security@hyperledger.org). 8 | 9 | The other way is to file a confidential security bug in our [JIRA bug tracking system](https://jira.hyperledger.org). Be sure to set the “Security Level” to “Security issue”. 10 | 11 | The process by which the Hyperledger Security Team handles security bugs is documented further in our [Defect Response page](https://wiki.hyperledger.org/display/HYP/Defect+Response) on our [wiki](https://wiki.hyperledger.org). 12 | -------------------------------------------------------------------------------- /build-universal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # NOTE: 4 | # MacOS universal build currently requires MacOS 11 (Big Sur) for the appropriate SDK, 5 | # and `sudo xcode-select --install` must be run to install the command line utilities. 6 | # Rust's `beta` channel must be installed because aarch64 is still a tier-2 target: 7 | # `rustup toolchain install beta`. 8 | 9 | RUSTUP=${RUSTUP:-`command -v rustup`} 10 | PROJECT=anoncreds 11 | 12 | if [ ! -x "$RUSTUP" ]; then 13 | echo "rustup command not found: it can be obtained from https://rustup.rs/" 14 | exit 1 15 | fi 16 | 17 | TOOLCHAIN=`$RUSTUP default` 18 | TARGET_DIR="${TARGET_DIR-./target/darwin-universal}" 19 | 20 | if [ -z "$BUILD_TOOLCHAIN" ]; then 21 | BUILD_TOOLCHAIN=${TOOLCHAIN%%-*} 22 | if [ -z "$BUILD_TOOLCHAIN" ]; then 23 | echo "Error: Could not determine default Rust toolchain" 24 | exit 1 25 | fi 26 | fi 27 | 28 | MACOS_UNIVERSAL_TARGETS="aarch64-apple-darwin x86_64-apple-darwin" 29 | 30 | # Fail on any execution errors 31 | set -e 32 | 33 | INSTALLED_TARGETS=`$RUSTUP +$BUILD_TOOLCHAIN target list --installed` 34 | # Install target(s) as needed 35 | echo "Checking install targets for MacOS universal build .." 36 | for target in $MACOS_UNIVERSAL_TARGETS; do 37 | if ! `echo "$INSTALLED_TARGETS" | grep -q $target`; then 38 | $RUSTUP +$BUILD_TOOLCHAIN target add $target 39 | fi 40 | done 41 | 42 | MAJOR_VER=`sw_vers | grep ProductVersion | cut -f 2 | cut -f 1 -d .` 43 | if [ "$MAJOR_VER" -lt 11 ]; then 44 | echo "MacOS universal build requires OS 11 (Big Sur) or newer" 45 | TARGET= 46 | fi 47 | 48 | # Build both targets and combine them into a universal library with `lipo` 49 | TARGET_LIBS= 50 | for target in $MACOS_UNIVERSAL_TARGETS; do 51 | echo "Building $PROJECT for toolchain '$BUILD_TOOLCHAIN', target '$target'.." 52 | $RUSTUP run $BUILD_TOOLCHAIN cargo build --lib --features=vendored --release --target $target 53 | TARGET_LIBS="./target/$target/release/libanoncreds.dylib $TARGET_LIBS" 54 | done 55 | 56 | mkdir -p "${TARGET_DIR}/release" ./target/release 57 | OUTPUT="${TARGET_DIR}/release/libanoncreds.dylib" 58 | echo "Combining targets into universal library" 59 | lipo -create -output $OUTPUT $TARGET_LIBS 60 | -------------------------------------------------------------------------------- /build-xcframework.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eo pipefail 4 | 5 | # Check if lipo and xcodebuild exist 6 | if [ -z `command -v lipo` ] || [ -z `command -v xcodebuild` ] || [ -z `command -v jq` ] 7 | then 8 | echo "!!! lipo, xcodebuild or jq could not be found !!!" 9 | help 10 | fi 11 | 12 | NAME="anoncreds" 13 | VERSION=$(cargo metadata --no-deps --format-version=1 | jq -r '.packages[] | select(.name == "anoncreds") | .version') 14 | BUNDLE_IDENTIFIER="org.hyperledger.$NAME" 15 | LIBRARY_NAME="lib$NAME.a" 16 | XC_FRAMEWORK_NAME="$NAME.xcframework" 17 | FRAMEWORK_LIBRARY_NAME="`tr '[:lower:]' '[:upper:]' <<< ${NAME:0:1}`${NAME:1}" 18 | FRAMEWORK_NAME="$FRAMEWORK_LIBRARY_NAME.framework" 19 | HEADER_NAME="lib$NAME.h" 20 | OUT_PATH="out" 21 | MIN_IOS_VERSION="12.0" 22 | 23 | # Setting some default paths 24 | AARCH64_APPLE_IOS_PATH="./target/aarch64-apple-ios/release" 25 | AARCH64_APPLE_IOS_SIM_PATH="./target/aarch64-apple-ios-sim/release" 26 | X86_64_APPLE_IOS_PATH="./target/x86_64-apple-ios/release" 27 | HEADER_PATH="./include" 28 | 29 | # Simple helper command to display some information 30 | Help() { 31 | echo "required dependencies:" 32 | echo " - lipo" 33 | echo " - jq" 34 | echo " - xcodebuild" 35 | echo "To build an xcframework with underlying Frameworks" 36 | echo "the following can be passed in as positional arguments" 37 | echo " 1. Path to the aarch64-apple-ios where the dylib is stored" 38 | echo " 2. Path to the aarch64-apple-ios-sim where the dylib is stored" 39 | echo " 3. Path to the x86_64-apple-ios where the dylib is stored" 40 | echo " 4. Path to the header file, excluding the header" 41 | echo "Make sure to add the 'release' section of the path for a" 42 | echo "release build." 43 | exit 44 | } 45 | 46 | # override if its provided 47 | if [ ! -z "$1" ] 48 | then 49 | AARCH64_APPLE_IOS_PATH=$1 50 | fi 51 | 52 | # override if its provided 53 | if [ ! -z "$2" ] 54 | then 55 | AARCH64_APPLE_IOS_SIM_PATH=$2 56 | fi 57 | 58 | # override if its provided 59 | if [ ! -z "$3" ] 60 | then 61 | X86_64_APPLE_IOS_PATH=$3 62 | fi 63 | 64 | # override if its provided 65 | if [ ! -z "$4" ] 66 | then 67 | HEADER_PATH=$4 68 | fi 69 | 70 | if [ ! -f $AARCH64_APPLE_IOS_SIM_PATH/$LIBRARY_NAME ] 71 | then 72 | echo "$AARCH64_APPLE_IOS_SIM_PATH/$LIBRARY_NAME does not exist!" 73 | exit 74 | fi 75 | 76 | if [ ! -f $AARCH64_APPLE_IOS_PATH/$LIBRARY_NAME ] 77 | then 78 | echo "$AARCH64_APPLE_IOS_PATH/$LIBRARY_NAME does not exist!" 79 | exit 80 | fi 81 | 82 | if [ ! -f $X86_64_APPLE_IOS_PATH/$LIBRARY_NAME ] 83 | then 84 | echo "$X86_64_APPLE_IOS_PATH/$LIBRARY_NAME does not exist!" 85 | exit 86 | fi 87 | 88 | if [ ! -f $HEADER_PATH/$HEADER_NAME ] 89 | then 90 | echo "$HEADER_PATH/$HEADER_NAME does not exist!" 91 | exit 92 | fi 93 | 94 | # Displaying the supplied paths to the user 95 | # So there will not be any mistakes 96 | cat << EOF 97 | Using $AARCH64_APPLE_IOS_PATH for aarch64-apple-ios 98 | Using $AARCH64_APPLE_IOS_SIM_PATH for aarch64-apple-ios-sim 99 | Using $X86_64_APPLE_IOS_PATH for x86_64-apple-ios 100 | 101 | Building xcframework with the following values: 102 | 103 | Name: $NAME 104 | Version: $VERSION 105 | Bundle identifier: $BUNDLE_IDENTIFIER 106 | Library name: $LIBRARY_NAME 107 | Framework name: $FRAMEWORK_NAME 108 | XCFramework name: $XC_FRAMEWORK_NAME 109 | Framework library name: $FRAMEWORK_LIBRARY_NAME 110 | 111 | EOF 112 | 113 | echo "Setting op output directory in $OUT_PATH" 114 | mkdir $OUT_PATH 115 | 116 | echo "Combining aarch64 and x86-64 for the simulator.." 117 | lipo -create $AARCH64_APPLE_IOS_SIM_PATH/$LIBRARY_NAME \ 118 | $X86_64_APPLE_IOS_PATH/$LIBRARY_NAME \ 119 | -output $OUT_PATH/sim-$LIBRARY_NAME 120 | 121 | echo "Creating a framework template..." 122 | mkdir $OUT_PATH/$FRAMEWORK_NAME 123 | cd $OUT_PATH/$FRAMEWORK_NAME 124 | mkdir Headers 125 | cp ../../$HEADER_PATH/$HEADER_NAME Headers/$FRAMEWORK_LIBRARY_NAME.h 126 | mkdir Modules 127 | touch Modules/module.modulemap 128 | cat <> Modules/module.modulemap 129 | framework module $FRAMEWORK_LIBRARY_NAME { 130 | umbrella header "$FRAMEWORK_LIBRARY_NAME.h" 131 | 132 | export * 133 | module * { export * } 134 | } 135 | EOT 136 | 137 | cat <> Info.plist 138 | 139 | 140 | 141 | 142 | CFBundleDevelopmentRegion 143 | en 144 | CFBundleExecutable 145 | $FRAMEWORK_LIBRARY_NAME 146 | CFBundleIdentifier 147 | $BUNDLE_IDENTIFIER 148 | CFBundleInfoDictionaryVersion 149 | 6.0 150 | CFBundleName 151 | $NAME 152 | CFBundlePackageType 153 | FMWK 154 | CFBundleShortVersionString 155 | 1.0 156 | CFBundleVersion 157 | $VERSION 158 | NSPrincipalClass 159 | 160 | MinimumOSVersion 161 | $MIN_IOS_VERSION 162 | 163 | 164 | EOT 165 | 166 | cd .. 167 | 168 | echo "Creating both frameworks (real device and simulator)..." 169 | mkdir sim 170 | mkdir real 171 | cp -r $FRAMEWORK_NAME sim/ 172 | cp -r $FRAMEWORK_NAME real/ 173 | mv sim-$LIBRARY_NAME sim/$FRAMEWORK_NAME/$FRAMEWORK_LIBRARY_NAME 174 | cp ../$AARCH64_APPLE_IOS_PATH/$LIBRARY_NAME real/$FRAMEWORK_NAME/$FRAMEWORK_LIBRARY_NAME 175 | 176 | echo "Creating XC Framework..." 177 | xcodebuild -create-xcframework \ 178 | -framework sim/$FRAMEWORK_NAME \ 179 | -framework real/$FRAMEWORK_NAME \ 180 | -output $XC_FRAMEWORK_NAME 181 | 182 | echo "cleaning up..." 183 | rm -rf $FRAMEWORK_NAME real sim 184 | 185 | echo "Framework written to $OUT_PATH/$XC_FRAMEWORK_NAME" 186 | -------------------------------------------------------------------------------- /include/README.md: -------------------------------------------------------------------------------- 1 | _Generating the C header:_ 2 | 3 | 4 | 1. use `nightly` instead of `stable` 5 | 6 | ```sh 7 | rustup default nightly 8 | ``` 9 | > **Note**: If you run into _'unknown feature'_ issues by using latest nightly, force it to 1.72.0 by executing: `rustup default nightly-2023-06-15` 10 | 11 | 2. Install [cbindgen](https://github.com/eqrion/cbindgen/) 12 | 13 | ```sh 14 | cargo install cbindgen 15 | ``` 16 | 17 | 3. Install [cargo expand](https://github.com/dtolnay/cargo-expand) 18 | 19 | ```sh 20 | cargo install cargo-expand 21 | ``` 22 | 23 | 4. Generate the header file: 24 | 25 | ```sh 26 | cbindgen --config include/cbindgen.toml --crate anoncreds --output include/libanoncreds.h 27 | ``` 28 | 29 | 5. Copy to React Native: 30 | 31 | ```sh 32 | cp include/libanoncreds.h wrappers/javascript/packages/anoncreds-react-native/cpp/include/ 33 | ``` 34 | -------------------------------------------------------------------------------- /include/cbindgen.toml: -------------------------------------------------------------------------------- 1 | language = "C" 2 | cpp_compat = true 3 | 4 | pragma_once = true 5 | autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" 6 | include_version = true 7 | usize_is_size_t = true 8 | sort_by = "Name" 9 | 10 | [parse] 11 | parse_deps = true 12 | include = ["ffi-support", "ffi"] 13 | 14 | [parse.expand] 15 | crates = ["anoncreds"] 16 | -------------------------------------------------------------------------------- /src/data_types/cred_offer.rs: -------------------------------------------------------------------------------- 1 | use crate::cl::CredentialKeyCorrectnessProof; 2 | use crate::error::ValidationError; 3 | use crate::utils::validation::Validatable; 4 | 5 | use super::{cred_def::CredentialDefinitionId, nonce::Nonce, schema::SchemaId}; 6 | 7 | #[derive(Debug, Deserialize, Serialize)] 8 | pub struct CredentialOffer { 9 | pub schema_id: SchemaId, 10 | pub cred_def_id: CredentialDefinitionId, 11 | pub key_correctness_proof: CredentialKeyCorrectnessProof, 12 | pub nonce: Nonce, 13 | #[serde(skip_serializing_if = "Option::is_none")] 14 | pub method_name: Option, 15 | } 16 | 17 | impl Validatable for CredentialOffer { 18 | fn validate(&self) -> Result<(), ValidationError> { 19 | self.schema_id.validate()?; 20 | self.cred_def_id.validate()?; 21 | Ok(()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/data_types/credential.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | 4 | #[cfg(feature = "zeroize")] 5 | use zeroize::Zeroize; 6 | 7 | use crate::cl::{CredentialSignature, RevocationRegistry, SignatureCorrectnessProof, Witness}; 8 | use crate::error::{ConversionError, ValidationError}; 9 | use crate::types::MakeCredentialValues; 10 | use crate::utils::validation::Validatable; 11 | use crate::Error; 12 | 13 | use super::rev_reg_def::RevocationRegistryDefinitionId; 14 | use super::{cred_def::CredentialDefinitionId, schema::SchemaId}; 15 | 16 | #[derive(Debug, Deserialize, Serialize)] 17 | pub struct Credential { 18 | pub schema_id: SchemaId, 19 | pub cred_def_id: CredentialDefinitionId, 20 | pub rev_reg_id: Option, 21 | pub values: CredentialValues, 22 | pub signature: CredentialSignature, 23 | pub signature_correctness_proof: SignatureCorrectnessProof, 24 | pub rev_reg: Option, 25 | pub witness: Option, 26 | } 27 | 28 | impl Credential { 29 | pub const QUALIFIABLE_TAGS: [&'static str; 5] = [ 30 | "issuer_did", 31 | "cred_def_id", 32 | "schema_id", 33 | "schema_issuer_did", 34 | "rev_reg_id", 35 | ]; 36 | 37 | pub fn try_clone(&self) -> Result { 38 | Ok(Self { 39 | schema_id: self.schema_id.clone(), 40 | cred_def_id: self.cred_def_id.clone(), 41 | rev_reg_id: self.rev_reg_id.clone(), 42 | values: self.values.clone(), 43 | signature: self.signature.try_clone().map_err(|e| e.to_string())?, 44 | signature_correctness_proof: self 45 | .signature_correctness_proof 46 | .try_clone() 47 | .map_err(|e| e.to_string())?, 48 | rev_reg: self.rev_reg.clone(), 49 | witness: self.witness.clone(), 50 | }) 51 | } 52 | } 53 | 54 | impl Validatable for Credential { 55 | fn validate(&self) -> Result<(), ValidationError> { 56 | self.values.validate()?; 57 | self.schema_id.validate()?; 58 | self.cred_def_id.validate()?; 59 | self.rev_reg_id 60 | .as_ref() 61 | .map(Validatable::validate) 62 | .transpose()?; 63 | 64 | if self.rev_reg_id.is_some() && (self.witness.is_none() || self.rev_reg.is_none()) { 65 | return Err("Credential validation failed: `witness` and `rev_reg` must be passed for revocable Credential".into()); 66 | } 67 | 68 | if self.values.0.is_empty() { 69 | return Err("Credential validation failed: `values` is empty".into()); 70 | } 71 | 72 | Ok(()) 73 | } 74 | } 75 | 76 | #[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)] 77 | pub struct RawCredentialValues(pub HashMap); 78 | 79 | #[cfg(feature = "zeroize")] 80 | impl Drop for RawCredentialValues { 81 | fn drop(&mut self) { 82 | self.zeroize(); 83 | } 84 | } 85 | 86 | #[cfg(feature = "zeroize")] 87 | impl Zeroize for RawCredentialValues { 88 | fn zeroize(&mut self) { 89 | for attr in self.0.values_mut() { 90 | attr.zeroize(); 91 | } 92 | } 93 | } 94 | 95 | impl Validatable for RawCredentialValues { 96 | fn validate(&self) -> Result<(), ValidationError> { 97 | if self.0.is_empty() { 98 | return Err("RawCredentialValues validation failed: empty list has been passed".into()); 99 | } 100 | 101 | Ok(()) 102 | } 103 | } 104 | 105 | impl From<&CredentialValues> for RawCredentialValues { 106 | fn from(values: &CredentialValues) -> Self { 107 | RawCredentialValues( 108 | values 109 | .0 110 | .iter() 111 | .map(|(attribute, values)| (attribute.to_owned(), values.raw.to_owned())) 112 | .collect(), 113 | ) 114 | } 115 | } 116 | 117 | impl RawCredentialValues { 118 | pub fn encode(&self) -> Result { 119 | let mut cred_values = MakeCredentialValues::default(); 120 | for (attribute, raw_value) in self.0.iter() { 121 | cred_values.add_raw(attribute, raw_value)?; 122 | } 123 | Ok(cred_values.into()) 124 | } 125 | } 126 | 127 | #[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)] 128 | pub struct CredentialValues(pub HashMap); 129 | 130 | #[cfg(feature = "zeroize")] 131 | impl Drop for CredentialValues { 132 | fn drop(&mut self) { 133 | self.zeroize(); 134 | } 135 | } 136 | 137 | impl Validatable for CredentialValues { 138 | fn validate(&self) -> Result<(), ValidationError> { 139 | if self.0.is_empty() { 140 | return Err("CredentialValues validation failed: empty list has been passed".into()); 141 | } 142 | 143 | Ok(()) 144 | } 145 | } 146 | 147 | #[cfg(feature = "zeroize")] 148 | impl Zeroize for CredentialValues { 149 | fn zeroize(&mut self) { 150 | for attr in self.0.values_mut() { 151 | attr.zeroize(); 152 | } 153 | } 154 | } 155 | 156 | #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] 157 | #[cfg_attr(feature = "zeroize", derive(Zeroize))] 158 | pub struct AttributeValues { 159 | pub raw: String, 160 | pub encoded: String, 161 | } 162 | -------------------------------------------------------------------------------- /src/data_types/issuer_id.rs: -------------------------------------------------------------------------------- 1 | use crate::impl_anoncreds_object_identifier; 2 | 3 | impl_anoncreds_object_identifier!(IssuerId); 4 | 5 | #[cfg(test)] 6 | mod test_issuer_identifiers { 7 | use super::*; 8 | 9 | #[test] 10 | fn should_validate_new_and_legacy_identifiers() { 11 | let valid_uri_identifier_1 = "did:uri:new"; 12 | let valid_uri_identifier_2 = "did:indy:idunion:test:2MZYuPv2Km7Q1eD4GCsSb6"; 13 | let valid_uri_identifier_3 = "did:indy:sovrin:staging:6cgbu8ZPoWTnR5Rv5JcSMB"; 14 | let valid_uri_identifier_4 = "did:indy:sovrin:7Tqg6BwSSWapxgUDm9KKgg"; 15 | let valid_uri_identifier_5 = "did:web:example.com#controller"; 16 | let valid_uri_identifier_6 = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"; 17 | 18 | let invalid_uri_identifier = "::::"; 19 | 20 | let valid_legacy_identifier_1 = "NcYxiDXkpYi6ov5FcYDi1e"; 21 | let valid_legacy_identifier_2 = "VsKV7grR1BUE29mG2Fm2kX"; 22 | 23 | let too_short_legacy_identifier = "abc"; 24 | let illegal_base58_legacy_identifier_zero = "0000000000000000000000"; 25 | let illegal_base58_legacy_identifier_captial_o = "OOOOOOOOOOOOOOOOOOOOOO"; 26 | let illegal_base58_legacy_identifier_captial_i = "IIIIIIIIIIIIIIIIIIIIII"; 27 | let illegal_base58_legacy_identifier_lower_l = "llllllllllllllllllllll"; 28 | 29 | // Instantiating a new IssuerId validates it 30 | assert!(IssuerId::new(valid_uri_identifier_1).is_ok()); 31 | assert!(IssuerId::new(valid_uri_identifier_2).is_ok()); 32 | assert!(IssuerId::new(valid_uri_identifier_3).is_ok()); 33 | assert!(IssuerId::new(valid_uri_identifier_4).is_ok()); 34 | assert!(IssuerId::new(valid_uri_identifier_5).is_ok()); 35 | assert!(IssuerId::new(valid_uri_identifier_6).is_ok()); 36 | 37 | assert!(IssuerId::new(invalid_uri_identifier).is_err()); 38 | 39 | assert!(IssuerId::new(valid_legacy_identifier_1).is_ok()); 40 | assert!(IssuerId::new(valid_legacy_identifier_2).is_ok()); 41 | 42 | assert!(IssuerId::new(too_short_legacy_identifier).is_err()); 43 | assert!(IssuerId::new(illegal_base58_legacy_identifier_zero).is_err()); 44 | assert!(IssuerId::new(illegal_base58_legacy_identifier_captial_o).is_err()); 45 | assert!(IssuerId::new(illegal_base58_legacy_identifier_captial_i).is_err()); 46 | assert!(IssuerId::new(illegal_base58_legacy_identifier_lower_l).is_err()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/data_types/link_secret.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::cl::{bn::BigNumber, Prover as CryptoProver}; 4 | use crate::error::ConversionError; 5 | 6 | pub struct LinkSecret(pub(crate) BigNumber); 7 | 8 | impl LinkSecret { 9 | pub fn new() -> Result { 10 | let value = CryptoProver::new_link_secret() 11 | .map_err(|err| ConversionError::from_msg(format!("Error creating link secret: {err}")))? 12 | .into(); 13 | 14 | Ok(Self(value)) 15 | } 16 | 17 | pub fn try_clone(&self) -> Result { 18 | let cloned = self.0.try_clone().map_err(|err| { 19 | ConversionError::from_msg(format!("Error cloning link secret: {err}")) 20 | })?; 21 | 22 | Ok(Self(cloned)) 23 | } 24 | } 25 | 26 | impl fmt::Debug for LinkSecret { 27 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 28 | f.debug_tuple("LinkSecret") 29 | .field(if cfg!(test) { &self.0 } else { &"" }) 30 | .finish() 31 | } 32 | } 33 | 34 | impl TryInto for LinkSecret { 35 | type Error = ConversionError; 36 | 37 | fn try_into(self) -> Result { 38 | self.0.to_dec().map_err(|err| { 39 | ConversionError::from_msg(format!("Error converting link secret: {err}")) 40 | }) 41 | } 42 | } 43 | 44 | impl TryFrom<&str> for LinkSecret { 45 | type Error = ConversionError; 46 | 47 | fn try_from(value: &str) -> Result { 48 | Ok(Self(BigNumber::from_dec(value).map_err(|err| { 49 | ConversionError::from_msg(format!("Error converting link secret: {err}")) 50 | })?)) 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod link_secret_tests { 56 | use super::*; 57 | 58 | #[test] 59 | fn should_create_new_link_secret() { 60 | let link_secret = LinkSecret::new(); 61 | assert!(link_secret.is_ok()); 62 | } 63 | 64 | #[test] 65 | fn should_convert_between_string_and_link_secret_roundtrip() { 66 | let ls = "123"; 67 | let link_secret = LinkSecret::try_from(ls).expect("Error creating link secret"); 68 | let link_secret_str: String = link_secret.try_into().expect("Error creating link secret"); 69 | assert_eq!(link_secret_str, ls); 70 | } 71 | 72 | #[test] 73 | fn should_clone_link_secret() { 74 | let link_secret = LinkSecret::new().expect("Unable to create link secret"); 75 | let cloned_link_secret = link_secret 76 | .try_clone() 77 | .expect("Unable to clone link secret"); 78 | 79 | assert_eq!(link_secret.0, cloned_link_secret.0); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/data_types/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! impl_anoncreds_object_identifier { 3 | ($i:ident) => { 4 | use $crate::error::ValidationError; 5 | use $crate::utils::validation::{ 6 | Validatable, LEGACY_CRED_DEF_IDENTIFIER, LEGACY_DID_IDENTIFIER, 7 | LEGACY_REV_REG_DEF_IDENTIFIER, LEGACY_SCHEMA_IDENTIFIER, URI_IDENTIFIER, 8 | }; 9 | 10 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, Default)] 11 | pub struct $i(pub String); 12 | 13 | impl $i { 14 | pub fn new_unchecked(s: impl Into) -> Self { 15 | Self(s.into()) 16 | } 17 | 18 | pub fn new(s: impl Into) -> Result { 19 | let s = Self(s.into()); 20 | Validatable::validate(&s)?; 21 | Ok(s) 22 | } 23 | 24 | pub fn is_legacy_did_identifier(&self) -> bool { 25 | LEGACY_DID_IDENTIFIER.captures(&self.0).is_some() 26 | } 27 | 28 | pub fn is_legacy_cred_def_identifier(&self) -> bool { 29 | LEGACY_CRED_DEF_IDENTIFIER.captures(&self.0).is_some() 30 | } 31 | 32 | pub fn is_legacy_rev_reg_def_identifier(&self) -> bool { 33 | LEGACY_REV_REG_DEF_IDENTIFIER.captures(&self.0).is_some() 34 | } 35 | 36 | pub fn is_legacy_schema_identifier(&self) -> bool { 37 | LEGACY_SCHEMA_IDENTIFIER.captures(&self.0).is_some() 38 | } 39 | 40 | pub fn is_uri(&self) -> bool { 41 | URI_IDENTIFIER.captures(&self.0).is_some() 42 | } 43 | } 44 | 45 | impl Validatable for $i { 46 | fn validate(&self) -> Result<(), ValidationError> { 47 | let legacy_regex = match stringify!($i) { 48 | "IssuerId" => &LEGACY_DID_IDENTIFIER, 49 | "CredentialDefinitionId" => &LEGACY_CRED_DEF_IDENTIFIER, 50 | "SchemaId" => &LEGACY_SCHEMA_IDENTIFIER, 51 | "RevocationRegistryDefinitionId" => &LEGACY_REV_REG_DEF_IDENTIFIER, 52 | invalid_name => { 53 | return Err($crate::invalid!( 54 | "type: {} does not have a validation regex", 55 | invalid_name, 56 | )) 57 | } 58 | }; 59 | 60 | if $crate::utils::validation::URI_IDENTIFIER 61 | .captures(&self.0) 62 | .is_some() 63 | { 64 | return Ok(()); 65 | } 66 | 67 | if legacy_regex.captures(&self.0).is_some() { 68 | return Ok(()); 69 | } 70 | 71 | Err($crate::invalid!( 72 | "type: {}, identifier: {} is invalid. It MUST be a URI or legacy identifier.", 73 | stringify!($i), 74 | self.0 75 | )) 76 | } 77 | } 78 | 79 | impl From<$i> for String { 80 | fn from(i: $i) -> Self { 81 | i.0 82 | } 83 | } 84 | 85 | impl TryFrom for $i { 86 | type Error = ValidationError; 87 | 88 | fn try_from(value: String) -> Result { 89 | $i::new(value) 90 | } 91 | } 92 | 93 | impl TryFrom<&str> for $i { 94 | type Error = ValidationError; 95 | 96 | fn try_from(value: &str) -> Result { 97 | $i::new(value.to_owned()) 98 | } 99 | } 100 | 101 | impl std::fmt::Display for $i { 102 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 103 | write!(f, "{}", self.0) 104 | } 105 | } 106 | }; 107 | } 108 | -------------------------------------------------------------------------------- /src/data_types/mod.rs: -------------------------------------------------------------------------------- 1 | /// Credential definitions 2 | pub mod cred_def; 3 | 4 | /// Credential offers 5 | pub mod cred_offer; 6 | 7 | /// Credential requests 8 | pub mod cred_request; 9 | 10 | /// Credentials 11 | pub mod credential; 12 | 13 | /// Identity link secret 14 | pub mod link_secret; 15 | 16 | /// Nonce used in presentation requests 17 | pub mod nonce; 18 | 19 | /// Presentation requests 20 | pub mod pres_request; 21 | 22 | /// Presentations 23 | pub mod presentation; 24 | 25 | /// Revocation registries 26 | pub mod rev_reg; 27 | 28 | /// Revocation registry definitions 29 | pub mod rev_reg_def; 30 | 31 | /// Revocation status list 32 | pub mod rev_status_list; 33 | 34 | /// Credential schemas 35 | pub mod schema; 36 | 37 | /// Macros for the data types 38 | pub mod macros; 39 | 40 | /// Identifier wrapper for the issuer 41 | pub mod issuer_id; 42 | 43 | #[cfg(feature = "w3c")] 44 | /// W3C Credential standard definitions 45 | pub mod w3c; 46 | -------------------------------------------------------------------------------- /src/data_types/nonce.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::fmt; 3 | use std::hash::{Hash, Hasher}; 4 | 5 | use crate::cl::{new_nonce, Nonce as CryptoNonce}; 6 | use crate::error::ConversionError; 7 | use serde::de::{Error, SeqAccess}; 8 | use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; 9 | use serde_json::Value; 10 | 11 | pub struct Nonce { 12 | strval: String, 13 | native: CryptoNonce, 14 | } 15 | 16 | impl Nonce { 17 | #[inline] 18 | pub fn new() -> Result { 19 | let native = new_nonce() 20 | .map_err(|err| ConversionError::from_msg(format!("Error creating nonce: {err}")))?; 21 | Self::from_native(native) 22 | } 23 | 24 | #[inline] 25 | pub fn from_native(native: CryptoNonce) -> Result { 26 | let strval = native.to_dec().map_err(|e| e.to_string())?; 27 | Ok(Self { strval, native }) 28 | } 29 | 30 | #[inline] 31 | #[must_use] 32 | pub const fn as_native(&self) -> &CryptoNonce { 33 | &self.native 34 | } 35 | 36 | #[inline] 37 | #[must_use] 38 | pub fn into_native(self) -> CryptoNonce { 39 | self.native 40 | } 41 | 42 | pub fn from_dec>(value: S) -> Result { 43 | let strval = value.into(); 44 | if strval.is_empty() { 45 | return Err("Invalid bignum: empty value".into()); 46 | } 47 | for c in strval.chars() { 48 | if !c.is_ascii_digit() { 49 | return Err("Invalid bignum value".into()); 50 | } 51 | } 52 | 53 | let native = CryptoNonce::from_dec(&strval).map_err(|e| e.to_string())?; 54 | Ok(Self { strval, native }) 55 | } 56 | 57 | pub fn from_bytes(bytes: &[u8]) -> Result { 58 | let native = CryptoNonce::from_bytes(bytes).map_err(|err| { 59 | ConversionError::from_msg(format!("Error converting nonce from bytes: {err}")) 60 | })?; 61 | Self::from_native(native) 62 | } 63 | 64 | pub fn try_clone(&self) -> Result { 65 | Self::from_dec(self.strval.clone()) 66 | } 67 | } 68 | 69 | impl Hash for Nonce { 70 | fn hash(&self, state: &mut H) { 71 | self.strval.hash(state); 72 | } 73 | } 74 | 75 | impl PartialEq for Nonce { 76 | fn eq(&self, other: &Self) -> bool { 77 | self.strval == other.strval 78 | } 79 | } 80 | 81 | impl Eq for Nonce {} 82 | 83 | impl TryFrom for Nonce { 84 | type Error = ConversionError; 85 | 86 | fn try_from(value: i64) -> Result { 87 | Self::from_dec(value.to_string()) 88 | } 89 | } 90 | 91 | impl TryFrom for Nonce { 92 | type Error = ConversionError; 93 | 94 | fn try_from(value: u64) -> Result { 95 | Self::from_dec(value.to_string()) 96 | } 97 | } 98 | 99 | impl TryFrom for Nonce { 100 | type Error = ConversionError; 101 | 102 | fn try_from(value: u128) -> Result { 103 | Self::from_dec(value.to_string()) 104 | } 105 | } 106 | 107 | impl TryFrom<&str> for Nonce { 108 | type Error = ConversionError; 109 | 110 | fn try_from(value: &str) -> Result { 111 | Self::from_dec(value) 112 | } 113 | } 114 | 115 | impl TryFrom for Nonce { 116 | type Error = ConversionError; 117 | 118 | fn try_from(value: String) -> Result { 119 | Self::from_dec(value) 120 | } 121 | } 122 | 123 | impl AsRef for Nonce { 124 | fn as_ref(&self) -> &str { 125 | &self.strval 126 | } 127 | } 128 | 129 | impl fmt::Debug for Nonce { 130 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 131 | f.debug_tuple("Nonce").field(&self.strval).finish() 132 | } 133 | } 134 | 135 | impl fmt::Display for Nonce { 136 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 137 | self.strval.fmt(f) 138 | } 139 | } 140 | 141 | impl std::ops::Deref for Nonce { 142 | type Target = str; 143 | 144 | fn deref(&self) -> &Self::Target { 145 | &self.strval 146 | } 147 | } 148 | 149 | impl Serialize for Nonce { 150 | fn serialize(&self, serializer: S) -> Result 151 | where 152 | S: Serializer, 153 | { 154 | serializer.serialize_str(&self.strval) 155 | } 156 | } 157 | 158 | impl<'a> Deserialize<'a> for Nonce { 159 | fn deserialize(deserializer: D) -> Result 160 | where 161 | D: Deserializer<'a>, 162 | { 163 | struct BigNumberVisitor; 164 | 165 | impl<'a> Visitor<'a> for BigNumberVisitor { 166 | type Value = Nonce; 167 | 168 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 169 | formatter.write_str("integer or string nonce or bytes") 170 | } 171 | 172 | fn visit_i64(self, value: i64) -> Result 173 | where 174 | E: serde::de::Error, 175 | { 176 | Nonce::try_from(value).map_err(E::custom) 177 | } 178 | 179 | fn visit_u64(self, value: u64) -> Result 180 | where 181 | E: serde::de::Error, 182 | { 183 | Nonce::try_from(value).map_err(E::custom) 184 | } 185 | 186 | fn visit_u128(self, value: u128) -> Result 187 | where 188 | E: serde::de::Error, 189 | { 190 | Nonce::try_from(value).map_err(E::custom) 191 | } 192 | 193 | fn visit_str(self, value: &str) -> Result 194 | where 195 | E: serde::de::Error, 196 | { 197 | Nonce::from_dec(value).map_err(E::custom) 198 | } 199 | 200 | fn visit_seq(self, mut seq: E) -> Result 201 | where 202 | E: SeqAccess<'a>, 203 | { 204 | let mut vec = Vec::new(); 205 | 206 | while let Ok(Some(Value::Number(elem))) = seq.next_element() { 207 | vec.push( 208 | elem.as_u64() 209 | .ok_or_else(|| E::Error::custom("invalid nonce"))? 210 | as u8, 211 | ); 212 | } 213 | 214 | Nonce::from_bytes(&vec).map_err(E::Error::custom) 215 | } 216 | } 217 | 218 | deserializer.deserialize_any(BigNumberVisitor) 219 | } 220 | } 221 | 222 | #[cfg(test)] 223 | mod tests { 224 | use super::*; 225 | 226 | #[test] 227 | fn nonce_validate() { 228 | let valid = ["0", "1000000000000000000000000000000000"]; 229 | for v in valid.iter() { 230 | assert!(Nonce::try_from(*v).is_ok()) 231 | } 232 | 233 | let invalid = [ 234 | "-1000000000000000000000000000000000", 235 | "-1", 236 | "notanumber", 237 | "", 238 | "-", 239 | "+1", 240 | "1a", 241 | ]; 242 | for v in invalid.iter() { 243 | assert!(Nonce::try_from(*v).is_err()) 244 | } 245 | } 246 | 247 | #[test] 248 | fn nonce_serialize() { 249 | let val = Nonce::try_from("10000").unwrap(); 250 | let ser = serde_json::to_string(&val).unwrap(); 251 | assert_eq!(ser, "\"10000\""); 252 | let des = serde_json::from_str::(&ser).unwrap(); 253 | assert_eq!(val, des); 254 | } 255 | 256 | #[test] 257 | fn nonce_convert() { 258 | let nonce = CryptoNonce::new().expect("Error creating nonce"); 259 | let ser = serde_json::to_string(&nonce).unwrap(); 260 | let des = serde_json::from_str::(&ser).unwrap(); 261 | let ser2 = serde_json::to_string(&des).unwrap(); 262 | let nonce_des = serde_json::from_str::(&ser2).unwrap(); 263 | assert_eq!(nonce, nonce_des); 264 | 265 | let nonce = Nonce::new().unwrap(); 266 | let strval = nonce.to_string(); 267 | let unonce = nonce.into_native(); 268 | assert_eq!(strval, unonce.to_dec().unwrap()); 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/data_types/presentation.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::cl::Proof; 4 | use crate::error::ValidationError; 5 | use crate::utils::validation::Validatable; 6 | 7 | use super::{ 8 | cred_def::CredentialDefinitionId, rev_reg_def::RevocationRegistryDefinitionId, schema::SchemaId, 9 | }; 10 | 11 | #[derive(Debug, Deserialize, Serialize)] 12 | pub struct Presentation { 13 | pub proof: Proof, 14 | pub requested_proof: RequestedProof, 15 | pub identifiers: Vec, 16 | } 17 | 18 | #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] 19 | pub struct RequestedProof { 20 | pub revealed_attrs: HashMap, 21 | #[serde(skip_serializing_if = "HashMap::is_empty")] 22 | #[serde(default)] 23 | pub revealed_attr_groups: HashMap, 24 | #[serde(default)] 25 | pub self_attested_attrs: HashMap, 26 | #[serde(default)] 27 | pub unrevealed_attrs: HashMap, 28 | #[serde(default)] 29 | pub predicates: HashMap, 30 | } 31 | 32 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] 33 | pub struct SubProofReferent { 34 | pub sub_proof_index: u32, 35 | } 36 | 37 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] 38 | pub struct RevealedAttributeInfo { 39 | pub sub_proof_index: u32, 40 | pub raw: String, 41 | pub encoded: String, 42 | } 43 | 44 | #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] 45 | pub struct RevealedAttributeGroupInfo { 46 | pub sub_proof_index: u32, 47 | pub values: HashMap, 48 | } 49 | 50 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] 51 | pub struct AttributeValue { 52 | pub raw: String, 53 | pub encoded: String, 54 | } 55 | 56 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 57 | pub struct Identifier { 58 | pub schema_id: SchemaId, 59 | pub cred_def_id: CredentialDefinitionId, 60 | pub rev_reg_id: Option, 61 | pub timestamp: Option, 62 | } 63 | 64 | impl Validatable for Presentation { 65 | fn validate(&self) -> Result<(), ValidationError> { 66 | for identifier in &self.identifiers { 67 | identifier.schema_id.validate()?; 68 | identifier.cred_def_id.validate()?; 69 | identifier 70 | .rev_reg_id 71 | .as_ref() 72 | .map(Validatable::validate) 73 | .transpose()?; 74 | } 75 | Ok(()) 76 | } 77 | } 78 | 79 | #[cfg(test)] 80 | mod tests { 81 | use super::*; 82 | 83 | #[test] 84 | fn deserialize_requested_proof_with_empty_revealed_attr_groups() { 85 | let mut req_proof_old: RequestedProof = Default::default(); 86 | req_proof_old.revealed_attrs.insert( 87 | "attr1".to_string(), 88 | RevealedAttributeInfo { 89 | sub_proof_index: 0, 90 | raw: "123".to_string(), 91 | encoded: "123".to_string(), 92 | }, 93 | ); 94 | let json = json!(req_proof_old).to_string(); 95 | let req_proof: RequestedProof = serde_json::from_str(&json).unwrap(); 96 | assert!(req_proof.revealed_attr_groups.is_empty()) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/data_types/rev_reg.rs: -------------------------------------------------------------------------------- 1 | use crate::cl::RevocationRegistry as CryptoRevocationRegistry; 2 | 3 | #[derive(Clone, Debug, Serialize, Deserialize)] 4 | pub struct RevocationRegistry { 5 | pub value: CryptoRevocationRegistry, 6 | } 7 | -------------------------------------------------------------------------------- /src/data_types/rev_reg_def.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use crate::cl::{RevocationKeyPrivate, RevocationKeyPublic}; 4 | use crate::{error::ConversionError, impl_anoncreds_object_identifier}; 5 | 6 | use super::{cred_def::CredentialDefinitionId, issuer_id::IssuerId}; 7 | 8 | pub const CL_ACCUM: &str = "CL_ACCUM"; 9 | 10 | impl_anoncreds_object_identifier!(RevocationRegistryDefinitionId); 11 | 12 | #[allow(non_camel_case_types)] 13 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] 14 | pub enum RegistryType { 15 | CL_ACCUM, 16 | } 17 | 18 | impl FromStr for RegistryType { 19 | type Err = ConversionError; 20 | 21 | fn from_str(s: &str) -> Result { 22 | match s { 23 | CL_ACCUM => Ok(Self::CL_ACCUM), 24 | _ => Err(ConversionError::from_msg("Invalid registry type")), 25 | } 26 | } 27 | } 28 | 29 | #[derive(Clone, Debug, Deserialize, Serialize)] 30 | #[serde(rename_all = "camelCase")] 31 | pub struct RevocationRegistryDefinitionValue { 32 | pub max_cred_num: u32, 33 | pub public_keys: RevocationRegistryDefinitionValuePublicKeys, 34 | pub tails_hash: String, 35 | pub tails_location: String, 36 | } 37 | 38 | #[derive(Clone, Debug, Deserialize, Serialize)] 39 | #[serde(rename_all = "camelCase")] 40 | pub struct RevocationRegistryDefinitionValuePublicKeys { 41 | pub accum_key: RevocationKeyPublic, 42 | } 43 | 44 | #[derive(Clone, Debug, Deserialize, Serialize)] 45 | #[serde(rename_all = "camelCase")] 46 | pub struct RevocationRegistryDefinition { 47 | pub issuer_id: IssuerId, 48 | pub revoc_def_type: RegistryType, 49 | pub tag: String, 50 | pub cred_def_id: CredentialDefinitionId, 51 | pub value: RevocationRegistryDefinitionValue, 52 | } 53 | 54 | impl Validatable for RevocationRegistryDefinition { 55 | fn validate(&self) -> Result<(), ValidationError> { 56 | self.cred_def_id.validate()?; 57 | self.issuer_id.validate()?; 58 | 59 | Ok(()) 60 | } 61 | } 62 | 63 | #[derive(Debug, Deserialize, Serialize)] 64 | pub struct RevocationRegistryDefinitionPrivate { 65 | pub value: RevocationKeyPrivate, 66 | } 67 | -------------------------------------------------------------------------------- /src/data_types/rev_status_list.rs: -------------------------------------------------------------------------------- 1 | use super::issuer_id::IssuerId; 2 | use super::rev_reg::RevocationRegistry; 3 | use super::rev_reg_def::RevocationRegistryDefinitionId; 4 | 5 | use crate::cl::{Accumulator, RevocationRegistry as CryptoRevocationRegistry}; 6 | use crate::{Error, Result}; 7 | 8 | use std::collections::BTreeSet; 9 | 10 | /// Data model for the revocation status list as defined in the [Anoncreds V1.0 11 | /// specification](https://hyperledger.github.io/anoncreds-spec/#creating-the-initial-revocation-status-list-object) 12 | #[derive(Clone, Debug, Serialize, Deserialize)] 13 | #[serde(rename_all = "camelCase")] 14 | pub struct RevocationStatusList { 15 | #[serde(skip_serializing_if = "Option::is_none")] 16 | rev_reg_def_id: Option, 17 | issuer_id: IssuerId, 18 | #[serde(with = "serde_revocation_list")] 19 | revocation_list: bitvec::vec::BitVec, 20 | #[serde( 21 | rename = "currentAccumulator", 22 | alias = "accum", 23 | skip_serializing_if = "Option::is_none" 24 | )] 25 | accum: Option, 26 | #[serde(skip_serializing_if = "Option::is_none")] 27 | timestamp: Option, 28 | } 29 | 30 | impl From<&RevocationStatusList> for Option { 31 | fn from(value: &RevocationStatusList) -> Self { 32 | value.accum.map(From::from) 33 | } 34 | } 35 | 36 | impl From<&RevocationStatusList> for Option { 37 | fn from(value: &RevocationStatusList) -> Self { 38 | value.accum.map(|registry| RevocationRegistry { 39 | value: registry.into(), 40 | }) 41 | } 42 | } 43 | 44 | impl RevocationStatusList { 45 | pub(crate) fn id(&self) -> Option { 46 | self.rev_reg_def_id.clone() 47 | } 48 | 49 | pub(crate) const fn timestamp(&self) -> Option { 50 | self.timestamp 51 | } 52 | 53 | pub(crate) const fn state(&self) -> &bitvec::vec::BitVec { 54 | &self.revocation_list 55 | } 56 | 57 | pub(crate) fn state_owned(&self) -> bitvec::vec::BitVec { 58 | self.revocation_list.clone() 59 | } 60 | 61 | pub fn set_registry(&mut self, registry: CryptoRevocationRegistry) -> Result<()> { 62 | self.accum = Some(registry.accum); 63 | Ok(()) 64 | } 65 | 66 | pub(crate) fn get(&self, idx: usize) -> Option { 67 | self.revocation_list.get(idx).as_deref().copied() 68 | } 69 | 70 | pub(crate) fn update( 71 | &mut self, 72 | registry: Option, 73 | issued: Option>, 74 | revoked: Option>, 75 | timestamp: Option, 76 | ) -> Result<()> { 77 | // only update if input is Some 78 | if let Some(reg) = registry { 79 | self.accum = Some(reg.accum); 80 | } 81 | let slots_count = self.revocation_list.len(); 82 | if let Some(issued) = issued { 83 | if let Some(max_idx) = issued.iter().last().copied() { 84 | if max_idx as usize >= slots_count { 85 | return Err(Error::from_msg( 86 | crate::ErrorKind::Unexpected, 87 | "Update Revocation List Index Out of Range", 88 | )); 89 | } 90 | } 91 | // issued credentials are assigned `false` 92 | // i.e. NOT revoked 93 | for i in issued { 94 | self.revocation_list.set(i as usize, false); 95 | } 96 | } 97 | if let Some(revoked) = revoked { 98 | if let Some(max_idx) = revoked.iter().last().copied() { 99 | if max_idx as usize >= slots_count { 100 | return Err(Error::from_msg( 101 | crate::ErrorKind::Unexpected, 102 | "Update Revocation List Index Out of Range", 103 | )); 104 | } 105 | } 106 | // revoked credentials are assigned `true` 107 | // i.e. IS revoked 108 | for i in revoked { 109 | self.revocation_list.set(i as usize, true); 110 | } 111 | } 112 | // only update if input is Some 113 | if let Some(t) = timestamp { 114 | self.timestamp = Some(t); 115 | } 116 | Ok(()) 117 | } 118 | 119 | pub(crate) fn new( 120 | rev_reg_def_id: Option<&str>, 121 | issuer_id: IssuerId, 122 | revocation_list: bitvec::vec::BitVec, 123 | registry: Option, 124 | timestamp: Option, 125 | ) -> Result { 126 | Ok(Self { 127 | rev_reg_def_id: rev_reg_def_id 128 | .map(RevocationRegistryDefinitionId::new) 129 | .transpose()?, 130 | issuer_id, 131 | revocation_list, 132 | accum: registry.map(|r| r.accum), 133 | timestamp, 134 | }) 135 | } 136 | } 137 | 138 | pub mod serde_revocation_list { 139 | use bitvec::vec::BitVec; 140 | use serde::{ 141 | de::{Deserializer, Error as DeError, SeqAccess, Visitor}, 142 | ser::{SerializeSeq, Serializer}, 143 | }; 144 | 145 | pub fn serialize(state: &bitvec::vec::BitVec, s: S) -> Result 146 | where 147 | S: Serializer, 148 | { 149 | let mut seq = s.serialize_seq(Some(state.len()))?; 150 | for element in state { 151 | let e = i32::from(*element); 152 | seq.serialize_element(&e)?; 153 | } 154 | seq.end() 155 | } 156 | 157 | pub fn deserialize<'de, D>(deserializer: D) -> Result 158 | where 159 | D: Deserializer<'de>, 160 | { 161 | struct JsonBitStringVisitor; 162 | 163 | impl<'de> Visitor<'de> for JsonBitStringVisitor { 164 | type Value = bitvec::vec::BitVec; 165 | 166 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 167 | write!( 168 | formatter, 169 | "a seq containing revocation state, i.e. [1, 0, 1]" 170 | ) 171 | } 172 | 173 | fn visit_seq(self, mut v: S) -> Result 174 | where 175 | S: SeqAccess<'de>, 176 | { 177 | // TODO: do we have a min size for this? 178 | let mut bv = BitVec::with_capacity(v.size_hint().unwrap_or_default()); 179 | while let Some(ele) = v.next_element()? { 180 | match ele { 181 | 0 => bv.push(false), 182 | 1 => bv.push(true), 183 | _ => { 184 | return Err(S::Error::custom("invalid revocation state")); 185 | } 186 | } 187 | } 188 | Ok(bv) 189 | } 190 | } 191 | deserializer.deserialize_seq(JsonBitStringVisitor) 192 | } 193 | } 194 | 195 | #[cfg(test)] 196 | mod rev_reg_tests { 197 | use super::*; 198 | use bitvec::prelude::*; 199 | 200 | const REVOCATION_LIST: &str = r#" 201 | { 202 | "revRegDefId": "reg", 203 | "revocationList": [1, 1, 1, 1], 204 | "issuerId": "mock:uri", 205 | "currentAccumulator": "1 1379509F4D411630D308A5ABB4F422FCE6737B330B1C5FD286AA5C26F2061E60 1 235535CC45D4816C7686C5A402A230B35A62DDE82B4A652E384FD31912C4E4BB 1 0C94B61595FCAEFC892BB98A27D524C97ED0B7ED1CC49AD6F178A59D4199C9A4 1 172482285606DEE8500FC8A13E6A35EC071F8B84F0EB4CD3DD091C0B4CD30E5E 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", 206 | "timestamp": 1234 207 | }"#; 208 | 209 | const REVOCATION_LIST_WITHOUT_ISSUER_ID: &str = r#" 210 | { 211 | "revRegDefId": "reg", 212 | "revocationList": [1, 1, 1, 1], 213 | "currentAccumulator": "1 1379509F4D411630D308A5ABB4F422FCE6737B330B1C5FD286AA5C26F2061E60 1 235535CC45D4816C7686C5A402A230B35A62DDE82B4A652E384FD31912C4E4BB 1 0C94B61595FCAEFC892BB98A27D524C97ED0B7ED1CC49AD6F178A59D4199C9A4 1 172482285606DEE8500FC8A13E6A35EC071F8B84F0EB4CD3DD091C0B4CD30E5E 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", 214 | "timestamp": 1234 215 | }"#; 216 | 217 | #[test] 218 | fn json_rev_list_can_be_deserialized() { 219 | let des = serde_json::from_str::(REVOCATION_LIST).unwrap(); 220 | let expected_state = bitvec![1;4]; 221 | assert_eq!(des.state(), &expected_state); 222 | } 223 | 224 | #[test] 225 | fn json_rev_list_can_not_be_deserialized_without_issuer_id() { 226 | let res = serde_json::from_str::(REVOCATION_LIST_WITHOUT_ISSUER_ID); 227 | assert!(res.is_err()); 228 | } 229 | 230 | #[test] 231 | fn test_revocation_list_roundtrip_serde() { 232 | let des_from_json = serde_json::from_str::(REVOCATION_LIST).unwrap(); 233 | let ser = serde_json::to_string(&des_from_json).unwrap(); 234 | let des = serde_json::from_str::(&ser).unwrap(); 235 | let ser2 = serde_json::to_string(&des).unwrap(); 236 | assert_eq!(ser, ser2) 237 | } 238 | 239 | #[test] 240 | fn update_rev_status_list_works() { 241 | let mut list = serde_json::from_str::(REVOCATION_LIST).unwrap(); 242 | let list_status = list.state_owned(); 243 | assert_eq!(list.timestamp().unwrap(), 1234); 244 | assert_eq!(list_status.get(0usize).unwrap(), true); 245 | 246 | list.update(None, Some(BTreeSet::from([0u32])), None, Some(1245)) 247 | .unwrap(); 248 | assert!(!list.get(0usize).unwrap()); 249 | assert_eq!(list.timestamp().unwrap(), 1245); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/data_types/schema.rs: -------------------------------------------------------------------------------- 1 | use crate::impl_anoncreds_object_identifier; 2 | 3 | use std::collections::HashSet; 4 | 5 | use super::issuer_id::IssuerId; 6 | 7 | pub const MAX_ATTRIBUTES_COUNT: usize = 125; 8 | 9 | impl_anoncreds_object_identifier!(SchemaId); 10 | 11 | #[derive(Debug, Clone, Serialize, Deserialize, Default)] 12 | #[serde(rename_all = "camelCase")] 13 | pub struct Schema { 14 | pub name: String, 15 | pub version: String, 16 | pub attr_names: AttributeNames, 17 | pub issuer_id: IssuerId, 18 | } 19 | 20 | // QUESTION: If these must be unique, why not directly store them as a set? 21 | #[derive(Debug, Clone, Serialize, Deserialize, Default)] 22 | pub struct AttributeNames(pub Vec); 23 | 24 | impl From<&[&str]> for AttributeNames { 25 | fn from(attrs: &[&str]) -> Self { 26 | Self(attrs.iter().map(|s| String::from(*s)).collect::>()) 27 | } 28 | } 29 | 30 | impl From> for AttributeNames { 31 | fn from(attrs: Vec) -> Self { 32 | Self(attrs) 33 | } 34 | } 35 | 36 | impl From> for AttributeNames { 37 | fn from(attrs: HashSet) -> Self { 38 | Self(attrs.into_iter().collect::>()) 39 | } 40 | } 41 | 42 | impl From for Vec { 43 | fn from(a: AttributeNames) -> Self { 44 | a.0 45 | } 46 | } 47 | 48 | impl Validatable for Schema { 49 | fn validate(&self) -> Result<(), ValidationError> { 50 | self.issuer_id.validate()?; 51 | self.attr_names.validate()?; 52 | Ok(()) 53 | } 54 | } 55 | 56 | impl Validatable for AttributeNames { 57 | fn validate(&self) -> Result<(), ValidationError> { 58 | let mut unique = HashSet::new(); 59 | let is_unique = self.0.iter().all(move |name| unique.insert(name)); 60 | 61 | if !is_unique { 62 | return Err("Attributes inside the schema must be unique".into()); 63 | } 64 | 65 | if self.0.is_empty() { 66 | return Err("Empty list of Schema attributes has been passed".into()); 67 | } 68 | 69 | if self.0.len() > MAX_ATTRIBUTES_COUNT { 70 | return Err(format!( 71 | "The number of Schema attributes {} cannot be greater than {}", 72 | self.0.len(), 73 | MAX_ATTRIBUTES_COUNT 74 | ) 75 | .into()); 76 | } 77 | Ok(()) 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod test_schema_validation { 83 | use super::*; 84 | 85 | #[test] 86 | fn test_schema_valid() { 87 | let schema_json = json!({ 88 | "name": "gvt", 89 | "version": "1.0", 90 | "attrNames": ["aaa", "bbb", "ccc"], 91 | "issuerId": "mock:uri" 92 | }); 93 | 94 | let schema: Schema = serde_json::from_value(schema_json).unwrap(); 95 | assert_eq!(schema.name, "gvt"); 96 | assert_eq!(schema.version, "1.0"); 97 | } 98 | 99 | #[test] 100 | fn test_attribute_names_valid_ordering_consistent() { 101 | // This test runs 10 times as the ordering can accidentally match 102 | for _ in 0..10 { 103 | let one: &[&str] = &["a", "b", "c", "d"]; 104 | let two: &[&str] = &["1", "2", "3", "4"]; 105 | 106 | let attr_names_one: AttributeNames = one.into(); 107 | let attr_names_two: AttributeNames = two.into(); 108 | 109 | assert_eq!(attr_names_one.0, one); 110 | assert_eq!(attr_names_two.0, two); 111 | } 112 | } 113 | 114 | #[test] 115 | fn test_schema_invalid_missing_properties() { 116 | let schema_json = json!({ 117 | "name": "gvt", 118 | }); 119 | 120 | let schema = serde_json::from_value::(schema_json); 121 | assert!(schema.is_err()); 122 | } 123 | 124 | #[test] 125 | fn test_schema_invalid_issuer_id() { 126 | let schema_json = json!({ 127 | "name": "gvt", 128 | "version": "1.0", 129 | "attrNames": ["aaa", "bbb", "ccc"], 130 | "issuerId": "bob" 131 | }); 132 | 133 | let schema: Schema = serde_json::from_value(schema_json).unwrap(); 134 | assert!(schema.validate().is_err()); 135 | } 136 | 137 | #[test] 138 | fn test_schema_invalid_attr_names() { 139 | let schema_json = json!({ 140 | "name": "gvt1", 141 | "version": "1.0", 142 | "attrNames": [], 143 | "issuerId": "mock:uri" 144 | }); 145 | 146 | let schema: Schema = serde_json::from_value(schema_json).unwrap(); 147 | assert!(schema.validate().is_err()); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/data_types/w3c/constants.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use serde_json::{json, Value}; 3 | use std::collections::HashSet; 4 | 5 | use crate::data_types::w3c::context::{Context, Contexts}; 6 | use crate::data_types::w3c::credential::Types; 7 | use crate::data_types::w3c::uri::URI; 8 | 9 | // Contexts 10 | pub const W3C_VC_1_1_BASE_CONTEXT: &str = "https://www.w3.org/2018/credentials/v1"; 11 | pub const W3C_VC_2_0_BASE_CONTEXT: &str = "https://www.w3.org/ns/credentials/v2"; 12 | pub const W3C_DATA_INTEGRITY_CONTEXT: &str = "https://w3id.org/security/data-integrity/v2"; 13 | 14 | pub static ISSUER_DEPENDENT_VOCABULARY: Lazy = Lazy::new(|| { 15 | json!({ 16 | "@vocab": "https://www.w3.org/ns/credentials/issuer-dependent#" 17 | }) 18 | }); 19 | 20 | pub(crate) static ANONCREDS_VC_1_1_CONTEXTS: Lazy = Lazy::new(|| { 21 | Contexts(vec![ 22 | Context::URI(URI::from(W3C_VC_1_1_BASE_CONTEXT)), 23 | Context::URI(URI::from(W3C_DATA_INTEGRITY_CONTEXT)), 24 | Context::Object(ISSUER_DEPENDENT_VOCABULARY.clone()), 25 | ]) 26 | }); 27 | 28 | pub(crate) static ANONCREDS_VC_2_0_CONTEXTS: Lazy = Lazy::new(|| { 29 | Contexts(vec![ 30 | Context::URI(URI::from(W3C_VC_2_0_BASE_CONTEXT)), 31 | Context::Object(ISSUER_DEPENDENT_VOCABULARY.clone()), 32 | ]) 33 | }); 34 | 35 | // Types 36 | pub const W3C_CREDENTIAL_TYPE: &str = "VerifiableCredential"; 37 | pub const W3C_PRESENTATION_TYPE: &str = "VerifiablePresentation"; 38 | 39 | pub(crate) static ANONCREDS_CREDENTIAL_TYPES: Lazy = 40 | Lazy::new(|| Types(HashSet::from([String::from(W3C_CREDENTIAL_TYPE)]))); 41 | 42 | pub(crate) static ANONCREDS_PRESENTATION_TYPES: Lazy = 43 | Lazy::new(|| Types(HashSet::from([String::from(W3C_PRESENTATION_TYPE)]))); 44 | -------------------------------------------------------------------------------- /src/data_types/w3c/context.rs: -------------------------------------------------------------------------------- 1 | use crate::data_types::w3c::constants::{ 2 | ANONCREDS_VC_1_1_CONTEXTS, ANONCREDS_VC_2_0_CONTEXTS, ISSUER_DEPENDENT_VOCABULARY, 3 | W3C_DATA_INTEGRITY_CONTEXT, W3C_VC_1_1_BASE_CONTEXT, W3C_VC_2_0_BASE_CONTEXT, 4 | }; 5 | use crate::data_types::w3c::uri::URI; 6 | use crate::data_types::w3c::VerifiableCredentialSpecVersion; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] 9 | #[serde(untagged)] 10 | pub enum Context { 11 | URI(URI), 12 | Object(serde_json::Value), 13 | } 14 | 15 | impl Context { 16 | pub fn uri(&self) -> crate::Result<&URI> { 17 | match self { 18 | Context::URI(uri) => Ok(uri), 19 | Context::Object(_) => Err(err_msg!("Unable to get URI context")), 20 | } 21 | } 22 | } 23 | 24 | #[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)] 25 | pub struct Contexts(pub Vec); 26 | 27 | impl Contexts { 28 | pub fn get(version: &VerifiableCredentialSpecVersion) -> Contexts { 29 | match version { 30 | VerifiableCredentialSpecVersion::V1_1 => ANONCREDS_VC_1_1_CONTEXTS.clone(), 31 | VerifiableCredentialSpecVersion::V2_0 => ANONCREDS_VC_2_0_CONTEXTS.clone(), 32 | } 33 | } 34 | 35 | pub fn version(&self) -> crate::Result { 36 | // First context defines the version of verifiable credential 37 | let first_context = self 38 | .0 39 | .get(0) 40 | .ok_or_else(|| err_msg!("Credential does not contain any context"))? 41 | .uri()?; 42 | 43 | if first_context.0 == W3C_VC_1_1_BASE_CONTEXT { 44 | return Ok(VerifiableCredentialSpecVersion::V1_1); 45 | } 46 | if first_context.0 == W3C_VC_2_0_BASE_CONTEXT { 47 | return Ok(VerifiableCredentialSpecVersion::V2_0); 48 | } 49 | 50 | Err(err_msg!("Unexpected context {:?}", first_context)) 51 | } 52 | 53 | pub fn validate(&self) -> crate::Result<()> { 54 | let vc_version = self.version()?; 55 | if vc_version == VerifiableCredentialSpecVersion::V1_1 { 56 | // for VC 1.1 credential context must include extra context for data integrity proofs 57 | // for VC 2.0 it's included in the main one 58 | if !self 59 | .0 60 | .contains(&Context::URI(URI::from(W3C_DATA_INTEGRITY_CONTEXT))) 61 | { 62 | return Err(err_msg!( 63 | "Credential does not contain w3c data integrity context" 64 | )); 65 | } 66 | } 67 | 68 | if !self 69 | .0 70 | .contains(&Context::Object(ISSUER_DEPENDENT_VOCABULARY.clone())) 71 | { 72 | return Err(err_msg!( 73 | "Credential does not contain issuer vocabulary context" 74 | )); 75 | } 76 | Ok(()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/data_types/w3c/credential.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use serde::{Deserialize, Serialize}; 3 | use std::collections::HashSet; 4 | use std::string::ToString; 5 | 6 | use crate::data_types::w3c::constants::ANONCREDS_CREDENTIAL_TYPES; 7 | use crate::data_types::w3c::context::Contexts; 8 | use crate::data_types::w3c::credential_attributes::CredentialSubject; 9 | use crate::data_types::w3c::proof::{ 10 | CredentialPresentationProofValue, CredentialSignatureProofValue, DataIntegrityProof, 11 | }; 12 | use crate::data_types::w3c::VerifiableCredentialSpecVersion; 13 | use crate::data_types::{ 14 | issuer_id::IssuerId, 15 | w3c::{constants::W3C_CREDENTIAL_TYPE, one_or_many::OneOrMany, uri::URI}, 16 | }; 17 | use crate::Result; 18 | 19 | /// AnonCreds W3C Credential definition 20 | /// Note, that this definition is tied to AnonCreds W3C form 21 | #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] 22 | #[serde(rename_all = "camelCase")] 23 | #[serde(deny_unknown_fields)] 24 | pub struct W3CCredential { 25 | #[serde(rename = "@context")] 26 | pub context: Contexts, 27 | #[serde(alias = "@type")] 28 | #[serde(rename = "type")] 29 | pub type_: Types, 30 | pub issuer: IssuerId, 31 | pub credential_subject: CredentialSubject, 32 | pub proof: OneOrMany, 33 | #[serde(alias = "@id")] 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | pub id: Option, 36 | 37 | // for VC 1.1 `issuance_date` property must be used 38 | // for VC 2.0 there is optional `valid_from` which we leave empty in case of anoncreds 39 | #[serde(skip_serializing_if = "Option::is_none")] 40 | pub issuance_date: Option, 41 | #[serde(skip_serializing_if = "Option::is_none")] 42 | pub valid_from: Option, 43 | } 44 | 45 | #[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)] 46 | pub struct Types(pub HashSet); 47 | 48 | pub type IssuanceDate = DateTime; 49 | 50 | pub type NonAnonCredsDataIntegrityProof = serde_json::Value; 51 | 52 | #[allow(clippy::large_enum_variant)] 53 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 54 | #[serde(untagged)] 55 | pub enum CredentialProof { 56 | AnonCredsDataIntegrityProof(DataIntegrityProof), 57 | NonAnonCredsDataIntegrityProof(NonAnonCredsDataIntegrityProof), 58 | } 59 | 60 | impl W3CCredential { 61 | pub fn new( 62 | issuer: IssuerId, 63 | credential_subject: CredentialSubject, 64 | proof: DataIntegrityProof, 65 | version: Option<&VerifiableCredentialSpecVersion>, 66 | ) -> Self { 67 | let version = version.cloned().unwrap_or_default(); 68 | let issuance_date = match version { 69 | VerifiableCredentialSpecVersion::V1_1 => Some(Utc::now()), 70 | VerifiableCredentialSpecVersion::V2_0 => None, 71 | }; 72 | Self { 73 | context: Contexts::get(&version), 74 | type_: ANONCREDS_CREDENTIAL_TYPES.clone(), 75 | issuance_date, 76 | issuer, 77 | credential_subject, 78 | proof: OneOrMany::Many(vec![CredentialProof::AnonCredsDataIntegrityProof(proof)]), 79 | valid_from: None, 80 | id: None, 81 | } 82 | } 83 | 84 | pub(crate) fn derive( 85 | credential_subject: CredentialSubject, 86 | proof: DataIntegrityProof, 87 | credential: &W3CCredential, 88 | ) -> W3CCredential { 89 | W3CCredential { 90 | context: credential.context.clone(), 91 | type_: credential.type_.clone(), 92 | issuer: credential.issuer.clone(), 93 | id: credential.id.clone(), 94 | issuance_date: credential.issuance_date, 95 | valid_from: credential.valid_from, 96 | credential_subject, 97 | proof: OneOrMany::One(CredentialProof::AnonCredsDataIntegrityProof(proof)), 98 | } 99 | } 100 | 101 | pub fn version(&self) -> Result { 102 | self.context.version() 103 | } 104 | 105 | pub fn get_credential_signature_proof(&self) -> Result<&CredentialSignatureProofValue> { 106 | self.get_data_integrity_proof()? 107 | .get_credential_signature_proof() 108 | } 109 | 110 | pub fn get_credential_presentation_proof(&self) -> Result<&CredentialPresentationProofValue> { 111 | self.get_data_integrity_proof()? 112 | .get_credential_presentation_proof() 113 | } 114 | 115 | pub(crate) fn get_data_integrity_proof(&self) -> Result<&DataIntegrityProof> { 116 | self.proof 117 | .find_value(&|proof: &CredentialProof| match proof { 118 | CredentialProof::AnonCredsDataIntegrityProof(proof) => Some(proof), 119 | _ => None, 120 | }) 121 | .ok_or_else(|| err_msg!("Credential does not contain data integrity proof")) 122 | } 123 | 124 | pub(crate) fn get_mut_data_integrity_proof(&mut self) -> Result<&mut DataIntegrityProof> { 125 | self.proof 126 | .find_mut_value(&|proof: &mut CredentialProof| match proof { 127 | CredentialProof::AnonCredsDataIntegrityProof(proof) => Some(proof), 128 | _ => None, 129 | }) 130 | .ok_or_else(|| err_msg!("Credential does not contain data integrity proof")) 131 | } 132 | 133 | pub(crate) fn validate(&self) -> Result<()> { 134 | let version = self.context.version()?; 135 | 136 | self.context.validate()?; 137 | 138 | if !self.type_.0.contains(&W3C_CREDENTIAL_TYPE.to_string()) { 139 | return Err(err_msg!("Credential does not contain w3c credential type")); 140 | } 141 | 142 | if version == VerifiableCredentialSpecVersion::V1_1 && self.issuance_date.is_none() { 143 | return Err(err_msg!( 144 | "V1.1 Credential must include `issuanceDate` property" 145 | )); 146 | } 147 | 148 | Ok(()) 149 | } 150 | } 151 | 152 | #[cfg(test)] 153 | mod tests { 154 | use super::W3CCredential; 155 | 156 | #[test] 157 | fn serde_w3c_credential() { 158 | let cred_json = include_str!("sample_credential.json"); 159 | let cred1: W3CCredential = 160 | serde_json::from_str(&cred_json).expect("Error deserializing w3c credential"); 161 | let out_json = serde_json::to_string(&cred1).expect("Error serializing w3c credential"); 162 | let cred2: W3CCredential = 163 | serde_json::from_str(&out_json).expect("Error deserializing w3c credential"); 164 | assert_eq!(cred1, cred2); 165 | } 166 | 167 | #[test] 168 | fn serde_w3c_credential_deny_unknown() { 169 | let cred_json = include_str!("sample_credential.json"); 170 | let mut cred: serde_json::Value = 171 | serde_json::from_str(cred_json).expect("Error deserializing w3c credential"); 172 | cred.as_object_mut() 173 | .unwrap() 174 | .insert("prop".into(), "val".into()); 175 | let res = serde_json::from_value::(cred); 176 | assert!(res.is_err()); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/data_types/w3c/credential_attributes.rs: -------------------------------------------------------------------------------- 1 | use crate::error::ValidationError; 2 | use crate::types::{CredentialValues, MakeCredentialValues}; 3 | use crate::utils::validation::Validatable; 4 | use std::collections::HashMap; 5 | use zeroize::Zeroize; 6 | 7 | #[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)] 8 | pub struct CredentialSubject(pub HashMap); 9 | 10 | #[cfg(feature = "zeroize")] 11 | impl Drop for CredentialSubject { 12 | fn drop(&mut self) { 13 | self.zeroize(); 14 | } 15 | } 16 | 17 | #[cfg(feature = "zeroize")] 18 | impl Zeroize for CredentialSubject { 19 | fn zeroize(&mut self) { 20 | for attr in self.0.values_mut() { 21 | if let CredentialAttributeValue::String(attr) = attr { 22 | attr.zeroize() 23 | } 24 | } 25 | } 26 | } 27 | 28 | impl Validatable for CredentialSubject { 29 | fn validate(&self) -> std::result::Result<(), ValidationError> { 30 | if self.0.is_empty() { 31 | return Err( 32 | "CredentialAttributes validation failed: empty list has been passed".into(), 33 | ); 34 | } 35 | Ok(()) 36 | } 37 | } 38 | 39 | impl From<&CredentialValues> for CredentialSubject { 40 | fn from(values: &CredentialValues) -> Self { 41 | CredentialSubject( 42 | values 43 | .0 44 | .iter() 45 | .map(|(attribute, values)| { 46 | if let Ok(number) = values.raw.parse::() { 47 | ( 48 | attribute.to_string(), 49 | CredentialAttributeValue::Number(number), 50 | ) 51 | } else { 52 | ( 53 | attribute.to_string(), 54 | CredentialAttributeValue::String(values.raw.to_string()), 55 | ) 56 | } 57 | }) 58 | .collect(), 59 | ) 60 | } 61 | } 62 | 63 | impl CredentialSubject { 64 | pub(crate) fn add_attribute(&mut self, attribute: String, value: CredentialAttributeValue) { 65 | self.0.insert(attribute, value); 66 | } 67 | 68 | pub(crate) fn add_predicate(&mut self, attribute: String) -> crate::Result<()> { 69 | match self.0.get(&attribute) { 70 | Some(value) => { 71 | match value { 72 | CredentialAttributeValue::String(_) | CredentialAttributeValue::Number(_) => { 73 | Err(err_msg!("Predicate cannot be added for revealed attribute")) 74 | } 75 | CredentialAttributeValue::Bool(_) => { 76 | // predicate already exists 77 | Ok(()) 78 | } 79 | } 80 | } 81 | None => { 82 | self.0 83 | .insert(attribute, CredentialAttributeValue::Bool(true)); 84 | Ok(()) 85 | } 86 | } 87 | } 88 | 89 | pub(crate) fn encode(&self) -> crate::Result { 90 | let mut cred_values = MakeCredentialValues::default(); 91 | for (attribute, raw_value) in self.0.iter() { 92 | match raw_value { 93 | CredentialAttributeValue::String(raw_value) => { 94 | cred_values.add_raw(attribute, raw_value)? 95 | } 96 | CredentialAttributeValue::Number(raw_value) => { 97 | cred_values.add_raw(attribute, raw_value.to_string())? 98 | } 99 | value => { 100 | return Err(err_msg!( 101 | "Encoding is not supported for credential value {:?}", 102 | value 103 | )); 104 | } 105 | } 106 | } 107 | Ok(cred_values.into()) 108 | } 109 | } 110 | 111 | #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] 112 | #[serde(untagged)] 113 | pub enum CredentialAttributeValue { 114 | // attribute representation 115 | String(String), 116 | Number(i32), 117 | // predicates representation 118 | Bool(bool), 119 | } 120 | 121 | impl ToString for CredentialAttributeValue { 122 | fn to_string(&self) -> String { 123 | match self { 124 | CredentialAttributeValue::String(string) => string.to_owned(), 125 | CredentialAttributeValue::Number(number) => number.to_string(), 126 | CredentialAttributeValue::Bool(bool) => bool.to_string(), 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/data_types/w3c/format.rs: -------------------------------------------------------------------------------- 1 | pub mod base64_msgpack { 2 | use serde::{de::Visitor, ser::Error, Deserialize, Serialize}; 3 | use std::marker::PhantomData; 4 | 5 | use crate::utils::{base64, msg_pack}; 6 | 7 | pub const BASE_HEADER: &str = "u"; 8 | 9 | pub fn serialize(obj: &T, serializer: S) -> std::result::Result 10 | where 11 | T: Serialize, 12 | S: serde::Serializer, 13 | { 14 | let msg_pack_encoded = msg_pack::encode(obj).map_err(S::Error::custom)?; 15 | let base64_encoded = base64::encode(msg_pack_encoded); 16 | serializer.collect_str(&format_args!("{}{}", BASE_HEADER, base64_encoded)) 17 | } 18 | 19 | pub fn deserialize<'de, T, D>(deserializer: D) -> std::result::Result 20 | where 21 | D: serde::Deserializer<'de>, 22 | T: for<'a> Deserialize<'a>, 23 | { 24 | struct DeserVisitor(PhantomData); 25 | 26 | impl<'v, VT> Visitor<'v> for DeserVisitor 27 | where 28 | VT: for<'a> Deserialize<'a>, 29 | { 30 | type Value = VT; 31 | 32 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 33 | formatter.write_str("expected base64-msgpack encoded value") 34 | } 35 | 36 | fn visit_str(self, v: &str) -> Result 37 | where 38 | E: serde::de::Error, 39 | { 40 | let Some(obj) = v 41 | .strip_prefix(BASE_HEADER) 42 | .and_then(|v| base64::decode(v).ok()) 43 | .and_then(|v| msg_pack::decode(&v).ok()) 44 | else { 45 | return Err(E::custom(format!( 46 | "Unexpected multibase base header: {:?}", 47 | v 48 | ))); 49 | }; 50 | Ok(obj) 51 | } 52 | } 53 | 54 | deserializer.deserialize_str(DeserVisitor(PhantomData)) 55 | } 56 | } 57 | 58 | #[cfg(test)] 59 | mod tests { 60 | 61 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 62 | struct TestObject { 63 | type_: String, 64 | value: i32, 65 | } 66 | 67 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 68 | #[serde(transparent)] 69 | struct Container(#[serde(with = "super::base64_msgpack")] TestObject); 70 | 71 | #[test] 72 | fn base64_msgpack_serde_works() { 73 | let obj = Container(TestObject { 74 | type_: "Test".to_string(), 75 | value: 1, 76 | }); 77 | let encoded = serde_json::to_string(&obj).unwrap(); 78 | assert_eq!("\"ugqV0eXBlX6RUZXN0pXZhbHVlAQ\"", encoded); 79 | let decoded: Container = serde_json::from_str(&encoded).unwrap(); 80 | assert_eq!(obj, decoded) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/data_types/w3c/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | 3 | pub mod constants; 4 | pub mod context; 5 | /// AnonCreds W3C Credentials definition 6 | pub mod credential; 7 | pub mod credential_attributes; 8 | pub mod one_or_many; 9 | /// AnonCreds W3C Presentation definition 10 | pub mod presentation; 11 | pub mod proof; 12 | pub mod uri; 13 | 14 | mod format; 15 | 16 | #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] 17 | pub enum VerifiableCredentialSpecVersion { 18 | V1_1, 19 | V2_0, 20 | } 21 | 22 | impl Default for VerifiableCredentialSpecVersion { 23 | fn default() -> Self { 24 | VerifiableCredentialSpecVersion::V1_1 25 | } 26 | } 27 | 28 | impl TryFrom<&str> for VerifiableCredentialSpecVersion { 29 | type Error = Error; 30 | 31 | fn try_from(value: &str) -> Result { 32 | match value { 33 | "1.1" => Ok(VerifiableCredentialSpecVersion::V1_1), 34 | "2.0" => Ok(VerifiableCredentialSpecVersion::V2_0), 35 | value => Err(err_msg!( 36 | "Unsupported w3c version of verifiable credential specification {}", 37 | value 38 | )), 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/data_types/w3c/one_or_many.rs: -------------------------------------------------------------------------------- 1 | /// Helper structure to handle the case when value is either single object or list of objects 2 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 3 | #[serde(untagged)] 4 | pub enum OneOrMany { 5 | Many(Vec), 6 | One(T), 7 | } 8 | 9 | impl Default for OneOrMany { 10 | fn default() -> Self { 11 | OneOrMany::Many(Vec::new()) 12 | } 13 | } 14 | 15 | impl OneOrMany { 16 | pub fn find_value<'a, F: 'a>(&'a self, closure: &dyn Fn(&'a T) -> Option) -> Option { 17 | match self { 18 | OneOrMany::One(value) => closure(value), 19 | OneOrMany::Many(values) => values.iter().find_map(closure), 20 | } 21 | } 22 | 23 | pub(crate) fn find_mut_value<'a, F: 'a>( 24 | &'a mut self, 25 | closure: &dyn Fn(&'a mut T) -> Option, 26 | ) -> Option { 27 | match self { 28 | OneOrMany::One(value) => closure(value), 29 | OneOrMany::Many(values) => values.iter_mut().find_map(closure), 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/data_types/w3c/presentation.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::data_types::w3c::constants::{ANONCREDS_PRESENTATION_TYPES, W3C_PRESENTATION_TYPE}; 4 | use crate::data_types::w3c::context::Contexts; 5 | use crate::data_types::w3c::credential::{Types, W3CCredential}; 6 | use crate::data_types::w3c::proof::{DataIntegrityProof, PresentationProofValue}; 7 | use crate::data_types::w3c::VerifiableCredentialSpecVersion; 8 | use crate::Result; 9 | 10 | /// AnonCreds W3C Presentation definition 11 | /// Note, that this definition is tied to AnonCreds W3C form 12 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 13 | #[serde(rename_all = "camelCase")] 14 | #[serde(deny_unknown_fields)] 15 | pub struct W3CPresentation { 16 | #[serde(rename = "@context")] 17 | pub context: Contexts, 18 | #[serde(alias = "@type")] 19 | #[serde(rename = "type")] 20 | pub type_: Types, 21 | pub verifiable_credential: Vec, 22 | pub proof: DataIntegrityProof, 23 | } 24 | 25 | impl W3CPresentation { 26 | pub fn new( 27 | verifiable_credential: Vec, 28 | proof: DataIntegrityProof, 29 | version: Option<&VerifiableCredentialSpecVersion>, 30 | ) -> Self { 31 | let version = version.cloned().unwrap_or_default(); 32 | Self { 33 | context: Contexts::get(&version), 34 | type_: ANONCREDS_PRESENTATION_TYPES.clone(), 35 | verifiable_credential, 36 | proof, 37 | } 38 | } 39 | 40 | pub fn version(&self) -> Result { 41 | self.context.version() 42 | } 43 | 44 | pub fn get_presentation_proof(&self) -> Result<&PresentationProofValue> { 45 | self.proof.get_presentation_proof() 46 | } 47 | 48 | pub(crate) fn validate(&self) -> Result<()> { 49 | self.context.validate()?; 50 | if !self.type_.0.contains(&W3C_PRESENTATION_TYPE.to_string()) { 51 | return Err(err_msg!( 52 | "Credential does not contain w3c presentation type" 53 | )); 54 | } 55 | Ok(()) 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::W3CPresentation; 62 | 63 | #[test] 64 | fn serde_w3c_presentation() { 65 | let pres_json = include_str!("sample_presentation.json"); 66 | let pres1: W3CPresentation = 67 | serde_json::from_str(&pres_json).expect("Error deserializing w3c presentation"); 68 | let out_json = serde_json::to_string(&pres1).expect("Error serializing w3c presentation"); 69 | let pres2: W3CPresentation = 70 | serde_json::from_str(&out_json).expect("Error deserializing w3c presentation"); 71 | assert_eq!(pres1, pres2); 72 | } 73 | 74 | #[test] 75 | fn serde_w3c_presentation_deny_unknown() { 76 | let pres_json = include_str!("sample_presentation.json"); 77 | let mut pres: serde_json::Value = 78 | serde_json::from_str(pres_json).expect("Error deserializing w3c presentation"); 79 | pres.as_object_mut() 80 | .unwrap() 81 | .insert("prop".into(), "val".into()); 82 | let res = serde_json::from_value::(pres); 83 | assert!(res.is_err()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/data_types/w3c/sample_credential.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | "https://www.w3.org/2018/credentials/v1", 4 | "https://w3id.org/security/data-integrity/v2", 5 | { "@vocab": "https://www.w3.org/ns/credentials/issuer-dependent#" } 6 | ], 7 | "type": ["VerifiableCredential"], 8 | "issuer": "issuer:id/path=bar", 9 | "credentialSubject": { 10 | "height": 175, 11 | "age": 28, 12 | "id": "example_id", 13 | "sex": "male", 14 | "name": "Alex" 15 | }, 16 | "proof": [ 17 | { 18 | "cryptosuite": "anoncreds-2023", 19 | "type": "DataIntegrityProof", 20 | "proofPurpose": "assertionMethod", 21 | "verificationMethod": "creddef:government", 22 | "proofValue": "ukgGEqXNjaGVtYV9pZLFzY2hlbWE6Z292ZXJubWVudKtjcmVkX2RlZl9pZLJjcmVkZGVmOmdvdmVybm1lbnSpc2lnbmF0dXJlgqxwX2NyZWRlbnRpYWyEo21fMtwAIGJNzM3M8cyxzNjM82jM-XTM7cylAMz4zNAcAGAwzO8wzI8oFEvMzcyMzOtgzMvMmCChYdwBAGZczOrMr8zwBszaS8ydzMBozMrM-wnM8xlVzMbMjDbMuEbM8MyzzIRKzOzMmcyrexbM2EHM3x9IUFvM_cyhfCXM_czizL4FJMzezLPMlWDM723MukF3zPfM4cyGW8zLawfMiEvMmADM6czTzM3Mtcz1zOoPci7M6cyVHMyazJPMrsyezOdBMVllXkvMxRHMzAIjzJLMiCc6zOTM7nbM8R_MqsyrSczLEcyhzJ_Mq8y0zJopzPVtfT3MhkA0zOJyIiMREF_M4C_M6QEEVHnMhEAHzMzM6ywczOUCzJ7MpczMzP0XzJRszIvMvCY8zLN6zJnMkFPMg8zbb8zGzIIzLi0yzNsuET7M8CPM-8zszLHMjszczNLMphRnK8ytLDXM-8zpzN9WzPJvSEUpzO5IzM93zN17DyxeczvMjHjM2sy_Isz6dXA9dB3MyhfMqcz9M1HM3m8ozK_M2mTM1wHM5MzEzP9AG8zZdxzM-8yWzNxUFMygH8zWzOXMslrMgQLM8KFl3ABLEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVczdzKA_zMllRBZUzKPMlcyjzNMacaF23AFVDMzYzKBqzPRBKcyrbcz-zN_M5cyHzOASzIHMpcyxE8ytOMzoC1kkU8ywzJ_M1syUzO9dJszBAFZMzPIoChZBzNvMo0hFzPvM9syCRsyxzPxjzP_M7szGzPk9zKDMgMyWRMzYzOAnzOzM4MykDDnMwit6T8znZ8z5CcyWzKFpzOXMucyfzK9ozNMnzMVqzNIHzK0NRcyoaxfMozvMzWTM4hsiLAPMkMzyzIkYzOIkzOTM_SrMlwRwNmYQYMy3zKg2zPI-zN_MzszTQcy6zMp2csziT0_M3AItzNFbbMzZzN1UHszkXDoSzIA0c8zrzIDMssynzNkqzKrMwsyRzMo0UczkQitOzNpyZQo8zJbMmMzcLmTMj8yTzNVzzKJdesyizKHM8szrBszYzIPMmhpozM48LVjM5MyKzIXM2jzMxkRBzJd_Ll7M9i7M_MybKcz6TBXM3yTM8czGO1YJzIDM1Uw5zPLMxcyfzKXMhkzMvcz0zI0BzM9azPHMlkfMucztY8zlWy8hGFDM3nbM2RVhFMy8VMy2zJMxzIfM-h7MzVgUzLQQzMTM2ynM_szxL1XMm8y1zK8czPrM7szAzJ_MmV47zK9ezJAbAAh3CczxK2TMwsyhAczEzKlXzIvM_Mz8zObMm24BchhvTgXMuxhTzLA9SnE8cDkxzLfMmsyRzIFuC6xyX2NyZWRlbnRpYWzAu3NpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZoKic2XcAQBjzLMfdsy5fMy7zLV-bQnM-EPMk8zpzMd7GMz1SszlWMyqzPTMyTjM7cyBzMMdzOXMiG9KzN_Mt8zRzNPMhxU8B8zKzKM3zIcDDjjMtMzPSWMZzMrMoyLM5czHeRLMtMyKXSjMh0rMz3oac8zdT0FvHszTzJ5qc8y3KTXM0MyuLszqFgZJzLrM4FLMkCg0zNXMkczxKTDMvU9kzOfMjMzUzLjM5syDzIEvzI1tNlPMmGgXzLQnzKzMk8ybzO0bzK7MqlHM-CfMrSLMjszxzKvM-8yKIyhpV3QpzM3M0cyAzKjM0MypfMytMl7M2Mz9Ksy-CiLM_1EpzIBRzNXM_8zzzIzMhMzXDMzcPMzxEsy8zM0ZSDk5zLwuFlAXzMHMzH_MqkTMijbMwMyVzL3Mx8z-zKvMowfM4cyzVnPM2cyfXMyEzNBKzJ4pzKoAzOzM2szXzPo1zIbM3MysLczyIcySCszlWcybOcydzJ4LzKnMmMzQzLzMj8yQIMy6HSVrJ8zleczMzOsVzJkizKyhY9wAIMypdcyGWSp7eSsvfczQUlzM7symJiTMzBLMjcyHzPM0zKwbWzoDVhFQdg" 23 | } 24 | ], 25 | "issuanceDate": "2024-02-01T18:59:54.691128Z" 26 | } 27 | -------------------------------------------------------------------------------- /src/data_types/w3c/uri.rs: -------------------------------------------------------------------------------- 1 | use serde::{de, Deserialize, Deserializer}; 2 | use serde_json::Value; 3 | 4 | use crate::utils::validation::URI_IDENTIFIER; 5 | 6 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize)] 7 | pub struct URI(pub String); 8 | 9 | impl From<&str> for URI { 10 | fn from(uri: &str) -> Self { 11 | URI(uri.to_string()) 12 | } 13 | } 14 | 15 | impl<'de> Deserialize<'de> for URI { 16 | fn deserialize(deserializer: D) -> Result 17 | where 18 | D: Deserializer<'de>, 19 | { 20 | let v = Value::deserialize(deserializer)?; 21 | 22 | let id: String = Deserialize::deserialize(v).map_err(de::Error::custom)?; 23 | 24 | URI_IDENTIFIER 25 | .captures(&id) 26 | .ok_or_else(|| de::Error::custom("Invalid URI passed"))?; 27 | 28 | Ok(URI(id)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ffi/cred_def.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use ffi_support::FfiStr; 4 | 5 | use super::error::{catch_error, ErrorCode}; 6 | use super::object::ObjectHandle; 7 | use crate::data_types::cred_def::CredentialDefinition; 8 | use crate::services::{ 9 | issuer::create_credential_definition, 10 | types::{ 11 | CredentialDefinitionConfig, CredentialDefinitionPrivate, 12 | CredentialKeyCorrectnessProof as KeyCorrectnessProof, SignatureType, 13 | }, 14 | }; 15 | 16 | #[no_mangle] 17 | pub extern "C" fn anoncreds_create_credential_definition( 18 | schema_id: FfiStr, 19 | schema: ObjectHandle, 20 | tag: FfiStr, 21 | issuer_id: FfiStr, 22 | signature_type: FfiStr, 23 | support_revocation: i8, 24 | cred_def_p: *mut ObjectHandle, 25 | cred_def_pvt_p: *mut ObjectHandle, 26 | key_proof_p: *mut ObjectHandle, 27 | ) -> ErrorCode { 28 | catch_error(|| { 29 | check_useful_c_ptr!(cred_def_p); 30 | check_useful_c_ptr!(cred_def_pvt_p); 31 | check_useful_c_ptr!(key_proof_p); 32 | let tag = tag.as_opt_str().ok_or_else(|| err_msg!("Missing tag"))?; 33 | let schema_id = schema_id 34 | .as_opt_str() 35 | .ok_or_else(|| err_msg!("Missing schema id"))? 36 | .try_into()?; 37 | let signature_type = { 38 | let stype = signature_type 39 | .as_opt_str() 40 | .ok_or_else(|| err_msg!("Missing signature type"))?; 41 | SignatureType::from_str(stype).map_err(err_map!(Input))? 42 | }; 43 | let issuer_id = issuer_id 44 | .as_opt_str() 45 | .ok_or_else(|| err_msg!("Missing issuer id"))? 46 | .try_into()?; 47 | 48 | let (cred_def, cred_def_pvt, key_proof) = create_credential_definition( 49 | schema_id, 50 | schema.load()?.cast_ref()?, 51 | issuer_id, 52 | tag, 53 | signature_type, 54 | CredentialDefinitionConfig { 55 | support_revocation: support_revocation != 0, 56 | }, 57 | )?; 58 | let cred_def = ObjectHandle::create(cred_def)?; 59 | let cred_def_pvt = ObjectHandle::create(cred_def_pvt)?; 60 | let key_proof = ObjectHandle::create(key_proof)?; 61 | unsafe { 62 | *cred_def_p = cred_def; 63 | *cred_def_pvt_p = cred_def_pvt; 64 | *key_proof_p = key_proof; 65 | } 66 | Ok(()) 67 | }) 68 | } 69 | 70 | impl_anoncreds_object!(CredentialDefinition, "CredentialDefinition"); 71 | impl_anoncreds_object_from_json!( 72 | CredentialDefinition, 73 | anoncreds_credential_definition_from_json 74 | ); 75 | 76 | impl_anoncreds_object!(CredentialDefinitionPrivate, "CredentialDefinitionPrivate"); 77 | impl_anoncreds_object_from_json!( 78 | CredentialDefinitionPrivate, 79 | anoncreds_credential_definition_private_from_json 80 | ); 81 | 82 | impl_anoncreds_object!(KeyCorrectnessProof, "KeyCorrectnessProof"); 83 | impl_anoncreds_object_from_json!( 84 | KeyCorrectnessProof, 85 | anoncreds_key_correctness_proof_from_json 86 | ); 87 | -------------------------------------------------------------------------------- /src/ffi/cred_offer.rs: -------------------------------------------------------------------------------- 1 | use ffi_support::FfiStr; 2 | 3 | use super::error::{catch_error, ErrorCode}; 4 | use super::object::ObjectHandle; 5 | use crate::services::{issuer::create_credential_offer, types::CredentialOffer}; 6 | 7 | #[no_mangle] 8 | pub extern "C" fn anoncreds_create_credential_offer( 9 | schema_id: FfiStr, 10 | cred_def_id: FfiStr, 11 | key_proof: ObjectHandle, 12 | cred_offer_p: *mut ObjectHandle, 13 | ) -> ErrorCode { 14 | catch_error(|| { 15 | check_useful_c_ptr!(cred_offer_p); 16 | let schema_id = schema_id 17 | .as_opt_str() 18 | .ok_or_else(|| err_msg!("Missing schema ID"))? 19 | .try_into()?; 20 | let cred_def_id = cred_def_id 21 | .as_opt_str() 22 | .ok_or_else(|| err_msg!("Missing cred def ID"))? 23 | .try_into()?; 24 | let cred_offer = 25 | create_credential_offer(schema_id, cred_def_id, key_proof.load()?.cast_ref()?)?; 26 | let cred_offer = ObjectHandle::create(cred_offer)?; 27 | unsafe { *cred_offer_p = cred_offer }; 28 | Ok(()) 29 | }) 30 | } 31 | 32 | impl_anoncreds_object!(CredentialOffer, "CredentialOffer"); 33 | impl_anoncreds_object_from_json!(CredentialOffer, anoncreds_credential_offer_from_json); 34 | -------------------------------------------------------------------------------- /src/ffi/cred_req.rs: -------------------------------------------------------------------------------- 1 | use ffi_support::FfiStr; 2 | 3 | use super::error::{catch_error, ErrorCode}; 4 | use super::object::ObjectHandle; 5 | use crate::data_types::cred_def::CredentialDefinition; 6 | use crate::data_types::link_secret::LinkSecret; 7 | use crate::services::{ 8 | prover::create_credential_request, 9 | types::{CredentialRequest, CredentialRequestMetadata}, 10 | }; 11 | 12 | #[no_mangle] 13 | pub extern "C" fn anoncreds_create_credential_request( 14 | entropy: FfiStr, 15 | prover_did: FfiStr, 16 | cred_def: ObjectHandle, 17 | link_secret: FfiStr, 18 | link_secret_id: FfiStr, 19 | cred_offer: ObjectHandle, 20 | cred_req_p: *mut ObjectHandle, 21 | cred_req_meta_p: *mut ObjectHandle, 22 | ) -> ErrorCode { 23 | catch_error(|| { 24 | check_useful_c_ptr!(cred_req_p); 25 | check_useful_c_ptr!(cred_req_meta_p); 26 | 27 | let link_secret = link_secret 28 | .as_opt_str() 29 | .ok_or_else(|| err_msg!("Missing link secret"))?; 30 | let link_secret = LinkSecret::try_from(link_secret)?; 31 | 32 | let link_secret_id = link_secret_id 33 | .as_opt_str() 34 | .ok_or_else(|| err_msg!("Missing link secret ID"))?; 35 | let entropy = entropy.as_opt_str(); 36 | let prover_did = prover_did.as_opt_str(); 37 | 38 | let cred_def = cred_def.load()?; 39 | let cred_def: &CredentialDefinition = cred_def.cast_ref()?; 40 | 41 | let (cred_req, cred_req_metadata) = create_credential_request( 42 | entropy, 43 | prover_did, 44 | cred_def, 45 | &link_secret, 46 | link_secret_id, 47 | cred_offer.load()?.cast_ref()?, 48 | )?; 49 | 50 | let cred_req = ObjectHandle::create(cred_req)?; 51 | let cred_req_metadata = ObjectHandle::create(cred_req_metadata)?; 52 | 53 | unsafe { 54 | *cred_req_p = cred_req; 55 | *cred_req_meta_p = cred_req_metadata; 56 | }; 57 | Ok(()) 58 | }) 59 | } 60 | 61 | impl_anoncreds_object!(CredentialRequest, "CredentialRequest"); 62 | impl_anoncreds_object_from_json!(CredentialRequest, anoncreds_credential_request_from_json); 63 | 64 | impl_anoncreds_object!(CredentialRequestMetadata, "CredentialRequestMetadata"); 65 | impl_anoncreds_object_from_json!( 66 | CredentialRequestMetadata, 67 | anoncreds_credential_request_metadata_from_json 68 | ); 69 | -------------------------------------------------------------------------------- /src/ffi/credential.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::c_char; 2 | use std::ptr; 3 | 4 | use ffi_support::{rust_string_to_c, FfiStr}; 5 | 6 | use super::error::{catch_error, ErrorCode}; 7 | use super::object::{AnoncredsObject, ObjectHandle}; 8 | use super::util::FfiStrList; 9 | use crate::data_types::link_secret::LinkSecret; 10 | use crate::error::Result; 11 | use crate::services::{ 12 | helpers::encode_credential_attribute, 13 | issuer::create_credential, 14 | prover::process_credential, 15 | types::{Credential, CredentialRevocationConfig, MakeCredentialValues}, 16 | }; 17 | use crate::types::CredentialValues; 18 | use crate::Error; 19 | 20 | #[derive(Debug)] 21 | #[repr(C)] 22 | pub struct FfiCredRevInfo { 23 | reg_def: ObjectHandle, 24 | reg_def_private: ObjectHandle, 25 | status_list: ObjectHandle, 26 | reg_idx: i64, 27 | } 28 | 29 | pub(crate) struct RevocationConfig { 30 | reg_def: AnoncredsObject, 31 | reg_def_private: AnoncredsObject, 32 | status_list: AnoncredsObject, 33 | reg_idx: u32, 34 | } 35 | 36 | impl TryFrom<&FfiCredRevInfo> for RevocationConfig { 37 | type Error = Error; 38 | 39 | fn try_from(value: &FfiCredRevInfo) -> Result { 40 | Ok(Self { 41 | reg_def: value.reg_def.load()?, 42 | reg_def_private: value.reg_def_private.load()?, 43 | reg_idx: value 44 | .reg_idx 45 | .try_into() 46 | .map_err(|_| err_msg!("Invalid revocation index"))?, 47 | status_list: value.status_list.load()?, 48 | }) 49 | } 50 | } 51 | 52 | impl<'a> TryFrom<&'a RevocationConfig> for CredentialRevocationConfig<'a> { 53 | type Error = Error; 54 | 55 | fn try_from(value: &'a RevocationConfig) -> Result { 56 | Ok(CredentialRevocationConfig { 57 | reg_def: value.reg_def.cast_ref()?, 58 | reg_def_private: value.reg_def_private.cast_ref()?, 59 | registry_idx: value.reg_idx, 60 | status_list: value.status_list.cast_ref()?, 61 | }) 62 | } 63 | } 64 | 65 | impl_anoncreds_object!(Credential, "Credential"); 66 | impl_anoncreds_object_from_json!(Credential, anoncreds_credential_from_json); 67 | 68 | #[no_mangle] 69 | pub extern "C" fn anoncreds_create_credential( 70 | cred_def: ObjectHandle, 71 | cred_def_private: ObjectHandle, 72 | cred_offer: ObjectHandle, 73 | cred_request: ObjectHandle, 74 | attr_names: FfiStrList, 75 | attr_raw_values: FfiStrList, 76 | attr_enc_values: FfiStrList, 77 | revocation: *const FfiCredRevInfo, 78 | cred_p: *mut ObjectHandle, 79 | ) -> ErrorCode { 80 | catch_error(|| { 81 | check_useful_c_ptr!(cred_p); 82 | 83 | let cred_values = _encoded_credential_values(attr_names, attr_raw_values, attr_enc_values)?; 84 | let revocation_config = _revocation_config(revocation)?; 85 | 86 | let cred = create_credential( 87 | cred_def.load()?.cast_ref()?, 88 | cred_def_private.load()?.cast_ref()?, 89 | cred_offer.load()?.cast_ref()?, 90 | cred_request.load()?.cast_ref()?, 91 | cred_values, 92 | revocation_config 93 | .as_ref() 94 | .map(TryInto::try_into) 95 | .transpose()?, 96 | )?; 97 | let cred = ObjectHandle::create(cred)?; 98 | unsafe { 99 | *cred_p = cred; 100 | }; 101 | Ok(()) 102 | }) 103 | } 104 | 105 | #[no_mangle] 106 | pub extern "C" fn anoncreds_encode_credential_attributes( 107 | attr_raw_values: FfiStrList, 108 | result_p: *mut *const c_char, 109 | ) -> ErrorCode { 110 | catch_error(|| { 111 | let mut result = String::new(); 112 | for raw_val in attr_raw_values.as_slice() { 113 | let enc_val = encode_credential_attribute( 114 | raw_val 115 | .as_opt_str() 116 | .ok_or_else(|| err_msg!("Missing attribute raw value"))?, 117 | )?; 118 | if !result.is_empty() { 119 | result.push(','); 120 | } 121 | result.push_str(enc_val.as_str()); 122 | } 123 | unsafe { *result_p = rust_string_to_c(result) }; 124 | Ok(()) 125 | }) 126 | } 127 | 128 | #[no_mangle] 129 | pub extern "C" fn anoncreds_process_credential( 130 | cred: ObjectHandle, 131 | cred_req_metadata: ObjectHandle, 132 | link_secret: FfiStr, 133 | cred_def: ObjectHandle, 134 | rev_reg_def: ObjectHandle, 135 | cred_p: *mut ObjectHandle, 136 | ) -> ErrorCode { 137 | catch_error(|| { 138 | check_useful_c_ptr!(cred_p); 139 | 140 | let link_secret = _link_secret(link_secret)?; 141 | 142 | let mut cred = cred 143 | .load()? 144 | .cast_ref::()? 145 | .try_clone() 146 | .map_err(err_map!(Unexpected, "Error copying credential"))?; 147 | process_credential( 148 | &mut cred, 149 | cred_req_metadata.load()?.cast_ref()?, 150 | &link_secret, 151 | cred_def.load()?.cast_ref()?, 152 | rev_reg_def 153 | .opt_load()? 154 | .as_ref() 155 | .map(AnoncredsObject::cast_ref) 156 | .transpose()?, 157 | )?; 158 | let cred = ObjectHandle::create(cred)?; 159 | unsafe { *cred_p = cred }; 160 | Ok(()) 161 | }) 162 | } 163 | 164 | #[no_mangle] 165 | pub extern "C" fn anoncreds_credential_get_attribute( 166 | handle: ObjectHandle, 167 | name: FfiStr, 168 | result_p: *mut *const c_char, 169 | ) -> ErrorCode { 170 | catch_error(|| { 171 | check_useful_c_ptr!(result_p); 172 | let cred = handle.load()?; 173 | let cred = cred.cast_ref::()?; 174 | let val = match name.as_opt_str().unwrap_or_default() { 175 | "schema_id" => rust_string_to_c(cred.schema_id.clone()), 176 | "cred_def_id" => rust_string_to_c(cred.cred_def_id.to_string()), 177 | "rev_reg_id" => cred 178 | .rev_reg_id 179 | .as_ref() 180 | .map_or(ptr::null_mut(), |s| rust_string_to_c(s.to_string())), 181 | "rev_reg_index" => cred 182 | .signature 183 | .extract_index() 184 | .map_or(ptr::null_mut(), |s| rust_string_to_c(s.to_string())), 185 | s => return Err(err_msg!("Unsupported attribute: {}", s)), 186 | }; 187 | unsafe { *result_p = val }; 188 | Ok(()) 189 | }) 190 | } 191 | 192 | pub(crate) fn _encoded_credential_values( 193 | attr_names: FfiStrList, 194 | attr_raw_values: FfiStrList, 195 | attr_enc_values: FfiStrList, 196 | ) -> Result { 197 | if attr_names.is_empty() { 198 | return Err(err_msg!("Cannot create credential with no attribute")); 199 | } 200 | if attr_names.len() != attr_raw_values.len() { 201 | return Err(err_msg!( 202 | "Mismatch between length of attribute names and raw values" 203 | )); 204 | } 205 | let enc_values = attr_enc_values.as_slice(); 206 | let mut cred_values = MakeCredentialValues::default(); 207 | for (attr_idx, (name, raw)) in attr_names 208 | .as_slice() 209 | .iter() 210 | .zip(attr_raw_values.as_slice()) 211 | .enumerate() 212 | { 213 | let name = name 214 | .as_opt_str() 215 | .ok_or_else(|| err_msg!("Missing attribute name"))? 216 | .to_string(); 217 | let raw = raw 218 | .as_opt_str() 219 | .ok_or_else(|| err_msg!("Missing attribute raw value"))? 220 | .to_string(); 221 | let encoded = if attr_idx < enc_values.len() { 222 | enc_values[attr_idx].as_opt_str().map(str::to_string) 223 | } else { 224 | None 225 | }; 226 | if let Some(encoded) = encoded { 227 | cred_values.add_encoded(name, raw, encoded); 228 | } else { 229 | cred_values.add_raw(name, raw)?; 230 | } 231 | } 232 | Ok(cred_values.into()) 233 | } 234 | 235 | pub(crate) fn _revocation_config( 236 | revocation: *const FfiCredRevInfo, 237 | ) -> Result> { 238 | let revocation_config = if revocation.is_null() { 239 | None 240 | } else { 241 | let revocation = unsafe { &*revocation }; 242 | Some(RevocationConfig::try_from(revocation)?) 243 | }; 244 | Ok(revocation_config) 245 | } 246 | 247 | pub(crate) fn _link_secret(link_secret: FfiStr) -> Result { 248 | let link_secret = link_secret 249 | .as_opt_str() 250 | .ok_or_else(|| err_msg!("Missing link secret"))?; 251 | let link_secret = LinkSecret::try_from(link_secret)?; 252 | Ok(link_secret) 253 | } 254 | -------------------------------------------------------------------------------- /src/ffi/error.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, ErrorKind, Result}; 2 | 3 | use std::os::raw::c_char; 4 | use std::panic::{catch_unwind, UnwindSafe}; 5 | use std::sync::RwLock; 6 | 7 | use ffi_support::rust_string_to_c; 8 | 9 | use once_cell::sync::Lazy; 10 | 11 | static LAST_ERROR: Lazy>> = Lazy::new(|| RwLock::new(None)); 12 | 13 | #[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize)] 14 | #[repr(usize)] 15 | pub enum ErrorCode { 16 | Success = 0, 17 | Input = 1, 18 | IOError = 2, 19 | InvalidState = 3, 20 | Unexpected = 4, 21 | CredentialRevoked = 5, 22 | InvalidUserRevocId = 6, 23 | ProofRejected = 7, 24 | RevocationRegistryFull = 8, 25 | } 26 | 27 | impl From for ErrorCode { 28 | fn from(kind: ErrorKind) -> Self { 29 | match kind { 30 | ErrorKind::Input => Self::Input, 31 | ErrorKind::IOError => Self::IOError, 32 | ErrorKind::InvalidState => Self::InvalidState, 33 | ErrorKind::Unexpected => Self::Unexpected, 34 | ErrorKind::CredentialRevoked => Self::CredentialRevoked, 35 | ErrorKind::InvalidUserRevocId => Self::InvalidUserRevocId, 36 | ErrorKind::ProofRejected => Self::ProofRejected, 37 | ErrorKind::RevocationRegistryFull => Self::RevocationRegistryFull, 38 | } 39 | } 40 | } 41 | 42 | impl From> for ErrorCode { 43 | fn from(result: Result) -> Self { 44 | match result { 45 | Ok(_) => Self::Success, 46 | Err(err) => Self::from(err.kind()), 47 | } 48 | } 49 | } 50 | 51 | #[no_mangle] 52 | pub extern "C" fn anoncreds_get_current_error(error_json_p: *mut *const c_char) -> ErrorCode { 53 | trace!("anoncreds_get_current_error"); 54 | 55 | let error = rust_string_to_c(get_current_error_json()); 56 | unsafe { *error_json_p = error }; 57 | 58 | ErrorCode::Success 59 | } 60 | 61 | pub fn catch_error(f: F) -> ErrorCode 62 | where 63 | F: FnOnce() -> Result<()> + UnwindSafe, 64 | { 65 | match catch_unwind(f) { 66 | Ok(Ok(_)) => ErrorCode::Success, 67 | Ok(Err(err)) => { 68 | // lib error 69 | set_last_error(Some(err)) 70 | } 71 | Err(_) => { 72 | // panic error 73 | let err = err_msg!(Unexpected, "Panic during execution"); 74 | set_last_error(Some(err)) 75 | } 76 | } 77 | } 78 | 79 | pub fn get_current_error_json() -> String { 80 | if let Some(err) = Option::take(&mut *LAST_ERROR.write().unwrap()) { 81 | let message = err.to_string(); 82 | let code = ErrorCode::from(err.kind()) as usize; 83 | serde_json::json!({"code": code, "message": message}).to_string() 84 | } else { 85 | r#"{"code":0,"message":null}"#.to_owned() 86 | } 87 | } 88 | 89 | pub fn set_last_error(error: Option) -> ErrorCode { 90 | trace!("anoncreds_set_last_error"); 91 | let code = error 92 | .as_ref() 93 | .map_or(ErrorCode::Success, |err| err.kind().into()); 94 | *LAST_ERROR.write().unwrap() = error; 95 | code 96 | } 97 | -------------------------------------------------------------------------------- /src/ffi/link_secret.rs: -------------------------------------------------------------------------------- 1 | use super::error::{catch_error, ErrorCode}; 2 | use crate::services::prover::create_link_secret; 3 | use ffi_support::rust_string_to_c; 4 | use std::os::raw::c_char; 5 | 6 | #[no_mangle] 7 | pub extern "C" fn anoncreds_create_link_secret(link_secret_p: *mut *const c_char) -> ErrorCode { 8 | catch_error(|| { 9 | check_useful_c_ptr!(link_secret_p); 10 | let secret = create_link_secret()?; 11 | let dec_secret: String = secret.try_into()?; 12 | unsafe { *link_secret_p = rust_string_to_c(dec_secret) }; 13 | Ok(()) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/ffi/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! check_useful_c_ptr { 2 | ($e:expr) => { 3 | if ($e).is_null() { 4 | return Err(err_msg!( 5 | "Invalid pointer for result value: {}", 6 | stringify!($e) 7 | )); 8 | } 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/ffi/mod.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::c_char; 2 | 3 | use ffi_support::{rust_string_to_c, ByteBuffer}; 4 | use zeroize::Zeroize; 5 | 6 | pub static LIB_VERSION: &str = env!("CARGO_PKG_VERSION"); 7 | 8 | ffi_support::define_string_destructor!(anoncreds_string_free); 9 | 10 | #[no_mangle] 11 | pub extern "C" fn anoncreds_buffer_free(buffer: ByteBuffer) { 12 | ffi_support::abort_on_panic::with_abort_on_panic(|| { 13 | buffer.destroy_into_vec().zeroize(); 14 | }); 15 | } 16 | 17 | #[macro_use] 18 | mod macros; 19 | 20 | mod error; 21 | use self::error::{catch_error, ErrorCode}; 22 | 23 | #[macro_use] 24 | mod object; 25 | 26 | mod util; 27 | 28 | mod cred_def; 29 | mod cred_offer; 30 | mod cred_req; 31 | mod credential; 32 | mod link_secret; 33 | mod pres_req; 34 | mod presentation; 35 | mod revocation; 36 | mod schema; 37 | 38 | #[cfg(feature = "w3c")] 39 | mod w3c; 40 | 41 | #[no_mangle] 42 | pub extern "C" fn anoncreds_set_default_logger() -> ErrorCode { 43 | catch_error(|| { 44 | env_logger::init(); 45 | debug!("Initialized default logger"); 46 | Ok(()) 47 | }) 48 | } 49 | 50 | #[no_mangle] 51 | pub extern "C" fn anoncreds_version() -> *mut c_char { 52 | rust_string_to_c(LIB_VERSION.to_owned()) 53 | } 54 | -------------------------------------------------------------------------------- /src/ffi/object.rs: -------------------------------------------------------------------------------- 1 | use std::any::TypeId; 2 | use std::cmp::Eq; 3 | use std::collections::{BTreeMap, HashMap}; 4 | use std::fmt::Debug; 5 | use std::hash::{Hash, Hasher}; 6 | use std::ops::{Deref, DerefMut}; 7 | use std::os::raw::c_char; 8 | use std::sync::{Arc, Mutex}; 9 | 10 | use ffi_support::{rust_string_to_c, ByteBuffer}; 11 | use once_cell::sync::Lazy; 12 | use serde::Serialize; 13 | 14 | use super::error::{catch_error, ErrorCode}; 15 | use crate::error::Result; 16 | use crate::new_handle_type; 17 | 18 | pub static FFI_OBJECTS: Lazy>> = 19 | Lazy::new(|| Mutex::new(BTreeMap::new())); 20 | 21 | new_handle_type!(ObjectHandle, FFI_OBJECT_COUNTER); 22 | 23 | impl ObjectHandle { 24 | pub(crate) fn create(value: O) -> Result { 25 | let handle = Self::next(); 26 | FFI_OBJECTS 27 | .lock() 28 | .map_err(|_| err_msg!("Error locking object store"))? 29 | .insert(handle, AnoncredsObject::new(value)); 30 | Ok(handle) 31 | } 32 | 33 | pub(crate) fn load(self) -> Result { 34 | FFI_OBJECTS 35 | .lock() 36 | .map_err(|_| err_msg!("Error locking object store"))? 37 | .get(&self) 38 | .cloned() 39 | .ok_or_else(|| err_msg!("Invalid object handle")) 40 | } 41 | 42 | pub(crate) fn opt_load(self) -> Result> { 43 | if self.0 == 0 { 44 | Ok(None) 45 | } else { 46 | Some( 47 | FFI_OBJECTS 48 | .lock() 49 | .map_err(|_| err_msg!("Error locking object store"))? 50 | .get(&self) 51 | .cloned() 52 | .ok_or_else(|| err_msg!("Invalid object handle")), 53 | ) 54 | .transpose() 55 | } 56 | } 57 | 58 | pub(crate) fn remove(self) -> Result { 59 | FFI_OBJECTS 60 | .lock() 61 | .map_err(|_| err_msg!("Error locking object store"))? 62 | .remove(&self) 63 | .ok_or_else(|| err_msg!("Invalid object handle")) 64 | } 65 | } 66 | 67 | #[derive(Clone, Debug)] 68 | #[repr(transparent)] 69 | pub struct AnoncredsObject(Arc); 70 | 71 | impl AnoncredsObject { 72 | pub fn new(value: O) -> Self { 73 | Self(Arc::new(value)) 74 | } 75 | 76 | pub fn cast_ref(&self) -> Result<&O> { 77 | let result = unsafe { &*(&*self.0 as *const _ as *const O) }; 78 | if self.0.type_id() == TypeId::of::() { 79 | Ok(result) 80 | } else { 81 | Err(err_msg!( 82 | "Expected {} instance, received {}", 83 | result.type_name(), 84 | self.0.type_name() 85 | )) 86 | } 87 | } 88 | 89 | pub fn type_name(&self) -> &'static str { 90 | self.0.type_name() 91 | } 92 | } 93 | 94 | impl Hash for AnoncredsObject { 95 | fn hash(&self, state: &mut H) { 96 | std::ptr::hash(&*self.0, state); 97 | } 98 | } 99 | 100 | pub trait ToJson { 101 | fn to_json(&self) -> Result>; 102 | } 103 | 104 | impl ToJson for AnoncredsObject { 105 | #[inline] 106 | fn to_json(&self) -> Result> { 107 | self.0.to_json() 108 | } 109 | } 110 | 111 | impl ToJson for T 112 | where 113 | T: Serialize, 114 | { 115 | fn to_json(&self) -> Result> { 116 | serde_json::to_vec(self).map_err(err_map!("Error serializing object")) 117 | } 118 | } 119 | 120 | pub trait AnyAnoncredsObject: Debug + ToJson + Send + Sync { 121 | fn type_name(&self) -> &'static str; 122 | 123 | #[doc(hidden)] 124 | fn type_id(&self) -> TypeId 125 | where 126 | Self: 'static, 127 | { 128 | TypeId::of::() 129 | } 130 | } 131 | 132 | macro_rules! impl_anoncreds_object { 133 | ($ident:path, $name:expr) => { 134 | impl $crate::ffi::object::AnyAnoncredsObject for $ident { 135 | fn type_name(&self) -> &'static str { 136 | $name 137 | } 138 | } 139 | }; 140 | } 141 | 142 | macro_rules! impl_anoncreds_object_from_json { 143 | ($ident:path, $method:ident) => { 144 | #[no_mangle] 145 | pub extern "C" fn $method( 146 | json: ffi_support::ByteBuffer, 147 | result_p: *mut $crate::ffi::object::ObjectHandle, 148 | ) -> $crate::ffi::error::ErrorCode { 149 | $crate::ffi::error::catch_error(|| { 150 | check_useful_c_ptr!(result_p); 151 | let obj = serde_json::from_slice::<$ident>(json.as_slice())?; 152 | let handle = $crate::ffi::object::ObjectHandle::create(obj)?; 153 | unsafe { *result_p = handle }; 154 | Ok(()) 155 | }) 156 | } 157 | }; 158 | } 159 | 160 | #[no_mangle] 161 | pub extern "C" fn anoncreds_object_get_json( 162 | handle: ObjectHandle, 163 | result_p: *mut ByteBuffer, 164 | ) -> ErrorCode { 165 | catch_error(|| { 166 | check_useful_c_ptr!(result_p); 167 | let obj = handle.load()?; 168 | let json = obj.to_json()?; 169 | unsafe { *result_p = ByteBuffer::from_vec(json) }; 170 | Ok(()) 171 | }) 172 | } 173 | 174 | #[no_mangle] 175 | pub extern "C" fn anoncreds_object_get_type_name( 176 | handle: ObjectHandle, 177 | result_p: *mut *const c_char, 178 | ) -> ErrorCode { 179 | catch_error(|| { 180 | check_useful_c_ptr!(result_p); 181 | let obj = handle.load()?; 182 | let name = obj.type_name(); 183 | unsafe { *result_p = rust_string_to_c(name) }; 184 | Ok(()) 185 | }) 186 | } 187 | 188 | #[no_mangle] 189 | pub extern "C" fn anoncreds_object_free(handle: ObjectHandle) { 190 | handle.remove().ok(); 191 | } 192 | 193 | #[repr(transparent)] 194 | pub struct AnoncredsObjectList(Vec); 195 | 196 | impl AnoncredsObjectList { 197 | pub fn load(handles: &[ObjectHandle]) -> Result { 198 | let loaded = handles 199 | .iter() 200 | .map(|h| ObjectHandle::load(*h)) 201 | .collect::>()?; 202 | Ok(Self(loaded)) 203 | } 204 | 205 | pub fn refs(&self) -> Result> 206 | where 207 | T: AnyAnoncredsObject + 'static, 208 | { 209 | let mut refs = Vec::with_capacity(self.0.len()); 210 | for inst in &self.0 { 211 | let inst = inst.cast_ref::()?; 212 | refs.push(inst); 213 | } 214 | Ok(refs) 215 | } 216 | 217 | pub fn refs_map<'a, I, T>(&'a self, ids: &'a [I]) -> Result> 218 | where 219 | T: AnyAnoncredsObject + 'static, 220 | I: Eq + Hash, 221 | { 222 | let mut refs = HashMap::with_capacity(self.0.len()); 223 | for (inst, id) in self.0.iter().zip(ids) { 224 | let inst = inst.cast_ref::()?; 225 | refs.insert(id, inst); 226 | } 227 | Ok(refs) 228 | } 229 | } 230 | 231 | impl Deref for AnoncredsObjectList { 232 | type Target = Vec; 233 | 234 | fn deref(&self) -> &Self::Target { 235 | &self.0 236 | } 237 | } 238 | 239 | impl DerefMut for AnoncredsObjectList { 240 | fn deref_mut(&mut self) -> &mut Self::Target { 241 | &mut self.0 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/ffi/pres_req.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::c_char; 2 | 3 | use ffi_support::rust_string_to_c; 4 | 5 | use super::error::{catch_error, ErrorCode}; 6 | use crate::services::{types::PresentationRequest, verifier::generate_nonce}; 7 | 8 | impl_anoncreds_object!(PresentationRequest, "PresentationRequest"); 9 | impl_anoncreds_object_from_json!( 10 | PresentationRequest, 11 | anoncreds_presentation_request_from_json 12 | ); 13 | 14 | #[no_mangle] 15 | pub extern "C" fn anoncreds_generate_nonce(nonce_p: *mut *const c_char) -> ErrorCode { 16 | catch_error(|| { 17 | check_useful_c_ptr!(nonce_p); 18 | let nonce = generate_nonce()?.to_string(); 19 | unsafe { *nonce_p = rust_string_to_c(nonce) }; 20 | Ok(()) 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /src/ffi/revocation.rs: -------------------------------------------------------------------------------- 1 | use super::error::{catch_error, ErrorCode}; 2 | use super::object::{AnoncredsObject, ObjectHandle}; 3 | use super::util::FfiList; 4 | use crate::data_types::rev_status_list::RevocationStatusList; 5 | use crate::data_types::{ 6 | rev_reg::RevocationRegistry, 7 | rev_reg_def::{ 8 | RegistryType, RevocationRegistryDefinition, RevocationRegistryDefinitionPrivate, 9 | }, 10 | }; 11 | use crate::issuer; 12 | use crate::services::issuer::create_revocation_registry_def; 13 | use crate::services::prover::create_or_update_revocation_state; 14 | use crate::services::tails::TailsFileWriter; 15 | use crate::services::types::CredentialRevocationState; 16 | use ffi_support::{rust_string_to_c, FfiStr}; 17 | use std::collections::BTreeSet; 18 | use std::os::raw::c_char; 19 | use std::str::FromStr; 20 | 21 | #[no_mangle] 22 | pub extern "C" fn anoncreds_create_revocation_status_list( 23 | cred_def: ObjectHandle, 24 | rev_reg_def_id: FfiStr, 25 | rev_reg_def: ObjectHandle, 26 | reg_rev_priv: ObjectHandle, 27 | _issuer_id: FfiStr, // leaving it here not to break existing code 28 | issuance_by_default: i8, 29 | timestamp: i64, 30 | rev_status_list_p: *mut ObjectHandle, 31 | ) -> ErrorCode { 32 | catch_error(|| { 33 | check_useful_c_ptr!(rev_status_list_p); 34 | let rev_reg_def_id = rev_reg_def_id 35 | .as_opt_str() 36 | .ok_or_else(|| err_msg!("Missing rev_reg_def_id"))? 37 | .try_into()?; 38 | 39 | let timestamp = if timestamp <= 0 { 40 | None 41 | } else { 42 | Some(timestamp as u64) 43 | }; 44 | 45 | let rev_status_list = issuer::create_revocation_status_list( 46 | cred_def.load()?.cast_ref()?, 47 | rev_reg_def_id, 48 | rev_reg_def.load()?.cast_ref()?, 49 | reg_rev_priv.load()?.cast_ref()?, 50 | issuance_by_default != 0, 51 | timestamp, 52 | )?; 53 | 54 | let rev_status_list_handle = ObjectHandle::create(rev_status_list)?; 55 | 56 | unsafe { *rev_status_list_p = rev_status_list_handle }; 57 | 58 | Ok(()) 59 | }) 60 | } 61 | 62 | #[no_mangle] 63 | pub extern "C" fn anoncreds_update_revocation_status_list( 64 | cred_def: ObjectHandle, 65 | rev_reg_def: ObjectHandle, 66 | rev_reg_priv: ObjectHandle, 67 | rev_current_list: ObjectHandle, 68 | issued: FfiList, 69 | revoked: FfiList, 70 | timestamp: i64, 71 | new_rev_status_list_p: *mut ObjectHandle, 72 | ) -> ErrorCode { 73 | catch_error(|| { 74 | check_useful_c_ptr!(new_rev_status_list_p); 75 | let timestamp = if timestamp <= 0 { 76 | None 77 | } else { 78 | Some(timestamp as u64) 79 | }; 80 | let revoked: Option> = if revoked.is_empty() { 81 | None 82 | } else { 83 | Some(revoked.as_slice().iter().map(|r| *r as u32).collect()) 84 | }; 85 | let issued: Option> = if issued.is_empty() { 86 | None 87 | } else { 88 | Some(issued.as_slice().iter().map(|r| *r as u32).collect()) 89 | }; 90 | let new_rev_status_list = issuer::update_revocation_status_list( 91 | cred_def.load()?.cast_ref()?, 92 | rev_reg_def.load()?.cast_ref()?, 93 | rev_reg_priv.load()?.cast_ref()?, 94 | rev_current_list.load()?.cast_ref()?, 95 | issued, 96 | revoked, 97 | timestamp, 98 | )?; 99 | 100 | let new_rev_status_list = ObjectHandle::create(new_rev_status_list)?; 101 | 102 | unsafe { *new_rev_status_list_p = new_rev_status_list }; 103 | 104 | Ok(()) 105 | }) 106 | } 107 | 108 | #[no_mangle] 109 | pub extern "C" fn anoncreds_update_revocation_status_list_timestamp_only( 110 | timestamp: i64, 111 | rev_current_list: ObjectHandle, 112 | rev_status_list_p: *mut ObjectHandle, 113 | ) -> ErrorCode { 114 | catch_error(|| { 115 | check_useful_c_ptr!(rev_status_list_p); 116 | let timestamp = timestamp as u64; 117 | 118 | let new_rev_status_list = issuer::update_revocation_status_list_timestamp_only( 119 | timestamp, 120 | rev_current_list.load()?.cast_ref()?, 121 | ); 122 | 123 | let new_rev_status_list_handle = ObjectHandle::create(new_rev_status_list)?; 124 | 125 | unsafe { *rev_status_list_p = new_rev_status_list_handle }; 126 | 127 | Ok(()) 128 | }) 129 | } 130 | 131 | #[no_mangle] 132 | pub extern "C" fn anoncreds_create_revocation_registry_def( 133 | cred_def: ObjectHandle, 134 | cred_def_id: FfiStr, 135 | _issuer_id: FfiStr, // leaving it here not to break existing code 136 | tag: FfiStr, 137 | rev_reg_type: FfiStr, 138 | max_cred_num: i64, 139 | tails_dir_path: FfiStr, 140 | reg_def_p: *mut ObjectHandle, 141 | reg_def_private_p: *mut ObjectHandle, 142 | ) -> ErrorCode { 143 | catch_error(|| { 144 | check_useful_c_ptr!(reg_def_p); 145 | check_useful_c_ptr!(reg_def_private_p); 146 | let tag = tag.as_opt_str().ok_or_else(|| err_msg!("Missing tag"))?; 147 | let cred_def_id = cred_def_id 148 | .as_opt_str() 149 | .ok_or_else(|| err_msg!("Missing cred def id"))? 150 | .try_into()?; 151 | let rev_reg_type = { 152 | let rtype = rev_reg_type 153 | .as_opt_str() 154 | .ok_or_else(|| err_msg!("Missing registry type"))?; 155 | RegistryType::from_str(rtype).map_err(err_map!(Input))? 156 | }; 157 | let mut tails_writer = TailsFileWriter::new(tails_dir_path.into_opt_string()); 158 | let (reg_def, reg_def_private) = create_revocation_registry_def( 159 | cred_def.load()?.cast_ref()?, 160 | cred_def_id, 161 | tag, 162 | rev_reg_type, 163 | max_cred_num 164 | .try_into() 165 | .map_err(|_| err_msg!("Invalid maximum credential count"))?, 166 | &mut tails_writer, 167 | )?; 168 | let reg_def = ObjectHandle::create(reg_def)?; 169 | let reg_def_private = ObjectHandle::create(reg_def_private)?; 170 | unsafe { 171 | *reg_def_p = reg_def; 172 | *reg_def_private_p = reg_def_private; 173 | }; 174 | Ok(()) 175 | }) 176 | } 177 | 178 | impl_anoncreds_object!(RevocationRegistryDefinition, "RevocationRegistryDefinition"); 179 | impl_anoncreds_object_from_json!( 180 | RevocationRegistryDefinition, 181 | anoncreds_revocation_registry_definition_from_json 182 | ); 183 | 184 | #[no_mangle] 185 | pub extern "C" fn anoncreds_revocation_registry_definition_get_attribute( 186 | handle: ObjectHandle, 187 | name: FfiStr, 188 | result_p: *mut *const c_char, 189 | ) -> ErrorCode { 190 | catch_error(|| { 191 | check_useful_c_ptr!(result_p); 192 | let reg_def = handle.load()?; 193 | let reg_def = reg_def.cast_ref::()?; 194 | let val = match name.as_opt_str().unwrap_or_default() { 195 | "max_cred_num" => reg_def.value.max_cred_num.to_string(), 196 | "tails_hash" => reg_def.value.tails_hash.to_string(), 197 | "tails_location" => reg_def.value.tails_location.to_string(), 198 | s => return Err(err_msg!("Unsupported attribute: {}", s)), 199 | }; 200 | unsafe { *result_p = rust_string_to_c(val) }; 201 | Ok(()) 202 | }) 203 | } 204 | 205 | impl_anoncreds_object!( 206 | RevocationRegistryDefinitionPrivate, 207 | "RevocationRegistryDefinitionPrivate" 208 | ); 209 | impl_anoncreds_object_from_json!( 210 | RevocationRegistryDefinitionPrivate, 211 | anoncreds_revocation_registry_definition_private_from_json 212 | ); 213 | 214 | impl_anoncreds_object!(RevocationRegistry, "RevocationRegistry"); 215 | impl_anoncreds_object_from_json!(RevocationRegistry, anoncreds_revocation_registry_from_json); 216 | 217 | impl_anoncreds_object!(RevocationStatusList, "RevocationStatusList"); 218 | impl_anoncreds_object_from_json!( 219 | RevocationStatusList, 220 | anoncreds_revocation_status_list_from_json 221 | ); 222 | 223 | #[no_mangle] 224 | pub extern "C" fn anoncreds_create_or_update_revocation_state( 225 | rev_reg_def: ObjectHandle, 226 | rev_status_list: ObjectHandle, 227 | rev_reg_index: i64, 228 | tails_path: FfiStr, 229 | rev_state: ObjectHandle, 230 | old_rev_status_list: ObjectHandle, 231 | rev_state_p: *mut ObjectHandle, 232 | ) -> ErrorCode { 233 | catch_error(|| { 234 | check_useful_c_ptr!(rev_state_p); 235 | let prev_rev_state = rev_state.opt_load()?; 236 | let prev_rev_status_list = old_rev_status_list.opt_load()?; 237 | let tails_path = tails_path 238 | .as_opt_str() 239 | .ok_or_else(|| err_msg!("Missing tails file path"))?; 240 | let rev_state = create_or_update_revocation_state( 241 | tails_path, 242 | rev_reg_def.load()?.cast_ref()?, 243 | rev_status_list.load()?.cast_ref()?, 244 | rev_reg_index 245 | .try_into() 246 | .map_err(|_| err_msg!("Invalid credential revocation index"))?, 247 | prev_rev_state 248 | .as_ref() 249 | .map(AnoncredsObject::cast_ref) 250 | .transpose()?, 251 | prev_rev_status_list 252 | .as_ref() 253 | .map(AnoncredsObject::cast_ref) 254 | .transpose()?, 255 | )?; 256 | let rev_state = ObjectHandle::create(rev_state)?; 257 | unsafe { *rev_state_p = rev_state }; 258 | Ok(()) 259 | }) 260 | } 261 | 262 | impl_anoncreds_object!(CredentialRevocationState, "CredentialRevocationState"); 263 | impl_anoncreds_object_from_json!( 264 | CredentialRevocationState, 265 | anoncreds_revocation_state_from_json 266 | ); 267 | -------------------------------------------------------------------------------- /src/ffi/schema.rs: -------------------------------------------------------------------------------- 1 | use ffi_support::FfiStr; 2 | 3 | use super::error::{catch_error, ErrorCode}; 4 | use super::object::ObjectHandle; 5 | use super::util::FfiStrList; 6 | use crate::data_types::schema::Schema; 7 | use crate::services::issuer::create_schema; 8 | 9 | #[no_mangle] 10 | pub extern "C" fn anoncreds_create_schema( 11 | schema_name: FfiStr, 12 | schema_version: FfiStr, 13 | issuer_id: FfiStr, 14 | attr_names: FfiStrList, 15 | result_p: *mut ObjectHandle, 16 | ) -> ErrorCode { 17 | catch_error(|| { 18 | check_useful_c_ptr!(result_p); 19 | let schema_name = schema_name 20 | .as_opt_str() 21 | .ok_or_else(|| err_msg!("Missing schema name"))?; 22 | let schema_version = schema_version 23 | .as_opt_str() 24 | .ok_or_else(|| err_msg!("Missing schema version"))?; 25 | let issuer_id = issuer_id 26 | .as_opt_str() 27 | .ok_or_else(|| err_msg!("Missing issuer_id"))? 28 | .try_into()?; 29 | let schema = create_schema( 30 | schema_name, 31 | schema_version, 32 | issuer_id, 33 | attr_names.to_string_vec()?.into(), 34 | )?; 35 | let handle = ObjectHandle::create(schema)?; 36 | unsafe { *result_p = handle }; 37 | Ok(()) 38 | }) 39 | } 40 | 41 | impl_anoncreds_object!(Schema, "Schema"); 42 | impl_anoncreds_object_from_json!(Schema, anoncreds_schema_from_json); 43 | -------------------------------------------------------------------------------- /src/ffi/util.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use std::slice; 3 | 4 | use ffi_support::FfiStr; 5 | 6 | use crate::error::Result; 7 | 8 | #[derive(Debug)] 9 | #[repr(C)] 10 | pub struct FfiList<'a, T> { 11 | count: usize, 12 | data: *const T, 13 | _pd: PhantomData<&'a ()>, 14 | } 15 | 16 | impl<'a, T> FfiList<'a, T> { 17 | #[inline] 18 | pub fn as_slice(&self) -> &[T] { 19 | if self.data.is_null() { 20 | &[] 21 | } else { 22 | unsafe { slice::from_raw_parts(self.data, self.count) } 23 | } 24 | } 25 | 26 | #[inline] 27 | pub fn try_collect(&self, mut f: impl FnMut(&T) -> Result) -> Result> { 28 | self.as_slice() 29 | .iter() 30 | .try_fold(Vec::with_capacity(self.len()), |mut rs, v| { 31 | rs.push(f(v)?); 32 | Ok(rs) 33 | }) 34 | } 35 | 36 | #[inline] 37 | pub fn is_empty(&self) -> bool { 38 | self.len() == 0 39 | } 40 | 41 | #[inline] 42 | pub fn len(&self) -> usize { 43 | if self.data.is_null() { 44 | 0 45 | } else { 46 | self.count 47 | } 48 | } 49 | } 50 | 51 | pub type FfiStrList<'a> = FfiList<'a, FfiStr<'a>>; 52 | 53 | impl<'a> FfiStrList<'a> { 54 | pub fn to_string_vec(&self) -> Result> { 55 | self.try_collect(|s| { 56 | Ok(s.as_opt_str() 57 | .ok_or_else(|| err_msg!("Expected non-empty string"))? 58 | .to_string()) 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/ffi/w3c/mod.rs: -------------------------------------------------------------------------------- 1 | mod credential; 2 | mod presentation; 3 | -------------------------------------------------------------------------------- /src/ffi/w3c/presentation.rs: -------------------------------------------------------------------------------- 1 | use crate::data_types::w3c::presentation::W3CPresentation; 2 | use crate::data_types::w3c::VerifiableCredentialSpecVersion; 3 | use crate::ffi::credential::_link_secret; 4 | use crate::ffi::error::{catch_error, ErrorCode}; 5 | use crate::ffi::object::ObjectHandle; 6 | use crate::ffi::presentation::{ 7 | FfiCredentialEntry, FfiCredentialProve, FfiNonrevokedIntervalOverride, _credentials, 8 | _nonrevoke_interval_override, _prepare_cred_defs, _prepare_schemas, _present_credentials, 9 | _rev_reg_defs, _rev_status_list, 10 | }; 11 | use crate::ffi::util::{FfiList, FfiStrList}; 12 | use crate::w3c::prover::create_presentation; 13 | use crate::w3c::verifier::verify_presentation; 14 | use ffi_support::FfiStr; 15 | 16 | impl_anoncreds_object!(W3CPresentation, "W3CPresentation"); 17 | impl_anoncreds_object_from_json!(W3CPresentation, anoncreds_w3c_presentation_from_json); 18 | 19 | /// Create W3C Presentation according to the specification. 20 | /// 21 | /// # Params 22 | /// pres_req: object handle pointing to presentation request 23 | /// credentials: credentials (in W3C form) to use for presentation preparation 24 | /// credentials_prove: attributes and predicates to prove per credential 25 | /// link_secret: holder link secret 26 | /// schemas: list of credential schemas 27 | /// schema_ids: list of schemas ids 28 | /// cred_defs: list of credential definitions 29 | /// cred_def_ids: list of credential definitions ids 30 | /// w3c_version: version of w3c verifiable credential specification (1.1 or 2.0) to use 31 | /// presentation_p: reference that will contain created presentation (in W3C form) instance pointer. 32 | /// 33 | /// # Returns 34 | /// Error code 35 | #[no_mangle] 36 | pub extern "C" fn anoncreds_create_w3c_presentation( 37 | pres_req: ObjectHandle, 38 | credentials: FfiList, 39 | credentials_prove: FfiList, 40 | link_secret: FfiStr, 41 | schemas: FfiList, 42 | schema_ids: FfiStrList, 43 | cred_defs: FfiList, 44 | cred_def_ids: FfiStrList, 45 | w3c_version: FfiStr, 46 | presentation_p: *mut ObjectHandle, 47 | ) -> ErrorCode { 48 | catch_error(|| { 49 | check_useful_c_ptr!(presentation_p); 50 | 51 | let link_secret = _link_secret(link_secret)?; 52 | let cred_defs = _prepare_cred_defs(cred_defs, cred_def_ids)?; 53 | let schemas = _prepare_schemas(schemas, schema_ids)?; 54 | let credentials = _credentials(credentials)?; 55 | let present_creds = _present_credentials(&credentials, credentials_prove)?; 56 | let w3c_version = match w3c_version.as_opt_str() { 57 | Some(value) => Some(VerifiableCredentialSpecVersion::try_from(value)?), 58 | None => None, 59 | }; 60 | 61 | let presentation = create_presentation( 62 | pres_req.load()?.cast_ref()?, 63 | present_creds, 64 | &link_secret, 65 | &schemas, 66 | &cred_defs, 67 | w3c_version, 68 | )?; 69 | 70 | let presentation = ObjectHandle::create(presentation)?; 71 | unsafe { *presentation_p = presentation }; 72 | Ok(()) 73 | }) 74 | } 75 | 76 | /// Verity W3C styled Presentation 77 | /// 78 | /// # Params 79 | /// presentation: object handle pointing to presentation 80 | /// pres_req: object handle pointing to presentation request 81 | /// schemas: list of credential schemas 82 | /// schema_ids: list of schemas ids 83 | /// cred_defs: list of credential definitions 84 | /// cred_def_ids: list of credential definitions ids 85 | /// rev_reg_defs: list of revocation definitions 86 | /// rev_reg_def_ids: list of revocation definitions ids 87 | /// rev_status_list: revocation status list 88 | /// nonrevoked_interval_override: not-revoked interval 89 | /// result_p: reference that will contain presentation verification result. 90 | /// 91 | /// # Returns 92 | /// Error code 93 | #[no_mangle] 94 | pub extern "C" fn anoncreds_verify_w3c_presentation( 95 | presentation: ObjectHandle, 96 | pres_req: ObjectHandle, 97 | schemas: FfiList, 98 | schema_ids: FfiStrList, 99 | cred_defs: FfiList, 100 | cred_def_ids: FfiStrList, 101 | rev_reg_defs: FfiList, 102 | rev_reg_def_ids: FfiStrList, 103 | rev_status_list: FfiList, 104 | nonrevoked_interval_override: FfiList, 105 | result_p: *mut i8, 106 | ) -> ErrorCode { 107 | catch_error(|| { 108 | let cred_defs = _prepare_cred_defs(cred_defs, cred_def_ids)?; 109 | let schemas = _prepare_schemas(schemas, schema_ids)?; 110 | let rev_reg_defs = _rev_reg_defs(rev_reg_defs, rev_reg_def_ids)?; 111 | let rev_status_lists = _rev_status_list(rev_status_list)?; 112 | let map_nonrevoked_interval_override = 113 | _nonrevoke_interval_override(nonrevoked_interval_override)?; 114 | 115 | let verify = verify_presentation( 116 | presentation.load()?.cast_ref()?, 117 | pres_req.load()?.cast_ref()?, 118 | &schemas, 119 | &cred_defs, 120 | rev_reg_defs.as_ref(), 121 | rev_status_lists, 122 | Some(&map_nonrevoked_interval_override), 123 | )?; 124 | unsafe { *result_p = i8::from(verify) }; 125 | Ok(()) 126 | }) 127 | } 128 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod macros; 3 | 4 | #[macro_use] 5 | extern crate log; 6 | 7 | #[macro_use] 8 | extern crate serde; 9 | 10 | #[cfg(test)] 11 | #[macro_use] 12 | extern crate serde_json; 13 | 14 | #[doc(hidden)] 15 | pub use anoncreds_clsignatures as cl; 16 | 17 | #[macro_use] 18 | mod error; 19 | #[doc(hidden)] 20 | pub use self::error::Result; 21 | pub use self::error::{Error, ErrorKind}; 22 | 23 | mod services; 24 | pub use services::*; 25 | 26 | mod utils; 27 | 28 | #[cfg(feature = "ffi")] 29 | mod ffi; 30 | 31 | pub mod data_types; 32 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | #[cfg(debug_assertions)] 2 | macro_rules! secret { 3 | ($val:expr) => {{ 4 | $val 5 | }}; 6 | } 7 | 8 | #[cfg(not(debug_assertions))] 9 | macro_rules! secret { 10 | ($val:expr) => {{ 11 | "_" 12 | }}; 13 | } 14 | -------------------------------------------------------------------------------- /src/services/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod helpers; 2 | pub mod issuer; 3 | pub mod prover; 4 | pub mod tails; 5 | pub mod types; 6 | pub mod verifier; 7 | 8 | #[cfg(feature = "w3c")] 9 | pub mod w3c; 10 | -------------------------------------------------------------------------------- /src/services/tails.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::fmt::Debug; 3 | use std::fs::File; 4 | use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write}; 5 | use std::path::{Path, PathBuf}; 6 | 7 | use rand::random; 8 | use sha2::{Digest, Sha256}; 9 | 10 | use crate::cl::{ 11 | Error as ClError, ErrorKind as ClErrorKind, RevocationTailsAccessor, RevocationTailsGenerator, 12 | Tail, 13 | }; 14 | use crate::error::Error; 15 | use crate::utils::base58; 16 | use crate::ErrorKind; 17 | 18 | const TAILS_BLOB_TAG_SZ: u8 = 2; 19 | const TAIL_SIZE: usize = Tail::BYTES_REPR_SIZE; 20 | 21 | #[derive(Debug)] 22 | pub struct TailsFileReader { 23 | file: RefCell>, 24 | } 25 | 26 | impl TailsFileReader { 27 | pub fn new

(path: P) -> Result 28 | where 29 | P: AsRef, 30 | { 31 | let file = RefCell::new(BufReader::new(File::open(path)?)); 32 | let reader = Self { file }; 33 | Ok(reader) 34 | } 35 | 36 | fn read(&self, size: usize, offset: usize) -> Result, Error> { 37 | let mut buf = vec![0u8; size]; 38 | 39 | let mut file = self 40 | .file 41 | .try_borrow_mut() 42 | .map_err(|err| Error::from(ErrorKind::IOError).with_cause(err))?; 43 | 44 | file.seek(SeekFrom::Start(offset as u64))?; 45 | file.read_exact(buf.as_mut_slice())?; 46 | 47 | Ok(buf) 48 | } 49 | } 50 | 51 | impl RevocationTailsAccessor for TailsFileReader { 52 | fn access_tail( 53 | &self, 54 | tail_id: u32, 55 | accessor: &mut dyn FnMut(&Tail), 56 | ) -> std::result::Result<(), ClError> { 57 | trace!("access_tail >>> tail_id: {:?}", tail_id); 58 | 59 | let tail_bytes = self 60 | .read( 61 | TAIL_SIZE, 62 | TAIL_SIZE * tail_id as usize + TAILS_BLOB_TAG_SZ as usize, 63 | ) 64 | .map_err(|e| { 65 | error!("IO error reading tails file: {e}"); 66 | ClError::new(ClErrorKind::InvalidState, "Could not read from tails file") 67 | })?; 68 | 69 | let tail = Tail::from_bytes(tail_bytes.as_slice())?; 70 | accessor(&tail); 71 | 72 | trace!("access_tail <<< res: ()"); 73 | Ok(()) 74 | } 75 | } 76 | 77 | pub trait TailsWriter: std::fmt::Debug { 78 | fn write( 79 | &mut self, 80 | generator: &mut RevocationTailsGenerator, 81 | ) -> Result<(String, String), Error>; 82 | } 83 | 84 | #[derive(Debug)] 85 | pub struct TailsFileWriter { 86 | root_path: PathBuf, 87 | } 88 | 89 | impl TailsFileWriter { 90 | pub fn new(root_path: Option) -> Self { 91 | Self { 92 | root_path: root_path 93 | .map(PathBuf::from) 94 | .unwrap_or_else(std::env::temp_dir), 95 | } 96 | } 97 | } 98 | 99 | impl TailsWriter for TailsFileWriter { 100 | fn write( 101 | &mut self, 102 | generator: &mut RevocationTailsGenerator, 103 | ) -> Result<(String, String), Error> { 104 | struct TempFile<'a>(&'a Path); 105 | impl TempFile<'_> { 106 | pub fn rename(self, target: &Path) -> Result<(), Error> { 107 | let path = std::mem::ManuallyDrop::new(self).0; 108 | std::fs::rename(path, target) 109 | .map_err(|e| err_msg!("Error moving tails temp file {path:?}: {e}")) 110 | } 111 | } 112 | impl Drop for TempFile<'_> { 113 | fn drop(&mut self) { 114 | if let Err(e) = std::fs::remove_file(self.0) { 115 | error!("Error removing tails temp file {:?}: {e}", self.0); 116 | } 117 | } 118 | } 119 | 120 | let temp_name = format!("{:020}.tmp", random::()); 121 | let temp_path = self.root_path.join(temp_name); 122 | let file = File::options() 123 | .read(true) 124 | .write(true) 125 | .create_new(true) 126 | .open(temp_path.clone()) 127 | .map_err(|e| err_msg!(IOError, "Error creating tails temp file {temp_path:?}: {e}"))?; 128 | let temp_handle = TempFile(&temp_path); 129 | let mut buf = BufWriter::new(file); 130 | let mut hasher = Sha256::default(); 131 | let version = &[0u8, 2u8]; 132 | buf.write_all(version)?; 133 | hasher.update(version); 134 | while let Some(tail) = generator.try_next()? { 135 | let tail_bytes = tail.to_bytes()?; 136 | buf.write_all(&tail_bytes)?; 137 | hasher.update(&tail_bytes); 138 | } 139 | let mut file = buf 140 | .into_inner() 141 | .map_err(|e| err_msg!("Error flushing output file: {e}"))?; 142 | let tails_size = file.stream_position()?; 143 | let hash = base58::encode(hasher.finalize()); 144 | let target_path = self.root_path.join(&hash); 145 | drop(file); 146 | temp_handle.rename(&target_path)?; 147 | let target_path = target_path.to_string_lossy().into_owned(); 148 | debug!( 149 | "TailsFileWriter: wrote tails file [size {}]: {}", 150 | tails_size, target_path 151 | ); 152 | Ok((target_path, hash)) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/services/types.rs: -------------------------------------------------------------------------------- 1 | use crate::cl::{RevocationRegistry as CryptoRevocationRegistry, Witness}; 2 | pub use crate::data_types::{ 3 | cred_def::{CredentialDefinitionPrivate, CredentialKeyCorrectnessProof, SignatureType}, 4 | cred_offer::CredentialOffer, 5 | cred_request::{CredentialRequest, CredentialRequestMetadata}, 6 | credential::{AttributeValues, Credential, CredentialValues}, 7 | link_secret::LinkSecret, 8 | pres_request::PresentationRequest, 9 | presentation::Presentation, 10 | rev_reg::RevocationRegistry, 11 | rev_reg_def::{ 12 | RegistryType, RevocationRegistryDefinition, RevocationRegistryDefinitionPrivate, 13 | }, 14 | rev_status_list::RevocationStatusList, 15 | schema::AttributeNames, 16 | }; 17 | use crate::services::helpers::encode_credential_attribute; 18 | use crate::{ 19 | error::{Error, ValidationError}, 20 | invalid, 21 | utils::validation::Validatable, 22 | }; 23 | use std::collections::HashSet; 24 | 25 | #[derive(Debug, Clone, Serialize, Deserialize, Default)] 26 | pub struct CredentialDefinitionConfig { 27 | pub support_revocation: bool, 28 | } 29 | 30 | impl CredentialDefinitionConfig { 31 | #[must_use] 32 | pub const fn new(support_revocation: bool) -> Self { 33 | Self { support_revocation } 34 | } 35 | } 36 | 37 | impl Validatable for CredentialDefinitionConfig {} 38 | 39 | #[derive(Debug, Default)] 40 | pub struct MakeCredentialValues(pub(crate) CredentialValues); 41 | 42 | impl MakeCredentialValues { 43 | pub fn add_encoded( 44 | &mut self, 45 | name: impl Into, 46 | raw: impl Into, 47 | encoded: String, 48 | ) { 49 | self.0 .0.insert( 50 | name.into(), 51 | AttributeValues { 52 | raw: raw.into(), 53 | encoded, 54 | }, 55 | ); 56 | } 57 | 58 | pub fn add_raw( 59 | &mut self, 60 | name: impl Into, 61 | raw: impl Into, 62 | ) -> Result<(), Error> { 63 | let raw = raw.into(); 64 | let encoded = encode_credential_attribute(&raw)?; 65 | self.0 66 | .0 67 | .insert(name.into(), AttributeValues { raw, encoded }); 68 | Ok(()) 69 | } 70 | } 71 | 72 | impl From for CredentialValues { 73 | fn from(m: MakeCredentialValues) -> Self { 74 | m.0 75 | } 76 | } 77 | 78 | #[derive(Debug)] 79 | pub struct PresentCredentials<'p, T>(pub(crate) Vec>); 80 | 81 | impl<'p, T> Default for PresentCredentials<'p, T> { 82 | fn default() -> Self { 83 | PresentCredentials(Vec::new()) 84 | } 85 | } 86 | 87 | impl<'p, T> PresentCredentials<'p, T> { 88 | pub fn add_credential( 89 | &mut self, 90 | cred: &'p T, 91 | timestamp: Option, 92 | rev_state: Option<&'p CredentialRevocationState>, 93 | ) -> AddCredential<'_, 'p, T> { 94 | let idx = self.0.len(); 95 | self.0.push(PresentCredential { 96 | cred, 97 | timestamp, 98 | rev_state, 99 | requested_attributes: HashSet::new(), 100 | requested_predicates: HashSet::new(), 101 | }); 102 | AddCredential { 103 | present: &mut self.0[idx], 104 | } 105 | } 106 | 107 | #[inline] 108 | #[must_use] 109 | pub fn is_empty(&self) -> bool { 110 | self.len() == 0 111 | } 112 | 113 | #[inline] 114 | #[must_use] 115 | pub fn len(&self) -> usize { 116 | self.0.iter().filter(|c| !c.is_empty()).count() 117 | } 118 | } 119 | 120 | impl Validatable for PresentCredentials<'_, T> { 121 | fn validate(&self) -> std::result::Result<(), ValidationError> { 122 | let mut attr_names = HashSet::new(); 123 | let mut pred_names = HashSet::new(); 124 | 125 | for c in &self.0 { 126 | for (name, _reveal) in &c.requested_attributes { 127 | if !attr_names.insert(name.as_str()) { 128 | return Err(invalid!("Duplicate requested attribute referent: {}", name)); 129 | } 130 | } 131 | 132 | for name in &c.requested_predicates { 133 | if !pred_names.insert(name.as_str()) { 134 | return Err(invalid!("Duplicate requested predicate referent: {}", name)); 135 | } 136 | } 137 | 138 | if c.timestamp.is_some() != c.rev_state.is_some() { 139 | return Err(invalid!( 140 | "Either timestamp and revocation state must be presented, or neither" 141 | )); 142 | } 143 | } 144 | 145 | Ok(()) 146 | } 147 | } 148 | 149 | #[derive(Debug)] 150 | pub(crate) struct PresentCredential<'p, T> { 151 | pub cred: &'p T, 152 | pub timestamp: Option, 153 | pub rev_state: Option<&'p CredentialRevocationState>, 154 | pub requested_attributes: HashSet<(String, bool)>, 155 | pub requested_predicates: HashSet, 156 | } 157 | 158 | impl PresentCredential<'_, T> { 159 | #[inline] 160 | pub fn is_empty(&self) -> bool { 161 | self.requested_attributes.is_empty() && self.requested_predicates.is_empty() 162 | } 163 | 164 | pub(crate) fn get_revealed_attributes(&self) -> HashSet { 165 | let mut referents = HashSet::new(); 166 | for (referent, revealed) in self.requested_attributes.iter() { 167 | if *revealed { 168 | referents.insert(referent.to_string()); 169 | } 170 | } 171 | referents 172 | } 173 | } 174 | 175 | #[derive(Debug)] 176 | pub struct AddCredential<'a, 'p, T> { 177 | present: &'a mut PresentCredential<'p, T>, 178 | } 179 | 180 | impl<'a, 'p, T> AddCredential<'a, 'p, T> { 181 | pub fn add_requested_attribute(&mut self, referent: impl Into, revealed: bool) { 182 | self.present 183 | .requested_attributes 184 | .insert((referent.into(), revealed)); 185 | } 186 | 187 | pub fn add_requested_predicate(&mut self, referent: impl Into) { 188 | self.present.requested_predicates.insert(referent.into()); 189 | } 190 | } 191 | 192 | #[allow(dead_code)] 193 | #[derive(Debug)] 194 | pub(crate) struct RequestedAttribute<'a> { 195 | pub cred_id: String, 196 | pub timestamp: Option, 197 | pub revealed: bool, 198 | pub rev_state: Option<&'a CredentialRevocationState>, 199 | } 200 | 201 | #[allow(dead_code)] 202 | #[derive(Debug)] 203 | pub(crate) struct RequestedPredicate<'a> { 204 | pub cred_id: String, 205 | pub timestamp: Option, 206 | pub rev_state: Option<&'a CredentialRevocationState>, 207 | } 208 | 209 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 210 | pub(crate) struct ProvingCredentialKey { 211 | pub cred_id: String, 212 | pub timestamp: Option, 213 | } 214 | 215 | #[derive(Clone, Debug, Serialize, Deserialize)] 216 | pub struct CredentialRevocationState { 217 | pub witness: Witness, 218 | pub rev_reg: CryptoRevocationRegistry, 219 | pub timestamp: u64, 220 | } 221 | 222 | impl Validatable for CredentialRevocationState { 223 | fn validate(&self) -> std::result::Result<(), ValidationError> { 224 | if self.timestamp == 0 { 225 | return Err(invalid!( 226 | "Credential Revocation State validation failed: `timestamp` must be greater than 0", 227 | )); 228 | } 229 | Ok(()) 230 | } 231 | } 232 | 233 | pub struct CredentialRevocationConfig<'a> { 234 | pub reg_def: &'a RevocationRegistryDefinition, 235 | pub reg_def_private: &'a RevocationRegistryDefinitionPrivate, 236 | pub status_list: &'a RevocationStatusList, 237 | pub registry_idx: u32, 238 | } 239 | 240 | impl<'a> std::fmt::Debug for CredentialRevocationConfig<'a> { 241 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 242 | write!( 243 | f, 244 | "CredentialRevocationConfig {{ reg_def: {:?}, private: {:?}, status_list: {:?}, idx: {} }}", 245 | self.reg_def, 246 | secret!(self.reg_def_private), 247 | self.status_list, 248 | secret!(self.registry_idx), 249 | ) 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/services/w3c/helpers.rs: -------------------------------------------------------------------------------- 1 | use crate::data_types::schema::Schema; 2 | use crate::data_types::w3c::credential::W3CCredential; 3 | use crate::data_types::w3c::credential_attributes::CredentialAttributeValue; 4 | use crate::error::Result; 5 | use crate::helpers::attr_common_view; 6 | 7 | impl W3CCredential { 8 | pub(crate) fn get_case_insensitive_attribute( 9 | &self, 10 | requested_attribute: &str, 11 | ) -> Result<(String, CredentialAttributeValue)> { 12 | let requested_attribute = attr_common_view(requested_attribute); 13 | self.credential_subject 14 | .0 15 | .iter() 16 | .find(|(attribute, _)| attr_common_view(attribute) == requested_attribute) 17 | .map(|(attribute, value)| (attribute.to_owned(), value.to_owned())) 18 | .ok_or_else(|| err_msg!("Credential attribute {} not found", requested_attribute)) 19 | } 20 | 21 | pub(crate) fn get_attribute( 22 | &self, 23 | requested_attribute: &str, 24 | ) -> Result<(String, CredentialAttributeValue)> { 25 | let (attribute, value) = self.get_case_insensitive_attribute(requested_attribute)?; 26 | match value { 27 | CredentialAttributeValue::String(_) => Ok((attribute, value)), 28 | CredentialAttributeValue::Number(_) => Ok((attribute, value)), 29 | CredentialAttributeValue::Bool(_) => Err(err_msg!( 30 | "Credential attribute {} not found", 31 | requested_attribute 32 | )), 33 | } 34 | } 35 | 36 | pub(crate) fn get_predicate( 37 | &self, 38 | requested_predicate: &str, 39 | ) -> Result<(String, CredentialAttributeValue)> { 40 | let (attribute, value) = self.get_case_insensitive_attribute(requested_predicate)?; 41 | match value { 42 | CredentialAttributeValue::Bool(_) => Ok((attribute, value)), 43 | CredentialAttributeValue::String(_) | CredentialAttributeValue::Number(_) => Err( 44 | err_msg!("Credential predicate {} not found", requested_predicate), 45 | ), 46 | } 47 | } 48 | } 49 | 50 | impl Schema { 51 | pub(crate) fn has_case_insensitive_attribute(&self, requested_attribute: &str) -> bool { 52 | let requested_attribute = attr_common_view(requested_attribute); 53 | self.attr_names 54 | .0 55 | .iter() 56 | .any(|attribute| attr_common_view(attribute) == requested_attribute) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/services/w3c/issuer.rs: -------------------------------------------------------------------------------- 1 | use crate::data_types::cred_def::CredentialDefinition; 2 | use crate::data_types::w3c::credential::W3CCredential; 3 | use crate::data_types::w3c::credential_attributes::CredentialSubject; 4 | use crate::data_types::w3c::proof::{CredentialSignatureProofValue, DataIntegrityProof}; 5 | use crate::data_types::w3c::VerifiableCredentialSpecVersion; 6 | use crate::error::Result; 7 | use crate::issuer::CLCredentialIssuer; 8 | 9 | use crate::types::{ 10 | CredentialDefinitionPrivate, CredentialOffer, CredentialRequest, CredentialRevocationConfig, 11 | }; 12 | 13 | /// Create an AnonCreds Credential in W3C form according to the [Anoncreds v1.0 specification - 14 | /// Credential](https://hyperledger.github.io/anoncreds-spec/#issue-credential) 15 | /// 16 | /// This object can be send to a holder which means that the credential is issued to that entity. 17 | /// 18 | /// # Example 19 | /// 20 | /// ```rust 21 | /// use anoncreds::{issuer, w3c}; 22 | /// use anoncreds::prover; 23 | /// use anoncreds::w3c::types::MakeCredentialAttributes; 24 | /// 25 | /// use anoncreds::types::CredentialDefinitionConfig; 26 | /// use anoncreds::types::SignatureType; 27 | /// use anoncreds::data_types::issuer_id::IssuerId; 28 | /// use anoncreds::data_types::schema::SchemaId; 29 | /// use anoncreds::data_types::cred_def::CredentialDefinitionId; 30 | /// 31 | /// let attribute_names: &[&str] = &["name", "age"]; 32 | /// let issuer_id = IssuerId::new("did:web:xyz").expect("Invalid issuer ID"); 33 | /// let schema_id = SchemaId::new("did:web:xyz/resource/schema").expect("Invalid schema ID"); 34 | /// let cred_def_id = CredentialDefinitionId::new("did:web:xyz/resource/cred-def",).expect("Invalid credential definition ID"); 35 | /// 36 | /// let schema = issuer::create_schema("schema name", 37 | /// "1.0", 38 | /// issuer_id.clone(), 39 | /// attribute_names.into() 40 | /// ).expect("Unable to create schema"); 41 | /// 42 | /// let (cred_def, cred_def_priv, key_correctness_proof) = 43 | /// issuer::create_credential_definition(schema_id.clone(), 44 | /// &schema, 45 | /// issuer_id, 46 | /// "default-tag", 47 | /// SignatureType::CL, 48 | /// CredentialDefinitionConfig::default() 49 | /// ).expect("Unable to create Credential Definition"); 50 | /// 51 | /// let credential_offer = 52 | /// issuer::create_credential_offer(schema_id, 53 | /// cred_def_id, 54 | /// &key_correctness_proof, 55 | /// ).expect("Unable to create Credential Offer"); 56 | /// 57 | /// let link_secret = 58 | /// prover::create_link_secret().expect("Unable to create link secret"); 59 | /// 60 | /// let (credential_request, credential_request_metadata) = 61 | /// prover::create_credential_request(Some("entropy"), 62 | /// None, 63 | /// &cred_def, 64 | /// &link_secret, 65 | /// "my-secret-id", 66 | /// &credential_offer, 67 | /// ).expect("Unable to create credential request"); 68 | /// 69 | /// let mut credential_values = MakeCredentialAttributes::default(); 70 | /// credential_values.add("name", "john"); 71 | /// credential_values.add("age", "28"); 72 | /// 73 | /// let credential = 74 | /// w3c::issuer::create_credential(&cred_def, 75 | /// &cred_def_priv, 76 | /// &credential_offer, 77 | /// &credential_request, 78 | /// credential_values.into(), 79 | /// None, 80 | /// None, 81 | /// ).expect("Unable to create credential"); 82 | /// ``` 83 | #[allow(clippy::too_many_arguments)] 84 | pub fn create_credential( 85 | cred_def: &CredentialDefinition, 86 | cred_def_private: &CredentialDefinitionPrivate, 87 | cred_offer: &CredentialOffer, 88 | cred_request: &CredentialRequest, 89 | raw_credential_values: CredentialSubject, 90 | revocation_config: Option, 91 | version: Option, 92 | ) -> Result { 93 | trace!("create_w3c_credential >>> cred_def: {:?}, cred_def_private: {:?}, cred_offer.nonce: {:?}, cred_request: {:?},\ 94 | cred_values: {:?}, revocation_config: {:?}, version: {:?}", 95 | cred_def, secret!(&cred_def_private), &cred_offer.nonce, &cred_request, secret!(&raw_credential_values), 96 | revocation_config, version, 97 | ); 98 | 99 | let credential_values = raw_credential_values.encode()?; 100 | 101 | let (credential_signature, signature_correctness_proof, rev_reg_id, rev_reg, witness) = 102 | CLCredentialIssuer::new(cred_def, cred_def_private).create_credential( 103 | cred_offer, 104 | cred_request, 105 | &credential_values, 106 | revocation_config, 107 | )?; 108 | 109 | let signature = CredentialSignatureProofValue { 110 | schema_id: cred_offer.schema_id.to_owned(), 111 | cred_def_id: cred_offer.cred_def_id.to_owned(), 112 | rev_reg_id, 113 | signature: credential_signature, 114 | signature_correctness_proof, 115 | rev_reg, 116 | witness, 117 | }; 118 | 119 | let proof = DataIntegrityProof::new_credential_proof(&signature)?; 120 | let credential = W3CCredential::new( 121 | cred_def.issuer_id.to_owned(), 122 | raw_credential_values, 123 | proof, 124 | version.as_ref(), 125 | ); 126 | 127 | trace!( 128 | "create_w3c_credential <<< credential {:?}", 129 | secret!(&credential), 130 | ); 131 | 132 | Ok(credential) 133 | } 134 | -------------------------------------------------------------------------------- /src/services/w3c/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod credential_conversion; 2 | pub mod helpers; 3 | pub mod issuer; 4 | pub mod prover; 5 | pub mod types; 6 | pub mod verifier; 7 | -------------------------------------------------------------------------------- /src/services/w3c/types.rs: -------------------------------------------------------------------------------- 1 | use crate::data_types::w3c::credential_attributes::CredentialAttributeValue; 2 | use crate::data_types::w3c::credential_attributes::CredentialSubject; 3 | 4 | #[derive(Debug, Default)] 5 | pub struct MakeCredentialAttributes(pub(crate) CredentialSubject); 6 | 7 | impl MakeCredentialAttributes { 8 | pub fn add(&mut self, name: impl Into, raw: impl Into) { 9 | let string_value = raw.into(); 10 | let value = if let Ok(number) = string_value.parse::() { 11 | CredentialAttributeValue::Number(number) 12 | } else { 13 | CredentialAttributeValue::String(string_value) 14 | }; 15 | 16 | self.0 .0.insert(name.into(), value); 17 | } 18 | } 19 | 20 | impl From for CredentialSubject { 21 | fn from(m: MakeCredentialAttributes) -> Self { 22 | m.0 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/base58.rs: -------------------------------------------------------------------------------- 1 | use bs58; 2 | 3 | pub fn encode>(val: T) -> String { 4 | bs58::encode(val).into_string() 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/base64.rs: -------------------------------------------------------------------------------- 1 | use base64::{engine, Engine}; 2 | 3 | use crate::Error; 4 | 5 | pub fn encode>(val: T) -> String { 6 | engine::general_purpose::URL_SAFE_NO_PAD.encode(val) 7 | } 8 | 9 | pub fn decode>(val: T) -> Result, Error> { 10 | engine::general_purpose::URL_SAFE_NO_PAD 11 | .decode(val) 12 | .map_err(|_| err_msg!("invalid base64 string")) 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use thiserror::Error; 3 | 4 | type DynError = Box; 5 | 6 | macro_rules! define_error { 7 | ($name:tt, $short:expr, $doc:tt) => { 8 | #[derive(Debug, Error)] 9 | #[doc=$doc] 10 | pub struct $name { 11 | pub context: Option, 12 | pub source: Option, 13 | } 14 | 15 | impl $name { 16 | pub fn from_msg>(msg: T) -> Self { 17 | Self::from(msg.into()) 18 | } 19 | 20 | pub fn from_err(err: E) -> Self 21 | where 22 | E: StdError + Send + Sync + 'static, 23 | { 24 | Self { 25 | context: None, 26 | source: Some(Box::new(err) as DynError), 27 | } 28 | } 29 | 30 | pub fn from_msg_err(msg: M, err: E) -> Self 31 | where 32 | M: Into, 33 | E: StdError + Send + Sync + 'static, 34 | { 35 | Self { 36 | context: Some(msg.into()), 37 | source: Some(Box::new(err) as DynError), 38 | } 39 | } 40 | } 41 | 42 | impl From<&str> for $name { 43 | fn from(context: &str) -> Self { 44 | Self { 45 | context: Some(context.to_owned()), 46 | source: None, 47 | } 48 | } 49 | } 50 | 51 | impl From for $name { 52 | fn from(context: String) -> Self { 53 | Self { 54 | context: Some(context), 55 | source: None, 56 | } 57 | } 58 | } 59 | 60 | impl From> for $name { 61 | fn from(context: Option) -> Self { 62 | Self { 63 | context, 64 | source: None, 65 | } 66 | } 67 | } 68 | 69 | impl From<(M, E)> for $name 70 | where 71 | M: Into, 72 | E: StdError + Send + Sync + 'static, 73 | { 74 | fn from((context, err): (M, E)) -> Self { 75 | Self::from_msg_err(context, err) 76 | } 77 | } 78 | 79 | impl From<$name> for String { 80 | fn from(s: $name) -> Self { 81 | s.to_string() 82 | } 83 | } 84 | 85 | impl std::fmt::Display for $name { 86 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 87 | write!(f, $short)?; 88 | match self.context { 89 | Some(ref context) => write!(f, ": {}", context), 90 | None => Ok(()), 91 | } 92 | } 93 | } 94 | }; 95 | } 96 | 97 | define_error!( 98 | ConversionError, 99 | "Conversion error", 100 | "Error type for general data conversion errors" 101 | ); 102 | 103 | define_error!( 104 | EncryptionError, 105 | "Encryption error", 106 | "Error type for failure of encryption and decryption operations" 107 | ); 108 | 109 | define_error!( 110 | UnexpectedError, 111 | "Unexpected error", 112 | "Error type for eventualities that shouldn't normally occur" 113 | ); 114 | 115 | define_error!( 116 | ValidationError, 117 | "Validation error", 118 | "Error type for failures of `Validatable::validate`" 119 | ); 120 | 121 | impl From for ConversionError { 122 | fn from(err: serde_json::error::Error) -> Self { 123 | Self::from_msg(err.to_string()) 124 | } 125 | } 126 | 127 | impl From for ConversionError { 128 | fn from(_err: std::str::Utf8Error) -> Self { 129 | Self::from("UTF-8 decoding error") 130 | } 131 | } 132 | 133 | impl From for ConversionError { 134 | fn from(_err: std::string::FromUtf8Error) -> Self { 135 | Self::from("UTF-8 decoding error") 136 | } 137 | } 138 | 139 | impl From for ConversionError { 140 | fn from(err: ValidationError) -> Self { 141 | Self { 142 | context: err.context, 143 | source: err.source, 144 | } 145 | } 146 | } 147 | 148 | impl From for ValidationError { 149 | fn from(err: ConversionError) -> Self { 150 | Self { 151 | context: err.context, 152 | source: err.source, 153 | } 154 | } 155 | } 156 | 157 | impl From for ConversionError { 158 | fn from(err: UnexpectedError) -> Self { 159 | Self { 160 | context: err.context, 161 | source: err.source, 162 | } 163 | } 164 | } 165 | 166 | impl From for EncryptionError { 167 | fn from(err: UnexpectedError) -> Self { 168 | Self { 169 | context: err.context, 170 | source: err.source, 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/utils/hash.rs: -------------------------------------------------------------------------------- 1 | pub use sha2::Digest; 2 | 3 | use crate::error::ValidationError; 4 | 5 | /// Derive a new hash type 6 | #[macro_export] 7 | macro_rules! hash_type { 8 | ($modname:ident, $digest:path, $doc:expr) => { 9 | #[doc=$doc] 10 | #[allow(non_snake_case)] 11 | pub mod $modname { 12 | #[cfg(test)] 13 | use once_cell::sync::Lazy; 14 | 15 | use sha2::Digest; 16 | 17 | pub type DigestType = $digest; 18 | 19 | pub fn digest>(input: V) -> Vec { 20 | DigestType::digest(input.as_ref()).to_vec() 21 | } 22 | 23 | #[cfg(test)] 24 | pub fn digest_empty() -> &'static [u8] { 25 | static EMPTY_HASH_BYTES: Lazy> = Lazy::new(|| digest(&[])); 26 | EMPTY_HASH_BYTES.as_slice() 27 | } 28 | 29 | #[cfg(test)] 30 | pub fn output_size() -> usize { 31 | DigestType::output_size() 32 | } 33 | } 34 | }; 35 | } 36 | 37 | hash_type!(SHA256, sha2::Sha256, "Sha256 hash"); 38 | 39 | /// A trait for producing hashes of merkle tree leaves and nodes 40 | pub trait TreeHash { 41 | fn hash_leaf(leaf: &T) -> Result, ValidationError> 42 | where 43 | T: Hashable; 44 | 45 | fn hash_nodes(left: &T, right: &T) -> Result, ValidationError> 46 | where 47 | T: Hashable; 48 | } 49 | 50 | impl TreeHash for H { 51 | fn hash_leaf(leaf: &T) -> Result, ValidationError> 52 | where 53 | T: Hashable, 54 | { 55 | let mut ctx = Self::new(); 56 | ctx.update([0x00]); 57 | leaf.update_context(&mut ctx)?; 58 | Ok(ctx.finalize().to_vec()) 59 | } 60 | 61 | fn hash_nodes(left: &T, right: &T) -> Result, ValidationError> 62 | where 63 | T: Hashable, 64 | { 65 | let mut ctx = Self::new(); 66 | ctx.update([0x01]); 67 | left.update_context(&mut ctx)?; 68 | right.update_context(&mut ctx)?; 69 | Ok(ctx.finalize().to_vec()) 70 | } 71 | } 72 | 73 | /// The type of values stored in a `MerkleTree` must implement 74 | /// this trait, in order for them to be able to be fed 75 | /// to a Ring `Context` when computing the hash of a leaf. 76 | /// 77 | /// A default instance for types that already implements 78 | /// `AsRef<[u8]>` is provided. 79 | /// 80 | /// ## Example 81 | /// 82 | /// Here is an example of how to implement `Hashable` for a type 83 | /// that does not (or cannot) implement `AsRef<[u8]>`: 84 | /// 85 | /// ```ignore 86 | /// impl Hashable for PublicKey { 87 | /// fn update_context(&self, context: &mut Hasher) -> Result<(), CommonError> { 88 | /// let bytes: Vec = self.to_bytes(); 89 | /// Ok(context.update(&bytes)?) 90 | /// } 91 | /// } 92 | /// ``` 93 | pub trait Hashable { 94 | /// Update the given `context` with `self`. 95 | /// 96 | /// See `openssl::hash::Hasher::update` for more information. 97 | fn update_context(&self, context: &mut D) -> Result<(), ValidationError>; 98 | } 99 | 100 | impl> Hashable for T { 101 | fn update_context(&self, context: &mut D) -> Result<(), ValidationError> { 102 | context.update(self.as_ref()); 103 | Ok(()) 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use super::*; 110 | 111 | #[test] 112 | fn hash_check_sha256() { 113 | assert_eq!(SHA256::output_size(), 32); 114 | assert_eq!( 115 | SHA256::digest_empty(), 116 | &[ 117 | 227, 176, 196, 66, 152, 252, 28, 20, 154, 251, 244, 200, 153, 111, 185, 36, 39, 118 | 174, 65, 228, 100, 155, 147, 76, 164, 149, 153, 27, 120, 82, 184, 85, 119 | ] 120 | ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/utils/macros.rs: -------------------------------------------------------------------------------- 1 | /// Used to optionally add Serialize and Deserialize traits to Qualifiable types 2 | #[macro_export] 3 | macro_rules! serde_derive_impl { 4 | ($def:item) => { 5 | #[derive(Serialize, Deserialize)] 6 | $def 7 | }; 8 | } 9 | 10 | /// Derive a new handle type having an atomically increasing sequence number 11 | #[macro_export] 12 | macro_rules! new_handle_type (($newtype:ident, $counter:ident) => ( 13 | static $counter: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); 14 | 15 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] 16 | #[repr(transparent)] 17 | pub struct $newtype(pub usize); 18 | 19 | impl $newtype { 20 | #[allow(dead_code)] 21 | pub const fn invalid() -> $newtype { 22 | $newtype(0) 23 | } 24 | 25 | #[allow(dead_code)] 26 | pub fn next() -> $newtype { 27 | $newtype($counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1) 28 | } 29 | } 30 | 31 | impl std::fmt::Display for $newtype { 32 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 33 | write!(f, "{}({})", stringify!($newtype), self.0) 34 | } 35 | } 36 | 37 | impl std::ops::Deref for $newtype { 38 | type Target = usize; 39 | fn deref(&self) -> &usize { 40 | &self.0 41 | } 42 | } 43 | 44 | impl PartialEq for $newtype { 45 | fn eq(&self, other: &usize) -> bool { 46 | self.0 == *other 47 | } 48 | } 49 | 50 | impl $crate::utils::validation::Validatable for $newtype { 51 | fn validate(&self) -> std::result::Result<(), $crate::error::ValidationError> { 52 | if(**self == 0) { 53 | Err("Invalid handle: zero".into()) 54 | } else { 55 | Ok(()) 56 | } 57 | } 58 | } 59 | )); 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | new_handle_type!(TestHandle, TEST_HANDLE_CTR); 64 | 65 | #[test] 66 | fn test_handle_seq() { 67 | assert_eq!(TestHandle::next(), 1); 68 | assert_eq!(TestHandle::next(), 2); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | /// Functions for quick validation 2 | pub mod validation; 3 | 4 | pub mod base58; 5 | 6 | #[cfg(feature = "w3c")] 7 | pub mod base64; 8 | 9 | pub mod hash; 10 | 11 | pub mod query; 12 | 13 | pub mod msg_pack; 14 | 15 | #[macro_use] 16 | pub mod macros; 17 | -------------------------------------------------------------------------------- /src/utils/msg_pack.rs: -------------------------------------------------------------------------------- 1 | use serde::de::DeserializeOwned; 2 | use serde::Serialize; 3 | 4 | use crate::Result; 5 | 6 | pub fn encode(val: T) -> Result> { 7 | rmp_serde::to_vec_named(&val) 8 | .map_err(|_| err_msg!("unable to encode message using message pack")) 9 | } 10 | 11 | pub fn decode(val: &[u8]) -> Result { 12 | rmp_serde::from_slice(val).map_err(|_| err_msg!("unable to decode message using message pack")) 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/validation.rs: -------------------------------------------------------------------------------- 1 | use crate::error::ValidationError; 2 | use once_cell::sync::Lazy; 3 | use regex::Regex; 4 | 5 | // TODO: stricten the URI regex. 6 | // Right now everything after the first colon is allowed, 7 | // we might want to restrict this 8 | pub static URI_IDENTIFIER: Lazy = 9 | Lazy::new(|| Regex::new(r"^[a-zA-Z][a-zA-Z0-9\+\-\.]*:.+$").unwrap()); 10 | 11 | /// base58 alpahet as defined in the [base58 12 | /// specification](https://datatracker.ietf.org/doc/html/draft-msporny-base58#section-2) This is 13 | /// used for legacy indy identifiers that we will keep supporting for backwards compatibility. This 14 | /// might validate invalid identifiers if they happen to fall within the base58 alphabet, but there 15 | /// is not much we can do about that. 16 | pub static LEGACY_DID_IDENTIFIER: Lazy = 17 | Lazy::new(|| Regex::new("^[1-9A-HJ-NP-Za-km-z]{21,22}$").unwrap()); 18 | 19 | pub static LEGACY_SCHEMA_IDENTIFIER: Lazy = 20 | Lazy::new(|| Regex::new("^[1-9A-HJ-NP-Za-km-z]{21,22}:2:[^:]+:[0-9.]+$").unwrap()); 21 | 22 | pub static LEGACY_CRED_DEF_IDENTIFIER: Lazy = Lazy::new(|| { 23 | Regex::new("^[1-9A-HJ-NP-Za-km-z]{21,22}:3:CL:(([1-9][0-9]*)|([a-zA-Z0-9]{21,22}:2:[^:]+:[0-9.]+)):([^:]+)?$").unwrap() 24 | }); 25 | 26 | pub static LEGACY_REV_REG_DEF_IDENTIFIER: Lazy = Lazy::new(|| { 27 | Regex::new("^[1-9A-HJ-NP-Za-km-z]{21,22}:4:[1-9A-HJ-NP-Za-km-z]{21,22}:3:CL:(([1-9][0-9]*)|([a-zA-Z0-9]{21,22}:2:[^:]+:[0-9.]+)):([^:]+):CL_ACCUM:([^:]+)?$").unwrap() 28 | }); 29 | 30 | pub fn is_uri_identifier(id: &str) -> bool { 31 | URI_IDENTIFIER.captures(id).is_some() 32 | } 33 | 34 | /// Macro to return a new `ValidationError` with an optional message 35 | #[macro_export] 36 | macro_rules! invalid { 37 | () => { $crate::error::ValidationError::from(None) }; 38 | ($($arg:tt)+) => { 39 | $crate::error::ValidationError::from(format!($($arg)+)) 40 | }; 41 | } 42 | 43 | /// Trait for data types which need validation after being loaded from external sources 44 | /// TODO: this should not default to Ok(()) 45 | pub trait Validatable { 46 | fn validate(&self) -> Result<(), ValidationError> { 47 | Ok(()) 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod test_identifiers { 53 | use super::*; 54 | 55 | #[test] 56 | fn should_validate_valid_identifiers() { 57 | let valid_uri_identifier = "mock:uri"; 58 | let valid_legacy_schema_identifier = "DXoTtQJNtXtiwWaZAK3rB1:2:example:1.0"; 59 | let valid_legacy_cred_def_identifier = "DXoTtQJNtXtiwWaZAK3rB1:3:CL:98153:default"; 60 | let valid_legacy_rev_reg_def_identifier = 61 | "DXoTtQJNtXtiwWaZAK3rB1:4:DXoTtQJNtXtiwWaZAK3rB1:3:CL:288602:example:CL_ACCUM:default"; 62 | let valid_legacy_did_identifier = "DXoTtQJNtXtiwWaZAK3rB1"; 63 | 64 | assert!(URI_IDENTIFIER.captures(valid_uri_identifier).is_some()); 65 | assert!(LEGACY_SCHEMA_IDENTIFIER 66 | .captures(valid_legacy_schema_identifier) 67 | .is_some()); 68 | assert!(LEGACY_CRED_DEF_IDENTIFIER 69 | .captures(valid_legacy_cred_def_identifier) 70 | .is_some()); 71 | assert!(LEGACY_REV_REG_DEF_IDENTIFIER 72 | .captures(valid_legacy_rev_reg_def_identifier) 73 | .is_some()); 74 | assert!(LEGACY_DID_IDENTIFIER 75 | .captures(valid_legacy_did_identifier) 76 | .is_some()); 77 | } 78 | 79 | #[test] 80 | fn should_not_validate_invalid_identifiers() { 81 | let invalid_uri_identifier = "DXoTtQJNtXtiwWaZAK3rB1"; 82 | let invalid_legacy_schema_identifier = "invalid:id"; 83 | let invalid_legacy_cred_def_identifier = "invalid:id"; 84 | let invalid_legacy_rev_reg_def_identifier = "invalid:id"; 85 | let invalid_legacy_did_identifier = "invalid:id"; 86 | 87 | assert!(URI_IDENTIFIER.captures(invalid_uri_identifier).is_none()); 88 | assert!(LEGACY_DID_IDENTIFIER 89 | .captures(invalid_legacy_schema_identifier) 90 | .is_none()); 91 | assert!(LEGACY_CRED_DEF_IDENTIFIER 92 | .captures(invalid_legacy_cred_def_identifier) 93 | .is_none()); 94 | assert!(LEGACY_REV_REG_DEF_IDENTIFIER 95 | .captures(invalid_legacy_rev_reg_def_identifier) 96 | .is_none()); 97 | assert!(LEGACY_DID_IDENTIFIER 98 | .captures(invalid_legacy_did_identifier) 99 | .is_none()); 100 | 101 | assert!(LEGACY_SCHEMA_IDENTIFIER 102 | .captures("DXoTtQJNtXtiwWaZAK3rB1:3:example:1.0") 103 | .is_none()); 104 | assert!(LEGACY_CRED_DEF_IDENTIFIER 105 | .captures("DXoTtQJNtXtiwWaZAK3rB1:4:CL:98153:default") 106 | .is_none()); 107 | assert!(LEGACY_REV_REG_DEF_IDENTIFIER 108 | .captures("DXoTtQJNtXtiwWaZAK3rB1:5:DXoTtQJNtXtiwWaZAK3rB1:3:CL:288602:example:CL_ACCUM:default") 109 | .is_none()); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tests/utils/fixtures.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | use std::collections::HashMap; 3 | 4 | use anoncreds::data_types::pres_request::{NonRevokedInterval, PresentationRequestPayload}; 5 | use anoncreds::w3c::types::MakeCredentialAttributes; 6 | use anoncreds::{ 7 | data_types::{ 8 | cred_def::{CredentialDefinition, CredentialDefinitionId}, 9 | schema::{Schema, SchemaId}, 10 | }, 11 | issuer, prover, 12 | tails::TailsFileWriter, 13 | types::{ 14 | CredentialDefinitionPrivate, CredentialKeyCorrectnessProof, CredentialRevocationState, 15 | MakeCredentialValues, PresentCredentials, Presentation, PresentationRequest, 16 | RevocationRegistryDefinition, RevocationRegistryDefinitionPrivate, RevocationStatusList, 17 | }, 18 | verifier, 19 | }; 20 | 21 | use super::storage::ProverWallet; 22 | 23 | // Government credential related fixtures 24 | pub const GVT_SCHEMA_NAME: &str = "Government Schema"; 25 | pub const GVT_SCHEMA_ID: &str = "schema:government"; 26 | pub const GVT_SCHEMA_VERSION: &str = "1.0"; 27 | pub const GVT_SCHEMA_ATTRIBUTES: &[&str; 5] = &["id", "name", "age", "sex", "height"]; 28 | 29 | pub const GVT_CRED_DEF_ID: &str = "creddef:government"; 30 | pub const GVT_CRED_DEF_TAG: &str = "govermenttag"; 31 | 32 | pub const GVT_ISSUER_ID: &str = "issuer:id/path=bar"; 33 | 34 | pub const GVT_REV_REG_DEF_ID: &str = "revreg:government/id"; 35 | pub const GVT_REV_REG_TAG: &str = "revreggovermenttag"; 36 | pub const GVT_REV_IDX: u32 = 9; 37 | pub const GVT_REV_MAX_CRED_NUM: u32 = 10; 38 | 39 | // Employee credential related fixtures 40 | pub const EMP_SCHEMA_NAME: &str = "Employee Schema"; 41 | pub const EMP_SCHEMA_ID: &str = "schema:employeebadge"; 42 | pub const EMP_SCHEMA_VERSION: &str = "1.0"; 43 | pub const EMP_SCHEMA_ATTRIBUTES: &[&str; 3] = &["name", "role", "department"]; 44 | 45 | pub const EMP_CRED_DEF_ID: &str = "creddef:employee"; 46 | pub const EMP_CRED_DEF_TAG: &str = "employeetag"; 47 | 48 | pub const EMP_ISSUER_ID: &str = "employer:id/path=bar"; 49 | 50 | pub const EMP_REV_REG_DEF_ID: &str = "revreg:employee/id"; 51 | pub const EMP_REV_REG_TAG: &str = "revregemployeetag"; 52 | pub const EMP_REV_IDX: u32 = 9; 53 | pub const EMP_REV_MAX_CRED_NUM: u32 = 10; 54 | 55 | pub const GVT_CRED: &str = "GVT"; 56 | pub const EMP_CRED: &str = "EMP"; 57 | 58 | // Create a `GVT` or `EMP` schema 59 | pub fn create_schema(name: &str) -> (Schema, &str) { 60 | match name { 61 | GVT_CRED => ( 62 | issuer::create_schema( 63 | GVT_SCHEMA_NAME, 64 | GVT_SCHEMA_VERSION, 65 | GVT_ISSUER_ID.try_into().unwrap(), 66 | GVT_SCHEMA_ATTRIBUTES[..].into(), 67 | ) 68 | .expect("error while creating GVT schema"), 69 | GVT_SCHEMA_ID, 70 | ), 71 | EMP_CRED => ( 72 | issuer::create_schema( 73 | EMP_SCHEMA_NAME, 74 | EMP_SCHEMA_VERSION, 75 | EMP_ISSUER_ID.try_into().unwrap(), 76 | EMP_SCHEMA_ATTRIBUTES[..].into(), 77 | ) 78 | .expect("error while creating EMP schema"), 79 | EMP_SCHEMA_ID, 80 | ), 81 | unsupported => panic!("Unsupported schema. {unsupported}"), 82 | } 83 | } 84 | 85 | pub fn create_cred_def( 86 | schema: &Schema, 87 | support_revocation: bool, 88 | ) -> ( 89 | ( 90 | CredentialDefinition, 91 | CredentialDefinitionPrivate, 92 | CredentialKeyCorrectnessProof, 93 | ), 94 | &str, 95 | ) { 96 | match schema.name.as_str() { 97 | GVT_SCHEMA_NAME => ( 98 | issuer::create_credential_definition( 99 | GVT_SCHEMA_ID.try_into().unwrap(), 100 | schema, 101 | GVT_ISSUER_ID.try_into().unwrap(), 102 | GVT_CRED_DEF_TAG, 103 | anoncreds::types::SignatureType::CL, 104 | anoncreds::types::CredentialDefinitionConfig { support_revocation }, 105 | ) 106 | .expect("error while creating GVT cred def"), 107 | GVT_CRED_DEF_ID, 108 | ), 109 | EMP_SCHEMA_NAME => ( 110 | issuer::create_credential_definition( 111 | EMP_SCHEMA_ID.try_into().unwrap(), 112 | schema, 113 | EMP_ISSUER_ID.try_into().unwrap(), 114 | EMP_CRED_DEF_TAG, 115 | anoncreds::types::SignatureType::CL, 116 | anoncreds::types::CredentialDefinitionConfig { support_revocation }, 117 | ) 118 | .expect("error while creating EMP cred def"), 119 | EMP_CRED_DEF_ID, 120 | ), 121 | unsupported => panic!("Unsupported schema name. {unsupported}"), 122 | } 123 | } 124 | 125 | pub fn create_rev_reg_def<'a>( 126 | cred_def: &CredentialDefinition, 127 | tf: &mut TailsFileWriter, 128 | ) -> ( 129 | ( 130 | RevocationRegistryDefinition, 131 | RevocationRegistryDefinitionPrivate, 132 | ), 133 | &'a str, 134 | ) { 135 | match cred_def.tag.as_str() { 136 | GVT_CRED_DEF_TAG => ( 137 | issuer::create_revocation_registry_def( 138 | cred_def, 139 | GVT_CRED_DEF_ID.try_into().unwrap(), 140 | GVT_REV_REG_TAG, 141 | anoncreds::types::RegistryType::CL_ACCUM, 142 | GVT_REV_MAX_CRED_NUM, 143 | tf, 144 | ) 145 | .expect("Error while creating GVT rev reg"), 146 | GVT_REV_REG_DEF_ID, 147 | ), 148 | EMP_CRED_DEF_TAG => ( 149 | issuer::create_revocation_registry_def( 150 | cred_def, 151 | EMP_CRED_DEF_ID.try_into().unwrap(), 152 | EMP_REV_REG_TAG, 153 | anoncreds::types::RegistryType::CL_ACCUM, 154 | EMP_REV_MAX_CRED_NUM, 155 | tf, 156 | ) 157 | .expect("Error while creating EMP rev reg"), 158 | EMP_REV_REG_DEF_ID, 159 | ), 160 | unsupported => panic!("Unsupported cred def. {unsupported}"), 161 | } 162 | } 163 | 164 | pub fn create_revocation_status_list( 165 | cred_def: &CredentialDefinition, 166 | rev_reg_def: &RevocationRegistryDefinition, 167 | rev_reg_priv: &RevocationRegistryDefinitionPrivate, 168 | time: Option, 169 | issuance_by_default: bool, 170 | ) -> RevocationStatusList { 171 | match rev_reg_def.tag.as_str() { 172 | GVT_REV_REG_TAG => issuer::create_revocation_status_list( 173 | cred_def, 174 | GVT_REV_REG_DEF_ID.try_into().unwrap(), 175 | rev_reg_def, 176 | rev_reg_priv, 177 | issuance_by_default, 178 | time, 179 | ) 180 | .expect("Error while creating GVT rev status list"), 181 | EMP_REV_REG_TAG => issuer::create_revocation_status_list( 182 | cred_def, 183 | EMP_REV_REG_DEF_ID.try_into().unwrap(), 184 | rev_reg_def, 185 | rev_reg_priv, 186 | issuance_by_default, 187 | time, 188 | ) 189 | .expect("Error while creating EMP rev status list"), 190 | unsupported => panic!("Unsupported rev reg def. {unsupported}"), 191 | } 192 | } 193 | 194 | pub fn credential_values(name: &str) -> MakeCredentialValues { 195 | match name { 196 | GVT_CRED => { 197 | let mut gvt_cred_values = MakeCredentialValues::default(); 198 | gvt_cred_values 199 | .add_raw("id", "example_id") 200 | .expect("Error encoding attribute"); 201 | gvt_cred_values 202 | .add_raw("sex", "male") 203 | .expect("Error encoding attribute"); 204 | gvt_cred_values 205 | .add_raw("name", "Alex") 206 | .expect("Error encoding attribute"); 207 | gvt_cred_values 208 | .add_raw("height", "175") 209 | .expect("Error encoding attribute"); 210 | gvt_cred_values 211 | .add_raw("age", "28") 212 | .expect("Error encoding attribute"); 213 | gvt_cred_values 214 | } 215 | EMP_CRED => { 216 | let mut emp_cred_values = MakeCredentialValues::default(); 217 | emp_cred_values 218 | .add_raw("name", "John") 219 | .expect("Error encoding attribute"); 220 | emp_cred_values 221 | .add_raw("role", "Developer") 222 | .expect("Error encoding attribute"); 223 | emp_cred_values 224 | .add_raw("department", "IT") 225 | .expect("Error encoding attribute"); 226 | emp_cred_values 227 | } 228 | unsupported => panic!("Unsupported credential values. {unsupported}"), 229 | } 230 | } 231 | 232 | pub fn raw_credential_values(name: &str) -> MakeCredentialAttributes { 233 | match name { 234 | GVT_CRED => { 235 | let mut gvt_cred_values = MakeCredentialAttributes::default(); 236 | gvt_cred_values.add("id", "example_id"); 237 | gvt_cred_values.add("sex", "male"); 238 | gvt_cred_values.add("name", "Alex"); 239 | gvt_cred_values.add("height", "175"); 240 | gvt_cred_values.add("age", "28"); 241 | gvt_cred_values 242 | } 243 | EMP_CRED => { 244 | let mut emp_cred_values = MakeCredentialAttributes::default(); 245 | emp_cred_values.add("name", "John"); 246 | emp_cred_values.add("role", "Developer"); 247 | emp_cred_values.add("department", "IT"); 248 | emp_cred_values 249 | } 250 | unsupported => panic!("Unsupported credential values. {unsupported}"), 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | pub mod fixtures; 4 | pub mod mock; 5 | pub mod storage; 6 | 7 | pub use fixtures::*; 8 | pub use mock::*; 9 | pub use storage::*; 10 | -------------------------------------------------------------------------------- /tests/utils/storage.rs: -------------------------------------------------------------------------------- 1 | use anoncreds::data_types::cred_def::{CredentialDefinition, CredentialDefinitionId}; 2 | use anoncreds::data_types::rev_reg_def::RevocationRegistryDefinitionId; 3 | use anoncreds::data_types::schema::{Schema, SchemaId}; 4 | use anoncreds::data_types::w3c::credential::W3CCredential; 5 | use anoncreds::types::{ 6 | Credential, CredentialDefinitionPrivate, CredentialKeyCorrectnessProof, CredentialOffer, 7 | CredentialRequest, CredentialRequestMetadata, CredentialRevocationState, LinkSecret, 8 | RevocationRegistryDefinition, RevocationRegistryDefinitionPrivate, RevocationStatusList, 9 | }; 10 | use std::collections::HashMap; 11 | 12 | #[derive(Debug)] 13 | pub struct StoredCredDef { 14 | pub public: CredentialDefinition, 15 | pub private: CredentialDefinitionPrivate, 16 | pub key_proof: CredentialKeyCorrectnessProof, 17 | } 18 | 19 | #[derive(Debug)] 20 | pub struct StoredRevDef { 21 | pub public: RevocationRegistryDefinition, 22 | pub private: RevocationRegistryDefinitionPrivate, 23 | } 24 | 25 | #[derive(Debug, Default)] 26 | pub struct Ledger<'a> { 27 | pub cred_defs: HashMap, 28 | pub schemas: HashMap, 29 | pub rev_reg_defs: HashMap, 30 | pub revocation_list: HashMap<&'a str, HashMap>, 31 | } 32 | 33 | // A struct for keeping all issuer-related objects together 34 | #[derive(Debug, Default)] 35 | pub struct IssuerWallet { 36 | // cred_def_id: StoredRevDef 37 | pub cred_defs: HashMap, 38 | // revocation_reg_id: StoredRevDef 39 | pub rev_defs: HashMap, 40 | } 41 | 42 | // A struct for keeping all issuer-related objects together 43 | #[derive(Debug)] 44 | pub struct ProverWallet<'a> { 45 | pub entropy: &'static str, 46 | pub link_secret_id: &'static str, 47 | pub credentials: HashMap, 48 | pub w3c_credentials: HashMap, 49 | pub rev_states: HashMap, Option)>, 50 | pub link_secret: LinkSecret, 51 | pub cred_offers: HashMap<&'a str, CredentialOffer>, 52 | pub cred_reqs: Vec<(CredentialRequest, CredentialRequestMetadata)>, 53 | } 54 | 55 | impl<'a> Default for ProverWallet<'a> { 56 | fn default() -> Self { 57 | let link_secret = LinkSecret::new().expect("Error creating prover link secret"); 58 | Self { 59 | entropy: "entropy", 60 | link_secret_id: "default", 61 | credentials: HashMap::new(), 62 | rev_states: HashMap::new(), 63 | link_secret, 64 | cred_offers: HashMap::new(), 65 | cred_reqs: vec![], 66 | w3c_credentials: HashMap::new(), 67 | } 68 | } 69 | } 70 | 71 | // A struct for keeping all verifier-related objects together 72 | #[derive(Debug, Default)] 73 | pub struct VerifierWallet {} 74 | -------------------------------------------------------------------------------- /wrappers/python/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.dylib 3 | *.dll 4 | *.so 5 | *.egg-info 6 | dist 7 | build 8 | -------------------------------------------------------------------------------- /wrappers/python/README.md: -------------------------------------------------------------------------------- 1 | # Anoncreds 2 | 3 | [![pypi releases](https://img.shields.io/pypi/v/anoncreds)](https://pypi.org/project/anoncreds/) 4 | 5 | A Python wrapper around the `anoncreds` Rust library, this module provides support for Hyperledger Anoncreds verifiable credential issuance, presentation, and verification. 6 | 7 | ## Credit 8 | 9 | The initial implementation of `anoncreds` / `indy-shared-rs` was developed by the Verifiable Organizations Network (VON) team based at the Province of British Columbia, and derives largely from the implementations within [Hyperledger Indy-SDK](https://github.com/hyperledger/indy-sdk). To learn more about VON and what's happening with decentralized identity in British Columbia, please go to [https://vonx.io](https://vonx.io). 10 | 11 | ## Contributing 12 | 13 | Pull requests are welcome! Please read our [contributions guide](https://github.com/hyperledger/anoncreds-rs/blob/main/CONTRIBUTING.md) and submit your PRs. We enforce [developer certificate of origin](https://developercertificate.org/) (DCO) commit signing. See guidance [here](https://github.com/apps/dco). 14 | 15 | We also welcome issues submitted about problems you encounter in using `anoncreds`. 16 | 17 | ## License 18 | 19 | [Apache License Version 2.0](https://github.com/hyperledger/anoncreds-rs/blob/main/LICENSE) 20 | -------------------------------------------------------------------------------- /wrappers/python/anoncreds/__init__.py: -------------------------------------------------------------------------------- 1 | """Anoncreds Python wrapper library""" 2 | 3 | from .bindings import encode_credential_attributes, generate_nonce, library_version, create_link_secret 4 | from .error import AnoncredsError, AnoncredsErrorCode 5 | from .types import ( 6 | Credential, 7 | CredentialDefinition, 8 | CredentialDefinitionPrivate, 9 | CredentialRevocationConfig, 10 | CredentialRevocationState, 11 | KeyCorrectnessProof, 12 | CredentialOffer, 13 | CredentialRequest, 14 | CredentialRequestMetadata, 15 | NonrevokedIntervalOverride, 16 | PresentationRequest, 17 | Presentation, 18 | PresentCredentials, 19 | Schema, 20 | RevocationRegistry, 21 | RevocationStatusList, 22 | RevocationRegistryDefinition, 23 | RevocationRegistryDefinitionPrivate, 24 | W3cCredential, 25 | W3cPresentation, 26 | ) 27 | 28 | __all__ = ( 29 | "create_link_secret", 30 | "encode_credential_attributes", 31 | "generate_nonce", 32 | "library_version", 33 | "AnoncredsError", 34 | "AnoncredsErrorCode", 35 | "Credential", 36 | "CredentialDefinition", 37 | "CredentialDefinitionPrivate", 38 | "CredentialRevocationConfig", 39 | "CredentialRevocationState", 40 | "KeyCorrectnessProof", 41 | "CredentialOffer", 42 | "CredentialRequest", 43 | "CredentialRequestMetadata", 44 | "NonrevokedIntervalOverride", 45 | "PresentationRequest", 46 | "Presentation", 47 | "PresentCredentials", 48 | "RevocationRegistry", 49 | "RevocationStatusList", 50 | "RevocationRegistryDefinition", 51 | "RevocationRegistryDefinitionPrivate", 52 | "Schema", 53 | "W3cCredential", 54 | "W3cPresentation" 55 | ) 56 | -------------------------------------------------------------------------------- /wrappers/python/anoncreds/error.py: -------------------------------------------------------------------------------- 1 | """Error classes.""" 2 | 3 | from enum import IntEnum 4 | 5 | 6 | class AnoncredsErrorCode(IntEnum): 7 | SUCCESS = 0 8 | INPUT = 1 9 | IO_ERROR = 2 10 | INVALID_STATE = 3 11 | UNEXPECTED = 4 12 | CREDENTIAL_REVOKED = 5 13 | INVALID_USER_REVOC_ID = 6 14 | PROOF_REJECTED = 7 15 | REVOCATION_REGISTRY_FULL = 8 16 | WRAPPER = 99 17 | 18 | 19 | class AnoncredsError(Exception): 20 | def __init__(self, code: AnoncredsErrorCode, message: str, extra: str = None): 21 | super().__init__(message) 22 | self.code = code 23 | self.extra = extra 24 | -------------------------------------------------------------------------------- /wrappers/python/anoncreds/version.py: -------------------------------------------------------------------------------- 1 | """anoncreds library wrapper version.""" 2 | 3 | __version__ = "0.2.0" 4 | -------------------------------------------------------------------------------- /wrappers/python/demo/test.py: -------------------------------------------------------------------------------- 1 | from anoncreds import ( 2 | generate_nonce, 3 | create_link_secret, 4 | Credential, 5 | CredentialDefinition, 6 | CredentialOffer, 7 | CredentialRequest, 8 | CredentialRevocationConfig, 9 | CredentialRevocationState, 10 | PresentationRequest, 11 | Presentation, 12 | PresentCredentials, 13 | RevocationRegistryDefinition, 14 | RevocationStatusList, 15 | Schema, 16 | ) 17 | 18 | issuer_id = "mock:uri" 19 | schema_id = "mock:uri" 20 | cred_def_id = "mock:uri" 21 | rev_reg_id = "mock:uri:revregid" 22 | entropy = "entropy" 23 | rev_idx = 1 24 | 25 | schema = Schema.create( 26 | "schema name", "schema version", issuer_id, ["name", "age", "sex", "height"] 27 | ) 28 | 29 | cred_def_pub, cred_def_priv, cred_def_correctness = CredentialDefinition.create( 30 | schema_id, schema, issuer_id, "tag", "CL", support_revocation=True 31 | ) 32 | 33 | (rev_reg_def_pub, rev_reg_def_private) = RevocationRegistryDefinition.create( 34 | cred_def_id, cred_def_pub, issuer_id, "some_tag", "CL_ACCUM", 10 35 | ) 36 | 37 | time_create_rev_status_list = 12 38 | revocation_status_list = RevocationStatusList.create( 39 | cred_def_pub, 40 | rev_reg_id, 41 | rev_reg_def_pub, 42 | rev_reg_def_private, 43 | issuer_id, 44 | True, 45 | time_create_rev_status_list, 46 | ) 47 | 48 | link_secret = create_link_secret() 49 | link_secret_id = "default" 50 | 51 | cred_offer = CredentialOffer.create(schema_id, cred_def_id, cred_def_correctness) 52 | 53 | cred_request, cred_request_metadata = CredentialRequest.create( 54 | entropy, None, cred_def_pub, link_secret, link_secret_id, cred_offer 55 | ) 56 | 57 | issue_cred = Credential.create( 58 | cred_def_pub, 59 | cred_def_priv, 60 | cred_offer, 61 | cred_request, 62 | {"sex": "male", "name": "Alex", "height": "175", "age": "28"}, 63 | None, 64 | CredentialRevocationConfig( 65 | rev_reg_def_pub, 66 | rev_reg_def_private, 67 | revocation_status_list, 68 | rev_idx, 69 | ), 70 | ) 71 | 72 | recv_cred = issue_cred.process( 73 | cred_request_metadata, link_secret, cred_def_pub, rev_reg_def_pub 74 | ) 75 | 76 | time_after_creating_cred = time_create_rev_status_list + 1 77 | issued_rev_status_list = revocation_status_list.update( 78 | cred_def_pub, 79 | rev_reg_def_pub, 80 | rev_reg_def_private, 81 | [rev_idx], 82 | None, 83 | time_after_creating_cred, 84 | ) 85 | 86 | nonce = generate_nonce() 87 | pres_req = PresentationRequest.load( 88 | { 89 | "nonce": nonce, 90 | "name": "pres_req_1", 91 | "version": "0.1", 92 | "requested_attributes": { 93 | "attr1_referent": {"name": "name", "issuer_id": issuer_id}, 94 | "attr2_referent": {"name": "sex"}, 95 | "attr3_referent": {"name": "phone"}, 96 | "attr4_referent": {"names": ["name", "height"]}, 97 | }, 98 | "requested_predicates": { 99 | "predicate1_referent": {"name": "age", "p_type": ">=", "p_value": 18} 100 | }, 101 | "non_revoked": {"from": 10, "to": 200}, 102 | } 103 | ) 104 | 105 | rev_state = CredentialRevocationState.create( 106 | rev_reg_def_pub, 107 | revocation_status_list, 108 | rev_idx, 109 | rev_reg_def_pub.tails_location, 110 | ) 111 | 112 | schemas = {schema_id: schema} 113 | cred_defs = {cred_def_id: cred_def_pub} 114 | rev_reg_defs = {rev_reg_id: rev_reg_def_pub} 115 | rev_status_lists = [issued_rev_status_list] 116 | 117 | present = PresentCredentials() 118 | 119 | present.add_attributes( 120 | recv_cred, 121 | "attr1_referent", 122 | reveal=True, 123 | timestamp=time_after_creating_cred, 124 | rev_state=rev_state, 125 | ) 126 | 127 | present.add_attributes( 128 | recv_cred, 129 | "attr2_referent", 130 | reveal=False, 131 | timestamp=time_after_creating_cred, 132 | rev_state=rev_state, 133 | ) 134 | 135 | present.add_attributes( 136 | recv_cred, 137 | "attr4_referent", 138 | reveal=True, 139 | timestamp=time_after_creating_cred, 140 | rev_state=rev_state, 141 | ) 142 | 143 | present.add_predicates( 144 | recv_cred, 145 | "predicate1_referent", 146 | timestamp=time_after_creating_cred, 147 | rev_state=rev_state, 148 | ) 149 | 150 | presentation = Presentation.create( 151 | pres_req, 152 | present, 153 | {"attr3_referent": "8-800-300"}, 154 | link_secret, 155 | schemas, 156 | cred_defs, 157 | ) 158 | 159 | verified = presentation.verify( 160 | pres_req, schemas, cred_defs, rev_reg_defs, rev_status_lists 161 | ) 162 | assert verified 163 | 164 | # Issuer revokes credential 165 | 166 | time_revoke_cred = time_after_creating_cred + 1 167 | revoked_status_list = issued_rev_status_list.update( 168 | cred_def_pub, 169 | rev_reg_def_pub, 170 | rev_reg_def_private, 171 | None, 172 | [rev_idx], 173 | time_revoke_cred, 174 | ) 175 | 176 | rev_status_lists.append(revoked_status_list) 177 | 178 | rev_state.update( 179 | rev_reg_def_pub, 180 | revocation_status_list, 181 | rev_idx, 182 | rev_reg_def_pub.tails_location, 183 | revoked_status_list, 184 | ) 185 | 186 | present = PresentCredentials() 187 | present.add_attributes( 188 | recv_cred, 189 | "attr1_referent", 190 | reveal=True, 191 | timestamp=time_revoke_cred, 192 | rev_state=rev_state, 193 | ) 194 | 195 | present.add_attributes( 196 | recv_cred, 197 | "attr2_referent", 198 | reveal=False, 199 | timestamp=time_revoke_cred, 200 | rev_state=rev_state, 201 | ) 202 | 203 | present.add_attributes( 204 | recv_cred, 205 | "attr4_referent", 206 | reveal=True, 207 | timestamp=time_revoke_cred, 208 | rev_state=rev_state, 209 | ) 210 | 211 | present.add_predicates( 212 | recv_cred, "predicate1_referent", timestamp=time_revoke_cred, rev_state=rev_state 213 | ) 214 | 215 | presentation = Presentation.create( 216 | pres_req, present, {"attr3_referent": "8-800-300"}, link_secret, schemas, cred_defs 217 | ) 218 | 219 | verified = presentation.verify( 220 | pres_req, 221 | schemas, 222 | cred_defs, 223 | rev_reg_defs, 224 | rev_status_lists, 225 | ) 226 | assert not verified 227 | 228 | print("ok") 229 | -------------------------------------------------------------------------------- /wrappers/python/demo/w3c_test.py: -------------------------------------------------------------------------------- 1 | from anoncreds import ( 2 | generate_nonce, 3 | create_link_secret, 4 | Credential, 5 | W3cCredential, 6 | CredentialDefinition, 7 | CredentialOffer, 8 | CredentialRequest, 9 | CredentialRevocationConfig, 10 | CredentialRevocationState, 11 | PresentationRequest, 12 | Presentation, 13 | W3cPresentation, 14 | PresentCredentials, 15 | RevocationRegistryDefinition, 16 | RevocationStatusList, 17 | Schema 18 | ) 19 | 20 | issuer_id = "mock:uri" 21 | schema_id = "mock:uri" 22 | cred_def_id = "mock:uri" 23 | rev_reg_id = "mock:uri:revregid" 24 | entropy = "entropy" 25 | rev_idx = 1 26 | 27 | schema = Schema.create( 28 | "schema name", "schema version", issuer_id, ["name", "age", "sex", "height"] 29 | ) 30 | 31 | cred_def_pub, cred_def_priv, cred_def_correctness = CredentialDefinition.create( 32 | schema_id, schema, issuer_id, "tag", "CL", support_revocation=True 33 | ) 34 | 35 | (rev_reg_def_pub, rev_reg_def_private) = RevocationRegistryDefinition.create( 36 | cred_def_id, cred_def_pub, issuer_id, "some_tag", "CL_ACCUM", 10 37 | ) 38 | 39 | time_create_rev_status_list = 12 40 | revocation_status_list = RevocationStatusList.create( 41 | cred_def_pub, 42 | rev_reg_id, 43 | rev_reg_def_pub, 44 | rev_reg_def_private, 45 | issuer_id, 46 | True, 47 | time_create_rev_status_list, 48 | ) 49 | 50 | link_secret = create_link_secret() 51 | link_secret_id = "default" 52 | 53 | cred_offer = CredentialOffer.create(schema_id, cred_def_id, cred_def_correctness) 54 | 55 | cred_request, cred_request_metadata = CredentialRequest.create( 56 | entropy, None, cred_def_pub, link_secret, link_secret_id, cred_offer 57 | ) 58 | 59 | issue_cred = W3cCredential.create( 60 | cred_def_pub, 61 | cred_def_priv, 62 | cred_offer, 63 | cred_request, 64 | {"sex": "male", "name": "Alex", "height": "175", "age": "28"}, 65 | CredentialRevocationConfig( 66 | rev_reg_def_pub, 67 | rev_reg_def_private, 68 | revocation_status_list, 69 | rev_idx, 70 | ), 71 | None, 72 | ) 73 | 74 | recv_cred = issue_cred.process( 75 | cred_request_metadata, link_secret, cred_def_pub, rev_reg_def_pub 76 | ) 77 | 78 | assert schema_id == recv_cred.schema_id 79 | assert cred_def_id == recv_cred.cred_def_id 80 | assert rev_idx == recv_cred.rev_reg_index 81 | assert None == recv_cred.timestamp 82 | 83 | print("W3c Credential") 84 | print(recv_cred.to_json()) 85 | 86 | legacy_cred = recv_cred.to_legacy() 87 | print("Legacy Credential `to_legacy`") 88 | print(legacy_cred.to_json()) 89 | 90 | legacy_cred = Credential.from_w3c(recv_cred) 91 | print("Legacy Credential `from_w3c`") 92 | print(legacy_cred.to_json()) 93 | 94 | w3c_cred = legacy_cred.to_w3c(issuer_id) 95 | print("W3c converted Credential `to_w3c`") 96 | print(w3c_cred.to_json()) 97 | 98 | w3c_cred_restored = W3cCredential.from_legacy(legacy_cred, issuer_id) 99 | print("W3C restored Credential `from_legacy`") 100 | print(w3c_cred_restored.to_json()) 101 | 102 | time_after_creating_cred = time_create_rev_status_list + 1 103 | issued_rev_status_list = revocation_status_list.update( 104 | cred_def_pub, 105 | rev_reg_def_pub, 106 | rev_reg_def_private, 107 | [rev_idx], 108 | None, 109 | time_after_creating_cred, 110 | ) 111 | 112 | nonce = generate_nonce() 113 | pres_req = PresentationRequest.load( 114 | { 115 | "nonce": nonce, 116 | "name": "pres_req_1", 117 | "version": "0.1", 118 | "requested_attributes": { 119 | "attr1_referent": {"name": "name", "issuer_id": issuer_id}, 120 | "attr2_referent": {"names": ["name", "height"]}, 121 | }, 122 | "requested_predicates": { 123 | "predicate1_referent": {"name": "age", "p_type": ">=", "p_value": 18} 124 | }, 125 | "non_revoked": {"from": 10, "to": 200}, 126 | } 127 | ) 128 | 129 | rev_state = CredentialRevocationState.create( 130 | rev_reg_def_pub, 131 | revocation_status_list, 132 | rev_idx, 133 | rev_reg_def_pub.tails_location, 134 | ) 135 | 136 | schemas = {schema_id: schema} 137 | cred_defs = {cred_def_id: cred_def_pub} 138 | rev_reg_defs = {rev_reg_id: rev_reg_def_pub} 139 | rev_status_lists = [issued_rev_status_list] 140 | 141 | # Create Presentation using W3C credential 142 | present = PresentCredentials() 143 | 144 | present.add_attributes( 145 | recv_cred, 146 | "attr1_referent", 147 | reveal=True, 148 | timestamp=time_after_creating_cred, 149 | rev_state=rev_state, 150 | ) 151 | 152 | present.add_attributes( 153 | recv_cred, 154 | "attr2_referent", 155 | reveal=True, 156 | timestamp=time_after_creating_cred, 157 | rev_state=rev_state, 158 | ) 159 | 160 | present.add_predicates( 161 | recv_cred, 162 | "predicate1_referent", 163 | timestamp=time_after_creating_cred, 164 | rev_state=rev_state, 165 | ) 166 | 167 | presentation = W3cPresentation.create( 168 | pres_req, 169 | present, 170 | link_secret, 171 | schemas, 172 | cred_defs, 173 | ) 174 | 175 | verified = presentation.verify( 176 | pres_req, schemas, cred_defs, rev_reg_defs, rev_status_lists 177 | ) 178 | assert verified 179 | 180 | # Create Presentation using Legacy credential 181 | present = PresentCredentials() 182 | 183 | present.add_attributes( 184 | legacy_cred, 185 | "attr1_referent", 186 | reveal=True, 187 | timestamp=time_after_creating_cred, 188 | rev_state=rev_state, 189 | ) 190 | 191 | present.add_attributes( 192 | legacy_cred, 193 | "attr2_referent", 194 | reveal=True, 195 | timestamp=time_after_creating_cred, 196 | rev_state=rev_state, 197 | ) 198 | 199 | present.add_predicates( 200 | legacy_cred, 201 | "predicate1_referent", 202 | timestamp=time_after_creating_cred, 203 | rev_state=rev_state, 204 | ) 205 | 206 | presentation = Presentation.create( 207 | pres_req, 208 | present, 209 | {}, 210 | link_secret, 211 | schemas, 212 | cred_defs, 213 | ) 214 | 215 | verified = presentation.verify( 216 | pres_req, schemas, cred_defs, rev_reg_defs, rev_status_lists 217 | ) 218 | assert verified 219 | 220 | # Issuer revokes credential 221 | 222 | time_revoke_cred = time_after_creating_cred + 1 223 | revoked_status_list = issued_rev_status_list.update( 224 | cred_def_pub, 225 | rev_reg_def_pub, 226 | rev_reg_def_private, 227 | None, 228 | [rev_idx], 229 | time_revoke_cred, 230 | ) 231 | 232 | rev_status_lists.append(revoked_status_list) 233 | 234 | rev_state.update( 235 | rev_reg_def_pub, 236 | revocation_status_list, 237 | rev_idx, 238 | rev_reg_def_pub.tails_location, 239 | revoked_status_list, 240 | ) 241 | 242 | present = PresentCredentials() 243 | present.add_attributes( 244 | recv_cred, 245 | "attr1_referent", 246 | reveal=True, 247 | timestamp=time_revoke_cred, 248 | rev_state=rev_state, 249 | ) 250 | 251 | present.add_attributes( 252 | recv_cred, 253 | "attr2_referent", 254 | reveal=True, 255 | timestamp=time_revoke_cred, 256 | rev_state=rev_state, 257 | ) 258 | 259 | present.add_predicates( 260 | recv_cred, "predicate1_referent", timestamp=time_revoke_cred, rev_state=rev_state 261 | ) 262 | 263 | presentation = W3cPresentation.create( 264 | pres_req, present, link_secret, schemas, cred_defs 265 | ) 266 | 267 | verified = presentation.verify( 268 | pres_req, 269 | schemas, 270 | cred_defs, 271 | rev_reg_defs, 272 | rev_status_lists, 273 | ) 274 | assert not verified 275 | 276 | print("ok") 277 | -------------------------------------------------------------------------------- /wrappers/python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /wrappers/python/setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # https://github.com/ambv/black#line-length 3 | max-line-length = 88 4 | exclude = 5 | */tests/** 6 | extend_ignore = D202, W503 7 | per_file_ignores = */__init__.py:D104 8 | -------------------------------------------------------------------------------- /wrappers/python/setup.py: -------------------------------------------------------------------------------- 1 | """Module setup.""" 2 | 3 | import os 4 | import runpy 5 | from setuptools import find_packages, setup 6 | 7 | PACKAGE_NAME = "anoncreds" 8 | version_meta = runpy.run_path("./{}/version.py".format(PACKAGE_NAME)) 9 | VERSION = version_meta["__version__"] 10 | 11 | with open(os.path.abspath("./README.md"), "r") as fh: 12 | long_description = fh.read() 13 | 14 | if __name__ == "__main__": 15 | setup( 16 | name=PACKAGE_NAME, 17 | version=VERSION, 18 | author="Hyperledger Anoncreds Contributors", 19 | author_email="anoncreds@lists.hyperledger.org", 20 | long_description=long_description, 21 | long_description_content_type="text/markdown", 22 | url="https://github.com/hyperledger/anoncreds-rs", 23 | packages=find_packages(), 24 | include_package_data=True, 25 | package_data={ 26 | "": [ 27 | "anoncreds.dll", 28 | "libanoncreds.dylib", 29 | "libanoncreds.so", 30 | ] 31 | }, 32 | python_requires=">=3.6.3", 33 | classifiers=[ 34 | "Programming Language :: Python :: 3", 35 | "License :: OSI Approved :: Apache Software License", 36 | "Operating System :: OS Independent", 37 | ], 38 | ) 39 | --------------------------------------------------------------------------------